From f39773f15b5937f634855ae0adb7b6b7e948c876 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sun, 30 Aug 2015 15:23:31 -0400 Subject: [PATCH 001/410] update readme --- README | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README b/README index 22fc58c7a..afd4876ca 100644 --- a/README +++ b/README @@ -2,6 +2,8 @@ Z3 is a theorem prover from Microsoft Research. Z3 is licensed under the MIT license. Z3 can be built using Visual Studio Command Prompt and make/g++. +This fork of Z3 adds support for solving equations in the theory of strings. + 1) Building Z3 on Windows using Visual Studio Command Prompt 32-bit builds, start with: From b30d4f757db057253dc64f61b84f12b7f820a7ef Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 2 Sep 2015 18:08:58 -0400 Subject: [PATCH 002/410] ignore Z3-str source --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 93194fc1b..037d9abd7 100644 --- a/.gitignore +++ b/.gitignore @@ -74,3 +74,6 @@ src/api/ml/z3.mllib *.bak doc/api doc/code +# reference code for z3str2 +Z3-str/** + From 1f96e19211eece215d6deaec3d53018d9c53579f Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 2 Sep 2015 18:55:45 -0400 Subject: [PATCH 003/410] failing test case: SMT2 parse string constants --- src/test/smt2print_parse.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/test/smt2print_parse.cpp b/src/test/smt2print_parse.cpp index 39543d141..349a3c9c8 100644 --- a/src/test/smt2print_parse.cpp +++ b/src/test/smt2print_parse.cpp @@ -98,6 +98,12 @@ void tst_smt2print_parse() { test_parseprint(spec5); + // Test strings + char const* spec6 = + "(assert (= \"abc\" \"abc\"))"; + + test_parseprint(spec6); + // Test ? } From e48ac4a97af1150ac3ce72c454dd2102f35bb32f Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 2 Sep 2015 21:12:03 -0400 Subject: [PATCH 004/410] create and register string theory plugin the parser gets a little bit further now! rejects input with "unexpected character" --- .gitignore | 1 + src/ast/ast.cpp | 5 +++ src/ast/ast.h | 6 +++ src/ast/reg_decl_plugins.cpp | 4 ++ src/ast/str_decl_plugin.cpp | 74 ++++++++++++++++++++++++++++++++ src/ast/str_decl_plugin.h | 75 +++++++++++++++++++++++++++++++++ src/parsers/smt2/smt2parser.cpp | 18 ++++++++ 7 files changed, 183 insertions(+) create mode 100644 src/ast/str_decl_plugin.cpp create mode 100644 src/ast/str_decl_plugin.h diff --git a/.gitignore b/.gitignore index 037d9abd7..97ca67cf4 100644 --- a/.gitignore +++ b/.gitignore @@ -75,5 +75,6 @@ src/api/ml/z3.mllib doc/api doc/code # reference code for z3str2 +Z3-str Z3-str/** diff --git a/src/ast/ast.cpp b/src/ast/ast.cpp index 3a6275e33..5a2dc4a52 100644 --- a/src/ast/ast.cpp +++ b/src/ast/ast.cpp @@ -17,6 +17,7 @@ Revision History: --*/ #include +#include #include"ast.h" #include"ast_pp.h" #include"ast_ll_pp.h" @@ -58,6 +59,7 @@ parameter& parameter::operator=(parameter const& other) { case PARAM_SYMBOL: new (m_symbol) symbol(other.get_symbol()); break; case PARAM_RATIONAL: new (m_rational) rational(other.get_rational()); break; case PARAM_DOUBLE: m_dval = other.m_dval; break; + case PARAM_STRING: m_string = other.m_string; break; case PARAM_EXTERNAL: m_ext_id = other.m_ext_id; break; default: UNREACHABLE(); @@ -90,6 +92,7 @@ bool parameter::operator==(parameter const & p) const { case PARAM_SYMBOL: return get_symbol() == p.get_symbol(); case PARAM_RATIONAL: return get_rational() == p.get_rational(); case PARAM_DOUBLE: return m_dval == p.m_dval; + case PARAM_STRING: return (m_string == NULL && p.m_string == NULL) || strcmp(m_string, p.m_string)==0; case PARAM_EXTERNAL: return m_ext_id == p.m_ext_id; default: UNREACHABLE(); return false; } @@ -103,6 +106,7 @@ unsigned parameter::hash() const { case PARAM_SYMBOL: b = get_symbol().hash(); break; case PARAM_RATIONAL: b = get_rational().hash(); break; case PARAM_DOUBLE: b = static_cast(m_dval); break; + case PARAM_STRING: /* TODO */ b = 42; break; case PARAM_EXTERNAL: b = m_ext_id; break; } return (b << 2) | m_kind; @@ -115,6 +119,7 @@ std::ostream& parameter::display(std::ostream& out) const { case PARAM_RATIONAL: return out << get_rational(); case PARAM_AST: return out << "#" << get_ast()->get_id(); case PARAM_DOUBLE: return out << m_dval; + case PARAM_STRING: return out << m_string; case PARAM_EXTERNAL: return out << "@" << m_ext_id; default: UNREACHABLE(); diff --git a/src/ast/ast.h b/src/ast/ast.h index a5f5c286f..9c1044ec7 100644 --- a/src/ast/ast.h +++ b/src/ast/ast.h @@ -86,6 +86,7 @@ public: PARAM_SYMBOL, PARAM_RATIONAL, PARAM_DOUBLE, + PARAM_STRING, // PARAM_EXTERNAL is used for handling decl_plugin specific parameters. // For example, it is used for handling mpf numbers in float_decl_plugin, // and irrational algebraic numbers in arith_decl_plugin. @@ -104,6 +105,7 @@ private: char m_symbol[sizeof(symbol)]; // for PARAM_SYMBOL char m_rational[sizeof(rational)]; // for PARAM_RATIONAL double m_dval; // for PARAM_DOUBLE (remark: this is not used in float_decl_plugin) + const char* m_string; // for PARAM_STRING unsigned m_ext_id; // for PARAM_EXTERNAL }; @@ -116,6 +118,7 @@ public: explicit parameter(symbol const & s): m_kind(PARAM_SYMBOL) { new (m_symbol) symbol(s); } explicit parameter(rational const & r): m_kind(PARAM_RATIONAL) { new (m_rational) rational(r); } explicit parameter(double d):m_kind(PARAM_DOUBLE), m_dval(d) {} + explicit parameter(const char *s):m_kind(PARAM_STRING), m_string(s) {} explicit parameter(unsigned ext_id, bool):m_kind(PARAM_EXTERNAL), m_ext_id(ext_id) {} parameter(parameter const&); @@ -129,6 +132,7 @@ public: bool is_symbol() const { return m_kind == PARAM_SYMBOL; } bool is_rational() const { return m_kind == PARAM_RATIONAL; } bool is_double() const { return m_kind == PARAM_DOUBLE; } + bool is_string() const { return m_kind == PARAM_STRING; } bool is_external() const { return m_kind == PARAM_EXTERNAL; } bool is_int(int & i) const { return is_int() && (i = get_int(), true); } @@ -136,6 +140,7 @@ public: bool is_symbol(symbol & s) const { return is_symbol() && (s = get_symbol(), true); } bool is_rational(rational & r) const { return is_rational() && (r = get_rational(), true); } bool is_double(double & d) const { return is_double() && (d = get_double(), true); } + // TODO is_string(char*) bool is_external(unsigned & id) const { return is_external() && (id = get_ext_id(), true); } /** @@ -155,6 +160,7 @@ public: symbol const & get_symbol() const { SASSERT(is_symbol()); return *(reinterpret_cast(m_symbol)); } rational const & get_rational() const { SASSERT(is_rational()); return *(reinterpret_cast(m_rational)); } double get_double() const { SASSERT(is_double()); return m_dval; } + const char * get_string() const { SASSERT(is_string()); return m_string; } unsigned get_ext_id() const { SASSERT(is_external()); return m_ext_id; } bool operator==(parameter const & p) const; diff --git a/src/ast/reg_decl_plugins.cpp b/src/ast/reg_decl_plugins.cpp index f46dd76d4..6a7e7b30c 100644 --- a/src/ast/reg_decl_plugins.cpp +++ b/src/ast/reg_decl_plugins.cpp @@ -25,6 +25,7 @@ Revision History: #include"dl_decl_plugin.h" #include"seq_decl_plugin.h" #include"fpa_decl_plugin.h" +#include"str_decl_plugin.h" void reg_decl_plugins(ast_manager & m) { if (!m.get_plugin(m.mk_family_id(symbol("arith")))) { @@ -48,4 +49,7 @@ void reg_decl_plugins(ast_manager & m) { if (!m.get_plugin(m.mk_family_id(symbol("fpa")))) { m.register_plugin(symbol("fpa"), alloc(fpa_decl_plugin)); } + if (!m.get_plugin(m.mk_family_id(symbol("str")))) { + m.register_plugin(symbol("str"), alloc(str_decl_plugin)); + } } diff --git a/src/ast/str_decl_plugin.cpp b/src/ast/str_decl_plugin.cpp new file mode 100644 index 000000000..540cd89c0 --- /dev/null +++ b/src/ast/str_decl_plugin.cpp @@ -0,0 +1,74 @@ +/*++ +Module Name: + + str_decl_plugin.h + +Abstract: + + + +Author: + + Murphy Berzish (mtrberzi) 2015-09-02. + +Revision History: + +--*/ +#include +#include"str_decl_plugin.h" +#include"string_buffer.h" +#include"warning.h" +#include"ast_pp.h" +#include"ast_smt2_pp.h" + +str_decl_plugin::str_decl_plugin(): + m_strv_sym("String"), + m_str_decl(0){ +} + +str_decl_plugin::~str_decl_plugin(){ +} + +void str_decl_plugin::finalize(void) { + #define DEC_REF(decl) if (decl) { m_manager->dec_ref(decl); } ((void) 0) + DEC_REF(m_str_decl); +} + +void str_decl_plugin::set_manager(ast_manager * m, family_id id) { + decl_plugin::set_manager(m, id); + m_str_decl = m->mk_sort(symbol("String"), sort_info(id, STRING_SORT)); + m->inc_ref(m_str_decl); + sort * s = m_str_decl; + /* TODO mk_pred, etc. */ +} + +decl_plugin * str_decl_plugin::mk_fresh() { + return alloc(str_decl_plugin); +} + +sort * str_decl_plugin::mk_sort(decl_kind k, unsigned num_parameters, parameter const * parameters) { + switch (k) { + case STRING_SORT: return m_str_decl; + default: return 0; + } +} + +func_decl * str_decl_plugin::mk_func_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain, sort * range) { + /* TODO */ + m_manager->raise_exception("str_decl_plugin::mk_func_decl() not yet implemented"); return 0; +} + +app * str_decl_plugin::mk_string(const char * val) { + parameter p[1] = {parameter(val)}; + func_decl * d; + d = m_manager->mk_const_decl(m_strv_sym, m_str_decl, func_decl_info(m_family_id, OP_STR, 1, p)); + return m_manager->mk_const(d); +} + +str_util::str_util(ast_manager &m) : + str_recognizers(m.mk_family_id(symbol("str"))), + m_manager(m) { + SASSERT(m.has_plugin(symbol("str"))); + m_plugin = static_cast(m.get_plugin(m.mk_family_id(symbol("str")))); +} diff --git a/src/ast/str_decl_plugin.h b/src/ast/str_decl_plugin.h new file mode 100644 index 000000000..2fd1db022 --- /dev/null +++ b/src/ast/str_decl_plugin.h @@ -0,0 +1,75 @@ +/*++ +Module Name: + + str_decl_plugin.h + +Abstract: + + + +Author: + + Murphy Berzish (mtrberzi) 2015-09-02. + +Revision History: + +--*/ +#ifndef _STR_DECL_PLUGIN_H_ +#define _STR_DECL_PLUGIN_H_ + +#include"ast.h" + +enum str_sort_kind { + STRING_SORT, +}; + +enum str_op_kind { + OP_STR, /* string constants */ + + LAST_STR_OP +}; + +class str_decl_plugin : public decl_plugin { +protected: + symbol m_strv_sym; + sort * m_str_decl; + + virtual void set_manager(ast_manager * m, family_id id); +public: + str_decl_plugin(); + virtual ~str_decl_plugin(); + virtual void finalize(); + + virtual decl_plugin * mk_fresh(); + virtual sort * mk_sort(decl_kind k, unsigned num_parameters, parameter const * parameters); + virtual func_decl * mk_func_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain, sort * range); + + app * mk_string(const char * val); + // TODO +}; + +class str_recognizers { + family_id m_afid; +public: + str_recognizers(family_id fid):m_afid(fid) {} + family_id get_fid() const { return m_afid; } + family_id get_family_id() const { return get_fid(); } + // TODO +}; + +class str_util : public str_recognizers { + ast_manager & m_manager; + str_decl_plugin * m_plugin; +public: + str_util(ast_manager & m); + ast_manager & get_manager() const { return m_manager; } + str_decl_plugin & plugin() { return *m_plugin; } + + app * mk_string(const char * val) { + return m_plugin->mk_string(val); + } + // TODO +}; + +#endif /* _STR_DECL_PLUGIN_H_ */ diff --git a/src/parsers/smt2/smt2parser.cpp b/src/parsers/smt2/smt2parser.cpp index 785f578f3..af752c82d 100644 --- a/src/parsers/smt2/smt2parser.cpp +++ b/src/parsers/smt2/smt2parser.cpp @@ -22,6 +22,7 @@ Revision History: #include"datatype_decl_plugin.h" #include"bv_decl_plugin.h" #include"arith_decl_plugin.h" +#include"str_decl_plugin.h" #include"ast_pp.h" #include"well_sorted.h" #include"pattern_validation.h" @@ -64,6 +65,7 @@ namespace smt2 { scoped_ptr m_bv_util; scoped_ptr m_arith_util; + scoped_ptr m_str_util; scoped_ptr m_pattern_validator; scoped_ptr m_var_shifter; @@ -272,6 +274,12 @@ namespace smt2 { return *(m_bv_util.get()); } + str_util & strutil() { + if (m_str_util.get() == 0) + m_str_util = alloc(str_util, m()); + return *(m_str_util.get()); + } + pattern_validator & pat_validator() { if (m_pattern_validator.get() == 0) { m_pattern_validator = alloc(pattern_validator, m()); @@ -1054,6 +1062,13 @@ namespace smt2 { next(); } + void parse_string() { + SASSERT(curr() == scanner::STRING_TOKEN); + TRACE("parse_string", tout << "new string constant: " << m_scanner.get_string() << "\n";); + expr_stack().push_back(strutil().mk_string(m_scanner.get_string())); + next(); + } + void push_pattern_frame() { // TODO: It seems the only reliable way to parse patterns is: // Parse as an S-Expr, then try to convert it to an useful pattern. @@ -1713,6 +1728,9 @@ namespace smt2 { case scanner::BV_TOKEN: parse_bv_numeral(); break; + case scanner::STRING_TOKEN: + parse_string(); + break; case scanner::LEFT_PAREN: push_expr_frame(m_num_expr_frames == 0 ? 0 : static_cast(m_stack.top())); break; From 02345ee5f190d7033373523743329ec5de016b78 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Thu, 3 Sep 2015 00:17:05 -0400 Subject: [PATCH 005/410] fix string constant representation in parser spec1 loopback OK --- src/ast/ast.h | 4 +++- src/ast/ast_smt_pp.cpp | 9 +++++++++ src/ast/str_decl_plugin.cpp | 8 ++++++++ src/ast/str_decl_plugin.h | 2 ++ src/parsers/smt2/smt2parser.cpp | 9 +++++++-- src/test/smt2print_parse.cpp | 13 +++++++++---- 6 files changed, 38 insertions(+), 7 deletions(-) diff --git a/src/ast/ast.h b/src/ast/ast.h index 9c1044ec7..14b869e51 100644 --- a/src/ast/ast.h +++ b/src/ast/ast.h @@ -118,7 +118,9 @@ public: explicit parameter(symbol const & s): m_kind(PARAM_SYMBOL) { new (m_symbol) symbol(s); } explicit parameter(rational const & r): m_kind(PARAM_RATIONAL) { new (m_rational) rational(r); } explicit parameter(double d):m_kind(PARAM_DOUBLE), m_dval(d) {} - explicit parameter(const char *s):m_kind(PARAM_STRING), m_string(s) {} + explicit parameter(const char *s):m_kind(PARAM_STRING), m_string(s) { + TRACE("parse_string", tout << "parameter(const char *): " << s << "\n";); + } explicit parameter(unsigned ext_id, bool):m_kind(PARAM_EXTERNAL), m_ext_id(ext_id) {} parameter(parameter const&); diff --git a/src/ast/ast_smt_pp.cpp b/src/ast/ast_smt_pp.cpp index 805f3070f..0785c7bfc 100644 --- a/src/ast/ast_smt_pp.cpp +++ b/src/ast/ast_smt_pp.cpp @@ -24,6 +24,7 @@ Revision History: #include"ast_smt_pp.h" #include"arith_decl_plugin.h" #include"bv_decl_plugin.h" +#include"str_decl_plugin.h" #include"array_decl_plugin.h" #include"datatype_decl_plugin.h" #include"vector.h" @@ -160,8 +161,10 @@ class smt_printer { unsigned m_num_lets; arith_util m_autil; bv_util m_bvutil; + str_util m_strutil; family_id m_basic_fid; family_id m_bv_fid; + family_id m_str_fid; family_id m_arith_fid; family_id m_array_fid; family_id m_dt_fid; @@ -394,6 +397,7 @@ class smt_printer { void visit_app(app* n) { rational val; + const char *str; bool is_int, pos; buffer names; unsigned bv_size; @@ -436,6 +440,9 @@ class smt_printer { m_out << ") bv1[1])"; } } + else if (m_strutil.is_string(n, &str)) { + m_out << "\"" << str << "\""; + } else if (m_manager.is_label(n, pos, names) && names.size() >= 1) { if (m_is_smt2) { m_out << "(! "; @@ -797,6 +804,7 @@ public: m_num_lets(0), m_autil(m), m_bvutil(m), + m_strutil(m), m_logic(logic), m_AUFLIRA("AUFLIRA"), // It's much easier to read those testcases with that. @@ -809,6 +817,7 @@ public: m_bv_fid = m.mk_family_id("bv"); m_arith_fid = m.mk_family_id("arith"); m_array_fid = m.mk_family_id("array"); + m_str_fid = m.mk_family_id("str"); m_dt_fid = m.mk_family_id("datatype"); } diff --git a/src/ast/str_decl_plugin.cpp b/src/ast/str_decl_plugin.cpp index 540cd89c0..66b6c23fc 100644 --- a/src/ast/str_decl_plugin.cpp +++ b/src/ast/str_decl_plugin.cpp @@ -66,6 +66,14 @@ app * str_decl_plugin::mk_string(const char * val) { return m_manager->mk_const(d); } +bool str_recognizers::is_string(expr const * n, const char ** val) const { + if (!is_app_of(n, m_afid, OP_STR)) + return false; + func_decl * decl = to_app(n)->get_decl(); + *val = decl->get_parameter(0).get_string(); + return true; +} + str_util::str_util(ast_manager &m) : str_recognizers(m.mk_family_id(symbol("str"))), m_manager(m) { diff --git a/src/ast/str_decl_plugin.h b/src/ast/str_decl_plugin.h index 2fd1db022..2d629e006 100644 --- a/src/ast/str_decl_plugin.h +++ b/src/ast/str_decl_plugin.h @@ -55,6 +55,8 @@ public: str_recognizers(family_id fid):m_afid(fid) {} family_id get_fid() const { return m_afid; } family_id get_family_id() const { return get_fid(); } + + bool is_string(expr const * n, const char ** val) const; // TODO }; diff --git a/src/parsers/smt2/smt2parser.cpp b/src/parsers/smt2/smt2parser.cpp index af752c82d..5c8d60700 100644 --- a/src/parsers/smt2/smt2parser.cpp +++ b/src/parsers/smt2/smt2parser.cpp @@ -1064,8 +1064,13 @@ namespace smt2 { void parse_string() { SASSERT(curr() == scanner::STRING_TOKEN); - TRACE("parse_string", tout << "new string constant: " << m_scanner.get_string() << "\n";); - expr_stack().push_back(strutil().mk_string(m_scanner.get_string())); + char const *original_token = m_scanner.get_string(); + size_t bufsize = strlen(original_token); + char * buf = alloc_svect(char, bufsize + 1); + strncpy(buf, original_token, bufsize); + buf[bufsize] = '\0'; + TRACE("parse_string", tout << "new string constant: " << buf << " length=" << bufsize << "\n";); + expr_stack().push_back(strutil().mk_string(buf)); next(); } diff --git a/src/test/smt2print_parse.cpp b/src/test/smt2print_parse.cpp index 349a3c9c8..1b491a022 100644 --- a/src/test/smt2print_parse.cpp +++ b/src/test/smt2print_parse.cpp @@ -7,8 +7,9 @@ void test_print(Z3_context ctx, Z3_ast a) { Z3_set_ast_print_mode(ctx, Z3_PRINT_SMTLIB2_COMPLIANT); char const* spec1 = Z3_benchmark_to_smtlib_string(ctx, "test", 0, 0, 0, 0, 0, a); - std::cout << spec1 << "\n"; + std::cout << "spec1: benchmark->string\n" << spec1 << "\n"; + std::cout << "attempting to parse spec1...\n"; Z3_ast b = Z3_parse_smtlib2_string(ctx, spec1, @@ -18,14 +19,14 @@ void test_print(Z3_context ctx, Z3_ast a) { 0, 0, 0); - + std::cout << "parse successful, converting ast->string\n"; char const* spec2 = Z3_ast_to_string(ctx, b); - std::cout << spec2 << "\n"; + std::cout << "spec2: string->ast->string\n" << spec2 << "\n"; } void test_parseprint(char const* spec) { Z3_context ctx = Z3_mk_context(0); - std::cout << spec << "\n"; + std::cout << "spec:\n" << spec << "\n"; Z3_ast a = Z3_parse_smtlib2_string(ctx, @@ -37,8 +38,12 @@ void test_parseprint(char const* spec) { 0, 0); + std::cout << "done parsing\n"; + test_print(ctx, a); + std::cout << "done printing\n"; + Z3_del_context(ctx); } From 744d2e3c9ca7b2407fb66b27ad9f150fe02dbd29 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Thu, 3 Sep 2015 01:12:08 -0400 Subject: [PATCH 006/410] pretty-printing of string constants in AST spec2 looks good now --- src/ast/ast_smt2_pp.cpp | 15 +++++++++++++++ src/ast/ast_smt2_pp.h | 7 ++++++- src/ast/str_decl_plugin.cpp | 5 +++++ src/ast/str_decl_plugin.h | 1 + src/cmd_context/cmd_context.cpp | 5 ++++- 5 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/ast/ast_smt2_pp.cpp b/src/ast/ast_smt2_pp.cpp index 035e228fb..0006d508c 100644 --- a/src/ast/ast_smt2_pp.cpp +++ b/src/ast/ast_smt2_pp.cpp @@ -298,6 +298,18 @@ format * smt2_pp_environment::mk_float(rational const & val) const { return mk_string(get_manager(), s.c_str()); } +format * smt2_pp_environment::pp_str_literal(app * t) { + TRACE("parse_string", tout << "pp_str_literal\n";); + str_util & u = get_strutil(); + SASSERT(u.is_string(t)); + const char * val; + u.is_string(t, &val); + ast_manager & m = get_manager(); + string_buffer<> buf; + buf << "\"" << val << "\""; + return mk_string(m, buf.c_str()); +} + format * smt2_pp_environment::pp_arith_literal(app * t, bool decimal, unsigned decimal_prec) { arith_util & u = get_autil(); SASSERT(u.is_numeral(t) || u.is_irrational_algebraic_numeral(t)); @@ -581,6 +593,9 @@ class smt2_printer { else if (m_env.get_dlutil().is_numeral(c)) { f = m_env.pp_datalog_literal(c); } + else if (m_env.get_strutil().is_string(c)) { + f = m_env.pp_str_literal(c); + } else { buffer names; if (m().is_label_lit(c, names)) { diff --git a/src/ast/ast_smt2_pp.h b/src/ast/ast_smt2_pp.h index 8aac71b8c..17bc322bc 100644 --- a/src/ast/ast_smt2_pp.h +++ b/src/ast/ast_smt2_pp.h @@ -29,6 +29,7 @@ Revision History: #include"array_decl_plugin.h" #include"fpa_decl_plugin.h" #include"dl_decl_plugin.h" +#include"str_decl_plugin.h" #include"smt2_util.h" class smt2_pp_environment { @@ -47,12 +48,14 @@ public: virtual bv_util & get_bvutil() = 0; virtual array_util & get_arutil() = 0; virtual fpa_util & get_futil() = 0; + virtual str_util & get_strutil() = 0; virtual datalog::dl_decl_util& get_dlutil() = 0; virtual bool uses(symbol const & s) const = 0; virtual format_ns::format * pp_fdecl(func_decl * f, unsigned & len); virtual format_ns::format * pp_bv_literal(app * t, bool use_bv_lits, bool bv_neg); virtual format_ns::format * pp_arith_literal(app * t, bool decimal, unsigned prec); virtual format_ns::format * pp_float_literal(app * t, bool use_bv_lits, bool use_float_real_lits); + virtual format_ns::format * pp_str_literal(app * t); virtual format_ns::format * pp_datalog_literal(app * t); virtual format_ns::format * pp_sort(sort * s); virtual format_ns::format * pp_fdecl_ref(func_decl * f); @@ -70,14 +73,16 @@ class smt2_pp_environment_dbg : public smt2_pp_environment { bv_util m_bvutil; array_util m_arutil; fpa_util m_futil; + str_util m_strutil; datalog::dl_decl_util m_dlutil; public: - smt2_pp_environment_dbg(ast_manager & m):m_manager(m), m_autil(m), m_bvutil(m), m_arutil(m), m_futil(m), m_dlutil(m) {} + smt2_pp_environment_dbg(ast_manager & m):m_manager(m), m_autil(m), m_bvutil(m), m_arutil(m), m_futil(m), m_strutil(m), m_dlutil(m) {} virtual ast_manager & get_manager() const { return m_manager; } virtual arith_util & get_autil() { return m_autil; } virtual bv_util & get_bvutil() { return m_bvutil; } virtual array_util & get_arutil() { return m_arutil; } virtual fpa_util & get_futil() { return m_futil; } + virtual str_util & get_strutil() { return m_strutil; } virtual datalog::dl_decl_util& get_dlutil() { return m_dlutil; } virtual bool uses(symbol const & s) const { return false; } }; diff --git a/src/ast/str_decl_plugin.cpp b/src/ast/str_decl_plugin.cpp index 66b6c23fc..3bd81970d 100644 --- a/src/ast/str_decl_plugin.cpp +++ b/src/ast/str_decl_plugin.cpp @@ -74,6 +74,11 @@ bool str_recognizers::is_string(expr const * n, const char ** val) const { return true; } +bool str_recognizers::is_string(expr const * n) const { + const char * tmp = 0; + return is_string(n, & tmp); +} + str_util::str_util(ast_manager &m) : str_recognizers(m.mk_family_id(symbol("str"))), m_manager(m) { diff --git a/src/ast/str_decl_plugin.h b/src/ast/str_decl_plugin.h index 2d629e006..57829d542 100644 --- a/src/ast/str_decl_plugin.h +++ b/src/ast/str_decl_plugin.h @@ -57,6 +57,7 @@ public: family_id get_family_id() const { return get_fid(); } bool is_string(expr const * n, const char ** val) const; + bool is_string(expr const * n) const; // TODO }; diff --git a/src/cmd_context/cmd_context.cpp b/src/cmd_context/cmd_context.cpp index 0c60d876b..77cbfe132 100644 --- a/src/cmd_context/cmd_context.cpp +++ b/src/cmd_context/cmd_context.cpp @@ -25,6 +25,7 @@ Notes: #include"datatype_decl_plugin.h" #include"seq_decl_plugin.h" #include"fpa_decl_plugin.h" +#include"str_decl_plugin.h" #include"ast_pp.h" #include"var_subst.h" #include"pp.h" @@ -241,6 +242,7 @@ protected: bv_util m_bvutil; array_util m_arutil; fpa_util m_futil; + str_util m_strutil; datalog::dl_decl_util m_dlutil; format_ns::format * pp_fdecl_name(symbol const & s, func_decls const & fs, func_decl * f, unsigned & len) { @@ -261,13 +263,14 @@ protected: } public: - pp_env(cmd_context & o):m_owner(o), m_autil(o.m()), m_bvutil(o.m()), m_arutil(o.m()), m_futil(o.m()), m_dlutil(o.m()) {} + pp_env(cmd_context & o):m_owner(o), m_autil(o.m()), m_bvutil(o.m()), m_arutil(o.m()), m_futil(o.m()), m_strutil(o.m()), m_dlutil(o.m()) {} virtual ~pp_env() {} virtual ast_manager & get_manager() const { return m_owner.m(); } virtual arith_util & get_autil() { return m_autil; } virtual bv_util & get_bvutil() { return m_bvutil; } virtual array_util & get_arutil() { return m_arutil; } virtual fpa_util & get_futil() { return m_futil; } + virtual str_util & get_strutil() { return m_strutil; } virtual datalog::dl_decl_util& get_dlutil() { return m_dlutil; } virtual bool uses(symbol const & s) const { return From 8137e022e3dc27e579521d4bc32fa944462d473c Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sun, 6 Sep 2015 20:53:08 -0400 Subject: [PATCH 007/410] load str decl plugin; recognize String sorted constants --- src/ast/str_decl_plugin.cpp | 10 ++++ src/ast/str_decl_plugin.h | 4 ++ src/cmd_context/cmd_context.cpp | 9 ++- src/cmd_context/cmd_context.h | 1 + src/smt/smt_setup.cpp | 18 ++++++ src/smt/smt_setup.h | 2 + src/smt/theory_str.cpp | 97 +++++++++++++++++++++++++++++++++ src/smt/theory_str.h | 50 +++++++++++++++++ 8 files changed, 190 insertions(+), 1 deletion(-) create mode 100644 src/smt/theory_str.cpp create mode 100644 src/smt/theory_str.h diff --git a/src/ast/str_decl_plugin.cpp b/src/ast/str_decl_plugin.cpp index 3bd81970d..b6ec25c46 100644 --- a/src/ast/str_decl_plugin.cpp +++ b/src/ast/str_decl_plugin.cpp @@ -66,6 +66,16 @@ app * str_decl_plugin::mk_string(const char * val) { return m_manager->mk_const(d); } +void str_decl_plugin::get_op_names(svector & op_names, symbol const & logic) { + // TODO + // we would do something like: + // op_names.push_back(builtin_name("<=",OP_LE)); +} + +void str_decl_plugin::get_sort_names(svector & sort_names, symbol const & logic) { + sort_names.push_back(builtin_name("String", STRING_SORT)); +} + bool str_recognizers::is_string(expr const * n, const char ** val) const { if (!is_app_of(n, m_afid, OP_STR)) return false; diff --git a/src/ast/str_decl_plugin.h b/src/ast/str_decl_plugin.h index 57829d542..854431366 100644 --- a/src/ast/str_decl_plugin.h +++ b/src/ast/str_decl_plugin.h @@ -46,6 +46,10 @@ public: unsigned arity, sort * const * domain, sort * range); app * mk_string(const char * val); + + virtual void get_op_names(svector & op_names, symbol const & logic); + + virtual void get_sort_names(svector & sort_names, symbol const & logic); // TODO }; diff --git a/src/cmd_context/cmd_context.cpp b/src/cmd_context/cmd_context.cpp index 77cbfe132..a7db2f16c 100644 --- a/src/cmd_context/cmd_context.cpp +++ b/src/cmd_context/cmd_context.cpp @@ -563,6 +563,10 @@ bool cmd_context::logic_has_fpa() const { return !has_logic() || m_logic == "QF_FP" || m_logic == "QF_FPBV"; } +bool cmd_context::logic_has_str() const { + return !has_logic() || m_logic == "QF_S"; +} + bool cmd_context::logic_has_array_core(symbol const & s) const { return s == "QF_AX" || @@ -605,6 +609,7 @@ void cmd_context::init_manager_core(bool new_manager) { register_plugin(symbol("datatype"), alloc(datatype_decl_plugin), logic_has_datatype()); register_plugin(symbol("seq"), alloc(seq_decl_plugin), logic_has_seq()); register_plugin(symbol("fpa"), alloc(fpa_decl_plugin), logic_has_fpa()); + register_plugin(symbol("str"), alloc(str_decl_plugin), logic_has_str()); } else { // the manager was created by an external module @@ -618,6 +623,7 @@ void cmd_context::init_manager_core(bool new_manager) { load_plugin(symbol("datatype"), logic_has_datatype(), fids); load_plugin(symbol("seq"), logic_has_seq(), fids); load_plugin(symbol("fpa"), logic_has_fpa(), fids); + load_plugin(symbol("str"), logic_has_str(), fids); svector::iterator it = fids.begin(); svector::iterator end = fids.end(); @@ -671,7 +677,8 @@ bool cmd_context::supported_logic(symbol const & s) const { logic_has_arith_core(s) || logic_has_bv_core(s) || logic_has_array_core(s) || logic_has_seq_core(s) || logic_has_horn(s) || - s == "QF_FP" || s == "QF_FPBV"; + s == "QF_FP" || s == "QF_FPBV" || + s == "QF_S"; } bool cmd_context::set_logic(symbol const & s) { diff --git a/src/cmd_context/cmd_context.h b/src/cmd_context/cmd_context.h index f9e50e611..37dccab8a 100644 --- a/src/cmd_context/cmd_context.h +++ b/src/cmd_context/cmd_context.h @@ -242,6 +242,7 @@ protected: bool logic_has_array() const; bool logic_has_datatype() const; bool logic_has_fpa() const; + bool logic_has_str() const; bool supported_logic(symbol const & s) const; void print_unsupported_msg() { regular_stream() << "unsupported" << std::endl; } diff --git a/src/smt/smt_setup.cpp b/src/smt/smt_setup.cpp index e6d21a1e2..5e4af91fd 100644 --- a/src/smt/smt_setup.cpp +++ b/src/smt/smt_setup.cpp @@ -31,6 +31,7 @@ Revision History: #include"theory_dl.h" #include"theory_seq_empty.h" #include"theory_fpa.h" +#include"theory_str.h" namespace smt { @@ -117,6 +118,8 @@ namespace smt { setup_QF_FP(); else if (m_logic == "QF_FPBV") setup_QF_FPBV(); + else if (m_logic == "QF_S") + setup_QF_S(); else setup_unknown(); } @@ -158,6 +161,8 @@ namespace smt { setup_QF_BVRE(); else if (m_logic == "QF_AUFLIA") setup_QF_AUFLIA(st); + else if (m_logic == "QF_S") + setup_QF_S(); else if (m_logic == "AUFLIA") setup_AUFLIA(st); else if (m_logic == "AUFLIRA") @@ -694,6 +699,11 @@ namespace smt { m_context.register_plugin(alloc(smt::theory_fpa, m_manager)); } + void setup::setup_QF_S() { + setup_QF_LIA(); + m_context.register_plugin(alloc(smt::theory_str, m_manager)); + } + bool is_arith(static_features const & st) { return st.m_num_arith_ineqs > 0 || st.m_num_arith_terms > 0 || st.m_num_arith_eqs > 0; } @@ -800,6 +810,11 @@ namespace smt { m_context.register_plugin(alloc(theory_fpa, m_manager)); } + void setup::setup_str() { + setup_arith(); + m_context.register_plugin(alloc(theory_str, m_manager)); + } + void setup::setup_unknown() { setup_arith(); setup_arrays(); @@ -808,6 +823,7 @@ namespace smt { setup_dl(); setup_seq(); setup_fpa(); + setup_str(); } void setup::setup_unknown(static_features & st) { @@ -906,6 +922,8 @@ namespace smt { return; } + // TODO setup_str() by features + setup_unknown(); } diff --git a/src/smt/smt_setup.h b/src/smt/smt_setup.h index 6cbcb9602..6beb0b239 100644 --- a/src/smt/smt_setup.h +++ b/src/smt/smt_setup.h @@ -77,6 +77,7 @@ namespace smt { void setup_QF_AUFLIA(static_features const & st); void setup_QF_FP(); void setup_QF_FPBV(); + void setup_QF_S(); void setup_LRA(); void setup_AUFLIA(bool simple_array = true); void setup_AUFLIA(static_features const & st); @@ -98,6 +99,7 @@ namespace smt { void setup_i_arith(); void setup_mi_arith(); void setup_fpa(); + void setup_str(); public: setup(context & c, smt_params & params); diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp new file mode 100644 index 000000000..bc1b59551 --- /dev/null +++ b/src/smt/theory_str.cpp @@ -0,0 +1,97 @@ +/*++ +Module Name: + + theory_str.cpp + +Abstract: + + String Theory Plugin + +Author: + + Murphy Berzish (mtrberzi) 2015-09-03 + +Revision History: + +--*/ +#include"ast_smt2_pp.h" +#include"smt_context.h" +#include"theory_str.h" +#include"smt_model_generator.h" + +namespace smt { + +theory_str::theory_str(ast_manager &m): + theory(m.mk_family_id("str")) +{ +} + +theory_str::~theory_str() { +} + +bool theory_str::internalize_atom(app * atom, bool gate_ctx) { + // TODO I have no idea if this is correct. + TRACE("t_str", tout << "internalizing atom: " << mk_ismt2_pp(atom, get_manager()) << "\n";); + SASSERT(atom->get_family_id() == get_family_id()); + + ast_manager & m = get_manager(); + context & ctx = get_context(); + + if (ctx.b_internalized(atom)) + return true; + + unsigned num_args = atom->get_num_args(); + for (unsigned i = 0; i < num_args; i++) + ctx.internalize(atom->get_arg(i), false); + + literal l(ctx.mk_bool_var(atom)); + ctx.set_var_theory(l.var(), get_id()); + + return true; +} + +bool theory_str::internalize_term(app * term) { + // TODO I have no idea if this is correct either. + ast_manager & m = get_manager(); + context & ctx = get_context(); + TRACE("t_str", tout << "internalizing term: " << mk_ismt2_pp(term, get_manager()) << "\n";); + SASSERT(term->get_family_id() == get_family_id()); + SASSERT(!ctx.e_internalized(term)); + + unsigned num_args = term->get_num_args(); + for (unsigned i = 0; i < num_args; i++) + ctx.internalize(term->get_arg(i), false); + + enode * e = (ctx.e_internalized(term)) ? ctx.get_enode(term) : + ctx.mk_enode(term, false, false, true); + + if (is_attached_to_var(e)) + return false; + + attach_new_th_var(e); + + return true; +} + +void theory_str::attach_new_th_var(enode * n) { + context & ctx = get_context(); + theory_var v = mk_var(n); + ctx.attach_th_var(n, this, v); + TRACE("t_str_detail", tout << "new theory var: " << mk_ismt2_pp(n->get_owner(), get_manager()) << " := " << v << "\n";); +} + +void theory_str::new_eq_eh(theory_var x, theory_var y) { + // TODO + TRACE("t_str", tout << "new eq: " << x << " = " << y << std::endl;); + TRACE("t_str_detail", tout << mk_ismt2_pp(get_enode(x)->get_owner(), get_manager()) << " = " << + mk_ismt2_pp(get_enode(y)->get_owner(), get_manager()) << std::endl;); +} + +void theory_str::new_diseq_eh(theory_var x, theory_var y) { + // TODO + TRACE("t_str", tout << "new diseq: " << x << " != " << y << std::endl;); + TRACE("t_str_detail", tout << mk_ismt2_pp(get_enode(x)->get_owner(), get_manager()) << " != " << + mk_ismt2_pp(get_enode(y)->get_owner(), get_manager()) << std::endl;); +} + +}; /* namespace smt */ diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h new file mode 100644 index 000000000..7bb5b5148 --- /dev/null +++ b/src/smt/theory_str.h @@ -0,0 +1,50 @@ +/*++ +Module Name: + + theory_str.h + +Abstract: + + String Theory Plugin + +Author: + + Murphy Berzish (mtrberzi) 2015-09-03 + +Revision History: + +--*/ +#ifndef _THEORY_STR_H_ +#define _THEORY_STR_H_ + +#include"smt_theory.h" +#include"trail.h" +#include"th_rewriter.h" +#include"value_factory.h" +#include"smt_model_generator.h" + +namespace smt { + + class str_value_factory : public value_factory { + // TODO + }; + + class theory_str : public theory { + // TODO + protected: + virtual bool internalize_atom(app * atom, bool gate_ctx); + virtual bool internalize_term(app * term); + + virtual void new_eq_eh(theory_var, theory_var); + virtual void new_diseq_eh(theory_var, theory_var); + virtual theory* mk_fresh(context*) { return alloc(theory_str, get_manager()); } + public: + theory_str(ast_manager& m); + virtual ~theory_str(); + protected: + void attach_new_th_var(enode * n); + }; + +}; + +#endif /* _THEORY_STR_H_ */ From f0c301e920affab0ae2586a762a74280b488412f Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sun, 6 Sep 2015 21:05:32 -0400 Subject: [PATCH 008/410] register Concat function now reaches str_decl_plugin::mk_func_decl() --- src/ast/str_decl_plugin.cpp | 19 ++++++++++++++----- src/ast/str_decl_plugin.h | 5 ++++- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/ast/str_decl_plugin.cpp b/src/ast/str_decl_plugin.cpp index b6ec25c46..eb309ecf0 100644 --- a/src/ast/str_decl_plugin.cpp +++ b/src/ast/str_decl_plugin.cpp @@ -23,7 +23,8 @@ Revision History: str_decl_plugin::str_decl_plugin(): m_strv_sym("String"), - m_str_decl(0){ + m_str_decl(0), + m_concat_decl(0){ } str_decl_plugin::~str_decl_plugin(){ @@ -39,7 +40,17 @@ void str_decl_plugin::set_manager(ast_manager * m, family_id id) { m_str_decl = m->mk_sort(symbol("String"), sort_info(id, STRING_SORT)); m->inc_ref(m_str_decl); sort * s = m_str_decl; - /* TODO mk_pred, etc. */ + +#define MK_AC_OP(FIELD, NAME, KIND, SORT) { \ + func_decl_info info(id, KIND); \ + info.set_associative(); \ + info.set_flat_associative(); \ + info.set_commutative(); \ + FIELD = m->mk_func_decl(symbol(NAME), SORT, SORT, SORT, info); \ + m->inc_ref(FIELD); \ + } + + MK_AC_OP(m_concat_decl, "Concat", OP_STRCAT, s); } decl_plugin * str_decl_plugin::mk_fresh() { @@ -67,9 +78,7 @@ app * str_decl_plugin::mk_string(const char * val) { } void str_decl_plugin::get_op_names(svector & op_names, symbol const & logic) { - // TODO - // we would do something like: - // op_names.push_back(builtin_name("<=",OP_LE)); + op_names.push_back(builtin_name("Concat", OP_STRCAT)); } void str_decl_plugin::get_sort_names(svector & sort_names, symbol const & logic) { diff --git a/src/ast/str_decl_plugin.h b/src/ast/str_decl_plugin.h index 854431366..16e1ef4a3 100644 --- a/src/ast/str_decl_plugin.h +++ b/src/ast/str_decl_plugin.h @@ -25,7 +25,8 @@ enum str_sort_kind { enum str_op_kind { OP_STR, /* string constants */ - + // + OP_STRCAT, LAST_STR_OP }; @@ -34,6 +35,8 @@ protected: symbol m_strv_sym; sort * m_str_decl; + func_decl * m_concat_decl; + virtual void set_manager(ast_manager * m, family_id id); public: str_decl_plugin(); From 7f0d9157ac9ec470f958902401e957e72871f177 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sun, 6 Sep 2015 21:47:57 -0400 Subject: [PATCH 009/410] at least for now, Concat is no longer associative this means that we'll always have (Concat a b) instead of variadic forms --- src/ast/str_decl_plugin.cpp | 31 ++++++++++++++++++++----------- src/ast/str_decl_plugin.h | 2 ++ 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/ast/str_decl_plugin.cpp b/src/ast/str_decl_plugin.cpp index eb309ecf0..70c8a6ebe 100644 --- a/src/ast/str_decl_plugin.cpp +++ b/src/ast/str_decl_plugin.cpp @@ -41,16 +41,11 @@ void str_decl_plugin::set_manager(ast_manager * m, family_id id) { m->inc_ref(m_str_decl); sort * s = m_str_decl; -#define MK_AC_OP(FIELD, NAME, KIND, SORT) { \ - func_decl_info info(id, KIND); \ - info.set_associative(); \ - info.set_flat_associative(); \ - info.set_commutative(); \ - FIELD = m->mk_func_decl(symbol(NAME), SORT, SORT, SORT, info); \ - m->inc_ref(FIELD); \ - } +#define MK_OP(FIELD, NAME, KIND, SORT) \ + FIELD = m->mk_func_decl(symbol(NAME), SORT, SORT, SORT, func_decl_info(id, KIND)); \ + m->inc_ref(FIELD) - MK_AC_OP(m_concat_decl, "Concat", OP_STRCAT, s); + MK_OP(m_concat_decl, "Concat", OP_STRCAT, s); } decl_plugin * str_decl_plugin::mk_fresh() { @@ -64,10 +59,24 @@ sort * str_decl_plugin::mk_sort(decl_kind k, unsigned num_parameters, parameter } } +func_decl * str_decl_plugin::mk_func_decl(decl_kind k) { + switch(k) { + case OP_STRCAT: return m_concat_decl; + default: return 0; + } +} + func_decl * str_decl_plugin::mk_func_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, unsigned arity, sort * const * domain, sort * range) { - /* TODO */ - m_manager->raise_exception("str_decl_plugin::mk_func_decl() not yet implemented"); return 0; + if (k == OP_STR) { + m_manager->raise_exception("OP_STR not yet implemented in mk_func_decl!"); + return 0; + } + if (arity == 0) { + m_manager->raise_exception("no arguments supplied to string operator"); + return 0; + } + return mk_func_decl(k); } app * str_decl_plugin::mk_string(const char * val) { diff --git a/src/ast/str_decl_plugin.h b/src/ast/str_decl_plugin.h index 16e1ef4a3..d190e9ff7 100644 --- a/src/ast/str_decl_plugin.h +++ b/src/ast/str_decl_plugin.h @@ -38,6 +38,8 @@ protected: func_decl * m_concat_decl; virtual void set_manager(ast_manager * m, family_id id); + + func_decl * mk_func_decl(decl_kind k); public: str_decl_plugin(); virtual ~str_decl_plugin(); From dc86385e7fb635484b596fc9fdd7ab17274e0e55 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Mon, 7 Sep 2015 16:13:48 -0400 Subject: [PATCH 010/410] add Length function to theory of strings --- src/ast/str_decl_plugin.cpp | 13 +++++++++++++ src/ast/str_decl_plugin.h | 6 ++++++ src/smt/theory_str.cpp | 37 +++++++++++++++++++++++++++++++++++-- src/smt/theory_str.h | 8 ++++++++ 4 files changed, 62 insertions(+), 2 deletions(-) diff --git a/src/ast/str_decl_plugin.cpp b/src/ast/str_decl_plugin.cpp index 70c8a6ebe..fa0a26f25 100644 --- a/src/ast/str_decl_plugin.cpp +++ b/src/ast/str_decl_plugin.cpp @@ -33,6 +33,9 @@ str_decl_plugin::~str_decl_plugin(){ void str_decl_plugin::finalize(void) { #define DEC_REF(decl) if (decl) { m_manager->dec_ref(decl); } ((void) 0) DEC_REF(m_str_decl); + DEC_REF(m_concat_decl); + DEC_REF(m_length_decl); + DEC_REF(m_int_sort); } void str_decl_plugin::set_manager(ast_manager * m, family_id id) { @@ -41,11 +44,19 @@ void str_decl_plugin::set_manager(ast_manager * m, family_id id) { m->inc_ref(m_str_decl); sort * s = m_str_decl; + m_arith_fid = m_manager->mk_family_id("arith"); + m_int_sort = m_manager->mk_sort(m_arith_fid, INT_SORT); + SASSERT(m_int_sort != 0); // arith_decl_plugin must be installed before str_decl_plugin. + m_manager->inc_ref(m_int_sort); + sort * i = m_int_sort; + #define MK_OP(FIELD, NAME, KIND, SORT) \ FIELD = m->mk_func_decl(symbol(NAME), SORT, SORT, SORT, func_decl_info(id, KIND)); \ m->inc_ref(FIELD) MK_OP(m_concat_decl, "Concat", OP_STRCAT, s); + + m_length_decl = m->mk_func_decl(symbol("Length"), s, i); m_manager->inc_ref(m_length_decl); } decl_plugin * str_decl_plugin::mk_fresh() { @@ -62,6 +73,7 @@ sort * str_decl_plugin::mk_sort(decl_kind k, unsigned num_parameters, parameter func_decl * str_decl_plugin::mk_func_decl(decl_kind k) { switch(k) { case OP_STRCAT: return m_concat_decl; + case OP_STRLEN: return m_length_decl; default: return 0; } } @@ -88,6 +100,7 @@ app * str_decl_plugin::mk_string(const char * val) { void str_decl_plugin::get_op_names(svector & op_names, symbol const & logic) { op_names.push_back(builtin_name("Concat", OP_STRCAT)); + op_names.push_back(builtin_name("Length", OP_STRLEN)); } void str_decl_plugin::get_sort_names(svector & sort_names, symbol const & logic) { diff --git a/src/ast/str_decl_plugin.h b/src/ast/str_decl_plugin.h index d190e9ff7..3fd5fb7e6 100644 --- a/src/ast/str_decl_plugin.h +++ b/src/ast/str_decl_plugin.h @@ -18,6 +18,7 @@ Revision History: #define _STR_DECL_PLUGIN_H_ #include"ast.h" +#include"arith_decl_plugin.h" enum str_sort_kind { STRING_SORT, @@ -27,6 +28,7 @@ enum str_op_kind { OP_STR, /* string constants */ // OP_STRCAT, + OP_STRLEN, LAST_STR_OP }; @@ -35,7 +37,11 @@ protected: symbol m_strv_sym; sort * m_str_decl; + sort * m_int_sort; + family_id m_arith_fid; + func_decl * m_concat_decl; + func_decl * m_length_decl; virtual void set_manager(ast_manager * m, family_id id); diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index bc1b59551..2bf67ed81 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -31,7 +31,7 @@ theory_str::~theory_str() { bool theory_str::internalize_atom(app * atom, bool gate_ctx) { // TODO I have no idea if this is correct. - TRACE("t_str", tout << "internalizing atom: " << mk_ismt2_pp(atom, get_manager()) << "\n";); + TRACE("t_str", tout << "internalizing atom: " << mk_ismt2_pp(atom, get_manager()) << std::endl;); SASSERT(atom->get_family_id() == get_family_id()); ast_manager & m = get_manager(); @@ -54,7 +54,7 @@ bool theory_str::internalize_term(app * term) { // TODO I have no idea if this is correct either. ast_manager & m = get_manager(); context & ctx = get_context(); - TRACE("t_str", tout << "internalizing term: " << mk_ismt2_pp(term, get_manager()) << "\n";); + TRACE("t_str", tout << "internalizing term: " << mk_ismt2_pp(term, get_manager()) << std::endl;); SASSERT(term->get_family_id() == get_family_id()); SASSERT(!ctx.e_internalized(term)); @@ -80,6 +80,20 @@ void theory_str::attach_new_th_var(enode * n) { TRACE("t_str_detail", tout << "new theory var: " << mk_ismt2_pp(n->get_owner(), get_manager()) << " := " << v << "\n";); } +void theory_str::init_search_eh() { + ast_manager & m = get_manager(); + context & ctx = get_context(); + TRACE("t_str", + tout << "search started, assignments are:" << std::endl; + expr_ref_vector assignment(m); + ctx.get_assignments(assignment); + for (expr_ref_vector::iterator i = assignment.begin(); i != assignment.end(); ++i) { + expr * ex = *i; + tout << mk_ismt2_pp(ex, m) << std::endl; + } + ); +} + void theory_str::new_eq_eh(theory_var x, theory_var y) { // TODO TRACE("t_str", tout << "new eq: " << x << " = " << y << std::endl;); @@ -94,4 +108,23 @@ void theory_str::new_diseq_eh(theory_var x, theory_var y) { mk_ismt2_pp(get_enode(y)->get_owner(), get_manager()) << std::endl;); } +void theory_str::relevant_eh(app * n) { + TRACE("t_str", tout << "relevant: " << mk_ismt2_pp(n, get_manager()) << "\n";); +} + +void theory_str::assign_eh(bool_var v, bool is_true) { + context & ctx = get_context(); + TRACE("t_str", tout << "assert: v" << v << " #" << ctx.bool_var2expr(v)->get_id() << " is_true: " << is_true << "\n";); +} + +void theory_str::push_scope_eh() { + TRACE("t_str", tout << "push" << std::endl;); +} + +final_check_status theory_str::final_check_eh() { + // TODO + TRACE("t_str", tout << "final check" << std::endl;); + return FC_DONE; +} + }; /* namespace smt */ diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 7bb5b5148..5ee5502de 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -38,6 +38,14 @@ namespace smt { virtual void new_eq_eh(theory_var, theory_var); virtual void new_diseq_eh(theory_var, theory_var); virtual theory* mk_fresh(context*) { return alloc(theory_str, get_manager()); } + + virtual void init_search_eh(); + + virtual void relevant_eh(app * n); + virtual void assign_eh(bool_var v, bool is_true); + virtual void push_scope_eh(); + + virtual final_check_status final_check_eh(); public: theory_str(ast_manager& m); virtual ~theory_str(); From 9b04f1570f45b55d409f981bd389c46dc14825e0 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Mon, 7 Sep 2015 19:40:25 -0400 Subject: [PATCH 011/410] instantiate length axiom for concatenation --- src/ast/str_decl_plugin.cpp | 3 +- src/smt/theory_str.cpp | 80 ++++++++++++++++++++++++++++++++++++- src/smt/theory_str.h | 15 ++++++- 3 files changed, 94 insertions(+), 4 deletions(-) diff --git a/src/ast/str_decl_plugin.cpp b/src/ast/str_decl_plugin.cpp index fa0a26f25..0e74493ff 100644 --- a/src/ast/str_decl_plugin.cpp +++ b/src/ast/str_decl_plugin.cpp @@ -24,7 +24,8 @@ Revision History: str_decl_plugin::str_decl_plugin(): m_strv_sym("String"), m_str_decl(0), - m_concat_decl(0){ + m_concat_decl(0), + m_length_decl(0){ } str_decl_plugin::~str_decl_plugin(){ diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 2bf67ed81..c6d51b1a4 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -18,17 +18,39 @@ Revision History: #include"smt_context.h" #include"theory_str.h" #include"smt_model_generator.h" +#include"ast_pp.h" +#include"ast_ll_pp.h" namespace smt { -theory_str::theory_str(ast_manager &m): - theory(m.mk_family_id("str")) +theory_str::theory_str(ast_manager & m): + theory(m.mk_family_id("str")), + search_started(false), + m_autil(m) { } theory_str::~theory_str() { } +void theory_str::assert_axiom(unsigned num_lits, literal * lits) { + context & ctx = get_context(); + TRACE("t_str_detail", + tout << "assert_axiom: literals:\n"; + for (unsigned i = 0; i < num_lits; ++i) { + expr * e = ctx.bool_var2expr(lits[i].var()); + if (lits[i].sign()) + tout << "not "; + tout << mk_pp(e, get_manager()) << " "; + tout << "\n"; + }); + ctx.mk_th_axiom(get_id(), num_lits, lits); +} + +void theory_str::assert_axiom(literal l) { + assert_axiom(1, &l); +} + bool theory_str::internalize_atom(app * atom, bool gate_ctx) { // TODO I have no idea if this is correct. TRACE("t_str", tout << "internalizing atom: " << mk_ismt2_pp(atom, get_manager()) << std::endl;); @@ -70,9 +92,62 @@ bool theory_str::internalize_term(app * term) { attach_new_th_var(e); + if (is_concat(term)) { + instantiate_concat_axiom(e); + } + return true; } +app * theory_str::mk_strlen(app * e) { + expr * args[1] = {e}; + return get_manager().mk_app(get_id(), OP_STRLEN, 0, 0, 1, args); +} + +/* + * Instantiate an axiom of the following form: + * Length(Concat(x, y)) = Length(x) + Length(y) + */ +void theory_str::instantiate_concat_axiom(enode * cat) { + SASSERT(is_concat(cat)); + app * a_cat = cat->get_owner(); + + context & ctx = get_context(); + ast_manager & m = get_manager(); + + // build LHS + expr_ref len_xy(m); + // TODO re-use ASTs for length subexpressions, like in old Z3-str? + // TODO should we use str_util for these and other expressions? + len_xy = mk_strlen(a_cat); + SASSERT(len_xy); + + // build RHS: start by extracting x and y from Concat(x, y) + unsigned nArgs = a_cat->get_num_args(); + SASSERT(nArgs == 2); + app * a_x = to_app(a_cat->get_arg(0)); + app * a_y = to_app(a_cat->get_arg(1)); + + expr_ref len_x(m); + len_x = mk_strlen(a_x); + SASSERT(len_x); + + expr_ref len_y(m); + len_y = mk_strlen(a_y); + SASSERT(len_y); + + // now build len_x + len_y + app * len_x_plus_len_y = m_autil.mk_add(len_x, len_y); + SASSERT(len_x_plus_len_y); + + TRACE("t_str", tout << mk_bounded_pp(len_xy, m) << " = " << mk_bounded_pp(len_x_plus_len_y, m) << "\n";); + + // finally assert equality between the two subexpressions + literal l(mk_eq(len_xy, len_x_plus_len_y, true)); + ctx.mark_as_relevant(l); + assert_axiom(l); +} + void theory_str::attach_new_th_var(enode * n) { context & ctx = get_context(); theory_var v = mk_var(n); @@ -92,6 +167,7 @@ void theory_str::init_search_eh() { tout << mk_ismt2_pp(ex, m) << std::endl; } ); + search_started = true; } void theory_str::new_eq_eh(theory_var x, theory_var y) { diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 5ee5502de..867c4316b 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -22,6 +22,7 @@ Revision History: #include"th_rewriter.h" #include"value_factory.h" #include"smt_model_generator.h" +#include"arith_decl_plugin.h" namespace smt { @@ -31,6 +32,8 @@ namespace smt { class theory_str : public theory { // TODO + protected: + bool search_started; protected: virtual bool internalize_atom(app * atom, bool gate_ctx); virtual bool internalize_term(app * term); @@ -46,9 +49,19 @@ namespace smt { virtual void push_scope_eh(); virtual final_check_status final_check_eh(); + + void assert_axiom(unsigned num_lits, literal * lits); + void assert_axiom(literal l); + + app * mk_strlen(app * e); + + bool is_concat(app const * a) const { return a->is_app_of(get_id(), OP_STRCAT); } + bool is_concat(enode const * n) const { return is_concat(n->get_owner()); } + void instantiate_concat_axiom(enode * cat); public: - theory_str(ast_manager& m); + theory_str(ast_manager & m); virtual ~theory_str(); + arith_util m_autil; protected: void attach_new_th_var(enode * n); }; From 799fd07c85f8ec89552454334fe8a3e4c3f0273e Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Mon, 7 Sep 2015 19:51:52 -0400 Subject: [PATCH 012/410] optimization: return integer consts for strlen() over constant strings --- src/smt/theory_str.cpp | 14 +++++++++++--- src/smt/theory_str.h | 3 ++- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index c6d51b1a4..568e6b5ae 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -26,7 +26,8 @@ namespace smt { theory_str::theory_str(ast_manager & m): theory(m.mk_family_id("str")), search_started(false), - m_autil(m) + m_autil(m), + m_strutil(m) { } @@ -100,8 +101,15 @@ bool theory_str::internalize_term(app * term) { } app * theory_str::mk_strlen(app * e) { - expr * args[1] = {e}; - return get_manager().mk_app(get_id(), OP_STRLEN, 0, 0, 1, args); + if (m_strutil.is_string(e)) { + const char * strval = 0; + m_strutil.is_string(e, &strval); + int len = strlen(strval); + return m_autil.mk_numeral(rational(len), true); + } else { + expr * args[1] = {e}; + return get_manager().mk_app(get_id(), OP_STRLEN, 0, 0, 1, args); + } } /* diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 867c4316b..a583a106e 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -34,6 +34,8 @@ namespace smt { // TODO protected: bool search_started; + arith_util m_autil; + str_util m_strutil; protected: virtual bool internalize_atom(app * atom, bool gate_ctx); virtual bool internalize_term(app * term); @@ -61,7 +63,6 @@ namespace smt { public: theory_str(ast_manager & m); virtual ~theory_str(); - arith_util m_autil; protected: void attach_new_th_var(enode * n); }; From 992fff8ba8c882b0724aafca2620bd8dd151365a Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Thu, 10 Sep 2015 18:43:14 -0400 Subject: [PATCH 013/410] set up theory of arithmetic correctly closes #1 --- src/cmd_context/cmd_context.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/cmd_context/cmd_context.cpp b/src/cmd_context/cmd_context.cpp index a7db2f16c..394350879 100644 --- a/src/cmd_context/cmd_context.cpp +++ b/src/cmd_context/cmd_context.cpp @@ -517,7 +517,8 @@ bool cmd_context::logic_has_arith_core(symbol const & s) const { s == "LRA" || s == "QF_FP" || s == "QF_FPBV" || - s == "HORN"; + s == "HORN" || + s == "QF_S"; } bool cmd_context::logic_has_arith() const { From 4d5a0ea53f42258e375be664aea15bbcbbc66a12 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sat, 26 Sep 2015 18:51:02 -0400 Subject: [PATCH 014/410] WIP add axioms --- src/ast/str_decl_plugin.cpp | 9 ++- src/ast/str_decl_plugin.h | 1 + src/smt/theory_str.cpp | 125 ++++++++++++++++++++++++++++-------- src/smt/theory_str.h | 25 ++++---- 4 files changed, 120 insertions(+), 40 deletions(-) diff --git a/src/ast/str_decl_plugin.cpp b/src/ast/str_decl_plugin.cpp index 0e74493ff..1502e3d3a 100644 --- a/src/ast/str_decl_plugin.cpp +++ b/src/ast/str_decl_plugin.cpp @@ -25,7 +25,10 @@ str_decl_plugin::str_decl_plugin(): m_strv_sym("String"), m_str_decl(0), m_concat_decl(0), - m_length_decl(0){ + m_length_decl(0), + m_arith_plugin(0), + m_arith_fid(0), + m_int_sort(0){ } str_decl_plugin::~str_decl_plugin(){ @@ -45,7 +48,11 @@ void str_decl_plugin::set_manager(ast_manager * m, family_id id) { m->inc_ref(m_str_decl); sort * s = m_str_decl; + SASSERT(m_manager->has_plugin(symbol("arith"))); m_arith_fid = m_manager->mk_family_id("arith"); + m_arith_plugin = static_cast(m_manager->get_plugin(m_arith_fid)); + SASSERT(m_arith_plugin); + m_int_sort = m_manager->mk_sort(m_arith_fid, INT_SORT); SASSERT(m_int_sort != 0); // arith_decl_plugin must be installed before str_decl_plugin. m_manager->inc_ref(m_int_sort); diff --git a/src/ast/str_decl_plugin.h b/src/ast/str_decl_plugin.h index 3fd5fb7e6..7e75fbaf0 100644 --- a/src/ast/str_decl_plugin.h +++ b/src/ast/str_decl_plugin.h @@ -37,6 +37,7 @@ protected: symbol m_strv_sym; sort * m_str_decl; + arith_decl_plugin * m_arith_plugin; sort * m_int_sort; family_id m_arith_fid; diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 568e6b5ae..ff4b3dd76 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -34,22 +34,25 @@ theory_str::theory_str(ast_manager & m): theory_str::~theory_str() { } -void theory_str::assert_axiom(unsigned num_lits, literal * lits) { +void theory_str::assert_axiom(ast * a) { + /* + if (search_started) { + // effectively Z3_theory_assert_axiom + NOT_IMPLEMENTED_YET(); + } else { + // effectively Z3_assert_cnstr + context & ctx = get_context(); + ctx.assert_expr(to_expr(a)); + } + */ + TRACE("t_str_detail", tout << "asserting " << mk_ismt2_pp(a, get_manager()) << "\n";); + expr * e = to_expr(a); context & ctx = get_context(); - TRACE("t_str_detail", - tout << "assert_axiom: literals:\n"; - for (unsigned i = 0; i < num_lits; ++i) { - expr * e = ctx.bool_var2expr(lits[i].var()); - if (lits[i].sign()) - tout << "not "; - tout << mk_pp(e, get_manager()) << " "; - tout << "\n"; - }); - ctx.mk_th_axiom(get_id(), num_lits, lits); -} - -void theory_str::assert_axiom(literal l) { - assert_axiom(1, &l); + ctx.internalize(e, false); + literal lit(ctx.get_literal(e)); + ctx.mark_as_relevant(lit); + ctx.mk_th_axiom(get_id(), 1, &lit); + TRACE("t_str_detail", tout << "done asserting " << mk_ismt2_pp(a, get_manager()) << "\n";); } bool theory_str::internalize_atom(app * atom, bool gate_ctx) { @@ -93,15 +96,17 @@ bool theory_str::internalize_term(app * term) { attach_new_th_var(e); + /* if (is_concat(term)) { instantiate_concat_axiom(e); } + */ return true; } app * theory_str::mk_strlen(app * e) { - if (m_strutil.is_string(e)) { + /*if (m_strutil.is_string(e)) {*/ if (false) { const char * strval = 0; m_strutil.is_string(e, &strval); int len = strlen(strval); @@ -145,22 +150,90 @@ void theory_str::instantiate_concat_axiom(enode * cat) { SASSERT(len_y); // now build len_x + len_y - app * len_x_plus_len_y = m_autil.mk_add(len_x, len_y); + expr_ref len_x_plus_len_y(m); + len_x_plus_len_y = m_autil.mk_add(len_x, len_y); SASSERT(len_x_plus_len_y); - TRACE("t_str", tout << mk_bounded_pp(len_xy, m) << " = " << mk_bounded_pp(len_x_plus_len_y, m) << "\n";); - // finally assert equality between the two subexpressions - literal l(mk_eq(len_xy, len_x_plus_len_y, true)); - ctx.mark_as_relevant(l); - assert_axiom(l); + app * eq = m.mk_eq(len_xy, len_x_plus_len_y); + SASSERT(eq); + TRACE("t_str", tout << mk_bounded_pp(eq, m) << std::endl;); + assert_axiom(eq); +} + +/* + * Add axioms that are true for any string variable: + * 1. Length(x) >= 0 + * 2. Length(x) == 0 <=> x == "" + */ +void theory_str::instantiate_basic_string_axioms(enode * str) { + // generate a stronger axiom for constant strings + if (m_strutil.is_string(str->get_owner())) { + // TODO + } else { + // TODO keep track of which enodes we have added axioms for, so we don't add the same ones twice? + app * a_str = str->get_owner(); + context & ctx = get_context(); + ast_manager & m = get_manager(); + + // TODO find out why these are crashing the SMT solver + + // build axiom 1: Length(a_str) >= 0 + { + // build LHS + expr_ref len_str(m); + len_str = mk_strlen(a_str); + SASSERT(len_str); + // build RHS + expr_ref zero(m); + zero = m_autil.mk_numeral(rational(0), true); + SASSERT(zero); + // build LHS >= RHS and assert + app * lhs_ge_rhs = m_autil.mk_ge(len_str, zero); + SASSERT(lhs_ge_rhs); + // TODO verify that this works + TRACE("t_str_detail", tout << "string axiom 1: " << mk_bounded_pp(lhs_ge_rhs, m) << std::endl;); + assert_axiom(lhs_ge_rhs); + } + + /* + // build axiom 2: Length(a_str) == 0 <=> a_str == "" + { + // build LHS of iff + expr_ref len_str(m); + len_str = mk_strlen(a_str); + SASSERT(len_str); + expr_ref zero(m); + zero = m_autil.mk_numeral(rational(0), true); + SASSERT(zero); + expr_ref lhs(m); + lhs = ctx.mk_eq_atom(len_str, zero); + SASSERT(lhs); + // build RHS of iff + expr_ref empty_str(m); + empty_str = m_strutil.mk_string(""); + SASSERT(empty_str); + expr_ref rhs(m); + rhs = ctx.mk_eq_atom(a_str, empty_str); + SASSERT(rhs); + // build LHS <=> RHS and assert + TRACE("t_str_detail", tout << "string axiom 2: " << mk_bounded_pp(lhs, m) << " <=> " << mk_bounded_pp(rhs, m) << std::endl;); + // TODO this is kind of a hack, maybe just ctx.assert_expr() will be enough? + literal l(mk_eq(lhs, rhs, true)); + ctx.mark_as_relevant(l); + assert_axiom(l); + } + */ + } } void theory_str::attach_new_th_var(enode * n) { context & ctx = get_context(); theory_var v = mk_var(n); ctx.attach_th_var(n, this, v); - TRACE("t_str_detail", tout << "new theory var: " << mk_ismt2_pp(n->get_owner(), get_manager()) << " := " << v << "\n";); + TRACE("t_str_detail", tout << "new theory var: " << mk_ismt2_pp(n->get_owner(), get_manager()) << " := v#" << v << std::endl;); + // probably okay...note however that this seems to miss constants and functions + //instantiate_basic_string_axioms(n); } void theory_str::init_search_eh() { @@ -180,14 +253,14 @@ void theory_str::init_search_eh() { void theory_str::new_eq_eh(theory_var x, theory_var y) { // TODO - TRACE("t_str", tout << "new eq: " << x << " = " << y << std::endl;); + TRACE("t_str", tout << "new eq: v#" << x << " = v#" << y << std::endl;); TRACE("t_str_detail", tout << mk_ismt2_pp(get_enode(x)->get_owner(), get_manager()) << " = " << mk_ismt2_pp(get_enode(y)->get_owner(), get_manager()) << std::endl;); } void theory_str::new_diseq_eh(theory_var x, theory_var y) { // TODO - TRACE("t_str", tout << "new diseq: " << x << " != " << y << std::endl;); + TRACE("t_str", tout << "new diseq: v#" << x << " != v#" << y << std::endl;); TRACE("t_str_detail", tout << mk_ismt2_pp(get_enode(x)->get_owner(), get_manager()) << " != " << mk_ismt2_pp(get_enode(y)->get_owner(), get_manager()) << std::endl;); } @@ -198,7 +271,7 @@ void theory_str::relevant_eh(app * n) { void theory_str::assign_eh(bool_var v, bool is_true) { context & ctx = get_context(); - TRACE("t_str", tout << "assert: v" << v << " #" << ctx.bool_var2expr(v)->get_id() << " is_true: " << is_true << "\n";); + TRACE("t_str", tout << "assert: v" << v << " #" << ctx.bool_var2expr(v)->get_id() << " is_true: " << is_true << std::endl;); } void theory_str::push_scope_eh() { diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index a583a106e..0e7b0bcc8 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -36,6 +36,18 @@ namespace smt { bool search_started; arith_util m_autil; str_util m_strutil; + protected: + void assert_axiom(ast * e); + + app * mk_strlen(app * e); + + bool is_concat(app const * a) const { return a->is_app_of(get_id(), OP_STRCAT); } + bool is_concat(enode const * n) const { return is_concat(n->get_owner()); } + void instantiate_concat_axiom(enode * cat); + void instantiate_basic_string_axioms(enode * str); + public: + theory_str(ast_manager & m); + virtual ~theory_str(); protected: virtual bool internalize_atom(app * atom, bool gate_ctx); virtual bool internalize_term(app * term); @@ -51,19 +63,6 @@ namespace smt { virtual void push_scope_eh(); virtual final_check_status final_check_eh(); - - void assert_axiom(unsigned num_lits, literal * lits); - void assert_axiom(literal l); - - app * mk_strlen(app * e); - - bool is_concat(app const * a) const { return a->is_app_of(get_id(), OP_STRCAT); } - bool is_concat(enode const * n) const { return is_concat(n->get_owner()); } - void instantiate_concat_axiom(enode * cat); - public: - theory_str(ast_manager & m); - virtual ~theory_str(); - protected: void attach_new_th_var(enode * n); }; From f6affe64d0cb72f1e434e05d69d4d7845600fb0a Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sat, 26 Sep 2015 21:02:56 -0400 Subject: [PATCH 015/410] deferred addition of basic string axioms no longer crashes the solver and got our first correct UNSAT! --- src/smt/theory_str.cpp | 53 +++++++++++++++++++++++++++++++++++------- src/smt/theory_str.h | 7 ++++++ 2 files changed, 51 insertions(+), 9 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index ff4b3dd76..b99087f29 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -117,6 +117,20 @@ app * theory_str::mk_strlen(app * e) { } } +bool theory_str::can_propagate() { + return !m_basicstr_axiom_todo.empty(); +} + +void theory_str::propagate() { + TRACE("t_str_detail", tout << "trying to propagate..." << std::endl;); + while (can_propagate()) { + for (unsigned i = 0; i < m_basicstr_axiom_todo.size(); ++i) { + instantiate_basic_string_axioms(m_basicstr_axiom_todo[i]); + } + m_basicstr_axiom_todo.reset(); + } +} + /* * Instantiate an axiom of the following form: * Length(Concat(x, y)) = Length(x) + Length(y) @@ -165,17 +179,32 @@ void theory_str::instantiate_concat_axiom(enode * cat) { * Add axioms that are true for any string variable: * 1. Length(x) >= 0 * 2. Length(x) == 0 <=> x == "" + * If the term is a string constant, we can assert something stronger: + * Length(x) == strlen(x) */ void theory_str::instantiate_basic_string_axioms(enode * str) { - // generate a stronger axiom for constant strings - if (m_strutil.is_string(str->get_owner())) { - // TODO - } else { - // TODO keep track of which enodes we have added axioms for, so we don't add the same ones twice? - app * a_str = str->get_owner(); - context & ctx = get_context(); - ast_manager & m = get_manager(); + // TODO keep track of which enodes we have added axioms for, so we don't add the same ones twice? + context & ctx = get_context(); + ast_manager & m = get_manager(); + + // generate a stronger axiom for constant strings + app * a_str = str->get_owner(); + if (m_strutil.is_string(str->get_owner())) { + expr_ref len_str(m); + len_str = mk_strlen(a_str); + SASSERT(len_str); + + const char * strconst = 0; + m_strutil.is_string(str->get_owner(), & strconst); + TRACE("t_str_detail", tout << "instantiating constant string axioms for \"" << strconst << "\"" << std::endl;); + int l = strlen(strconst); + expr_ref len(m_autil.mk_numeral(rational(l), true), m); + + literal lit(mk_eq(len_str, len, false)); + ctx.mark_as_relevant(lit); + ctx.mk_th_axiom(get_id(), 1, &lit); + } else { // TODO find out why these are crashing the SMT solver // build axiom 1: Length(a_str) >= 0 @@ -233,7 +262,13 @@ void theory_str::attach_new_th_var(enode * n) { ctx.attach_th_var(n, this, v); TRACE("t_str_detail", tout << "new theory var: " << mk_ismt2_pp(n->get_owner(), get_manager()) << " := v#" << v << std::endl;); // probably okay...note however that this seems to miss constants and functions - //instantiate_basic_string_axioms(n); + m_basicstr_axiom_todo.push_back(n); +} + +void theory_str::reset_eh() { + TRACE("t_str", tout << "resetting" << std::endl;); + m_basicstr_axiom_todo.reset(); + pop_scope_eh(0); } void theory_str::init_search_eh() { diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 0e7b0bcc8..a336ec649 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -36,6 +36,8 @@ namespace smt { bool search_started; arith_util m_autil; str_util m_strutil; + + ptr_vector m_basicstr_axiom_todo; protected: void assert_axiom(ast * e); @@ -62,6 +64,11 @@ namespace smt { virtual void assign_eh(bool_var v, bool is_true); virtual void push_scope_eh(); + virtual void reset_eh(); + + virtual bool can_propagate(); + virtual void propagate(); + virtual final_check_status final_check_eh(); void attach_new_th_var(enode * n); }; From 4085db99906b01321921132d458c736ef1ea4239 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sat, 26 Sep 2015 23:35:23 -0400 Subject: [PATCH 016/410] recursive descent through all assertions to discover all String terms set up axioms on these terms to be asserted during propagation --- src/smt/theory_str.cpp | 56 +++++++++++++++++++++++++++++++++--------- src/smt/theory_str.h | 2 ++ 2 files changed, 47 insertions(+), 11 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index b99087f29..63378a700 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -122,13 +122,14 @@ bool theory_str::can_propagate() { } void theory_str::propagate() { - TRACE("t_str_detail", tout << "trying to propagate..." << std::endl;); while (can_propagate()) { + TRACE("t_str_detail", tout << "propagating..." << std::endl;); for (unsigned i = 0; i < m_basicstr_axiom_todo.size(); ++i) { instantiate_basic_string_axioms(m_basicstr_axiom_todo[i]); } m_basicstr_axiom_todo.reset(); } + TRACE("t_str_detail", tout << "done propagating" << std::endl;); } /* @@ -205,8 +206,6 @@ void theory_str::instantiate_basic_string_axioms(enode * str) { ctx.mark_as_relevant(lit); ctx.mk_th_axiom(get_id(), 1, &lit); } else { - // TODO find out why these are crashing the SMT solver - // build axiom 1: Length(a_str) >= 0 { // build LHS @@ -261,8 +260,6 @@ void theory_str::attach_new_th_var(enode * n) { theory_var v = mk_var(n); ctx.attach_th_var(n, this, v); TRACE("t_str_detail", tout << "new theory var: " << mk_ismt2_pp(n->get_owner(), get_manager()) << " := v#" << v << std::endl;); - // probably okay...note however that this seems to miss constants and functions - m_basicstr_axiom_todo.push_back(n); } void theory_str::reset_eh() { @@ -271,18 +268,55 @@ void theory_str::reset_eh() { pop_scope_eh(0); } +void theory_str::set_up_axioms(expr * ex) { + // TODO check to make sure we don't set up axioms on the same term twice + TRACE("t_str_detail", tout << "setting up axioms for " << mk_ismt2_pp(ex, get_manager()) << std::endl;); + + ast_manager & m = get_manager(); + context & ctx = get_context(); + + sort * ex_sort = m.get_sort(ex); + sort * str_sort = m.mk_sort(get_family_id(), STRING_SORT); + + if (ex_sort == str_sort) { + TRACE("t_str_detail", tout << "expr is of sort String" << std::endl;); + // set up basic string axioms + enode * n = ctx.get_enode(ex); + SASSERT(n); + m_basicstr_axiom_todo.push_back(n); + } else { + TRACE("t_str_detail", tout << "expr is of wrong sort, ignoring" << std::endl;); + } + + // if expr is an application, recursively inspect all arguments + if (is_app(ex)) { + app * term = (app*)ex; + unsigned num_args = term->get_num_args(); + for (unsigned i = 0; i < num_args; i++) { + set_up_axioms(term->get_arg(i)); + } + } +} + void theory_str::init_search_eh() { ast_manager & m = get_manager(); context & ctx = get_context(); - TRACE("t_str", - tout << "search started, assignments are:" << std::endl; - expr_ref_vector assignment(m); - ctx.get_assignments(assignment); - for (expr_ref_vector::iterator i = assignment.begin(); i != assignment.end(); ++i) { - expr * ex = *i; + + TRACE("t_str_detail", + tout << "dumping all asserted formulas:" << std::endl; + unsigned nFormulas = ctx.get_num_asserted_formulas(); + for (unsigned i = 0; i < nFormulas; ++i) { + expr * ex = ctx.get_asserted_formula(i); tout << mk_ismt2_pp(ex, m) << std::endl; } ); + // recursive descent through all asserted formulas to set up axioms + unsigned nFormulas = ctx.get_num_asserted_formulas(); + for (unsigned i = 0; i < nFormulas; ++i) { + expr * ex = ctx.get_asserted_formula(i); + set_up_axioms(ex); + } + search_started = true; } diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index a336ec649..23abc3c9d 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -47,6 +47,8 @@ namespace smt { bool is_concat(enode const * n) const { return is_concat(n->get_owner()); } void instantiate_concat_axiom(enode * cat); void instantiate_basic_string_axioms(enode * str); + + void set_up_axioms(expr * ex); public: theory_str(ast_manager & m); virtual ~theory_str(); From 91e9cf272a58dd87f8f523c1f26320953479584a Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sun, 27 Sep 2015 00:12:04 -0400 Subject: [PATCH 017/410] assert string axiom 2 --- src/smt/theory_str.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 63378a700..ee230d027 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -224,7 +224,6 @@ void theory_str::instantiate_basic_string_axioms(enode * str) { assert_axiom(lhs_ge_rhs); } - /* // build axiom 2: Length(a_str) == 0 <=> a_str == "" { // build LHS of iff @@ -246,12 +245,11 @@ void theory_str::instantiate_basic_string_axioms(enode * str) { SASSERT(rhs); // build LHS <=> RHS and assert TRACE("t_str_detail", tout << "string axiom 2: " << mk_bounded_pp(lhs, m) << " <=> " << mk_bounded_pp(rhs, m) << std::endl;); - // TODO this is kind of a hack, maybe just ctx.assert_expr() will be enough? literal l(mk_eq(lhs, rhs, true)); ctx.mark_as_relevant(l); - assert_axiom(l); + ctx.mk_th_axiom(get_id(), 1, &l); } - */ + } } From 114b51dec89319412031c18c71e43f6b63ccc8f2 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sun, 27 Sep 2015 17:26:52 -0400 Subject: [PATCH 018/410] only handle equalities in assignments during init_search_eh --- src/smt/theory_str.cpp | 34 +++++++++++++++++++++++++++++++++- src/smt/theory_str.h | 5 ++--- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index ee230d027..ab0324a57 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -266,6 +266,10 @@ void theory_str::reset_eh() { pop_scope_eh(0); } +void theory_str::handle_equality(expr * lhs, expr * rhs) { + +} + void theory_str::set_up_axioms(expr * ex) { // TODO check to make sure we don't set up axioms on the same term twice TRACE("t_str_detail", tout << "setting up axioms for " << mk_ismt2_pp(ex, get_manager()) << std::endl;); @@ -308,13 +312,40 @@ void theory_str::init_search_eh() { tout << mk_ismt2_pp(ex, m) << std::endl; } ); - // recursive descent through all asserted formulas to set up axioms + /* + * Recursive descent through all asserted formulas to set up axioms. + * Note that this is just the input structure and not necessarily things + * that we know to be true or false. We're just doing this to see + * which terms are explicitly mentioned. + */ unsigned nFormulas = ctx.get_num_asserted_formulas(); for (unsigned i = 0; i < nFormulas; ++i) { expr * ex = ctx.get_asserted_formula(i); set_up_axioms(ex); } + /* + * Similar recursive descent, except over all initially assigned terms. + * This is done to find equalities between terms, etc. that we otherwise + * wouldn't get a chance to see. + */ + expr_ref_vector assignments(m); + ctx.get_assignments(assignments); + for (expr_ref_vector::iterator i = assignments.begin(); i != assignments.end(); ++i) { + expr * ex = *i; + TRACE("t_str_detail", tout << "processing assignment " << mk_ismt2_pp(ex, m) << std::endl;); + if (m.is_eq(ex)) { + TRACE("t_str_detail", tout << "expr is equality" << std::endl;); + app * eq = (app*)ex; + SASSERT(eq->get_num_args() == 2); + expr * lhs = eq->get_arg(0); + expr * rhs = eq->get_arg(1); + handle_equality(lhs, rhs); + } else { + TRACE("t_str_detail", tout << "expr ignored" << std::endl;); + } + } + search_started = true; } @@ -323,6 +354,7 @@ void theory_str::new_eq_eh(theory_var x, theory_var y) { TRACE("t_str", tout << "new eq: v#" << x << " = v#" << y << std::endl;); TRACE("t_str_detail", tout << mk_ismt2_pp(get_enode(x)->get_owner(), get_manager()) << " = " << mk_ismt2_pp(get_enode(y)->get_owner(), get_manager()) << std::endl;); + handle_equality(get_enode(x)->get_owner(), get_enode(y)->get_owner()); } void theory_str::new_diseq_eh(theory_var x, theory_var y) { diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 23abc3c9d..f58ddea91 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -49,6 +49,7 @@ namespace smt { void instantiate_basic_string_axioms(enode * str); void set_up_axioms(expr * ex); + void handle_equality(expr * lhs, expr * rhs); public: theory_str(ast_manager & m); virtual ~theory_str(); @@ -58,14 +59,12 @@ namespace smt { virtual void new_eq_eh(theory_var, theory_var); virtual void new_diseq_eh(theory_var, theory_var); + virtual theory* mk_fresh(context*) { return alloc(theory_str, get_manager()); } - virtual void init_search_eh(); - virtual void relevant_eh(app * n); virtual void assign_eh(bool_var v, bool is_true); virtual void push_scope_eh(); - virtual void reset_eh(); virtual bool can_propagate(); From 6481fe941ae853e87b462edc0e84b95287c5d37d Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sun, 27 Sep 2015 17:48:53 -0400 Subject: [PATCH 019/410] instantiate string-eq length-eq axiom --- src/smt/theory_str.cpp | 66 +++++++++++++++++++++++++++++++++++++++++- src/smt/theory_str.h | 2 ++ 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index ab0324a57..3b9054132 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -118,7 +118,7 @@ app * theory_str::mk_strlen(app * e) { } bool theory_str::can_propagate() { - return !m_basicstr_axiom_todo.empty(); + return !m_basicstr_axiom_todo.empty() || !m_str_eq_length_axiom_todo.empty(); } void theory_str::propagate() { @@ -128,6 +128,14 @@ void theory_str::propagate() { instantiate_basic_string_axioms(m_basicstr_axiom_todo[i]); } m_basicstr_axiom_todo.reset(); + + for (unsigned i = 0; i < m_str_eq_length_axiom_todo.size(); ++i) { + std::pair pair = m_str_eq_length_axiom_todo[i]; + enode * lhs = pair.first; + enode * rhs = pair.second; + instantiate_str_eq_length_axiom(lhs, rhs); + } + m_str_eq_length_axiom_todo.reset(); } TRACE("t_str_detail", tout << "done propagating" << std::endl;); } @@ -253,6 +261,33 @@ void theory_str::instantiate_basic_string_axioms(enode * str) { } } +/* + * Add an axiom of the form: + * (lhs == rhs) -> ( Length(lhs) == Length(rhs) ) + */ +void theory_str::instantiate_str_eq_length_axiom(enode * lhs, enode * rhs) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + app * a_lhs = lhs->get_owner(); + app * a_rhs = rhs->get_owner(); + + // build premise: (lhs == rhs) + expr_ref premise(ctx.mk_eq_atom(a_lhs, a_rhs), m); + + // build conclusion: ( Length(lhs) == Length(rhs) ) + expr_ref len_lhs(mk_strlen(a_lhs), m); + SASSERT(len_lhs); + expr_ref len_rhs(mk_strlen(a_rhs), m); + SASSERT(len_rhs); + expr_ref conclusion(ctx.mk_eq_atom(len_lhs, len_rhs), m); + + // build (premise -> conclusion) and assert + expr_ref axiom(m.mk_implies(premise, conclusion), m); + TRACE("t_str_detail", tout << "string-eq length-eq axiom: " << mk_bounded_pp(axiom, m) << std::endl;); + assert_axiom(axiom); +} + void theory_str::attach_new_th_var(enode * n) { context & ctx = get_context(); theory_var v = mk_var(n); @@ -263,11 +298,40 @@ void theory_str::attach_new_th_var(enode * n) { void theory_str::reset_eh() { TRACE("t_str", tout << "resetting" << std::endl;); m_basicstr_axiom_todo.reset(); + m_str_eq_length_axiom_todo.reset(); pop_scope_eh(0); } void theory_str::handle_equality(expr * lhs, expr * rhs) { + ast_manager & m = get_manager(); + context & ctx = get_context(); + // both terms must be of sort String + sort * lhs_sort = m.get_sort(lhs); + sort * rhs_sort = m.get_sort(rhs); + sort * str_sort = m.mk_sort(get_family_id(), STRING_SORT); + if (lhs_sort != str_sort || rhs_sort != str_sort) { + TRACE("t_str_detail", tout << "skip equality: not String sort" << std::endl;); + return; + } + + // TODO freeVarAttempt()? + + // TODO simplify concat? + + // TODO newEqCheck()? + + // BEGIN new_eq_handler() in strTheory + + // TODO there's some setup with getLenValue() that I don't think is necessary + // because we should already be generating the string length axioms for all string terms + + // set up string length axiom: + // (lhs == rhs) -> (Length(lhs) == Length(rhs)) + enode * e_lhs = ctx.get_enode(lhs); + enode * e_rhs = ctx.get_enode(rhs); + std::pair eq_pair(e_lhs, e_rhs); + m_str_eq_length_axiom_todo.push_back(eq_pair); } void theory_str::set_up_axioms(expr * ex) { diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index f58ddea91..b9c11c2f0 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -38,6 +38,7 @@ namespace smt { str_util m_strutil; ptr_vector m_basicstr_axiom_todo; + svector > m_str_eq_length_axiom_todo; protected: void assert_axiom(ast * e); @@ -47,6 +48,7 @@ namespace smt { bool is_concat(enode const * n) const { return is_concat(n->get_owner()); } void instantiate_concat_axiom(enode * cat); void instantiate_basic_string_axioms(enode * str); + void instantiate_str_eq_length_axiom(enode * lhs, enode * rhs); void set_up_axioms(expr * ex); void handle_equality(expr * lhs, expr * rhs); From 86e60877187049d3a8800fa27fda8c07a565e4c7 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sun, 27 Sep 2015 21:30:45 -0400 Subject: [PATCH 020/410] starting solve_concat_eq_str(); currently there is an unsoundness bug --- src/smt/theory_str.cpp | 206 ++++++++++++++++++++++++++++++++++++++++- src/smt/theory_str.h | 9 ++ 2 files changed, 212 insertions(+), 3 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 3b9054132..faaa7fb70 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -144,6 +144,7 @@ void theory_str::propagate() { * Instantiate an axiom of the following form: * Length(Concat(x, y)) = Length(x) + Length(y) */ +// TODO this isn't used yet void theory_str::instantiate_concat_axiom(enode * cat) { SASSERT(is_concat(cat)); app * a_cat = cat->get_owner(); @@ -302,6 +303,99 @@ void theory_str::reset_eh() { pop_scope_eh(0); } +/* + * Check equality among equivalence class members of LHS and RHS + * to discover an incorrect LHS == RHS. + * For example, if we have y2 == "str3" + * and the equivalence classes are + * { y2, (Concat ce m2) } + * { "str3", (Concat abc x2) } + * then y2 can't be equal to "str3". + * Then add an assertion: (y2 == (Concat ce m2)) AND ("str3" == (Concat abc x2)) -> (y2 != "str3") + */ +bool theory_str::new_eq_check(expr * lhs, expr * rhs) { + // TODO this involves messing around with enodes and equivalence classes + return true; +} + +void theory_str::group_terms_by_eqc(expr * n, std::set & concats, std::set & vars, std::set & consts) { + ast_manager & m = get_manager(); + context & ctx = get_context(); + enode * nNode = ctx.get_enode(n); + enode * eqcNode = nNode; + do { + app * ast = eqcNode->get_owner(); + if (is_concat(eqcNode)) { + // TODO simplify_concat + /* + Z3_ast simConcat = simplifyConcat(t, eqcNode); + if (simConcat != eqcNode) { + if (isConcatFunc(t, simConcat)) { + concats.insert(simConcat); + } else { + if (isConstStr(t, simConcat)) { + constStrs.insert(simConcat); + } else { + vars.insert(simConcat); + } + } + } else { + concats.insert(simConcat); + } + */ + concats.insert(ast); + } else if (is_string(eqcNode)) { + consts.insert(ast); + } else { + vars.insert(ast); + } + eqcNode = eqcNode->get_next(); + } while (eqcNode != nNode); +} + +void theory_str::simplify_concat_equality(expr * lhs, expr * rhs) { + // TODO strArgmt::simplifyConcatEq() +} + +/* + * strArgmt::solve_concat_eq_str() + * Solve concatenations of the form: + * const == Concat(const, X) + * const == Concat(X, const) + */ +void theory_str::solve_concat_eq_str(expr * concat, expr * str) { + ast_manager & m = get_manager(); + context & ctx = get_context(); + + TRACE("t_str_detail", tout << mk_ismt2_pp(concat, m) << " == " << mk_ismt2_pp(str, m) << std::endl;); + + if (is_concat(to_app(concat)) && is_string(to_app(str))) { + const char * tmp = 0; + m_strutil.is_string(str, & tmp); + std::string const_str(tmp); + app * a_concat = to_app(concat); + SASSERT(a_concat->get_num_args() == 2); + expr * a1 = a_concat->get_arg(0); + expr * a2 = a_concat->get_arg(1); + + if (const_str == "") { + TRACE("t_str", tout << "quick path: concat == \"\"" << std::endl;); + // assert the following axiom: + // ( (Concat a1 a2) == str ) -> ( (a1 == "") AND (a2 == "") ) + expr_ref premise(ctx.mk_eq_atom(concat, str), m); + expr_ref empty_str(m_strutil.mk_string(""), m); + expr_ref c1(ctx.mk_eq_atom(a1, empty_str), m); + expr_ref c2(ctx.mk_eq_atom(a2, empty_str), m); + expr_ref conclusion(m.mk_and(c1, c2), m); + expr_ref axiom(m.mk_implies(premise, conclusion), m); + TRACE("t_str_detail", tout << "learn " << mk_ismt2_pp(axiom, m) << std::endl;); + assert_axiom(axiom); + return; + } + // TODO the rest... + } +} + void theory_str::handle_equality(expr * lhs, expr * rhs) { ast_manager & m = get_manager(); context & ctx = get_context(); @@ -319,7 +413,10 @@ void theory_str::handle_equality(expr * lhs, expr * rhs) { // TODO simplify concat? - // TODO newEqCheck()? + // newEqCheck() -- check consistency wrt. existing equivalence classes + if (!new_eq_check(lhs, rhs)) { + return; + } // BEGIN new_eq_handler() in strTheory @@ -332,6 +429,94 @@ void theory_str::handle_equality(expr * lhs, expr * rhs) { enode * e_rhs = ctx.get_enode(rhs); std::pair eq_pair(e_lhs, e_rhs); m_str_eq_length_axiom_todo.push_back(eq_pair); + + // group terms by equivalence class (groupNodeInEqc()) + std::set eqc_lhs_concat; + std::set eqc_lhs_var; + std::set eqc_lhs_const; + group_terms_by_eqc(lhs, eqc_lhs_concat, eqc_lhs_var, eqc_lhs_const); + + TRACE("t_str_detail", + tout << "eqc[lhs]:" << std::endl; + tout << "Concats:" << std::endl; + for (std::set::iterator it = eqc_lhs_concat.begin(); it != eqc_lhs_concat.end(); ++it) { + expr * ex = *it; + tout << mk_ismt2_pp(ex, get_manager()) << std::endl; + } + tout << "Variables:" << std::endl; + for (std::set::iterator it = eqc_lhs_var.begin(); it != eqc_lhs_var.end(); ++it) { + expr * ex = *it; + tout << mk_ismt2_pp(ex, get_manager()) << std::endl; + } + tout << "Constants:" << std::endl; + for (std::set::iterator it = eqc_lhs_const.begin(); it != eqc_lhs_const.end(); ++it) { + expr * ex = *it; + tout << mk_ismt2_pp(ex, get_manager()) << std::endl; + } + ); + + std::set eqc_rhs_concat; + std::set eqc_rhs_var; + std::set eqc_rhs_const; + group_terms_by_eqc(rhs, eqc_rhs_concat, eqc_rhs_var, eqc_rhs_const); + + TRACE("t_str_detail", + tout << "eqc[rhs]:" << std::endl; + tout << "Concats:" << std::endl; + for (std::set::iterator it = eqc_rhs_concat.begin(); it != eqc_rhs_concat.end(); ++it) { + expr * ex = *it; + tout << mk_ismt2_pp(ex, get_manager()) << std::endl; + } + tout << "Variables:" << std::endl; + for (std::set::iterator it = eqc_rhs_var.begin(); it != eqc_rhs_var.end(); ++it) { + expr * ex = *it; + tout << mk_ismt2_pp(ex, get_manager()) << std::endl; + } + tout << "Constants:" << std::endl; + for (std::set::iterator it = eqc_rhs_const.begin(); it != eqc_rhs_const.end(); ++it) { + expr * ex = *it; + tout << mk_ismt2_pp(ex, get_manager()) << std::endl; + } + ); + + // step 1: Concat == Concat + bool hasCommon = false; + if (eqc_lhs_concat.size() != 0 && eqc_rhs_concat.size() != 0) { + std::set::iterator itor1 = eqc_lhs_concat.begin(); + std::set::iterator itor2 = eqc_rhs_concat.begin(); + for (; itor1 != eqc_lhs_concat.end(); ++itor1) { + if (eqc_rhs_concat.find(*itor1) != eqc_rhs_concat.end()) { + hasCommon = true; + break; + } + } + for (; !hasCommon && itor2 != eqc_rhs_concat.end(); ++itor2) { + if (eqc_lhs_concat.find(*itor2) != eqc_lhs_concat.end()) { + hasCommon = true; + break; + } + } + if (!hasCommon) { + simplify_concat_equality(*(eqc_lhs_concat.begin()), *(eqc_rhs_concat.begin())); + } + } + + // step 2: Concat == Constant + if (eqc_lhs_const.size() != 0) { + expr * conStr = *(eqc_lhs_const.begin()); + std::set::iterator itor2 = eqc_rhs_concat.begin(); + for (; itor2 != eqc_rhs_concat.end(); ++itor2) { + solve_concat_eq_str(*itor2, conStr); + } + } else if (eqc_rhs_const.size() != 0) { + expr * conStr = *(eqc_rhs_const.begin()); + std::set::iterator itor1 = eqc_lhs_concat.begin(); + for (; itor1 != eqc_lhs_concat.end(); ++itor1) { + solve_concat_eq_str(*itor1, conStr); + } + } + + // TODO regex unroll? (much later) } void theory_str::set_up_axioms(expr * ex) { @@ -368,6 +553,9 @@ void theory_str::init_search_eh() { ast_manager & m = get_manager(); context & ctx = get_context(); + // TODO it would be better to refactor this function so that instead of deferring the axioms + // instead we defer the evaluation of the expression + TRACE("t_str_detail", tout << "dumping all asserted formulas:" << std::endl; unsigned nFormulas = ctx.get_num_asserted_formulas(); @@ -410,11 +598,11 @@ void theory_str::init_search_eh() { } } + TRACE("t_str", tout << "search started" << std::endl;); search_started = true; } void theory_str::new_eq_eh(theory_var x, theory_var y) { - // TODO TRACE("t_str", tout << "new eq: v#" << x << " = v#" << y << std::endl;); TRACE("t_str_detail", tout << mk_ismt2_pp(get_enode(x)->get_owner(), get_manager()) << " = " << mk_ismt2_pp(get_enode(y)->get_owner(), get_manager()) << std::endl;); @@ -422,7 +610,6 @@ void theory_str::new_eq_eh(theory_var x, theory_var y) { } void theory_str::new_diseq_eh(theory_var x, theory_var y) { - // TODO TRACE("t_str", tout << "new diseq: v#" << x << " != v#" << y << std::endl;); TRACE("t_str_detail", tout << mk_ismt2_pp(get_enode(x)->get_owner(), get_manager()) << " != " << mk_ismt2_pp(get_enode(y)->get_owner(), get_manager()) << std::endl;); @@ -442,8 +629,21 @@ void theory_str::push_scope_eh() { } final_check_status theory_str::final_check_eh() { + ast_manager & m = get_manager(); + context & ctx = get_context(); // TODO TRACE("t_str", tout << "final check" << std::endl;); + + TRACE("t_str_detail", + tout << "dumping all assignments:" << std::endl; + expr_ref_vector assignments(m); + ctx.get_assignments(assignments); + for (expr_ref_vector::iterator i = assignments.begin(); i != assignments.end(); ++i) { + expr * ex = *i; + tout << mk_ismt2_pp(ex, m) << std::endl; + } + ); + return FC_DONE; } diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index b9c11c2f0..286de818a 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -23,6 +23,7 @@ Revision History: #include"value_factory.h" #include"smt_model_generator.h" #include"arith_decl_plugin.h" +#include namespace smt { @@ -46,12 +47,20 @@ namespace smt { bool is_concat(app const * a) const { return a->is_app_of(get_id(), OP_STRCAT); } bool is_concat(enode const * n) const { return is_concat(n->get_owner()); } + bool is_string(app const * a) const { return a->is_app_of(get_id(), OP_STR); } + bool is_string(enode const * n) const { return is_string(n->get_owner()); } void instantiate_concat_axiom(enode * cat); void instantiate_basic_string_axioms(enode * str); void instantiate_str_eq_length_axiom(enode * lhs, enode * rhs); void set_up_axioms(expr * ex); void handle_equality(expr * lhs, expr * rhs); + + void simplify_concat_equality(expr * lhs, expr * rhs); + void solve_concat_eq_str(expr * concat, expr * str); + + bool new_eq_check(expr * lhs, expr * rhs); + void group_terms_by_eqc(expr * n, std::set & concats, std::set & vars, std::set & consts); public: theory_str(ast_manager & m); virtual ~theory_str(); From 02cb329ca5b980c02b825bb54dc163149b3f75dc Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sun, 27 Sep 2015 23:24:41 -0400 Subject: [PATCH 021/410] defer equalities uncovered during init_search --- src/smt/theory_str.cpp | 34 +++++++++++++++------------------- src/smt/theory_str.h | 2 +- 2 files changed, 16 insertions(+), 20 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index faaa7fb70..bba37f5f0 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -118,7 +118,7 @@ app * theory_str::mk_strlen(app * e) { } bool theory_str::can_propagate() { - return !m_basicstr_axiom_todo.empty() || !m_str_eq_length_axiom_todo.empty(); + return !m_basicstr_axiom_todo.empty() || !m_str_eq_todo.empty(); } void theory_str::propagate() { @@ -129,13 +129,13 @@ void theory_str::propagate() { } m_basicstr_axiom_todo.reset(); - for (unsigned i = 0; i < m_str_eq_length_axiom_todo.size(); ++i) { - std::pair pair = m_str_eq_length_axiom_todo[i]; + for (unsigned i = 0; i < m_str_eq_todo.size(); ++i) { + std::pair pair = m_str_eq_todo[i]; enode * lhs = pair.first; enode * rhs = pair.second; - instantiate_str_eq_length_axiom(lhs, rhs); + handle_equality(lhs->get_owner(), rhs->get_owner()); } - m_str_eq_length_axiom_todo.reset(); + m_str_eq_todo.reset(); } TRACE("t_str_detail", tout << "done propagating" << std::endl;); } @@ -299,7 +299,7 @@ void theory_str::attach_new_th_var(enode * n) { void theory_str::reset_eh() { TRACE("t_str", tout << "resetting" << std::endl;); m_basicstr_axiom_todo.reset(); - m_str_eq_length_axiom_todo.reset(); + m_str_eq_todo.reset(); pop_scope_eh(0); } @@ -382,10 +382,10 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { TRACE("t_str", tout << "quick path: concat == \"\"" << std::endl;); // assert the following axiom: // ( (Concat a1 a2) == str ) -> ( (a1 == "") AND (a2 == "") ) - expr_ref premise(ctx.mk_eq_atom(concat, str), m); + expr_ref premise(m.mk_eq(concat, str), m); expr_ref empty_str(m_strutil.mk_string(""), m); - expr_ref c1(ctx.mk_eq_atom(a1, empty_str), m); - expr_ref c2(ctx.mk_eq_atom(a2, empty_str), m); + expr_ref c1(m.mk_eq(a1, empty_str), m); + expr_ref c2(m.mk_eq(a2, empty_str), m); expr_ref conclusion(m.mk_and(c1, c2), m); expr_ref axiom(m.mk_implies(premise, conclusion), m); TRACE("t_str_detail", tout << "learn " << mk_ismt2_pp(axiom, m) << std::endl;); @@ -423,12 +423,7 @@ void theory_str::handle_equality(expr * lhs, expr * rhs) { // TODO there's some setup with getLenValue() that I don't think is necessary // because we should already be generating the string length axioms for all string terms - // set up string length axiom: - // (lhs == rhs) -> (Length(lhs) == Length(rhs)) - enode * e_lhs = ctx.get_enode(lhs); - enode * e_rhs = ctx.get_enode(rhs); - std::pair eq_pair(e_lhs, e_rhs); - m_str_eq_length_axiom_todo.push_back(eq_pair); + instantiate_str_eq_length_axiom(ctx.get_enode(lhs), ctx.get_enode(rhs)); // group terms by equivalence class (groupNodeInEqc()) std::set eqc_lhs_concat; @@ -553,9 +548,6 @@ void theory_str::init_search_eh() { ast_manager & m = get_manager(); context & ctx = get_context(); - // TODO it would be better to refactor this function so that instead of deferring the axioms - // instead we defer the evaluation of the expression - TRACE("t_str_detail", tout << "dumping all asserted formulas:" << std::endl; unsigned nFormulas = ctx.get_num_asserted_formulas(); @@ -592,7 +584,11 @@ void theory_str::init_search_eh() { SASSERT(eq->get_num_args() == 2); expr * lhs = eq->get_arg(0); expr * rhs = eq->get_arg(1); - handle_equality(lhs, rhs); + + enode * e_lhs = ctx.get_enode(lhs); + enode * e_rhs = ctx.get_enode(rhs); + std::pair eq_pair(e_lhs, e_rhs); + m_str_eq_todo.push_back(eq_pair); } else { TRACE("t_str_detail", tout << "expr ignored" << std::endl;); } diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 286de818a..76bef4561 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -39,7 +39,7 @@ namespace smt { str_util m_strutil; ptr_vector m_basicstr_axiom_todo; - svector > m_str_eq_length_axiom_todo; + svector > m_str_eq_todo; protected: void assert_axiom(ast * e); From 0d54e4e4ae99933b4330fba9df9224f9486ee361 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sun, 27 Sep 2015 23:57:41 -0400 Subject: [PATCH 022/410] implement str_decl_plugin::is_value() and ::is_unique_value() we can now prove that (= "abc" "def") is unsatisfiable --- src/ast/str_decl_plugin.cpp | 12 ++++++++++++ src/ast/str_decl_plugin.h | 4 +++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/ast/str_decl_plugin.cpp b/src/ast/str_decl_plugin.cpp index 1502e3d3a..60db88b63 100644 --- a/src/ast/str_decl_plugin.cpp +++ b/src/ast/str_decl_plugin.cpp @@ -115,6 +115,18 @@ void str_decl_plugin::get_sort_names(svector & sort_names, symbol sort_names.push_back(builtin_name("String", STRING_SORT)); } +bool str_decl_plugin::is_value(app * e) const { + if (e->get_family_id() != m_family_id) { + return false; + } + switch (e->get_decl_kind()) { + case OP_STR: + return true; + default: + return false; + } +} + bool str_recognizers::is_string(expr const * n, const char ** val) const { if (!is_app_of(n, m_afid, OP_STR)) return false; diff --git a/src/ast/str_decl_plugin.h b/src/ast/str_decl_plugin.h index 7e75fbaf0..a64e0c05f 100644 --- a/src/ast/str_decl_plugin.h +++ b/src/ast/str_decl_plugin.h @@ -60,8 +60,10 @@ public: app * mk_string(const char * val); virtual void get_op_names(svector & op_names, symbol const & logic); - virtual void get_sort_names(svector & sort_names, symbol const & logic); + + virtual bool is_value(app * e) const; + virtual bool is_unique_value(app * e) const { return is_value(e); } // TODO }; From 7da3854a8b488188641b4b8c2d691e5d59df1df8 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Mon, 28 Sep 2015 01:56:13 -0400 Subject: [PATCH 023/410] really lousy model-building, WIP --- src/smt/theory_str.cpp | 46 ++++++++++++++++++++++++++++-------------- src/smt/theory_str.h | 23 ++++++++++++++++++++- 2 files changed, 53 insertions(+), 16 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index bba37f5f0..aaae3e373 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -35,18 +35,9 @@ theory_str::~theory_str() { } void theory_str::assert_axiom(ast * a) { - /* - if (search_started) { - // effectively Z3_theory_assert_axiom - NOT_IMPLEMENTED_YET(); - } else { - // effectively Z3_assert_cnstr - context & ctx = get_context(); - ctx.assert_expr(to_expr(a)); - } - */ - TRACE("t_str_detail", tout << "asserting " << mk_ismt2_pp(a, get_manager()) << "\n";); expr * e = to_expr(a); + if (get_manager().is_true(e)) return; + TRACE("t_str_detail", tout << "asserting " << mk_ismt2_pp(a, get_manager()) << "\n";); context & ctx = get_context(); ctx.internalize(e, false); literal lit(ctx.get_literal(e)); @@ -381,11 +372,11 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { if (const_str == "") { TRACE("t_str", tout << "quick path: concat == \"\"" << std::endl;); // assert the following axiom: - // ( (Concat a1 a2) == str ) -> ( (a1 == "") AND (a2 == "") ) - expr_ref premise(m.mk_eq(concat, str), m); + // ( (Concat a1 a2) == "" ) -> ( (a1 == "") AND (a2 == "") ) expr_ref empty_str(m_strutil.mk_string(""), m); - expr_ref c1(m.mk_eq(a1, empty_str), m); - expr_ref c2(m.mk_eq(a2, empty_str), m); + expr_ref premise(ctx.mk_eq_atom(concat, empty_str), m); + expr_ref c1(ctx.mk_eq_atom(a1, empty_str), m); + expr_ref c2(ctx.mk_eq_atom(a2, empty_str), m); expr_ref conclusion(m.mk_and(c1, c2), m); expr_ref axiom(m.mk_implies(premise, conclusion), m); TRACE("t_str_detail", tout << "learn " << mk_ismt2_pp(axiom, m) << std::endl;); @@ -643,4 +634,29 @@ final_check_status theory_str::final_check_eh() { return FC_DONE; } +void theory_str::init_model(model_generator & mg) { + TRACE("t_str", tout << "initializing model" << std::endl; display(tout);); + m_factory = alloc(str_value_factory, get_manager(), get_family_id()); + mg.register_factory(m_factory); +} + +model_value_proc * theory_str::mk_value(enode * n, model_generator & mg) { + TRACE("t_str", tout << "mk_value for: " << mk_ismt2_pp(n->get_owner(), get_manager()) << + " (sort " << mk_ismt2_pp(get_manager().get_sort(n->get_owner()), get_manager()) << ")\n";); + ast_manager & m = get_manager(); + context & ctx = get_context(); + app_ref owner(m); + owner = n->get_owner(); + + // If the owner is not internalized, it doesn't have an enode associated. + SASSERT(ctx.e_internalized(owner)); + + if (m_strutil.is_string(owner)) { + return alloc(expr_wrapper_proc, owner); + } + NOT_IMPLEMENTED_YET(); // TODO +} + +void theory_str::finalize_model(model_generator & mg) {} + }; /* namespace smt */ diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 76bef4561..65a401580 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -28,7 +28,22 @@ Revision History: namespace smt { class str_value_factory : public value_factory { - // TODO + str_util m_util; + public: + str_value_factory(ast_manager & m, family_id fid) : + value_factory(m, fid), + m_util(m) {} + virtual ~str_value_factory() {} + virtual expr * get_some_value(sort * s) { + return m_util.mk_string("some value"); + } + virtual bool get_some_values(sort * s, expr_ref & v1, expr_ref & v2) { + v1 = m_util.mk_string("value 1"); + v2 = m_util.mk_string("value 2"); + return true; + } + virtual expr * get_fresh_value(sort * s) { NOT_IMPLEMENTED_YET(); } + virtual void register_value(expr * n) { /* Ignore */ } }; class theory_str : public theory { @@ -38,6 +53,8 @@ namespace smt { arith_util m_autil; str_util m_strutil; + str_value_factory * m_factory; + ptr_vector m_basicstr_axiom_todo; svector > m_str_eq_todo; protected: @@ -83,6 +100,10 @@ namespace smt { virtual final_check_status final_check_eh(); void attach_new_th_var(enode * n); + + virtual void init_model(model_generator & m); + virtual model_value_proc * mk_value(enode * n, model_generator & mg); + virtual void finalize_model(model_generator & mg); }; }; From 87b5765e3d3ef04aface95303666ed3f4daf7026 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Mon, 28 Sep 2015 02:04:35 -0400 Subject: [PATCH 024/410] clean up traces and make them much easier to read --- src/smt/theory_str.cpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index aaae3e373..467d94c5f 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -128,7 +128,6 @@ void theory_str::propagate() { } m_str_eq_todo.reset(); } - TRACE("t_str_detail", tout << "done propagating" << std::endl;); } /* @@ -507,8 +506,6 @@ void theory_str::handle_equality(expr * lhs, expr * rhs) { void theory_str::set_up_axioms(expr * ex) { // TODO check to make sure we don't set up axioms on the same term twice - TRACE("t_str_detail", tout << "setting up axioms for " << mk_ismt2_pp(ex, get_manager()) << std::endl;); - ast_manager & m = get_manager(); context & ctx = get_context(); @@ -516,13 +513,15 @@ void theory_str::set_up_axioms(expr * ex) { sort * str_sort = m.mk_sort(get_family_id(), STRING_SORT); if (ex_sort == str_sort) { - TRACE("t_str_detail", tout << "expr is of sort String" << std::endl;); + TRACE("t_str_detail", tout << "setting up axioms for " << mk_ismt2_pp(ex, get_manager()) << + ": expr is of sort String" << std::endl;); // set up basic string axioms enode * n = ctx.get_enode(ex); SASSERT(n); m_basicstr_axiom_todo.push_back(n); } else { - TRACE("t_str_detail", tout << "expr is of wrong sort, ignoring" << std::endl;); + TRACE("t_str_detail", tout << "setting up axioms for " << mk_ismt2_pp(ex, get_manager()) << + ": expr is of wrong sort, ignoring" << std::endl;); } // if expr is an application, recursively inspect all arguments @@ -568,9 +567,9 @@ void theory_str::init_search_eh() { ctx.get_assignments(assignments); for (expr_ref_vector::iterator i = assignments.begin(); i != assignments.end(); ++i) { expr * ex = *i; - TRACE("t_str_detail", tout << "processing assignment " << mk_ismt2_pp(ex, m) << std::endl;); if (m.is_eq(ex)) { - TRACE("t_str_detail", tout << "expr is equality" << std::endl;); + TRACE("t_str_detail", tout << "processing assignment " << mk_ismt2_pp(ex, m) << + ": expr is equality" << std::endl;); app * eq = (app*)ex; SASSERT(eq->get_num_args() == 2); expr * lhs = eq->get_arg(0); @@ -581,7 +580,8 @@ void theory_str::init_search_eh() { std::pair eq_pair(e_lhs, e_rhs); m_str_eq_todo.push_back(eq_pair); } else { - TRACE("t_str_detail", tout << "expr ignored" << std::endl;); + TRACE("t_str_detail", tout << "processing assignment " << mk_ismt2_pp(ex, m) + << ": expr ignored" << std::endl;); } } @@ -590,15 +590,15 @@ void theory_str::init_search_eh() { } void theory_str::new_eq_eh(theory_var x, theory_var y) { - TRACE("t_str", tout << "new eq: v#" << x << " = v#" << y << std::endl;); - TRACE("t_str_detail", tout << mk_ismt2_pp(get_enode(x)->get_owner(), get_manager()) << " = " << + //TRACE("t_str_detail", tout << "new eq: v#" << x << " = v#" << y << std::endl;); + TRACE("t_str", tout << "new eq: " << mk_ismt2_pp(get_enode(x)->get_owner(), get_manager()) << " = " << mk_ismt2_pp(get_enode(y)->get_owner(), get_manager()) << std::endl;); handle_equality(get_enode(x)->get_owner(), get_enode(y)->get_owner()); } void theory_str::new_diseq_eh(theory_var x, theory_var y) { - TRACE("t_str", tout << "new diseq: v#" << x << " != v#" << y << std::endl;); - TRACE("t_str_detail", tout << mk_ismt2_pp(get_enode(x)->get_owner(), get_manager()) << " != " << + //TRACE("t_str_detail", tout << "new diseq: v#" << x << " != v#" << y << std::endl;); + TRACE("t_str", tout << "new diseq: " << mk_ismt2_pp(get_enode(x)->get_owner(), get_manager()) << " != " << mk_ismt2_pp(get_enode(y)->get_owner(), get_manager()) << std::endl;); } From 5fe129b5716aabca5313adfb0f5e77f3b3ea3fc9 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Mon, 28 Sep 2015 02:09:35 -0400 Subject: [PATCH 025/410] use mk_ismt2_pp() instead of mk_bounded_pp() --- src/smt/theory_str.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 467d94c5f..b3780265d 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -171,7 +171,7 @@ void theory_str::instantiate_concat_axiom(enode * cat) { // finally assert equality between the two subexpressions app * eq = m.mk_eq(len_xy, len_x_plus_len_y); SASSERT(eq); - TRACE("t_str", tout << mk_bounded_pp(eq, m) << std::endl;); + TRACE("t_str", tout << mk_ismt2_pp(eq, m) << std::endl;); assert_axiom(eq); } @@ -219,7 +219,7 @@ void theory_str::instantiate_basic_string_axioms(enode * str) { app * lhs_ge_rhs = m_autil.mk_ge(len_str, zero); SASSERT(lhs_ge_rhs); // TODO verify that this works - TRACE("t_str_detail", tout << "string axiom 1: " << mk_bounded_pp(lhs_ge_rhs, m) << std::endl;); + TRACE("t_str_detail", tout << "string axiom 1: " << mk_ismt2_pp(lhs_ge_rhs, m) << std::endl;); assert_axiom(lhs_ge_rhs); } @@ -243,7 +243,7 @@ void theory_str::instantiate_basic_string_axioms(enode * str) { rhs = ctx.mk_eq_atom(a_str, empty_str); SASSERT(rhs); // build LHS <=> RHS and assert - TRACE("t_str_detail", tout << "string axiom 2: " << mk_bounded_pp(lhs, m) << " <=> " << mk_bounded_pp(rhs, m) << std::endl;); + TRACE("t_str_detail", tout << "string axiom 2: " << mk_ismt2_pp(lhs, m) << " <=> " << mk_ismt2_pp(rhs, m) << std::endl;); literal l(mk_eq(lhs, rhs, true)); ctx.mark_as_relevant(l); ctx.mk_th_axiom(get_id(), 1, &l); @@ -275,7 +275,7 @@ void theory_str::instantiate_str_eq_length_axiom(enode * lhs, enode * rhs) { // build (premise -> conclusion) and assert expr_ref axiom(m.mk_implies(premise, conclusion), m); - TRACE("t_str_detail", tout << "string-eq length-eq axiom: " << mk_bounded_pp(axiom, m) << std::endl;); + TRACE("t_str_detail", tout << "string-eq length-eq axiom: " << mk_ismt2_pp(axiom, m) << std::endl;); assert_axiom(axiom); } From bccadedfee53f5d1cd097300044bfc39c1a5bdac Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Mon, 28 Sep 2015 03:20:13 -0400 Subject: [PATCH 026/410] instead of building axiom (=> x y), build (or (not x) y) this may be a bug in Z3 as it suggests that implications are ignored e.g. I can assert the axiom (=> true false) and Z3 is okay with this --- src/smt/theory_str.cpp | 23 +++++++++++++++-------- src/smt/theory_str.h | 1 + 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index b3780265d..6d2284d67 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -39,7 +39,9 @@ void theory_str::assert_axiom(ast * a) { if (get_manager().is_true(e)) return; TRACE("t_str_detail", tout << "asserting " << mk_ismt2_pp(a, get_manager()) << "\n";); context & ctx = get_context(); - ctx.internalize(e, false); + if (!ctx.b_internalized(e)) { + ctx.internalize(e, true); + } literal lit(ctx.get_literal(e)); ctx.mark_as_relevant(lit); ctx.mk_th_axiom(get_id(), 1, &lit); @@ -372,14 +374,15 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { TRACE("t_str", tout << "quick path: concat == \"\"" << std::endl;); // assert the following axiom: // ( (Concat a1 a2) == "" ) -> ( (a1 == "") AND (a2 == "") ) - expr_ref empty_str(m_strutil.mk_string(""), m); - expr_ref premise(ctx.mk_eq_atom(concat, empty_str), m); - expr_ref c1(ctx.mk_eq_atom(a1, empty_str), m); - expr_ref c2(ctx.mk_eq_atom(a2, empty_str), m); + + + expr_ref premise(ctx.mk_eq_atom(concat, str), m); + expr_ref c1(ctx.mk_eq_atom(a1, str), m); + expr_ref c2(ctx.mk_eq_atom(a2, str), m); expr_ref conclusion(m.mk_and(c1, c2), m); - expr_ref axiom(m.mk_implies(premise, conclusion), m); - TRACE("t_str_detail", tout << "learn " << mk_ismt2_pp(axiom, m) << std::endl;); + expr_ref axiom(m.mk_or(m.mk_not(premise), conclusion), m); assert_axiom(axiom); + return; } // TODO the rest... @@ -561,7 +564,7 @@ void theory_str::init_search_eh() { /* * Similar recursive descent, except over all initially assigned terms. * This is done to find equalities between terms, etc. that we otherwise - * wouldn't get a chance to see. + * might not get a chance to see. */ expr_ref_vector assignments(m); ctx.get_assignments(assignments); @@ -615,6 +618,10 @@ void theory_str::push_scope_eh() { TRACE("t_str", tout << "push" << std::endl;); } +void theory_str::pop_scope_eh(unsigned num_scopes) { + TRACE("t_str", tout << "pop " << num_scopes << std::endl;); +} + final_check_status theory_str::final_check_eh() { ast_manager & m = get_manager(); context & ctx = get_context(); diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 65a401580..afac8b7f1 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -93,6 +93,7 @@ namespace smt { virtual void relevant_eh(app * n); virtual void assign_eh(bool_var v, bool is_true); virtual void push_scope_eh(); + virtual void pop_scope_eh(unsigned num_scopes); virtual void reset_eh(); virtual bool can_propagate(); From 62cd633b63b3452f913faaec383676900ec91052 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Mon, 28 Sep 2015 03:26:46 -0400 Subject: [PATCH 027/410] create helper function theory_str::assert_implication() --- src/smt/theory_str.cpp | 23 +++++++++++++---------- src/smt/theory_str.h | 3 ++- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 6d2284d67..3ef2f06e5 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -34,10 +34,9 @@ theory_str::theory_str(ast_manager & m): theory_str::~theory_str() { } -void theory_str::assert_axiom(ast * a) { - expr * e = to_expr(a); +void theory_str::assert_axiom(expr * e) { if (get_manager().is_true(e)) return; - TRACE("t_str_detail", tout << "asserting " << mk_ismt2_pp(a, get_manager()) << "\n";); + TRACE("t_str_detail", tout << "asserting " << mk_ismt2_pp(e, get_manager()) << "\n";); context & ctx = get_context(); if (!ctx.b_internalized(e)) { ctx.internalize(e, true); @@ -45,7 +44,13 @@ void theory_str::assert_axiom(ast * a) { literal lit(ctx.get_literal(e)); ctx.mark_as_relevant(lit); ctx.mk_th_axiom(get_id(), 1, &lit); - TRACE("t_str_detail", tout << "done asserting " << mk_ismt2_pp(a, get_manager()) << "\n";); + TRACE("t_str_detail", tout << "done asserting " << mk_ismt2_pp(e, get_manager()) << "\n";); +} + +void theory_str::assert_implication(expr * premise, expr * conclusion) { + ast_manager & m = get_manager(); + expr_ref axiom(m.mk_or(m.mk_not(premise), conclusion), m); + assert_axiom(axiom); } bool theory_str::internalize_atom(app * atom, bool gate_ctx) { @@ -275,10 +280,9 @@ void theory_str::instantiate_str_eq_length_axiom(enode * lhs, enode * rhs) { SASSERT(len_rhs); expr_ref conclusion(ctx.mk_eq_atom(len_lhs, len_rhs), m); - // build (premise -> conclusion) and assert - expr_ref axiom(m.mk_implies(premise, conclusion), m); - TRACE("t_str_detail", tout << "string-eq length-eq axiom: " << mk_ismt2_pp(axiom, m) << std::endl;); - assert_axiom(axiom); + TRACE("t_str_detail", tout << "string-eq length-eq axiom: " + << mk_ismt2_pp(premise, m) << " -> " << mk_ismt2_pp(conclusion, m) << std::endl;); + assert_implication(premise, conclusion); } void theory_str::attach_new_th_var(enode * n) { @@ -380,8 +384,7 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { expr_ref c1(ctx.mk_eq_atom(a1, str), m); expr_ref c2(ctx.mk_eq_atom(a2, str), m); expr_ref conclusion(m.mk_and(c1, c2), m); - expr_ref axiom(m.mk_or(m.mk_not(premise), conclusion), m); - assert_axiom(axiom); + assert_implication(premise, conclusion); return; } diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index afac8b7f1..c7a4a5952 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -58,7 +58,8 @@ namespace smt { ptr_vector m_basicstr_axiom_todo; svector > m_str_eq_todo; protected: - void assert_axiom(ast * e); + void assert_axiom(expr * e); + void assert_implication(expr * premise, expr * conclusion); app * mk_strlen(app * e); From 9bc685b21d86ffd815300dd77a4a996a73be71de Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Mon, 28 Sep 2015 10:43:34 -0400 Subject: [PATCH 028/410] solve_concat_eq_str() for concat(const,const) == const --- src/smt/theory_str.cpp | 107 ++++++++++++++++++++++++++++++++++++++--- src/smt/theory_str.h | 3 ++ 2 files changed, 103 insertions(+), 7 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 3ef2f06e5..f3e6496b7 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -54,11 +54,9 @@ void theory_str::assert_implication(expr * premise, expr * conclusion) { } bool theory_str::internalize_atom(app * atom, bool gate_ctx) { - // TODO I have no idea if this is correct. TRACE("t_str", tout << "internalizing atom: " << mk_ismt2_pp(atom, get_manager()) << std::endl;); SASSERT(atom->get_family_id() == get_family_id()); - ast_manager & m = get_manager(); context & ctx = get_context(); if (ctx.b_internalized(atom)) @@ -75,8 +73,6 @@ bool theory_str::internalize_atom(app * atom, bool gate_ctx) { } bool theory_str::internalize_term(app * term) { - // TODO I have no idea if this is correct either. - ast_manager & m = get_manager(); context & ctx = get_context(); TRACE("t_str", tout << "internalizing term: " << mk_ismt2_pp(term, get_manager()) << std::endl;); SASSERT(term->get_family_id() == get_family_id()); @@ -115,6 +111,16 @@ app * theory_str::mk_strlen(app * e) { } } +app * theory_str::mk_concat(app * e1, app * e2) { + ast_manager & m = get_manager(); + if (e1 == NULL || e2 == NULL) { + m.raise_exception("strings to be concatenated cannot be NULL"); + } + // TODO there's a *TON* of missing code here from strTheory::mk_concat() + expr * args[2] = {e1, e2}; + return get_manager().mk_app(get_id(), OP_STRCAT, 0, 0, 2, args); +} + bool theory_str::can_propagate() { return !m_basicstr_axiom_todo.empty() || !m_str_eq_todo.empty(); } @@ -225,7 +231,6 @@ void theory_str::instantiate_basic_string_axioms(enode * str) { // build LHS >= RHS and assert app * lhs_ge_rhs = m_autil.mk_ge(len_str, zero); SASSERT(lhs_ge_rhs); - // TODO verify that this works TRACE("t_str_detail", tout << "string axiom 1: " << mk_ismt2_pp(lhs_ge_rhs, m) << std::endl;); assert_axiom(lhs_ge_rhs); } @@ -315,7 +320,6 @@ bool theory_str::new_eq_check(expr * lhs, expr * rhs) { } void theory_str::group_terms_by_eqc(expr * n, std::set & concats, std::set & vars, std::set & consts) { - ast_manager & m = get_manager(); context & ctx = get_context(); enode * nNode = ctx.get_enode(n); enode * eqcNode = nNode; @@ -352,6 +356,27 @@ void theory_str::group_terms_by_eqc(expr * n, std::set & concats, std::se void theory_str::simplify_concat_equality(expr * lhs, expr * rhs) { // TODO strArgmt::simplifyConcatEq() } +/* + * Look through the equivalence class of n to find a string constant. + * Return that constant if it is found, and set hasEqcValue to true. + * Otherwise, return n, and set hasEqcValue to false. + */ +expr * theory_str::get_eqc_value(expr * n, bool & hasEqcValue) { + context & ctx = get_context(); + enode * nNode = ctx.get_enode(n); + enode * eqcNode = nNode; + do { + app * ast = eqcNode->get_owner(); + if (is_string(eqcNode)) { + hasEqcValue = true; + return ast; + } + eqcNode = eqcNode->get_next(); + } while (eqcNode != nNode); + // not found + hasEqcValue = false; + return n; +} /* * strArgmt::solve_concat_eq_str() @@ -388,7 +413,75 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { return; } - // TODO the rest... + bool arg1_has_eqc_value = false; + bool arg2_has_eqc_value = false; + expr * arg1 = get_eqc_value(a1, arg1_has_eqc_value); + expr * arg2 = get_eqc_value(a2, arg2_has_eqc_value); + expr_ref newConcat(m); + if (arg1 != a1 || arg2 != a2) { + TRACE("t_str", tout << "resolved concat argument(s) to eqc string constants" << std::endl;); + int iPos = 0; + app * item1[2]; + if (a1 != arg1) { + item1[iPos++] = ctx.mk_eq_atom(a1, arg1); + } + if (a2 != arg2) { + item1[iPos++] = ctx.mk_eq_atom(a2, arg2); + } + expr_ref implyL1(m); + if (iPos == 1) { + implyL1 = item1[0]; + } else { + implyL1 = m.mk_and(item1[0], item1[1]); + } + newConcat = mk_concat(to_app(arg1), to_app(arg2)); + if (newConcat != str) { + expr_ref implyR1(ctx.mk_eq_atom(concat, newConcat), m); + assert_implication(implyL1, implyR1); + } + } else { + newConcat = concat; + } + if (newConcat == str) { + return; + } + if (!is_concat(to_app(newConcat))) { + return; + } + if (arg1_has_eqc_value && arg2_has_eqc_value) { + // Case 1: Concat(const, const) == const + TRACE("t_str", tout << "Case 1: Concat(const, const) == const" << std::endl;); + const char * str1; + m_strutil.is_string(arg1, & str1); + std::string arg1_str(str1); + + const char * str2; + m_strutil.is_string(arg2, & str2); + std::string arg2_str(str2); + + std::string result_str = arg1_str + arg2_str; + if (result_str != const_str) { + // Inconsistency + TRACE("t_str", tout << "inconsistency detected: \"" + << arg1_str << "\" + \"" << arg2_str << + "\" != \"" << const_str << "\"" << std::endl;); + expr_ref equality(ctx.mk_eq_atom(concat, str), m); + expr_ref diseq(m.mk_not(equality), m); + assert_axiom(diseq); + } + } else if (!arg1_has_eqc_value && arg2_has_eqc_value) { + // Case 2: Concat(var, const) == const + TRACE("t_str", tout << "Case 2: Concat(var, const) == const" << std::endl;); + NOT_IMPLEMENTED_YET(); + } else if (arg1_has_eqc_value && !arg2_has_eqc_value) { + // Case 3: Concat(const, var) == const + TRACE("t_str", tout << "Case 3: Concat(const, var) == const" << std::endl;); + NOT_IMPLEMENTED_YET(); + } else { + // Case 4: Concat(var, var) == const + TRACE("t_str", tout << "Case 4: Concat(var, var) == const" << std::endl;); + NOT_IMPLEMENTED_YET(); + } } } diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index c7a4a5952..4839b417b 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -62,6 +62,7 @@ namespace smt { void assert_implication(expr * premise, expr * conclusion); app * mk_strlen(app * e); + app * mk_concat(app * e1, app * e2); bool is_concat(app const * a) const { return a->is_app_of(get_id(), OP_STRCAT); } bool is_concat(enode const * n) const { return is_concat(n->get_owner()); } @@ -74,6 +75,8 @@ namespace smt { void set_up_axioms(expr * ex); void handle_equality(expr * lhs, expr * rhs); + expr * get_eqc_value(expr * n, bool & hasEqcValue); + void simplify_concat_equality(expr * lhs, expr * rhs); void solve_concat_eq_str(expr * concat, expr * str); From 876af399e394c1974019c897d081df76160e2177 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Mon, 28 Sep 2015 14:44:25 -0400 Subject: [PATCH 029/410] probably fix duplication of mk_string() terms also implement Case 2 of solve_concat_eq_str() --- src/ast/str_decl_plugin.cpp | 22 +++++++++++++++++---- src/ast/str_decl_plugin.h | 7 +++++++ src/smt/theory_str.cpp | 39 ++++++++++++++++++++++++++++++++++++- 3 files changed, 63 insertions(+), 5 deletions(-) diff --git a/src/ast/str_decl_plugin.cpp b/src/ast/str_decl_plugin.cpp index 60db88b63..9398dbf34 100644 --- a/src/ast/str_decl_plugin.cpp +++ b/src/ast/str_decl_plugin.cpp @@ -99,11 +99,25 @@ func_decl * str_decl_plugin::mk_func_decl(decl_kind k, unsigned num_parameters, return mk_func_decl(k); } +app * str_decl_plugin::mk_string(std::string & val) { + std::map::iterator it = string_cache.find(val); + if (it == string_cache.end()) { + char * new_buffer = alloc_svect(char, val.length() + 1); + strcpy(new_buffer, val.c_str()); + parameter p[1] = {parameter(new_buffer)}; + func_decl * d; + d = m_manager->mk_const_decl(m_strv_sym, m_str_decl, func_decl_info(m_family_id, OP_STR, 1, p)); + app * str = m_manager->mk_const(d); + string_cache[val] = str; + return str; + } else { + return it->second; + } +} + app * str_decl_plugin::mk_string(const char * val) { - parameter p[1] = {parameter(val)}; - func_decl * d; - d = m_manager->mk_const_decl(m_strv_sym, m_str_decl, func_decl_info(m_family_id, OP_STR, 1, p)); - return m_manager->mk_const(d); + std::string key(val); + return mk_string(key); } void str_decl_plugin::get_op_names(svector & op_names, symbol const & logic) { diff --git a/src/ast/str_decl_plugin.h b/src/ast/str_decl_plugin.h index a64e0c05f..f84c1ec31 100644 --- a/src/ast/str_decl_plugin.h +++ b/src/ast/str_decl_plugin.h @@ -19,6 +19,7 @@ Revision History: #include"ast.h" #include"arith_decl_plugin.h" +#include enum str_sort_kind { STRING_SORT, @@ -44,6 +45,8 @@ protected: func_decl * m_concat_decl; func_decl * m_length_decl; + std::map string_cache; + virtual void set_manager(ast_manager * m, family_id id); func_decl * mk_func_decl(decl_kind k); @@ -58,6 +61,7 @@ public: unsigned arity, sort * const * domain, sort * range); app * mk_string(const char * val); + app * mk_string(std::string & val); virtual void get_op_names(svector & op_names, symbol const & logic); virtual void get_sort_names(svector & sort_names, symbol const & logic); @@ -90,6 +94,9 @@ public: app * mk_string(const char * val) { return m_plugin->mk_string(val); } + app * mk_string(std::string & val) { + return m_plugin->mk_string(val); + } // TODO }; diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index f3e6496b7..08f83fdd3 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -468,11 +468,48 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { expr_ref equality(ctx.mk_eq_atom(concat, str), m); expr_ref diseq(m.mk_not(equality), m); assert_axiom(diseq); + return; } } else if (!arg1_has_eqc_value && arg2_has_eqc_value) { // Case 2: Concat(var, const) == const TRACE("t_str", tout << "Case 2: Concat(var, const) == const" << std::endl;); - NOT_IMPLEMENTED_YET(); + const char * str2; + m_strutil.is_string(arg2, & str2); + std::string arg2_str(str2); + int resultStrLen = const_str.length(); + int arg2StrLen = arg2_str.length(); + if (resultStrLen < arg2StrLen) { + // Inconsistency + TRACE("t_str", tout << "inconsistency detected: \"" + << arg2_str << + "\" is longer than \"" << const_str << "\"," + << " so cannot be concatenated with anything to form it" << std::endl;); + expr_ref equality(ctx.mk_eq_atom(newConcat, str), m); + expr_ref diseq(m.mk_not(equality), m); + assert_axiom(diseq); + return; + } else { + int varStrLen = resultStrLen - arg2StrLen; + std::string firstPart = const_str.substr(0, varStrLen); + std::string secondPart = const_str.substr(varStrLen, arg2StrLen); + if (arg2_str != secondPart) { + // Inconsistency + TRACE("t_str", tout << "inconsistency detected: " + << "suffix of concatenation result expected \"" << secondPart << "\", " + << "actually \"" << arg2_str << "\"" + << std::endl;); + expr_ref equality(ctx.mk_eq_atom(newConcat, str), m); + expr_ref diseq(m.mk_not(equality), m); + assert_axiom(diseq); + return; + } else { + expr_ref tmpStrConst(m_strutil.mk_string(firstPart), m); + expr_ref premise(ctx.mk_eq_atom(newConcat, str), m); + expr_ref conclusion(ctx.mk_eq_atom(arg1, tmpStrConst), m); + assert_implication(premise, conclusion); + return; + } + } } else if (arg1_has_eqc_value && !arg2_has_eqc_value) { // Case 3: Concat(const, var) == const TRACE("t_str", tout << "Case 3: Concat(const, var) == const" << std::endl;); From 871b08bd8cd42dd35a9ed19a8bc4e2d77fca4155 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Mon, 28 Sep 2015 14:52:43 -0400 Subject: [PATCH 030/410] solve_concat_eq_str() case 3 --- src/smt/theory_str.cpp | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 08f83fdd3..d6edc2f6b 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -513,7 +513,43 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { } else if (arg1_has_eqc_value && !arg2_has_eqc_value) { // Case 3: Concat(const, var) == const TRACE("t_str", tout << "Case 3: Concat(const, var) == const" << std::endl;); - NOT_IMPLEMENTED_YET(); + const char * str1; + m_strutil.is_string(arg1, & str1); + std::string arg1_str(str1); + int resultStrLen = const_str.length(); + int arg1StrLen = arg1_str.length(); + if (resultStrLen < arg1StrLen) { + // Inconsistency + TRACE("t_str", tout << "inconsistency detected: \"" + << arg1_str << + "\" is longer than \"" << const_str << "\"," + << " so cannot be concatenated with anything to form it" << std::endl;); + expr_ref equality(ctx.mk_eq_atom(newConcat, str), m); + expr_ref diseq(m.mk_not(equality), m); + assert_axiom(diseq); + return; + } else { + int varStrLen = resultStrLen - arg1StrLen; + std::string firstPart = const_str.substr(0, arg1StrLen); + std::string secondPart = const_str.substr(arg1StrLen, varStrLen); + if (arg1_str != firstPart) { + // Inconsistency + TRACE("t_str", tout << "inconsistency detected: " + << "prefix of concatenation result expected \"" << secondPart << "\", " + << "actually \"" << arg1_str << "\"" + << std::endl;); + expr_ref equality(ctx.mk_eq_atom(newConcat, str), m); + expr_ref diseq(m.mk_not(equality), m); + assert_axiom(diseq); + return; + } else { + expr_ref tmpStrConst(m_strutil.mk_string(secondPart), m); + expr_ref premise(ctx.mk_eq_atom(newConcat, str), m); + expr_ref conclusion(ctx.mk_eq_atom(arg2, tmpStrConst), m); + assert_implication(premise, conclusion); + return; + } + } } else { // Case 4: Concat(var, var) == const TRACE("t_str", tout << "Case 4: Concat(var, var) == const" << std::endl;); From f473b92d5c20352cb8cafcea0adb9d02fcc87f4a Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Mon, 28 Sep 2015 17:41:01 -0400 Subject: [PATCH 031/410] solve_concat_eq_str() case 4 WIP --- src/smt/theory_str.cpp | 174 ++++++++++++++++++++++++++++++++++++++++- src/smt/theory_str.h | 5 ++ 2 files changed, 177 insertions(+), 2 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index d6edc2f6b..458786110 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -27,7 +27,8 @@ theory_str::theory_str(ast_manager & m): theory(m.mk_family_id("str")), search_started(false), m_autil(m), - m_strutil(m) + m_strutil(m), + tmpXorVarCount(0) { } @@ -99,6 +100,31 @@ bool theory_str::internalize_term(app * term) { return true; } +Z3_ast mk_internal_xor_var(Z3_theory t) { + Z3_context ctx = Z3_theory_get_context(t); + std::stringstream ss; + ss << tmpXorVarCount; + tmpXorVarCount++; + std::string name = "$$_xor_" + ss.str(); + return mk_int_var(ctx, name.c_str()); +} + +app * theory_str::mk_internal_xor_var() { + context & ctx = get_context(); + ast_manager & m = get_manager(); + std::stringstream ss; + ss << tmpXorVarCount; + tmpXorVarCount++; + std::string name = "$$_xor_" + ss.str(); + // Z3_sort r = of_sort(mk_c(c)->m().mk_sort(mk_c(c)->get_arith_fid(), INT_SORT)); + sort * int_sort = m.mk_sort(m_autil.get_family_id(), INT_SORT); + symbol sym(name); + + app* a = m.mk_const(m.mk_const_decl(sym, int_sort)); + // TODO ctx.save_ast_trail(a)? + return a; +} + app * theory_str::mk_strlen(app * e) { /*if (m_strutil.is_string(e)) {*/ if (false) { const char * strval = 0; @@ -553,7 +579,151 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { } else { // Case 4: Concat(var, var) == const TRACE("t_str", tout << "Case 4: Concat(var, var) == const" << std::endl;); - NOT_IMPLEMENTED_YET(); + // TODO large additions required in this section + if (true) { /* if (Concat(arg1, arg2) == NULL) { */ + int arg1Len = -1; /* = getLenValue(arg1); */ + int arg2Len = -1; /* = getLenValue(arg2); */ + if (arg1Len != -1 || arg2Len != -1) { + NOT_IMPLEMENTED_YET(); // TODO + } else { + /* + Z3_ast xorFlag = NULL; + std::pair key1(arg1, arg2); + std::pair key2(arg2, arg1); + if (varForBreakConcat.find(key1) == varForBreakConcat.end() && varForBreakConcat.find(key2) == varForBreakConcat.end()) { + xorFlag = mk_internal_xor_var(t); + varForBreakConcat[key1][0] = xorFlag; + } else { + if (varForBreakConcat.find(key1) != varForBreakConcat.end()) { + xorFlag = varForBreakConcat[key1][0]; + } else { + xorFlag = varForBreakConcat[key2][0]; + } + } + + int concatStrLen = const_str.length(); + int xor_pos = 0; + int and_count = 1; + Z3_ast * xor_items = new Z3_ast[concatStrLen + 1]; + Z3_ast * and_items = new Z3_ast[4 * (concatStrLen + 1) + 1]; + Z3_ast arg1_eq = NULL; + Z3_ast arg2_eq = NULL; + for (int i = 0; i < concatStrLen + 1; i++) { + std::string prefixStr = const_str.substr(0, i); + std::string suffixStr = const_str.substr(i, concatStrLen - i); + + // skip invalidate options + if (isConcatFunc(t, arg1) && canConcatEqStr(t, arg1, prefixStr) == 0) { + continue; + } + if (isConcatFunc(t, arg2) && canConcatEqStr(t, arg2, suffixStr) == 0) { + continue; + } + + Z3_ast xorAst = Z3_mk_eq(ctx, xorFlag, mk_int(ctx, xor_pos)); + xor_items[xor_pos++] = xorAst; + + Z3_ast prefixAst = my_mk_str_value(t, prefixStr.c_str()); + arg1_eq = Z3_mk_eq(ctx, arg1, prefixAst); + and_items[and_count++] = Z3_mk_eq(ctx, xorAst, arg1_eq); + + Z3_ast suffixAst = my_mk_str_value(t, suffixStr.c_str()); + arg2_eq = Z3_mk_eq(ctx, arg2, suffixAst); + and_items[and_count++] = Z3_mk_eq(ctx, xorAst, arg2_eq); + } + */ + expr_ref xorFlag(m); + std::pair key1(arg1, arg2); + std::pair key2(arg2, arg1); + std::map, std::map >::iterator varBreak_key1 = + varForBreakConcat.find(key1); + std::map, std::map >::iterator varBreak_key2 = + varForBreakConcat.find(key2); + if (varBreak_key1 == varForBreakConcat.end() && varBreak_key2 == varForBreakConcat.end()) { + xorFlag = mk_internal_xor_var(); + varForBreakConcat[key1][0] = xorFlag; + } else if (varBreak_key1 != varForBreakConcat.end()) { + xorFlag = varForBreakConcat[key1][0]; + } else { // varBreak_key2 != varForBreakConcat.end() + xorFlag = varForBreakConcat[key2][0]; + } + + int concatStrLen = const_str.length(); + int xor_pos = 0; + int and_count = 1; + expr * xor_items[] = new expr*[concatStrLen + 1]; + expr * and_items[] = new expr*[4 * (concatStrLen+1) + 1]; + + expr_ref arg1_eq(m); + expr_ref arg2_eq(m); + + for (int i = 0; i < concatStrLen + 1; ++i) { + std::string prefixStr = const_str.substr(0, i); + std::string suffixStr = const_str.substr(i, concatStrLen - i); + // skip invalid options + // TODO canConcatEqStr() checks: + /* + if (isConcatFunc(t, arg1) && canConcatEqStr(t, arg1, prefixStr) == 0) { + continue; + } + if (isConcatFunc(t, arg2) && canConcatEqStr(t, arg2, suffixStr) == 0) { + continue; + } + */ + expr_ref xorAst(ctx.mk_eq_atom(xorFlag, mk_int(xor_pos)), m); + xor_items[xor_pos++] = xorAst; + + expr_ref prefixAst(m_strutil.mk_string(prefixStr), m); + arg1_eq = ctx.mk_eq_atom(arg1, prefixAst); + and_items[and_count++] = ctx.mk_eq_atom(xorAst, arg1_eq); + + expr_ref suffixAst(m_strutil.mk_string(prefixStr), m); + arg2_eq = ctx.mk_eq_atom(arg2, suffixAst); + and_items[and_count++] = ctx.mk_eq_atom(xorAst, arg2_eq); + } + + expr_ref implyL(ctx.mk_eq_atom(concat, str), m); + expr_ref implyR(m); + if (xor_pos == 0) { + // negate + expr_ref concat_eq_str(ctx.mk_eq_atom(concat, str), m); + expr_ref negate_ast(m.mk_not(concat_eq_str), m); + assert_axiom(negate_ast); + } else { + // TODO + if (xor_pos == 1) { + + } else { + + } + } + delete[] xor_items; + delete[] and_items; + + /* + + Z3_ast implyL = Z3_mk_eq(ctx, concatAst, constStr); + Z3_ast implyR1 = NULL; + if (xor_pos == 0) { + // negate + Z3_ast negateAst = Z3_mk_not(ctx, Z3_mk_eq(ctx, concatAst, constStr)); + addAxiom(t, negateAst, __LINE__); + } else { + if (xor_pos == 1) { + and_items[0] = xor_items[0]; + implyR1 = Z3_mk_and(ctx, and_count, and_items); + } else { + and_items[0] = Z3_mk_or(ctx, xor_pos, xor_items); + implyR1 = Z3_mk_and(ctx, and_count, and_items); + } + Z3_ast implyToAssert = Z3_mk_implies(ctx, implyL, implyR1); + addAxiom(t, implyToAssert, __LINE__); + } + delete[] xor_items; + delete[] and_items; + */ + } + } } } } diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 4839b417b..ea6ec8551 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -57,6 +57,9 @@ namespace smt { ptr_vector m_basicstr_axiom_todo; svector > m_str_eq_todo; + + int tmpXorVarCount; + std::map, std::map > varForBreakConcat; protected: void assert_axiom(expr * e); void assert_implication(expr * premise, expr * conclusion); @@ -64,6 +67,8 @@ namespace smt { app * mk_strlen(app * e); app * mk_concat(app * e1, app * e2); + app * mk_internal_xor_var(); + bool is_concat(app const * a) const { return a->is_app_of(get_id(), OP_STRCAT); } bool is_concat(enode const * n) const { return is_concat(n->get_owner()); } bool is_string(app const * a) const { return a->is_app_of(get_id(), OP_STR); } From 2320b6dc48106a2ccbb8748781b7820d62304e5e Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 29 Sep 2015 17:46:51 -0400 Subject: [PATCH 032/410] solve_concat_eq_str() case 4: somewhat working something's wrong but it may be very simple to fix --- src/ast/str_decl_plugin.h | 8 +-- src/smt/theory_str.cpp | 120 +++++++------------------------------- 2 files changed, 26 insertions(+), 102 deletions(-) diff --git a/src/ast/str_decl_plugin.h b/src/ast/str_decl_plugin.h index f84c1ec31..61d1bc2f2 100644 --- a/src/ast/str_decl_plugin.h +++ b/src/ast/str_decl_plugin.h @@ -38,13 +38,13 @@ protected: symbol m_strv_sym; sort * m_str_decl; - arith_decl_plugin * m_arith_plugin; - sort * m_int_sort; - family_id m_arith_fid; - func_decl * m_concat_decl; func_decl * m_length_decl; + arith_decl_plugin * m_arith_plugin; + family_id m_arith_fid; + sort * m_int_sort; + std::map string_cache; virtual void set_manager(ast_manager * m, family_id id); diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 458786110..e2896f4f5 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -37,7 +37,7 @@ theory_str::~theory_str() { void theory_str::assert_axiom(expr * e) { if (get_manager().is_true(e)) return; - TRACE("t_str_detail", tout << "asserting " << mk_ismt2_pp(e, get_manager()) << "\n";); + TRACE("t_str_detail", tout << "asserting " << mk_ismt2_pp(e, get_manager()) << std::endl;); context & ctx = get_context(); if (!ctx.b_internalized(e)) { ctx.internalize(e, true); @@ -45,11 +45,12 @@ void theory_str::assert_axiom(expr * e) { literal lit(ctx.get_literal(e)); ctx.mark_as_relevant(lit); ctx.mk_th_axiom(get_id(), 1, &lit); - TRACE("t_str_detail", tout << "done asserting " << mk_ismt2_pp(e, get_manager()) << "\n";); + TRACE("t_str_detail", tout << "done asserting " << mk_ismt2_pp(e, get_manager()) << std::endl;); } void theory_str::assert_implication(expr * premise, expr * conclusion) { ast_manager & m = get_manager(); + TRACE("t_str_detail", tout << "asserting implication " << mk_ismt2_pp(premise, m) << " -> " << mk_ismt2_pp(conclusion, m) << std::endl;); expr_ref axiom(m.mk_or(m.mk_not(premise), conclusion), m); assert_axiom(axiom); } @@ -100,15 +101,6 @@ bool theory_str::internalize_term(app * term) { return true; } -Z3_ast mk_internal_xor_var(Z3_theory t) { - Z3_context ctx = Z3_theory_get_context(t); - std::stringstream ss; - ss << tmpXorVarCount; - tmpXorVarCount++; - std::string name = "$$_xor_" + ss.str(); - return mk_int_var(ctx, name.c_str()); -} - app * theory_str::mk_internal_xor_var() { context & ctx = get_context(); ast_manager & m = get_manager(); @@ -118,7 +110,9 @@ app * theory_str::mk_internal_xor_var() { std::string name = "$$_xor_" + ss.str(); // Z3_sort r = of_sort(mk_c(c)->m().mk_sort(mk_c(c)->get_arith_fid(), INT_SORT)); sort * int_sort = m.mk_sort(m_autil.get_family_id(), INT_SORT); - symbol sym(name); + char * new_buffer = alloc_svect(char, name.length() + 1); + strcpy(new_buffer, name.c_str()); + symbol sym(new_buffer); app* a = m.mk_const(m.mk_const_decl(sym, int_sort)); // TODO ctx.save_ast_trail(a)? @@ -585,53 +579,7 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { int arg2Len = -1; /* = getLenValue(arg2); */ if (arg1Len != -1 || arg2Len != -1) { NOT_IMPLEMENTED_YET(); // TODO - } else { - /* - Z3_ast xorFlag = NULL; - std::pair key1(arg1, arg2); - std::pair key2(arg2, arg1); - if (varForBreakConcat.find(key1) == varForBreakConcat.end() && varForBreakConcat.find(key2) == varForBreakConcat.end()) { - xorFlag = mk_internal_xor_var(t); - varForBreakConcat[key1][0] = xorFlag; - } else { - if (varForBreakConcat.find(key1) != varForBreakConcat.end()) { - xorFlag = varForBreakConcat[key1][0]; - } else { - xorFlag = varForBreakConcat[key2][0]; - } - } - - int concatStrLen = const_str.length(); - int xor_pos = 0; - int and_count = 1; - Z3_ast * xor_items = new Z3_ast[concatStrLen + 1]; - Z3_ast * and_items = new Z3_ast[4 * (concatStrLen + 1) + 1]; - Z3_ast arg1_eq = NULL; - Z3_ast arg2_eq = NULL; - for (int i = 0; i < concatStrLen + 1; i++) { - std::string prefixStr = const_str.substr(0, i); - std::string suffixStr = const_str.substr(i, concatStrLen - i); - - // skip invalidate options - if (isConcatFunc(t, arg1) && canConcatEqStr(t, arg1, prefixStr) == 0) { - continue; - } - if (isConcatFunc(t, arg2) && canConcatEqStr(t, arg2, suffixStr) == 0) { - continue; - } - - Z3_ast xorAst = Z3_mk_eq(ctx, xorFlag, mk_int(ctx, xor_pos)); - xor_items[xor_pos++] = xorAst; - - Z3_ast prefixAst = my_mk_str_value(t, prefixStr.c_str()); - arg1_eq = Z3_mk_eq(ctx, arg1, prefixAst); - and_items[and_count++] = Z3_mk_eq(ctx, xorAst, arg1_eq); - - Z3_ast suffixAst = my_mk_str_value(t, suffixStr.c_str()); - arg2_eq = Z3_mk_eq(ctx, arg2, suffixAst); - and_items[and_count++] = Z3_mk_eq(ctx, xorAst, arg2_eq); - } - */ + } else { /* ! (arg1Len != 1 || arg2Len != 1) */ expr_ref xorFlag(m); std::pair key1(arg1, arg2); std::pair key2(arg2, arg1); @@ -651,11 +599,8 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { int concatStrLen = const_str.length(); int xor_pos = 0; int and_count = 1; - expr * xor_items[] = new expr*[concatStrLen + 1]; - expr * and_items[] = new expr*[4 * (concatStrLen+1) + 1]; - - expr_ref arg1_eq(m); - expr_ref arg2_eq(m); + expr ** xor_items = new expr*[concatStrLen + 1]; + expr ** and_items = new expr*[4 * (concatStrLen+1) + 1]; for (int i = 0; i < concatStrLen + 1; ++i) { std::string prefixStr = const_str.substr(0, i); @@ -670,60 +615,39 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { continue; } */ - expr_ref xorAst(ctx.mk_eq_atom(xorFlag, mk_int(xor_pos)), m); + expr_ref xorAst(ctx.mk_eq_atom(xorFlag, m_autil.mk_numeral(rational(xor_pos), true)), m); xor_items[xor_pos++] = xorAst; expr_ref prefixAst(m_strutil.mk_string(prefixStr), m); - arg1_eq = ctx.mk_eq_atom(arg1, prefixAst); + expr_ref arg1_eq (ctx.mk_eq_atom(arg1, prefixAst), m); and_items[and_count++] = ctx.mk_eq_atom(xorAst, arg1_eq); expr_ref suffixAst(m_strutil.mk_string(prefixStr), m); - arg2_eq = ctx.mk_eq_atom(arg2, suffixAst); + expr_ref arg2_eq (ctx.mk_eq_atom(arg2, suffixAst), m); and_items[and_count++] = ctx.mk_eq_atom(xorAst, arg2_eq); } expr_ref implyL(ctx.mk_eq_atom(concat, str), m); - expr_ref implyR(m); + expr_ref implyR1(m); if (xor_pos == 0) { // negate expr_ref concat_eq_str(ctx.mk_eq_atom(concat, str), m); expr_ref negate_ast(m.mk_not(concat_eq_str), m); assert_axiom(negate_ast); } else { - // TODO if (xor_pos == 1) { - + and_items[0] = xor_items[0]; + implyR1 = m.mk_and(and_count, and_items); } else { - + and_items[0] = m.mk_or(xor_pos, xor_items); + implyR1 = m.mk_and(and_count, and_items); } + assert_implication(implyL, implyR1); } delete[] xor_items; delete[] and_items; - - /* - - Z3_ast implyL = Z3_mk_eq(ctx, concatAst, constStr); - Z3_ast implyR1 = NULL; - if (xor_pos == 0) { - // negate - Z3_ast negateAst = Z3_mk_not(ctx, Z3_mk_eq(ctx, concatAst, constStr)); - addAxiom(t, negateAst, __LINE__); - } else { - if (xor_pos == 1) { - and_items[0] = xor_items[0]; - implyR1 = Z3_mk_and(ctx, and_count, and_items); - } else { - and_items[0] = Z3_mk_or(ctx, xor_pos, xor_items); - implyR1 = Z3_mk_and(ctx, and_count, and_items); - } - Z3_ast implyToAssert = Z3_mk_implies(ctx, implyL, implyR1); - addAxiom(t, implyToAssert, __LINE__); - } - delete[] xor_items; - delete[] and_items; - */ - } - } + } /* (arg1Len != 1 || arg2Len != 1) */ + } /* if (Concat(arg1, arg2) == NULL) */ } } } @@ -945,7 +869,7 @@ void theory_str::new_diseq_eh(theory_var x, theory_var y) { } void theory_str::relevant_eh(app * n) { - TRACE("t_str", tout << "relevant: " << mk_ismt2_pp(n, get_manager()) << "\n";); + TRACE("t_str", tout << "relevant: " << mk_ismt2_pp(n, get_manager()) << std::endl;); } void theory_str::assign_eh(bool_var v, bool is_true) { @@ -988,7 +912,7 @@ void theory_str::init_model(model_generator & mg) { model_value_proc * theory_str::mk_value(enode * n, model_generator & mg) { TRACE("t_str", tout << "mk_value for: " << mk_ismt2_pp(n->get_owner(), get_manager()) << - " (sort " << mk_ismt2_pp(get_manager().get_sort(n->get_owner()), get_manager()) << ")\n";); + " (sort " << mk_ismt2_pp(get_manager().get_sort(n->get_owner()), get_manager()) << ")" << std::endl;); ast_manager & m = get_manager(); context & ctx = get_context(); app_ref owner(m); From 191c50b529310cae9f39463d371eb92b896560d5 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 29 Sep 2015 17:52:19 -0400 Subject: [PATCH 033/410] fix solve_concat_eq_str() case 4: prefixStr should have been suffixStr --- src/smt/theory_str.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index e2896f4f5..f9fb7e3a4 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -622,7 +622,7 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { expr_ref arg1_eq (ctx.mk_eq_atom(arg1, prefixAst), m); and_items[and_count++] = ctx.mk_eq_atom(xorAst, arg1_eq); - expr_ref suffixAst(m_strutil.mk_string(prefixStr), m); + expr_ref suffixAst(m_strutil.mk_string(suffixStr), m); expr_ref arg2_eq (ctx.mk_eq_atom(arg2, suffixAst), m); and_items[and_count++] = ctx.mk_eq_atom(xorAst, arg2_eq); } From 8ed86d2f19a074dd68d10ca5832a4cfa18351cbb Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 29 Sep 2015 18:02:05 -0400 Subject: [PATCH 034/410] add concatenation axiom --- src/smt/theory_str.cpp | 16 ++++++++++++++-- src/smt/theory_str.h | 1 + 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index f9fb7e3a4..ea7b84d62 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -142,7 +142,7 @@ app * theory_str::mk_concat(app * e1, app * e2) { } bool theory_str::can_propagate() { - return !m_basicstr_axiom_todo.empty() || !m_str_eq_todo.empty(); + return !m_basicstr_axiom_todo.empty() || !m_str_eq_todo.empty() || !m_concat_axiom_todo.empty(); } void theory_str::propagate() { @@ -160,6 +160,11 @@ void theory_str::propagate() { handle_equality(lhs->get_owner(), rhs->get_owner()); } m_str_eq_todo.reset(); + + for (unsigned i = 0; i < m_concat_axiom_todo.empty(); ++i) { + instantiate_concat_axiom(m_concat_axiom_todo[i]); + } + m_concat_axiom_todo.reset(); } } @@ -167,7 +172,6 @@ void theory_str::propagate() { * Instantiate an axiom of the following form: * Length(Concat(x, y)) = Length(x) + Length(y) */ -// TODO this isn't used yet void theory_str::instantiate_concat_axiom(enode * cat) { SASSERT(is_concat(cat)); app * a_cat = cat->get_owner(); @@ -321,6 +325,7 @@ void theory_str::reset_eh() { TRACE("t_str", tout << "resetting" << std::endl;); m_basicstr_axiom_todo.reset(); m_str_eq_todo.reset(); + m_concat_axiom_todo.reset(); pop_scope_eh(0); } @@ -767,6 +772,8 @@ void theory_str::handle_equality(expr * lhs, expr * rhs) { } } + // TODO simplify_parent over eqc + // TODO regex unroll? (much later) } @@ -785,6 +792,11 @@ void theory_str::set_up_axioms(expr * ex) { enode * n = ctx.get_enode(ex); SASSERT(n); m_basicstr_axiom_todo.push_back(n); + + // if additionally ex is a concatenation, set up concatenation axioms + if (is_app(ex) && is_concat(to_app(ex))) { + m_concat_axiom_todo.push_back(n); + } } else { TRACE("t_str_detail", tout << "setting up axioms for " << mk_ismt2_pp(ex, get_manager()) << ": expr is of wrong sort, ignoring" << std::endl;); diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index ea6ec8551..458287392 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -57,6 +57,7 @@ namespace smt { ptr_vector m_basicstr_axiom_todo; svector > m_str_eq_todo; + ptr_vector m_concat_axiom_todo; int tmpXorVarCount; std::map, std::map > varForBreakConcat; From 1cdfe159b8d23a458a5b97c8c76325c86e2fe366 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 29 Sep 2015 20:19:43 -0400 Subject: [PATCH 035/410] simplify_concat_equality() and easy cases there still WIP especially wrt. model generation but what's here does work --- src/ast/str_decl_plugin.cpp | 2 +- src/smt/theory_str.cpp | 392 +++++++++++++++++++++++++++++++++++- src/smt/theory_str.h | 12 +- 3 files changed, 401 insertions(+), 5 deletions(-) diff --git a/src/ast/str_decl_plugin.cpp b/src/ast/str_decl_plugin.cpp index 9398dbf34..c72a5dbc2 100644 --- a/src/ast/str_decl_plugin.cpp +++ b/src/ast/str_decl_plugin.cpp @@ -102,7 +102,7 @@ func_decl * str_decl_plugin::mk_func_decl(decl_kind k, unsigned num_parameters, app * str_decl_plugin::mk_string(std::string & val) { std::map::iterator it = string_cache.find(val); if (it == string_cache.end()) { - char * new_buffer = alloc_svect(char, val.length() + 1); + char * new_buffer = alloc_svect(char, (val.length() + 1)); strcpy(new_buffer, val.c_str()); parameter p[1] = {parameter(new_buffer)}; func_decl * d; diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index ea7b84d62..92edbc22b 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -119,7 +119,7 @@ app * theory_str::mk_internal_xor_var() { return a; } -app * theory_str::mk_strlen(app * e) { +app * theory_str::mk_strlen(expr * e) { /*if (m_strutil.is_string(e)) {*/ if (false) { const char * strval = 0; m_strutil.is_string(e, &strval); @@ -378,9 +378,258 @@ void theory_str::group_terms_by_eqc(expr * n, std::set & concats, std::se } while (eqcNode != nNode); } -void theory_str::simplify_concat_equality(expr * lhs, expr * rhs) { - // TODO strArgmt::simplifyConcatEq() +void theory_str::get_nodes_in_concat(expr * node, ptr_vector & nodeList) { + app * a_node = to_app(node); + if (!is_concat(a_node)) { + nodeList.push_back(node); + return; + } else { + SASSERT(a_node->get_num_args() == 2); + expr * leftArg = a_node->get_arg(0); + expr * rightArg = a_node->get_arg(1); + get_nodes_in_concat(leftArg, nodeList); + get_nodes_in_concat(rightArg, nodeList); + } } + +/* + * The inputs: + * ~ nn: non const node + * ~ eq_str: the equivalent constant string of nn + * Iterate the parent of all eqc nodes of nn, looking for: + * ~ concat node + * to see whether some concat nodes can be simplified. + */ + +void theory_str::simplify_parent(expr * nn, expr * eq_str) { + // TODO strTheory::simplifyParent() +} + +expr * theory_str::simplify_concat(expr * node) { + ast_manager & m = get_manager(); + context & ctx = get_context(); + std::map resolvedMap; + ptr_vector argVec; + get_nodes_in_concat(node, argVec); + + for (unsigned i = 0; i < argVec.size(); ++i) { + bool vArgHasEqcValue = false; + expr * vArg = get_eqc_value(argVec[i], vArgHasEqcValue); + if (vArg != argVec[i]) { + resolvedMap[argVec[i]] = vArg; + } + } + + if (resolvedMap.size() == 0) { + // no simplification possible + return node; + } else { + app * resultAst = m_strutil.mk_string(""); + for (unsigned i = 0; i < argVec.size(); ++i) { + bool vArgHasEqcValue = false; + expr * vArg = get_eqc_value(argVec[i], vArgHasEqcValue); + resultAst = mk_concat(to_app(resultAst), to_app(vArg)); + } + TRACE("t_str_detail", tout << mk_ismt2_pp(node, m) << " is simplified to " << mk_ismt2_pp(resultAst, m) << std::endl;); + + if (in_same_eqc(node, resultAst)) { + TRACE("t_str_detail", tout << "SKIP: both concats are already in the same equivalence class" << std::endl;); + } else { + expr ** items = alloc_svect(expr*, resolvedMap.size()); + int pos = 0; + std::map::iterator itor = resolvedMap.begin(); + for (; itor != resolvedMap.end(); ++itor) { + items[pos++] = ctx.mk_eq_atom(itor->first, itor->second); + } + expr_ref premise(m); + if (pos == 1) { + premise = items[0]; + } else { + premise = m.mk_and(pos, items); + } + expr_ref conclusion(ctx.mk_eq_atom(node, resultAst), m); + assert_implication(premise, conclusion); + } + return resultAst; + } + +} + +/* + * Handle two equivalent Concats. + */ +void theory_str::simplify_concat_equality(expr * nn1, expr * nn2) { + ast_manager & m = get_manager(); + context & ctx = get_context(); + + app * a_nn1 = to_app(nn1); + SASSERT(a_nn1->get_num_args() == 2); + app * a_nn2 = to_app(nn2); + SASSERT(a_nn2->get_num_args() == 2); + + expr * a1_arg0 = a_nn1->get_arg(0); + expr * a1_arg1 = a_nn1->get_arg(1); + expr * a2_arg0 = a_nn2->get_arg(0); + expr * a2_arg1 = a_nn2->get_arg(1); + + // TODO + /* + int a1_arg0_len = getLenValue(t, a1_arg0); + int a1_arg1_len = getLenValue(t, a1_arg1); + int a2_arg0_len = getLenValue(t, a2_arg0); + int a2_arg1_len = getLenValue(t, a2_arg1); + */ + int a1_arg0_len = -1; + int a1_arg1_len = -1; + int a2_arg0_len = -1; + int a2_arg1_len = -1; + + TRACE("t_str", tout << "nn1 = " << mk_ismt2_pp(nn1, m) << std::endl + << "nn2 = " << mk_ismt2_pp(nn2, m) << std::endl;); + + // TODO inferLenConcatEq(nn1, nn2); + + if (a1_arg0 == a2_arg0) { + if (!in_same_eqc(a1_arg1, a2_arg1)) { + expr_ref premise(ctx.mk_eq_atom(nn1, nn2), m); + expr_ref eq1(ctx.mk_eq_atom(a1_arg1, a2_arg1), m); + expr_ref eq2(ctx.mk_eq_atom(mk_strlen(a1_arg1), mk_strlen(a2_arg1)), m); + expr_ref conclusion(m.mk_and(eq1, eq2), m); + assert_implication(premise, conclusion); + } + TRACE("t_str_detail", tout << "SKIP: a1_arg0 == a2_arg0" << std::endl;); + return; + } + + if (a1_arg1 == a2_arg1) { + if (!in_same_eqc(a1_arg0, a2_arg0)) { + expr_ref premise(ctx.mk_eq_atom(nn1, nn2), m); + expr_ref eq1(ctx.mk_eq_atom(a1_arg0, a2_arg0), m); + expr_ref eq2(ctx.mk_eq_atom(mk_strlen(a1_arg0), mk_strlen(a2_arg0)), m); + expr_ref conclusion(m.mk_and(eq1, eq2), m); + assert_implication(premise, conclusion); + } + TRACE("t_str_detail", tout << "SKIP: a1_arg1 == a2_arg1" << std::endl;); + return; + } + + // quick path + + if (in_same_eqc(a1_arg0, a2_arg0)) { + if (in_same_eqc(a1_arg1, a2_arg1)) { + TRACE("t_str_detail", tout << "SKIP: a1_arg0 =~ a2_arg0 and a1_arg1 =~ a2_arg1" << std::endl;); + return; + } else { + TRACE("t_str_detail", tout << "quick path 1-1: a1_arg0 =~ a2_arg0" << std::endl;); + expr_ref premise(m.mk_and(ctx.mk_eq_atom(nn1, nn2), ctx.mk_eq_atom(a1_arg0, a2_arg0)), m); + expr_ref conclusion(m.mk_and(ctx.mk_eq_atom(a1_arg1, a2_arg1), ctx.mk_eq_atom(mk_strlen(a1_arg1), mk_strlen(a2_arg1))), m); + assert_implication(premise, conclusion); + return; + } + } else { + if (in_same_eqc(a1_arg1, a2_arg1)) { + TRACE("t_str_detail", tout << "quick path 1-2: a1_arg1 =~ a2_arg1" << std::endl;); + expr_ref premise(m.mk_and(ctx.mk_eq_atom(nn1, nn2), ctx.mk_eq_atom(a1_arg1, a2_arg1)), m); + expr_ref conclusion(m.mk_and(ctx.mk_eq_atom(a1_arg0, a2_arg0), ctx.mk_eq_atom(mk_strlen(a1_arg0), mk_strlen(a2_arg0))), m); + assert_implication(premise, conclusion); + return; + } + } + + // TODO quick path 1-2 + /* + if(a1_arg0_len != -1 && a2_arg0_len != -1 && a1_arg0_len == a2_arg0_len){ + if (! inSameEqc(t, a1_arg0, a2_arg0)) { + __debugPrint(logFile, ">> [simplifyConcatEq] Quick Path 2-1: len(nn1.arg0) == len(nn2.arg0)\n"); + Z3_ast ax_l1 = Z3_mk_eq(ctx, nn1, nn2); + Z3_ast ax_l2 = Z3_mk_eq(ctx, mk_length(t, a1_arg0), mk_length(t, a2_arg0)); + Z3_ast ax_r1 = Z3_mk_eq(ctx, a1_arg0, a2_arg0); + Z3_ast ax_r2 = Z3_mk_eq(ctx, a1_arg1, a2_arg1); + Z3_ast toAdd = Z3_mk_implies(ctx, mk_2_and(t, ax_l1, ax_l2), mk_2_and(t, ax_r1, ax_r2)); + addAxiom(t, toAdd, __LINE__); + return; + } + } + + if (a1_arg1_len != -1 && a2_arg1_len != -1 && a1_arg1_len == a2_arg1_len) + { + if (!inSameEqc(t, a1_arg1, a2_arg1)) { + __debugPrint(logFile, ">> [simplifyConcatEq] Quick Path 2-2: len(nn1.arg1) == len(nn2.arg1)\n"); + Z3_ast ax_l1 = Z3_mk_eq(ctx, nn1, nn2); + Z3_ast ax_l2 = Z3_mk_eq(ctx, mk_length(t, a1_arg1), mk_length(t, a2_arg1)); + Z3_ast ax_r1 = Z3_mk_eq(ctx, a1_arg0, a2_arg0); + Z3_ast ax_r2 = Z3_mk_eq(ctx, a1_arg1, a2_arg1); + Z3_ast toAdd = Z3_mk_implies(ctx, mk_2_and(t, ax_l1, ax_l2), mk_2_and(t, ax_r1, ax_r2)); + addAxiom(t, toAdd, __LINE__); + return; + } + } + */ + + expr * new_nn1 = simplify_concat(nn1); + expr * new_nn2 = simplify_concat(nn2); + app * a_new_nn1 = to_app(new_nn1); + app * a_new_nn2 = to_app(new_nn2); + expr * v1_arg0 = a_new_nn1->get_arg(0); + expr * v1_arg1 = a_new_nn1->get_arg(1); + expr * v2_arg0 = a_new_nn2->get_arg(0); + expr * v2_arg1 = a_new_nn2->get_arg(1); + + TRACE("t_str_detail", tout << "new_nn1 = " << mk_ismt2_pp(new_nn1, m) << std::endl + << "new_nn2 = " << mk_ismt2_pp(new_nn2, m) << std::endl;); + + if (new_nn1 == new_nn2) { + TRACE("t_str_detail", tout << "equal concats, return" << std::endl;); + return; + } + + if (!can_two_nodes_eq(new_nn1, new_nn2)) { + expr_ref detected(m.mk_not(ctx.mk_eq_atom(new_nn1, new_nn2)), m); + TRACE("t_str_detail", tout << "inconsistency detected: " << mk_ismt2_pp(detected, m) << std::endl;); + assert_axiom(detected); + return; + } + + // check whether new_nn1 and new_nn2 are still concats + + bool n1IsConcat = is_concat(a_new_nn1); + bool n2IsConcat = is_concat(a_new_nn2); + if (!n1IsConcat && n2IsConcat) { + TRACE("t_str_detail", tout << "nn1_new is not a concat" << std::endl;); + if (is_string(a_new_nn1)) { + simplify_parent(new_nn2, new_nn1); + } + return; + } else if (n1IsConcat && !n2IsConcat) { + TRACE("t_str_detail", tout << "nn2_new is not a concat" << std::endl;); + if (is_string(a_new_nn2)) { + simplify_parent(new_nn1, new_nn2); + } + return; + } + + if (!in_same_eqc(new_nn1, new_nn2) && (nn1 != new_nn1 || nn2 != new_nn2)) { + int ii4 = 0; + expr* item[3]; + if (nn1 != new_nn1) { + item[ii4++] = ctx.mk_eq_atom(nn1, new_nn1); + } + if (nn2 != new_nn2) { + item[ii4++] = ctx.mk_eq_atom(nn2, new_nn2); + } + item[ii4++] = ctx.mk_eq_atom(nn1, nn2); + expr_ref premise(m.mk_and(ii4, item), m); + expr_ref conclusion(ctx.mk_eq_atom(new_nn1, new_nn2), m); + assert_implication(premise, conclusion); + } + + // start to split both concats + + // TODO + NOT_IMPLEMENTED_YET(); + +} + /* * Look through the equivalence class of n to find a string constant. * Return that constant if it is found, and set hasEqcValue to true. @@ -403,6 +652,119 @@ expr * theory_str::get_eqc_value(expr * n, bool & hasEqcValue) { return n; } +/* + * Decide whether n1 and n2 are already in the same equivalence class. + * This only checks whether the core considers them to be equal; + * they may not actually be equal. + */ +bool theory_str::in_same_eqc(expr * n1, expr * n2) { + if (n1 == n2) return true; + context & ctx = get_context(); + enode * n1Node = ctx.get_enode(n1); + enode * n2Node = ctx.get_enode(n2); + + // here's what the old Z3str2 would have done; we can do something much better + /* + n1Node->get_root(); + enode * curr = n1Node->get_next(); + while (curr != n1Node) { + if (curr == n2Node) { + return true; + } + curr = curr->get_next(); + } + return false; + */ + return n1Node->get_root() == n2Node->get_root(); +} + +/* +bool canTwoNodesEq(Z3_theory t, Z3_ast n1, Z3_ast n2) { + Z3_ast n1_curr = n1; + Z3_ast n2_curr = n2; + + // case 0: n1_curr is const string, n2_curr is const string + if (isConstStr(t, n1_curr) && isConstStr(t, n2_curr)) { + if (n1_curr != n2_curr) { + return false; + } + } + // case 1: n1_curr is concat, n2_curr is const string + else if (isConcatFunc(t, n1_curr) && isConstStr(t, n2_curr)) { + std::string n2_curr_str = getConstStrValue(t, n2_curr); + if (canConcatEqStr(t, n1_curr, n2_curr_str) != 1) { + return false; + } + } + // case 2: n2_curr is concat, n1_curr is const string + else if (isConcatFunc(t, n2_curr) && isConstStr(t, n1_curr)) { + std::string n1_curr_str = getConstStrValue(t, n1_curr); + if (canConcatEqStr(t, n2_curr, n1_curr_str) != 1) { + return false; + } + } else if (isConcatFunc(t, n1_curr) && isConcatFunc(t, n2_curr)) { + if (canConcatEqConcat(t, n1_curr, n2_curr) != 1) { + return false; + } + } + + return true; +} +*/ + +bool theory_str::can_concat_eq_str(expr * concat, std::string str) { + // TODO + return true; +} + +bool theory_str::can_concat_eq_concat(expr * concat1, expr * concat2) { + // TODO + return true; +} + +/* + * Check whether n1 and n2 could be equal. + * Returns true if n1 could equal n2 (maybe), + * and false if n1 is definitely not equal to n2 (no). + */ +bool theory_str::can_two_nodes_eq(expr * n1, expr * n2) { + app * n1_curr = to_app(n1); + app * n2_curr = to_app(n2); + + // case 0: n1_curr is const string, n2_curr is const string + if (is_string(n1_curr) && is_string(n2_curr)) { + if (n1_curr != n2_curr) { + return false; + } + } + // case 1: n1_curr is concat, n2_curr is const string + else if (is_concat(n1_curr) && is_string(n2_curr)) { + const char * tmp = 0; + m_strutil.is_string(n2_curr, & tmp); + std::string n2_curr_str(tmp); + if (!can_concat_eq_str(n1_curr, n2_curr_str)) { + return false; + } + } + // case 2: n2_curr is concat, n1_curr is const string + else if (is_concat(n2_curr) && is_string(n1_curr)) { + const char * tmp = 0; + m_strutil.is_string(n1_curr, & tmp); + std::string n1_curr_str(tmp); + if (!can_concat_eq_str(n2_curr, n1_curr_str)) { + return false; + } + } + // case 3: both are concats + else if (is_concat(n1_curr) && is_concat(n2_curr)) { + if (!can_concat_eq_concat(n1_curr, n2_curr)) { + return false; + } + } + + return true; +} + /* * strArgmt::solve_concat_eq_str() * Solve concatenations of the form: @@ -604,8 +966,12 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { int concatStrLen = const_str.length(); int xor_pos = 0; int and_count = 1; + /* expr ** xor_items = new expr*[concatStrLen + 1]; expr ** and_items = new expr*[4 * (concatStrLen+1) + 1]; + */ + expr ** xor_items = alloc_svect(expr*, (concatStrLen+1)); + expr ** and_items = alloc_svect(expr*, (4 * (concatStrLen+1) + 1)); for (int i = 0; i < concatStrLen + 1; ++i) { std::string prefixStr = const_str.substr(0, i); @@ -736,6 +1102,11 @@ void theory_str::handle_equality(expr * lhs, expr * rhs) { ); // step 1: Concat == Concat + // I'm disabling this entire code block for now. It may no longer be useful. + // Z3 seems to be putting LHS and RHS into the same equivalence class extremely early. + // As a result, simplify_concat_equality() is never getting called, + // and if it were called, it would probably get called with the same element on both sides. + /* bool hasCommon = false; if (eqc_lhs_concat.size() != 0 && eqc_rhs_concat.size() != 0) { std::set::iterator itor1 = eqc_lhs_concat.begin(); @@ -756,6 +1127,21 @@ void theory_str::handle_equality(expr * lhs, expr * rhs) { simplify_concat_equality(*(eqc_lhs_concat.begin()), *(eqc_rhs_concat.begin())); } } + */ + if (eqc_lhs_concat.size() != 0 && eqc_rhs_concat.size() != 0) { + // let's pick the first concat in the LHS's eqc + // and find some concat in the RHS's eqc that is + // distinct from the first one we picked + expr * lhs = *eqc_lhs_concat.begin(); + std::set::iterator itor2 = eqc_rhs_concat.begin(); + for (; itor2 != eqc_rhs_concat.end(); ++itor2) { + expr * rhs = *itor2; + if (lhs != rhs) { + simplify_concat_equality(lhs, rhs); + break; + } + } + } // step 2: Concat == Constant if (eqc_lhs_const.size() != 0) { diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 458287392..c3641016f 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -65,7 +65,7 @@ namespace smt { void assert_axiom(expr * e); void assert_implication(expr * premise, expr * conclusion); - app * mk_strlen(app * e); + app * mk_strlen(expr * e); app * mk_concat(app * e1, app * e2); app * mk_internal_xor_var(); @@ -82,6 +82,16 @@ namespace smt { void handle_equality(expr * lhs, expr * rhs); expr * get_eqc_value(expr * n, bool & hasEqcValue); + bool in_same_eqc(expr * n1, expr * n2); + + bool can_two_nodes_eq(expr * n1, expr * n2); + bool can_concat_eq_str(expr * concat, std::string str); + bool can_concat_eq_concat(expr * concat1, expr * concat2); + + void get_nodes_in_concat(expr * node, ptr_vector & nodeList); + expr * simplify_concat(expr * node); + + void simplify_parent(expr * nn, expr * eq_str); void simplify_concat_equality(expr * lhs, expr * rhs); void solve_concat_eq_str(expr * concat, expr * str); From a62d15403e434fe5358523b52cfc1442bad11917 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 29 Sep 2015 22:31:11 -0400 Subject: [PATCH 036/410] start simplify_concat_eq(), WIP but some cases OK also fix model generation for concats and nested concats --- src/smt/theory_str.cpp | 116 +++++++++++++++++++++++++++++++++++++---- src/smt/theory_str.h | 4 +- 2 files changed, 108 insertions(+), 12 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 92edbc22b..3f8de3d6f 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -131,14 +131,60 @@ app * theory_str::mk_strlen(expr * e) { } } -app * theory_str::mk_concat(app * e1, app * e2) { +/* + * Returns the simplified concatenation of two expressions, + * where either both expressions are constant strings + * or one expression is the empty string. + * If this precondition does not hold, the function returns NULL. + * (note: this function was strTheory::Concat()) + */ +expr * theory_str::mk_concat_const_str(expr * n1, expr * n2) { + bool n1HasEqcValue = false; + bool n2HasEqcValue = false; + expr * v1 = get_eqc_value(n1, n1HasEqcValue); + expr * v2 = get_eqc_value(n2, n2HasEqcValue); + if (n1HasEqcValue && n2HasEqcValue) { + const char * n1_str_tmp; + m_strutil.is_string(v1, & n1_str_tmp); + std::string n1_str(n1_str_tmp); + const char * n2_str_tmp; + m_strutil.is_string(v2, & n2_str_tmp); + std::string n2_str(n2_str_tmp); + std::string result = n1_str + n2_str; + return m_strutil.mk_string(result); + } else if (n1HasEqcValue && !n2HasEqcValue) { + const char * n1_str_tmp; + m_strutil.is_string(v1, & n1_str_tmp); + if (strcmp(n1_str_tmp, "") == 0) { + return n2; + } + } else if (!n1HasEqcValue && n2HasEqcValue) { + const char * n2_str_tmp; + m_strutil.is_string(v2, & n2_str_tmp); + if (strcmp(n2_str_tmp, "") == 0) { + return n1; + } + } + return NULL; +} + +expr * theory_str::mk_concat(expr * n1, expr * n2) { ast_manager & m = get_manager(); - if (e1 == NULL || e2 == NULL) { + if (n1 == NULL || n2 == NULL) { m.raise_exception("strings to be concatenated cannot be NULL"); } - // TODO there's a *TON* of missing code here from strTheory::mk_concat() - expr * args[2] = {e1, e2}; - return get_manager().mk_app(get_id(), OP_STRCAT, 0, 0, 2, args); + bool n1HasEqcValue = false; + bool n2HasEqcValue = false; + n1 = get_eqc_value(n1, n1HasEqcValue); + n2 = get_eqc_value(n2, n2HasEqcValue); + if (n1HasEqcValue && n2HasEqcValue) { + return mk_concat_const_str(n1, n2); + } else { + // TODO there's a *TON* of missing code here from strTheory::mk_concat() + // if all else fails, just build the application AST + expr * args[2] = {n1, n2}; + return get_manager().mk_app(get_id(), OP_STRCAT, 0, 0, 2, args); + } } bool theory_str::can_propagate() { @@ -424,11 +470,11 @@ expr * theory_str::simplify_concat(expr * node) { // no simplification possible return node; } else { - app * resultAst = m_strutil.mk_string(""); + expr * resultAst = m_strutil.mk_string(""); for (unsigned i = 0; i < argVec.size(); ++i) { bool vArgHasEqcValue = false; expr * vArg = get_eqc_value(argVec[i], vArgHasEqcValue); - resultAst = mk_concat(to_app(resultAst), to_app(vArg)); + resultAst = mk_concat(resultAst, vArg); } TRACE("t_str_detail", tout << mk_ismt2_pp(node, m) << " is simplified to " << mk_ismt2_pp(resultAst, m) << std::endl;); @@ -821,7 +867,7 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { } else { implyL1 = m.mk_and(item1[0], item1[1]); } - newConcat = mk_concat(to_app(arg1), to_app(arg2)); + newConcat = mk_concat(arg1, arg2); if (newConcat != str) { expr_ref implyR1(ctx.mk_eq_atom(concat, newConcat), m); assert_implication(implyL1, implyR1); @@ -1308,6 +1354,52 @@ void theory_str::init_model(model_generator & mg) { mg.register_factory(m_factory); } +/* + * Helper function for mk_value(). + * Attempts to resolve the expression 'n' to a string constant. + * Stronger than get_eqc_value() in that it will perform recursive descent + * through every subexpression and attempt to resolve those to concrete values as well. + * Returns the concrete value obtained from this process, + * guaranteed to satisfy m_strutil.is_string(), + * if one could be obtained, + * or else returns NULL if no concrete value was derived. + */ +app * theory_str::mk_value_helper(app * n) { + if (m_strutil.is_string(n)) { + return n; + } else if (is_concat(n)) { + // recursively call this function on each argument + SASSERT(n->get_num_args() == 2); + expr * a0 = n->get_arg(0); + expr * a1 = n->get_arg(1); + + app * a0_conststr = mk_value_helper(to_app(a0)); + app * a1_conststr = mk_value_helper(to_app(a1)); + + if (a0_conststr != NULL && a1_conststr != NULL) { + const char * a0_str = 0; + m_strutil.is_string(a0_conststr, &a0_str); + + const char * a1_str = 0; + m_strutil.is_string(a1_conststr, &a1_str); + + std::string a0_s(a0_str); + std::string a1_s(a1_str); + std::string result = a0_s + a1_s; + return m_strutil.mk_string(result); + } + } + // fallback path + // try to find some constant string, anything, in the equivalence class of n + bool hasEqc = false; + expr * n_eqc = get_eqc_value(n, hasEqc); + if (hasEqc) { + return to_app(n_eqc); + } else { + return NULL; + } +} + model_value_proc * theory_str::mk_value(enode * n, model_generator & mg) { TRACE("t_str", tout << "mk_value for: " << mk_ismt2_pp(n->get_owner(), get_manager()) << " (sort " << mk_ismt2_pp(get_manager().get_sort(n->get_owner()), get_manager()) << ")" << std::endl;); @@ -1319,10 +1411,12 @@ model_value_proc * theory_str::mk_value(enode * n, model_generator & mg) { // If the owner is not internalized, it doesn't have an enode associated. SASSERT(ctx.e_internalized(owner)); - if (m_strutil.is_string(owner)) { - return alloc(expr_wrapper_proc, owner); + app * val = mk_value_helper(owner); + if (val != NULL) { + return alloc(expr_wrapper_proc, val); + } else { + m.raise_exception("failed to find concrete value"); return NULL; } - NOT_IMPLEMENTED_YET(); // TODO } void theory_str::finalize_model(model_generator & mg) {} diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index c3641016f..daa5656bb 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -66,7 +66,8 @@ namespace smt { void assert_implication(expr * premise, expr * conclusion); app * mk_strlen(expr * e); - app * mk_concat(app * e1, app * e2); + expr * mk_concat(expr * n1, expr * n2); + expr * mk_concat_const_str(expr * n1, expr * n2); app * mk_internal_xor_var(); @@ -81,6 +82,7 @@ namespace smt { void set_up_axioms(expr * ex); void handle_equality(expr * lhs, expr * rhs); + app * mk_value_helper(app * n); expr * get_eqc_value(expr * n, bool & hasEqcValue); bool in_same_eqc(expr * n1, expr * n2); From ed7b343822e2829f63c9eb2378b454b333d01d18 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 30 Sep 2015 05:15:14 -0400 Subject: [PATCH 037/410] detect and process concat eq type 1 (WIP UNTESTED) --- src/smt/theory_str.cpp | 462 ++++++++++++++++++++++++++++++++++++++++- src/smt/theory_str.h | 37 +++- 2 files changed, 495 insertions(+), 4 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 3f8de3d6f..fc4548f7a 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -28,7 +28,10 @@ theory_str::theory_str(ast_manager & m): search_started(false), m_autil(m), m_strutil(m), - tmpXorVarCount(0) + tmpStringVarCount(0), + tmpXorVarCount(0), + avoidLoopCut(true), + loopDetected(false) { } @@ -101,6 +104,122 @@ bool theory_str::internalize_term(app * term) { return true; } +static void cut_vars_map_copy(std::map & dest, std::map & src) { + std::map::iterator itor = src.begin(); + for (; itor != src.end(); itor++) { + dest[itor->first] = 1; + } +} + +/* +bool hasSelfCut(Z3_ast n1, Z3_ast n2) { + if (cut_VARMap.find(n1) == cut_VARMap.end()) + return false; + + if (cut_VARMap.find(n2) == cut_VARMap.end()) + return false; + + if (cut_VARMap[n1].empty() || cut_VARMap[n2].empty()) + return false; + + std::map::iterator itor = cut_VARMap[n1].top()->vars.begin(); + for (; itor != cut_VARMap[n1].top()->vars.end(); itor++) { + if (cut_VARMap[n2].top()->vars.find(itor->first) != cut_VARMap[n2].top()->vars.end()) + return true; + } + return false; +} +*/ + +bool theory_str::has_self_cut(expr * n1, expr * n2) { + if (cut_var_map.find(n1) == cut_var_map.end()) { + return false; + } + if (cut_var_map.find(n2) == cut_var_map.end()) { + return false; + } + if (cut_var_map[n1].empty() || cut_var_map[n2].empty()) { + return false; + } + + std::map::iterator itor = cut_var_map[n1].top()->vars.begin(); + for (; itor != cut_var_map[n1].top()->vars.end(); ++itor) { + if (cut_var_map[n2].top()->vars.find(itor->first) != cut_var_map[n2].top()->vars.end()) { + return true; + } + } + return false; +} + +void theory_str::add_cut_info_one_node(expr * baseNode, int slevel, expr * node) { + if (cut_var_map.find(baseNode) == cut_var_map.end()) { + T_cut * varInfo = alloc(T_cut); + varInfo->level = slevel; + varInfo->vars[node] = 1; + cut_var_map[baseNode].push(varInfo); + } else { + if (cut_var_map[baseNode].empty()) { + T_cut * varInfo = alloc(T_cut); + varInfo->level = slevel; + varInfo->vars[node] = 1; + cut_var_map[baseNode].push(varInfo); + } else { + if (cut_var_map[baseNode].top()->level < slevel) { + T_cut * varInfo = alloc(T_cut); + varInfo->level = slevel; + cut_vars_map_copy(varInfo->vars, cut_var_map[baseNode].top()->vars); + varInfo->vars[node] = 1; + cut_var_map[baseNode].push(varInfo); + } else if (cut_var_map[baseNode].top()->level == slevel) { + cut_var_map[baseNode].top()->vars[node] = 1; + } else { + get_manager().raise_exception("entered illegal state during add_cut_info_one_node()"); + } + } + } +} + +void theory_str::add_cut_info_merge(expr * destNode, int slevel, expr * srcNode) { + if (cut_var_map.find(srcNode) == cut_var_map.end()) { + get_manager().raise_exception("illegal state in add_cut_info_merge(): cut_var_map doesn't contain srcNode"); + } + + if (cut_var_map[srcNode].empty()) { + get_manager().raise_exception("illegal state in add_cut_info_merge(): cut_var_map[srcNode] is empty"); + } + + if (cut_var_map.find(destNode) == cut_var_map.end()) { + T_cut * varInfo = alloc(T_cut); + varInfo->level = slevel; + cut_vars_map_copy(varInfo->vars, cut_var_map[srcNode].top()->vars); + cut_var_map[destNode].push(varInfo); + } else { + if (cut_var_map[destNode].empty() || cut_var_map[destNode].top()->level < slevel) { + T_cut * varInfo = alloc(T_cut); + varInfo->level = slevel; + cut_vars_map_copy(varInfo->vars, cut_var_map[destNode].top()->vars); + cut_vars_map_copy(varInfo->vars, cut_var_map[srcNode].top()->vars); + cut_var_map[destNode].push(varInfo); + } else if (cut_var_map[destNode].top()->level == slevel) { + cut_vars_map_copy(cut_var_map[destNode].top()->vars, cut_var_map[srcNode].top()->vars); + } else { + get_manager().raise_exception("illegal state in add_cut_info_merge(): inconsistent slevels"); + } + } +} + +void theory_str::check_and_init_cut_var(expr * node) { + if (cut_var_map.find(node) != cut_var_map.end()) { + return; + } else if (!m_strutil.is_string(node)) { + add_cut_info_one_node(node, -1, node); + } +} + +app * theory_str::mk_int(int n) { + return m_autil.mk_numeral(rational(n), true); +} + app * theory_str::mk_internal_xor_var() { context & ctx = get_context(); ast_manager & m = get_manager(); @@ -119,6 +238,49 @@ app * theory_str::mk_internal_xor_var() { return a; } +/* + Z3_context ctx = Z3_theory_get_context(t); + PATheoryData * td = (PATheoryData *) Z3_theory_get_ext_data(t); + std::stringstream ss; + ss << tmpStringVarCount; + tmpStringVarCount++; + std::string name = "$$_str" + ss.str(); + Z3_ast varAst = mk_var(ctx, name.c_str(), td->String); + nonEmptyStrVarAxiom(t, varAst, __LINE__); + return varAst; +*/ + +app * theory_str::mk_nonempty_str_var() { + context & ctx = get_context(); + ast_manager & m = get_manager(); + std::stringstream ss; + ss << tmpStringVarCount; + tmpStringVarCount++; + std::string name = "$$_str" + ss.str(); + sort * string_sort = m.mk_sort(m_strutil.get_family_id(), STRING_SORT); + char * new_buffer = alloc_svect(char, name.length() + 1); + strcpy(new_buffer, name.c_str()); + symbol sym(new_buffer); + + app* a = m.mk_const(m.mk_const_decl(sym, string_sort)); + // assert a variation of the basic string axioms that ensures this string is nonempty + { + // build LHS + expr_ref len_str(m); + len_str = mk_strlen(a); + SASSERT(len_str); + // build RHS + expr_ref zero(m); + zero = m_autil.mk_numeral(rational(0), true); + SASSERT(zero); + // build LHS > RHS and assert + app * lhs_gt_rhs = m_autil.mk_gt(len_str, zero); + SASSERT(lhs_gt_rhs); + assert_axiom(lhs_gt_rhs); + } + return a; +} + app * theory_str::mk_strlen(expr * e) { /*if (m_strutil.is_string(e)) {*/ if (false) { const char * strval = 0; @@ -372,7 +534,7 @@ void theory_str::reset_eh() { m_basicstr_axiom_todo.reset(); m_str_eq_todo.reset(); m_concat_axiom_todo.reset(); - pop_scope_eh(0); + pop_scope_eh(get_context().get_scope_level()); } /* @@ -670,9 +832,289 @@ void theory_str::simplify_concat_equality(expr * nn1, expr * nn2) { } // start to split both concats + check_and_init_cut_var(v1_arg0); + check_and_init_cut_var(v1_arg1); + check_and_init_cut_var(v2_arg0); + check_and_init_cut_var(v2_arg1); + //************************************************************* + // case 1: concat(x, y) = concat(m, n) + //************************************************************* + if (is_concat_eq_type1(new_nn1, new_nn2)) { + process_concat_eq_type1(new_nn1, new_nn2); + return; + } + + //************************************************************* + // case 2: concat(x, y) = concat(m, "str") + //************************************************************* + if (is_concat_eq_type2(new_nn1, new_nn2)) { + process_concat_eq_type2(new_nn1, new_nn2); + return; + } + + //************************************************************* + // case 3: concat(x, y) = concat("str", n) + //************************************************************* + if (is_concat_eq_type3(new_nn1, new_nn2)) { + process_concat_eq_type3(new_nn1, new_nn2); + return; + } + + //************************************************************* + // case 4: concat("str1", y) = concat("str2", n) + //************************************************************* + if (is_concat_eq_type4(new_nn1, new_nn2)) { + process_concat_eq_type4(new_nn1, new_nn2); + return; + } + + //************************************************************* + // case 5: concat(x, "str1") = concat(m, "str2") + //************************************************************* + if (is_concat_eq_type5(new_nn1, new_nn2)) { + process_concat_eq_type5(new_nn1, new_nn2); + return; + } + //************************************************************* + // case 6: concat("str1", y) = concat(m, "str2") + //************************************************************* + if (is_concat_eq_type6(new_nn1, new_nn2)) { + process_concat_eq_type6(new_nn1, new_nn2); + return; + } + +} + +bool theory_str::is_concat_eq_type1(expr * concatAst1, expr * concatAst2) { + expr * x = to_app(concatAst1)->get_arg(0); + expr * y = to_app(concatAst1)->get_arg(1); + expr * m = to_app(concatAst2)->get_arg(0); + expr * n = to_app(concatAst2)->get_arg(1); + + if (!m_strutil.is_string(x) && !m_strutil.is_string(y) && !m_strutil.is_string(m) && !m_strutil.is_string(n)) { + return true; + } else { + return false; + } +} + +void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { + ast_manager & mgr = get_manager(); + context & ctx = get_context(); + TRACE("t_str_detail", tout << "process_concat_eq TYPE 1" << std::endl + << "concatAst1 = " << mk_ismt2_pp(concatAst1, m) << std::endl + << "concatAst2 = " << mk_ismt2_pp(concatAst2, m) << std::endl; + ); + + if (!is_concat(to_app(concatAst1))) { + TRACE("t_str_detail", tout << "concatAst1 is not a concat function" << std::endl;); + return; + } + if (!is_concat(to_app(concatAst2))) { + TRACE("t_str_detail", tout << "concatAst2 is not a concat function" << std::endl;); + return; + } + expr * x = to_app(concatAst1)->get_arg(0); + expr * y = to_app(concatAst1)->get_arg(1); + expr * m = to_app(concatAst2)->get_arg(0); + expr * n = to_app(concatAst2)->get_arg(1); + + /* TODO query the integer theory: + int x_len = getLenValue(t, x); + int y_len = getLenValue(t, y); + int m_len = getLenValue(t, m); + int n_len = getLenValue(t, n); + */ + int x_len = -1; + int y_len = -1; + int m_len = -1; + int n_len = -1; + + int splitType = -1; + if (x_len != -1 && m_len != -1) { + if (x_len < m_len) + splitType = 0; + else if (x_len == m_len) + splitType = 1; + else + splitType = 2; + } + + if (splitType == -1 && y_len != -1 && n_len != -1) { + if (y_len > n_len) + splitType = 0; + else if (y_len == n_len) + splitType = 1; + else + splitType = 2; + } + + TRACE("t_str_detail", tout << "split type " << splitType << std::endl;); + + expr * t1 = NULL; + expr * t2 = NULL; + expr * xorFlag = NULL; + + std::pair key1(concatAst1, concatAst2); + std::pair key2(concatAst2, concatAst1); + + if (varForBreakConcat.find(key1) == varForBreakConcat.end() && varForBreakConcat.find(key2) == varForBreakConcat.end()) { + t1 = mk_nonempty_str_var(); + t2 = mk_nonempty_str_var(); + xorFlag = mk_internal_xor_var(); + check_and_init_cut_var(t1); + check_and_init_cut_var(t2); + varForBreakConcat[key1][0] = t1; + varForBreakConcat[key1][1] = t2; + varForBreakConcat[key1][2] = xorFlag; + } else { + // match found + if (varForBreakConcat.find(key1) != varForBreakConcat.end()) { + t1 = varForBreakConcat[key1][0]; + t2 = varForBreakConcat[key1][1]; + xorFlag = varForBreakConcat[key1][2]; + } else { + t1 = varForBreakConcat[key2][0]; + t2 = varForBreakConcat[key2][1]; + xorFlag = varForBreakConcat[key2][2]; + } + } + + // For split types 0 through 2, we can get away with providing + // fewer split options since more length information is available. + if (splitType == 0) { + NOT_IMPLEMENTED_YET(); // TODO + } else if (splitType == 1) { + NOT_IMPLEMENTED_YET(); // TODO + } else if (splitType == 2) { + NOT_IMPLEMENTED_YET(); // TODO + } else if (splitType == -1) { + // Here we don't really have a choice. We have no length information at all... + expr ** or_item = alloc_svect(expr*, 3); + expr ** and_item = alloc_svect(expr*, 20); + int option = 0; + int pos = 1; + + // break option 1: m cuts y + // len(x) < len(m) || len(y) > len(n) + if (!avoidLoopCut || !has_self_cut(m, y)) { + // break down option 1-1 + expr * x_t1 = mk_concat(x, t1); + expr * t1_n = mk_concat(t1, n); + or_item[option] = ctx.mk_eq_atom(xorFlag, mk_int(option)); + and_item[pos++] = ctx.mk_eq_atom(or_item[option], ctx.mk_eq_atom(m, x_t1)); + and_item[pos++] = ctx.mk_eq_atom(or_item[option], ctx.mk_eq_atom(y, t1_n)); + + expr_ref x_plus_t1(m_autil.mk_add(mk_strlen(x), mk_strlen(t1)), mgr); + and_item[pos++] = ctx.mk_eq_atom(or_item[option], ctx.mk_eq_atom(mk_strlen(m), x_plus_t1)); + and_item[pos++] = ctx.mk_eq_atom(or_item[option], m_autil.mk_gt(mk_strlen(m), mk_strlen(x))); + and_item[pos++] = ctx.mk_eq_atom(or_item[option], m_autil.mk_gt(mk_strlen(y), mk_strlen(n))); + + option++; + + add_cut_info_merge(t1, ctx.get_scope_level(), m); + add_cut_info_merge(t1, ctx.get_scope_level(), y); + } else { + loopDetected = true; + TRACE("t_str", tout << "AVOID LOOP: SKIPPED" << std::endl;); + // TODO printCutVar(m, y); + } + + // break option 2: + // x = m || y = n + if (!avoidLoopCut || !has_self_cut(x, n)) { + // break down option 1-2 + expr * m_t2 = mk_concat(m, t2); + expr * t2_y = mk_concat(t2, y); + or_item[option] = ctx.mk_eq_atom(xorFlag, mk_int(option)); + and_item[pos++] = ctx.mk_eq_atom(or_item[option], ctx.mk_eq_atom(x, m_t2)); + and_item[pos++] = ctx.mk_eq_atom(or_item[option], ctx.mk_eq_atom(n, t2_y)); + + + expr_ref m_plus_t2(m_autil.mk_add(mk_strlen(m), mk_strlen(t2)), mgr); + + and_item[pos++] = ctx.mk_eq_atom(or_item[option], ctx.mk_eq_atom(mk_strlen(x), m_plus_t2)); + and_item[pos++] = ctx.mk_eq_atom(or_item[option], ctx.mk_eq_atom(mk_strlen(x), mk_strlen(m))); + and_item[pos++] = ctx.mk_eq_atom(or_item[option], ctx.mk_eq_atom(mk_strlen(n), mk_strlen(y))); + + + option++; + + add_cut_info_merge(t2, sLevel, x); + add_cut_info_merge(t2, sLevel, n); + } else { + loopDetected = true; + TRACE("t_str", tout << "AVOID LOOP: SKIPPED" << std::endl;); + // TODO printCutVar(x, n); + } + + if (can_two_nodes_eq(x, m) && can_two_nodes_eq(y, n)) { + or_item[option] = ctx.mk_eq_atom(xorFlag, mk_int(option)); + and_item[pos++] = ctx.mk_eq_atom(or_item[option], ctx.mk_eq_atom(x, m)); + and_item[pos++] = ctx.mk_eq_atom(or_item[option], ctx.mk_eq_atom(y, n)); + and_item[pos++] = ctx.mk_eq_atom(or_item[option], ctx.mk_eq_atom(mk_strlen(x), mk_strlen(m))); + and_item[pos++] = ctx.mk_eq_atom(or_item[option], ctx.mk_eq_atom(mk_strlen(y), mk_strlen(n))); + ++option; + } + + if (option > 0) { + if (option == 1) { + and_item[0] = or_item[0]; + } else { + and_item[0] = mgr.mk_or(option, or_item); + } + expr_ref premise(ctx.mk_eq_atom(concatAst1, concatAst2), m); + expr_ref conclusion(mgr.mk_and(pos, and_item), m); + assert_implication(premise, conclusion); + } else { + TRACE("t_str", tout << "STOP: no split option found for two EQ concats." << std::endl;); + } + } // (splitType == -1) +} + +bool theory_str::is_concat_eq_type2(expr * concatAst1, expr * concatAst2) { // TODO - NOT_IMPLEMENTED_YET(); + return false; +} + +void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { + +} + +bool theory_str::is_concat_eq_type3(expr * concatAst1, expr * concatAst2) { + // TODO + return false; +} + +void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { + +} + +bool theory_str::is_concat_eq_type4(expr * concatAst1, expr * concatAst2) { + // TODO + return false; +} + +void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { + +} + +bool theory_str::is_concat_eq_type5(expr * concatAst1, expr * concatAst2) { + // TODO + return false; +} + +void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { + +} + +bool theory_str::is_concat_eq_type6(expr * concatAst1, expr * concatAst2) { + // TODO + return false; +} + +void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { } @@ -1327,6 +1769,20 @@ void theory_str::push_scope_eh() { void theory_str::pop_scope_eh(unsigned num_scopes) { TRACE("t_str", tout << "pop " << num_scopes << std::endl;); + context & ctx = get_context(); + unsigned sLevel = ctx.get_scope_level(); + std::map >::iterator varItor = cut_var_map.begin(); + while (varItor != cut_var_map.end()) { + while ((varItor->second.size() > 0) && (varItor->second.top()->level != 0) && (varItor->second.top()->level >= sLevel)) { + T_cut * aCut = varItor->second.top(); + varItor->second.pop(); + dealloc(aCut); + } + if (varItor->second.size() == 0) { + cut_var_map.erase(varItor); + } + ++varItor; + } } final_check_status theory_str::final_check_eh() { diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index daa5656bb..b66eef4ad 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -47,7 +47,15 @@ namespace smt { }; class theory_str : public theory { - // TODO + struct T_cut + { + int level; + std::map vars; + + T_cut() { + level = -100; + } + }; protected: bool search_started; arith_util m_autil; @@ -59,8 +67,13 @@ namespace smt { svector > m_str_eq_todo; ptr_vector m_concat_axiom_todo; + int tmpStringVarCount; int tmpXorVarCount; std::map, std::map > varForBreakConcat; + + bool avoidLoopCut = true; + bool loopDetected = false; + std::map > cut_var_map; protected: void assert_axiom(expr * e); void assert_implication(expr * premise, expr * conclusion); @@ -69,6 +82,14 @@ namespace smt { expr * mk_concat(expr * n1, expr * n2); expr * mk_concat_const_str(expr * n1, expr * n2); + app * mk_int(int n); + + void check_and_init_cut_var(expr * node); + void add_cut_info_one_node(expr * baseNode, int slevel, expr * node); + void add_cut_info_merge(expr * destNode, int slevel, expr * srcNode); + bool has_self_cut(expr * n1, expr * n2); + + app * mk_nonempty_str_var(); app * mk_internal_xor_var(); bool is_concat(app const * a) const { return a->is_app_of(get_id(), OP_STRCAT); } @@ -98,6 +119,20 @@ namespace smt { void simplify_concat_equality(expr * lhs, expr * rhs); void solve_concat_eq_str(expr * concat, expr * str); + bool is_concat_eq_type1(expr * concatAst1, expr * concatAst2); + bool is_concat_eq_type2(expr * concatAst1, expr * concatAst2); + bool is_concat_eq_type3(expr * concatAst1, expr * concatAst2); + bool is_concat_eq_type4(expr * concatAst1, expr * concatAst2); + bool is_concat_eq_type5(expr * concatAst1, expr * concatAst2); + bool is_concat_eq_type6(expr * concatAst1, expr * concatAst2); + + void process_concat_eq_type1(expr * concatAst1, expr * concatAst2); + void process_concat_eq_type2(expr * concatAst1, expr * concatAst2); + void process_concat_eq_type3(expr * concatAst1, expr * concatAst2); + void process_concat_eq_type4(expr * concatAst1, expr * concatAst2); + void process_concat_eq_type5(expr * concatAst1, expr * concatAst2); + void process_concat_eq_type6(expr * concatAst1, expr * concatAst2); + bool new_eq_check(expr * lhs, expr * rhs); void group_terms_by_eqc(expr * n, std::set & concats, std::set & vars, std::set & consts); public: From e2901fff1ea1394bdba0a892e60d06263ff38db4 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 30 Sep 2015 05:21:16 -0400 Subject: [PATCH 038/410] fix compilation errors --- src/smt/theory_str.cpp | 22 +++++++++++----------- src/smt/theory_str.h | 5 +++-- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index fc4548f7a..3458ec60c 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -903,8 +903,8 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { ast_manager & mgr = get_manager(); context & ctx = get_context(); TRACE("t_str_detail", tout << "process_concat_eq TYPE 1" << std::endl - << "concatAst1 = " << mk_ismt2_pp(concatAst1, m) << std::endl - << "concatAst2 = " << mk_ismt2_pp(concatAst2, m) << std::endl; + << "concatAst1 = " << mk_ismt2_pp(concatAst1, mgr) << std::endl + << "concatAst2 = " << mk_ismt2_pp(concatAst2, mgr) << std::endl; ); if (!is_concat(to_app(concatAst1))) { @@ -1041,8 +1041,8 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { option++; - add_cut_info_merge(t2, sLevel, x); - add_cut_info_merge(t2, sLevel, n); + add_cut_info_merge(t2, ctx.get_scope_level(), x); + add_cut_info_merge(t2, ctx.get_scope_level(), n); } else { loopDetected = true; TRACE("t_str", tout << "AVOID LOOP: SKIPPED" << std::endl;); @@ -1064,8 +1064,8 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { } else { and_item[0] = mgr.mk_or(option, or_item); } - expr_ref premise(ctx.mk_eq_atom(concatAst1, concatAst2), m); - expr_ref conclusion(mgr.mk_and(pos, and_item), m); + expr_ref premise(ctx.mk_eq_atom(concatAst1, concatAst2), mgr); + expr_ref conclusion(mgr.mk_and(pos, and_item), mgr); assert_implication(premise, conclusion); } else { TRACE("t_str", tout << "STOP: no split option found for two EQ concats." << std::endl;); @@ -1078,7 +1078,7 @@ bool theory_str::is_concat_eq_type2(expr * concatAst1, expr * concatAst2) { return false; } -void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { +void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { } @@ -1087,7 +1087,7 @@ bool theory_str::is_concat_eq_type3(expr * concatAst1, expr * concatAst2) { return false; } -void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { +void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { } @@ -1096,7 +1096,7 @@ bool theory_str::is_concat_eq_type4(expr * concatAst1, expr * concatAst2) { return false; } -void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { +void theory_str::process_concat_eq_type4(expr * concatAst1, expr * concatAst2) { } @@ -1105,7 +1105,7 @@ bool theory_str::is_concat_eq_type5(expr * concatAst1, expr * concatAst2) { return false; } -void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { +void theory_str::process_concat_eq_type5(expr * concatAst1, expr * concatAst2) { } @@ -1114,7 +1114,7 @@ bool theory_str::is_concat_eq_type6(expr * concatAst1, expr * concatAst2) { return false; } -void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { +void theory_str::process_concat_eq_type6(expr * concatAst1, expr * concatAst2) { } diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index b66eef4ad..5d0ec96db 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -24,6 +24,7 @@ Revision History: #include"smt_model_generator.h" #include"arith_decl_plugin.h" #include +#include namespace smt { @@ -71,8 +72,8 @@ namespace smt { int tmpXorVarCount; std::map, std::map > varForBreakConcat; - bool avoidLoopCut = true; - bool loopDetected = false; + bool avoidLoopCut; + bool loopDetected; std::map > cut_var_map; protected: void assert_axiom(expr * e); From ecb2116927eecd15ff2898cb03b42ffe77cfc8e9 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 30 Sep 2015 05:23:22 -0400 Subject: [PATCH 039/410] fix memory corruption bug caused by invalid use of delete[] --- src/smt/theory_str.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 3458ec60c..071f07619 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -1503,8 +1503,6 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { } assert_implication(implyL, implyR1); } - delete[] xor_items; - delete[] and_items; } /* (arg1Len != 1 || arg2Len != 1) */ } /* if (Concat(arg1, arg2) == NULL) */ } From 5189c24d42c1fa8648bee09f2548e127d05ef294 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 30 Sep 2015 05:45:16 -0400 Subject: [PATCH 040/410] fix theory of arithmetic complaints about wanting to write A > B "what could possibly go wrong?" --- src/smt/theory_str.cpp | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 071f07619..440aaeb9f 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -274,7 +274,8 @@ app * theory_str::mk_nonempty_str_var() { zero = m_autil.mk_numeral(rational(0), true); SASSERT(zero); // build LHS > RHS and assert - app * lhs_gt_rhs = m_autil.mk_gt(len_str, zero); + // we have to build !(LHS <= RHS) instead + app * lhs_gt_rhs = m.mk_not(m_autil.mk_le(len_str, zero)); SASSERT(lhs_gt_rhs); assert_axiom(lhs_gt_rhs); } @@ -1008,8 +1009,18 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { expr_ref x_plus_t1(m_autil.mk_add(mk_strlen(x), mk_strlen(t1)), mgr); and_item[pos++] = ctx.mk_eq_atom(or_item[option], ctx.mk_eq_atom(mk_strlen(m), x_plus_t1)); - and_item[pos++] = ctx.mk_eq_atom(or_item[option], m_autil.mk_gt(mk_strlen(m), mk_strlen(x))); - and_item[pos++] = ctx.mk_eq_atom(or_item[option], m_autil.mk_gt(mk_strlen(y), mk_strlen(n))); + // TODO these are crashing the solvers because the integer theory + // expects a constant on the right-hand side. + // The things we want to assert here are len(m) > len(x) and len(y) > len(n). + // We rewrite A > B as A-B > 0 and then as not(A-B <= 0), + // and then, *because we aren't allowed to use subtraction*, + // as not(A + -1*B <= 0) + and_item[pos++] = ctx.mk_eq_atom(or_item[option], + mgr.mk_not(m_autil.mk_le(m_autil.mk_add(mk_strlen(m), + m_autil.mk_mul(mk_int(-1), mk_strlen(x))), mk_int(0))) ); + and_item[pos++] = ctx.mk_eq_atom(or_item[option], + mgr.mk_not(m_autil.mk_le(m_autil.mk_add(mk_strlen(y), + m_autil.mk_mul(mk_int(-1), mk_strlen(n))), mk_int(0))) ); option++; From f8c13792a355f5af3505c77e6f9cf883794910ad Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 30 Sep 2015 09:45:00 -0400 Subject: [PATCH 041/410] mark the position of the bug I found so I can recall it later in process_concat_eq_type1() line 1048 --- src/smt/theory_str.cpp | 35 +++++++++++++++++++++++------------ src/smt/theory_str.h | 2 ++ 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 440aaeb9f..aad15bec8 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -1045,6 +1045,7 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { expr_ref m_plus_t2(m_autil.mk_add(mk_strlen(m), mk_strlen(t2)), mgr); + // TODO here is the bug: these EQs should be GTs and_item[pos++] = ctx.mk_eq_atom(or_item[option], ctx.mk_eq_atom(mk_strlen(x), m_plus_t2)); and_item[pos++] = ctx.mk_eq_atom(or_item[option], ctx.mk_eq_atom(mk_strlen(x), mk_strlen(m))); and_item[pos++] = ctx.mk_eq_atom(or_item[option], ctx.mk_eq_atom(mk_strlen(n), mk_strlen(y))); @@ -1724,6 +1725,7 @@ void theory_str::init_search_eh() { * This is done to find equalities between terms, etc. that we otherwise * might not get a chance to see. */ + /* expr_ref_vector assignments(m); ctx.get_assignments(assignments); for (expr_ref_vector::iterator i = assignments.begin(); i != assignments.end(); ++i) { @@ -1745,6 +1747,7 @@ void theory_str::init_search_eh() { << ": expr ignored" << std::endl;); } } + */ TRACE("t_str", tout << "search started" << std::endl;); search_started = true; @@ -1755,12 +1758,16 @@ void theory_str::new_eq_eh(theory_var x, theory_var y) { TRACE("t_str", tout << "new eq: " << mk_ismt2_pp(get_enode(x)->get_owner(), get_manager()) << " = " << mk_ismt2_pp(get_enode(y)->get_owner(), get_manager()) << std::endl;); handle_equality(get_enode(x)->get_owner(), get_enode(y)->get_owner()); + + TRACE("t_str_detail", dump_assignments();); } void theory_str::new_diseq_eh(theory_var x, theory_var y) { //TRACE("t_str_detail", tout << "new diseq: v#" << x << " != v#" << y << std::endl;); TRACE("t_str", tout << "new diseq: " << mk_ismt2_pp(get_enode(x)->get_owner(), get_manager()) << " != " << mk_ismt2_pp(get_enode(y)->get_owner(), get_manager()) << std::endl;); + + TRACE("t_str_detail", dump_assignments();); } void theory_str::relevant_eh(app * n) { @@ -1779,7 +1786,7 @@ void theory_str::push_scope_eh() { void theory_str::pop_scope_eh(unsigned num_scopes) { TRACE("t_str", tout << "pop " << num_scopes << std::endl;); context & ctx = get_context(); - unsigned sLevel = ctx.get_scope_level(); + int sLevel = ctx.get_scope_level(); std::map >::iterator varItor = cut_var_map.begin(); while (varItor != cut_var_map.end()) { while ((varItor->second.size() > 0) && (varItor->second.top()->level != 0) && (varItor->second.top()->level >= sLevel)) { @@ -1794,21 +1801,25 @@ void theory_str::pop_scope_eh(unsigned num_scopes) { } } +void theory_str::dump_assignments() { + ast_manager & m = get_manager(); + context & ctx = get_context(); + TRACE("t_str_detail", + tout << "dumping all assignments:" << std::endl; + expr_ref_vector assignments(m); + ctx.get_assignments(assignments); + for (expr_ref_vector::iterator i = assignments.begin(); i != assignments.end(); ++i) { + expr * ex = *i; + tout << mk_ismt2_pp(ex, m) << std::endl; + } + ); +} + final_check_status theory_str::final_check_eh() { - ast_manager & m = get_manager(); - context & ctx = get_context(); // TODO TRACE("t_str", tout << "final check" << std::endl;); - TRACE("t_str_detail", - tout << "dumping all assignments:" << std::endl; - expr_ref_vector assignments(m); - ctx.get_assignments(assignments); - for (expr_ref_vector::iterator i = assignments.begin(); i != assignments.end(); ++i) { - expr * ex = *i; - tout << mk_ismt2_pp(ex, m) << std::endl; - } - ); + TRACE("t_str_detail", dump_assignments();); return FC_DONE; } diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 5d0ec96db..930c8e9c8 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -136,6 +136,8 @@ namespace smt { bool new_eq_check(expr * lhs, expr * rhs); void group_terms_by_eqc(expr * n, std::set & concats, std::set & vars, std::set & consts); + + void dump_assignments(); public: theory_str(ast_manager & m); virtual ~theory_str(); From fb5f3cbc136651a0c4113f7a857d84d3aad13dba Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 30 Sep 2015 11:41:55 -0400 Subject: [PATCH 042/410] fix greater-than bug now we just have to tweak model gen for internal variables --- src/smt/theory_str.cpp | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index aad15bec8..7514f8a23 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -1016,11 +1016,13 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { // and then, *because we aren't allowed to use subtraction*, // as not(A + -1*B <= 0) and_item[pos++] = ctx.mk_eq_atom(or_item[option], - mgr.mk_not(m_autil.mk_le(m_autil.mk_add(mk_strlen(m), - m_autil.mk_mul(mk_int(-1), mk_strlen(x))), mk_int(0))) ); + mgr.mk_not(m_autil.mk_le( + m_autil.mk_add(mk_strlen(m), m_autil.mk_mul(mk_int(-1), mk_strlen(x))), + mk_int(0))) ); and_item[pos++] = ctx.mk_eq_atom(or_item[option], - mgr.mk_not(m_autil.mk_le(m_autil.mk_add(mk_strlen(y), - m_autil.mk_mul(mk_int(-1), mk_strlen(n))), mk_int(0))) ); + mgr.mk_not(m_autil.mk_le( + m_autil.mk_add(mk_strlen(y),m_autil.mk_mul(mk_int(-1), mk_strlen(n))), + mk_int(0))) ); option++; @@ -1045,10 +1047,16 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { expr_ref m_plus_t2(m_autil.mk_add(mk_strlen(m), mk_strlen(t2)), mgr); - // TODO here is the bug: these EQs should be GTs and_item[pos++] = ctx.mk_eq_atom(or_item[option], ctx.mk_eq_atom(mk_strlen(x), m_plus_t2)); - and_item[pos++] = ctx.mk_eq_atom(or_item[option], ctx.mk_eq_atom(mk_strlen(x), mk_strlen(m))); - and_item[pos++] = ctx.mk_eq_atom(or_item[option], ctx.mk_eq_atom(mk_strlen(n), mk_strlen(y))); + // want len(x) > len(m) and len(n) > len(y) + and_item[pos++] = ctx.mk_eq_atom(or_item[option], + mgr.mk_not(m_autil.mk_le( + m_autil.mk_add(mk_strlen(x), m_autil.mk_mul(mk_int(-1), mk_strlen(m))), + mk_int(0))) ); + and_item[pos++] = ctx.mk_eq_atom(or_item[option], + mgr.mk_not(m_autil.mk_le( + m_autil.mk_add(mk_strlen(n), m_autil.mk_mul(mk_int(-1), mk_strlen(y))), + mk_int(0))) ); option++; @@ -1759,7 +1767,7 @@ void theory_str::new_eq_eh(theory_var x, theory_var y) { mk_ismt2_pp(get_enode(y)->get_owner(), get_manager()) << std::endl;); handle_equality(get_enode(x)->get_owner(), get_enode(y)->get_owner()); - TRACE("t_str_detail", dump_assignments();); + TRACE("t_str_dump_assign", dump_assignments();); } void theory_str::new_diseq_eh(theory_var x, theory_var y) { @@ -1767,7 +1775,7 @@ void theory_str::new_diseq_eh(theory_var x, theory_var y) { TRACE("t_str", tout << "new diseq: " << mk_ismt2_pp(get_enode(x)->get_owner(), get_manager()) << " != " << mk_ismt2_pp(get_enode(y)->get_owner(), get_manager()) << std::endl;); - TRACE("t_str_detail", dump_assignments();); + TRACE("t_str_dump_assign", dump_assignments();); } void theory_str::relevant_eh(app * n) { From bdf755156cd761c1247d83cbb034306455b45a28 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Thu, 1 Oct 2015 20:31:40 -0400 Subject: [PATCH 043/410] fix model generation: don't build interpretations for Length() --- src/ast/str_decl_plugin.cpp | 16 +++++++++- src/ast/str_decl_plugin.h | 4 +++ src/smt/theory_str.cpp | 60 ++++++++++++++++++++++++++++++++----- src/smt/theory_str.h | 9 +++++- 4 files changed, 80 insertions(+), 9 deletions(-) diff --git a/src/ast/str_decl_plugin.cpp b/src/ast/str_decl_plugin.cpp index c72a5dbc2..5589db56c 100644 --- a/src/ast/str_decl_plugin.cpp +++ b/src/ast/str_decl_plugin.cpp @@ -64,7 +64,7 @@ void str_decl_plugin::set_manager(ast_manager * m, family_id id) { MK_OP(m_concat_decl, "Concat", OP_STRCAT, s); - m_length_decl = m->mk_func_decl(symbol("Length"), s, i); m_manager->inc_ref(m_length_decl); + m_length_decl = m->mk_func_decl(symbol("Length"), s, i, func_decl_info(id, OP_STRLEN)); m_manager->inc_ref(m_length_decl); } decl_plugin * str_decl_plugin::mk_fresh() { @@ -120,6 +120,20 @@ app * str_decl_plugin::mk_string(const char * val) { return mk_string(key); } +app * str_decl_plugin::mk_fresh_string() { + // cheating. + // take the longest string in the cache, append the letter "A", and call it fresh. + std::string longestString = ""; + std::map::iterator it = string_cache.begin(); + for (; it != string_cache.end(); ++it) { + if (it->first.length() > longestString.length()) { + longestString = it->first; + } + } + longestString += "A"; + return mk_string(longestString); +} + void str_decl_plugin::get_op_names(svector & op_names, symbol const & logic) { op_names.push_back(builtin_name("Concat", OP_STRCAT)); op_names.push_back(builtin_name("Length", OP_STRLEN)); diff --git a/src/ast/str_decl_plugin.h b/src/ast/str_decl_plugin.h index 61d1bc2f2..f1978ab8b 100644 --- a/src/ast/str_decl_plugin.h +++ b/src/ast/str_decl_plugin.h @@ -62,6 +62,7 @@ public: app * mk_string(const char * val); app * mk_string(std::string & val); + app * mk_fresh_string(); virtual void get_op_names(svector & op_names, symbol const & logic); virtual void get_sort_names(svector & sort_names, symbol const & logic); @@ -97,6 +98,9 @@ public: app * mk_string(std::string & val) { return m_plugin->mk_string(val); } + app * mk_fresh_string() { + return m_plugin->mk_fresh_string(); + } // TODO }; diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 7514f8a23..221f472d2 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -279,6 +279,11 @@ app * theory_str::mk_nonempty_str_var() { SASSERT(lhs_gt_rhs); assert_axiom(lhs_gt_rhs); } + + // add 'a' to variable sets, so we can keep track of it + variable_set.insert(a); + internal_variable_set.insert(a); + return a; } @@ -1685,9 +1690,17 @@ void theory_str::set_up_axioms(expr * ex) { SASSERT(n); m_basicstr_axiom_todo.push_back(n); - // if additionally ex is a concatenation, set up concatenation axioms - if (is_app(ex) && is_concat(to_app(ex))) { - m_concat_axiom_todo.push_back(n); + + if (is_app(ex)) { + app * ap = to_app(ex); + if (is_concat(ap)) { + // if ex is a concat, set up concat axioms later + m_concat_axiom_todo.push_back(n); + } else if (ap->get_num_args() == 0 && !is_string(ap)) { + // if ex is a variable, add it to our list of variables + TRACE("t_str_detail", tout << "tracking variable" << std::endl;); + variable_set.insert(ex); + } } } else { TRACE("t_str_detail", tout << "setting up axioms for " << mk_ismt2_pp(ex, get_manager()) << @@ -1824,12 +1837,41 @@ void theory_str::dump_assignments() { } final_check_status theory_str::final_check_eh() { - // TODO - TRACE("t_str", tout << "final check" << std::endl;); + ast_manager & m = get_manager(); + context & ctx = get_context(); + TRACE("t_str", tout << "final check" << std::endl;); TRACE("t_str_detail", dump_assignments();); - return FC_DONE; + // Check every variable to see if it's eq. to some string constant. + // If not, mark it as free. + bool needToAssignFreeVars = false; + std::set free_variables; + for (std::set::iterator it = variable_set.begin(); it != variable_set.end(); ++it) { + bool has_eqc_value = false; + get_eqc_value(*it, has_eqc_value); + if (!has_eqc_value) { + needToAssignFreeVars = true; + free_variables.insert(*it); + } + } + + if (!needToAssignFreeVars) { + TRACE("t_str", tout << "All variables are assigned. Done!" << std::endl;); + return FC_DONE; + } + + for (std::set::iterator it = free_variables.begin(); it != free_variables.end(); ++it) { + expr * var = *it; + if (internal_variable_set.find(var) != internal_variable_set.end()) { + TRACE("t_str", tout << "assigning arbitrary string to internal variable " << mk_ismt2_pp(var, m) << std::endl;); + app * val = m_strutil.mk_string("**unused**"); + assert_axiom(ctx.mk_eq_atom(var, val)); + } else { + NOT_IMPLEMENTED_YET(); // TODO free variable assignment from strTheory::cb_final_check() + } + } + return FC_CONTINUE; } void theory_str::init_model(model_generator & mg) { @@ -1899,7 +1941,11 @@ model_value_proc * theory_str::mk_value(enode * n, model_generator & mg) { if (val != NULL) { return alloc(expr_wrapper_proc, val); } else { - m.raise_exception("failed to find concrete value"); return NULL; + TRACE("t_str", tout << "WARNING: failed to find a concrete value, falling back" << std::endl;); + // TODO make absolutely sure the reason we can't find a concrete value is because of an unassigned temporary + // e.g. for an expression like (Concat X $$_str0) + //return alloc(expr_wrapper_proc, m_strutil.mk_string("")); + NOT_IMPLEMENTED_YET(); } } diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 930c8e9c8..1c2e2fbee 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -43,7 +43,11 @@ namespace smt { v2 = m_util.mk_string("value 2"); return true; } - virtual expr * get_fresh_value(sort * s) { NOT_IMPLEMENTED_YET(); } + virtual expr * get_fresh_value(sort * s) { + // TODO this may be causing crashes in model gen? investigate + //return m_util.mk_fresh_string(); + NOT_IMPLEMENTED_YET(); + } virtual void register_value(expr * n) { /* Ignore */ } }; @@ -75,6 +79,9 @@ namespace smt { bool avoidLoopCut; bool loopDetected; std::map > cut_var_map; + + std::set variable_set; + std::set internal_variable_set; protected: void assert_axiom(expr * e); void assert_implication(expr * premise, expr * conclusion); From 96d99dfb3888852cd1cb4521fb3513f7b9c7a817 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Fri, 2 Oct 2015 14:05:17 -0400 Subject: [PATCH 044/410] process_concat_eq_type2 implementation, not tested WIP --- src/smt/theory_str.cpp | 184 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 182 insertions(+), 2 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 221f472d2..a6bdc4944 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -1098,13 +1098,193 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { } // (splitType == -1) } +/************************************************************* + * Type 2: concat(x, y) = concat(m, "str") + *************************************************************/ bool theory_str::is_concat_eq_type2(expr * concatAst1, expr * concatAst2) { - // TODO - return false; + expr * v1_arg0 = to_app(concatAst1)->get_arg(0); + expr * v1_arg1 = to_app(concatAst1)->get_arg(1); + expr * v2_arg0 = to_app(concatAst2)->get_arg(0); + expr * v2_arg1 = to_app(concatAst2)->get_arg(1); + + if ((!m_strutil.is_string(v1_arg0)) && m_strutil.is_string(v1_arg1) + && (!m_strutil.is_string(v2_arg0)) && (!m_strutil.is_string(v2_arg1))) { + return true; + } else if ((!m_strutil.is_string(v2_arg0)) && m_strutil.is_string(v2_arg1) + && (!m_strutil.is_string(v1_arg0)) && (!m_strutil.is_string(v1_arg1))) { + return true; + } else { + return false; + } } void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { + ast_manager & mgr = get_manager(); + context & ctx = get_context(); + TRACE("t_str_detail", tout << "process_concat_eq TYPE 2" << std::endl + << "concatAst1 = " << mk_ismt2_pp(concatAst1, mgr) << std::endl + << "concatAst2 = " << mk_ismt2_pp(concatAst2, mgr) << std::endl; + ); + if (!is_concat(to_app(concatAst1))) { + TRACE("t_str_detail", tout << "concatAst1 is not a concat function" << std::endl;); + return; + } + if (!is_concat(to_app(concatAst2))) { + TRACE("t_str_detail", tout << "concatAst2 is not a concat function" << std::endl;); + return; + } + + expr * x = NULL; + expr * y = NULL; + expr * strAst = NULL; + expr * m = NULL; + + expr * v1_arg0 = to_app(concatAst1)->get_arg(0); + expr * v1_arg1 = to_app(concatAst1)->get_arg(1); + expr * v2_arg0 = to_app(concatAst2)->get_arg(0); + expr * v2_arg1 = to_app(concatAst2)->get_arg(1); + + if (m_strutil.is_string(v1_arg1) && !m_strutil.is_string(v2_arg1)) { + m = v1_arg0; + strAst = v1_arg1; + x = v2_arg0; + y = v2_arg1; + } else { + m = v2_arg0; + strAst = v2_arg1; + x = v1_arg0; + y = v1_arg1; + } + + const char * strValue_tmp = 0; + m_strutil.is_string(strAst, &strValue_tmp); + std::string strValue(strValue_tmp); + // TODO integer theory interaction + /* + int x_len = getLenValue(t, x); + int y_len = getLenValue(t, y); + int m_len = getLenValue(t, m); + int str_len = getLenValue(t, strAst); + */ + + int x_len = -1; + int y_len = -1; + int m_len = -1; + int str_len = -1; + + // setup + + expr * xorFlag = NULL; + expr * temp1 = NULL; + std::pair key1(concatAst1, concatAst2); + std::pair key2(concatAst2, concatAst1); + + if (varForBreakConcat.find(key1) == varForBreakConcat.end() + && varForBreakConcat.find(key2) == varForBreakConcat.end()) { + temp1 = mk_nonempty_str_var(); + xorFlag = mk_internal_xor_var(); + varForBreakConcat[key1][0] = temp1; + varForBreakConcat[key1][1] = xorFlag; + } else { + if (varForBreakConcat.find(key1) != varForBreakConcat.end()) { + temp1 = varForBreakConcat[key1][0]; + xorFlag = varForBreakConcat[key1][1]; + } else if (varForBreakConcat.find(key2) != varForBreakConcat.end()) { + temp1 = varForBreakConcat[key2][0]; + xorFlag = varForBreakConcat[key2][1]; + } + } + + int splitType = -1; + if (x_len != -1 && m_len != -1) { + if (x_len < m_len) + splitType = 0; + else if (x_len == m_len) + splitType = 1; + else + splitType = 2; + } + if (splitType == -1 && y_len != -1 && str_len != -1) { + if (y_len > str_len) + splitType = 0; + else if (y_len == str_len) + splitType = 1; + else + splitType = 2; + } + + TRACE("t_str_detail", tout << "Split type " << splitType << std::endl;); + + // Provide fewer split options when length information is available. + + if (splitType == 0) { + NOT_IMPLEMENTED_YET(); // TODO + } else if (splitType == 1) { + NOT_IMPLEMENTED_YET(); // TODO + } else if (splitType == 2) { + NOT_IMPLEMENTED_YET(); // TODO + } else { + // Split type -1: no idea about the length... + int optionTotal = 2 + strValue.length(); + expr ** or_item = alloc_svect(expr*, optionTotal); + expr ** and_item = alloc_svect(expr*, (1 + 6 + 4 * (strValue.length() + 1))); + int option = 0; + int pos = 1; + + expr_ref temp1_strAst(mk_concat(temp1, strAst), mgr); // TODO assert concat axioms? + + // m cuts y + if (can_two_nodes_eq(y, temp1_strAst)) { + if (!avoidLoopCut || !has_self_cut(m, y)) { + // break down option 2-1 + // TODO + or_item[option] = ctx.mk_eq_atom(xorFlag, mk_int(option)); + expr_ref x_temp1(mk_concat(x, temp1), mgr); // TODO assert concat axioms? + and_item[pos++] = ctx.mk_eq_atom(or_item[option], ctx.mk_eq_atom(m, x_temp1)); + and_item[pos++] = ctx.mk_eq_atom(or_item[option], ctx.mk_eq_atom(y, temp1_strAst)); + + and_item[pos++] = ctx.mk_eq_atom(or_item[option], ctx.mk_eq_atom(mk_strlen(m), + m_autil.mk_add(mk_strlen(x), mk_strlen(temp1)))); + + ++option; + add_cut_info_merge(temp1, ctx.get_scope_level(), y); + add_cut_info_merge(temp1, ctx.get_scope_level(), m); + } else { + loopDetected = true; + TRACE("t_str", tout << "AVOID LOOP: SKIPPED" << std::endl;); + // TODO printCutVar(m, y) + } + } + + for (int i = 0; i <= (int)strValue.size(); ++i) { + std::string part1Str = strValue.substr(0, i); + std::string part2Str = strValue.substr(i, strValue.size() - i); + expr_ref prefixStr(m_strutil.mk_string(part1Str), mgr); + expr_ref x_concat(mk_concat(m, prefixStr), mgr); // TODO concat axioms? + expr_ref cropStr(m_strutil.mk_string(part2Str), mgr); + if (can_two_nodes_eq(x, x_concat) && can_two_nodes_eq(y, cropStr)) { + // break down option 2-2 + or_item[option] = ctx.mk_eq_atom(xorFlag, mk_int(option)); + and_item[pos++] = ctx.mk_eq_atom(or_item[option], ctx.mk_eq_atom(x, x_concat)); + and_item[pos++] = ctx.mk_eq_atom(or_item[option], ctx.mk_eq_atom(y, cropStr)); + and_item[pos++] = ctx.mk_eq_atom(or_item[option], ctx.mk_eq_atom(mk_strlen(y), mk_int(part2Str.length()))); + ++option; + } + } + + if (option > 0) { + if (option == 1) { + and_item[0] = or_item[0]; + } else { + and_item[0] = mgr.mk_or(option, or_item); + } + expr_ref implyR(mgr.mk_and(pos, and_item), mgr); + assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); + } else { + TRACE("t_str", tout << "STOP: Should not split two EQ concats." << std::endl;); + } + } // (splitType == -1) } bool theory_str::is_concat_eq_type3(expr * concatAst1, expr * concatAst2) { From ff4706dd40b3334724d81b385ed2cad1fcc0f8ba Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sat, 3 Oct 2015 12:07:55 -0400 Subject: [PATCH 045/410] process_concat_eq_type3 still wip because i'm just trying to get these all done --- src/smt/theory_str.cpp | 199 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 197 insertions(+), 2 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index a6bdc4944..7b555d6bb 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -892,6 +892,10 @@ void theory_str::simplify_concat_equality(expr * nn1, expr * nn2) { } +/************************************************************* + * Type 1: concat(x, y) = concat(m, n) + * x, y, m and n all variables + *************************************************************/ bool theory_str::is_concat_eq_type1(expr * concatAst1, expr * concatAst2) { expr * x = to_app(concatAst1)->get_arg(0); expr * y = to_app(concatAst1)->get_arg(1); @@ -1287,12 +1291,203 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { } // (splitType == -1) } +/************************************************************* + * Type 3: concat(x, y) = concat("str", n) + *************************************************************/ bool theory_str::is_concat_eq_type3(expr * concatAst1, expr * concatAst2) { - // TODO - return false; + expr * v1_arg0 = to_app(concatAst1)->get_arg(0); + expr * v1_arg1 = to_app(concatAst1)->get_arg(1); + expr * v2_arg0 = to_app(concatAst2)->get_arg(0); + expr * v2_arg1 = to_app(concatAst2)->get_arg(1); + + if (m_strutil.is_string(v1_arg0) && (!m_strutil.is_string(v1_arg1)) + && (!m_strutil.is_string(v2_arg0)) && (!m_strutil.is_string(v2_arg1))) { + return true; + } else if (m_strutil.is_string(v2_arg0) && (!m_strutil.is_string(v2_arg1)) + && (!m_strutil.is_string(v1_arg0)) && (!m_strutil.is_string(v1_arg1))) { + return true; + } else { + return false; + } } void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { + ast_manager & mgr = get_manager(); + context & ctx = get_context(); + TRACE("t_str_detail", tout << "process_concat_eq TYPE 3" << std::endl + << "concatAst1 = " << mk_ismt2_pp(concatAst1, mgr) << std::endl + << "concatAst2 = " << mk_ismt2_pp(concatAst2, mgr) << std::endl; + ); + + if (!is_concat(to_app(concatAst1))) { + TRACE("t_str_detail", tout << "concatAst1 is not a concat function" << std::endl;); + return; + } + if (!is_concat(to_app(concatAst2))) { + TRACE("t_str_detail", tout << "concatAst2 is not a concat function" << std::endl;); + return; + } + + expr * v1_arg0 = to_app(concatAst1)->get_arg(0); + expr * v1_arg1 = to_app(concatAst1)->get_arg(1); + expr * v2_arg0 = to_app(concatAst2)->get_arg(0); + expr * v2_arg1 = to_app(concatAst2)->get_arg(1); + + expr * x = NULL; + expr * y = NULL; + expr * strAst = NULL; + expr * n = NULL; + + if (m_strutil.is_string(v1_arg0) && !m_strutil.is_string(v2_arg0)) { + strAst = v1_arg0; + n = v1_arg1; + x = v2_arg0; + y = v2_arg1; + } else { + strAst = v2_arg0; + n = v2_arg1; + x = v1_arg0; + y = v1_arg1; + } + + const char * strValue_tmp = 0; + m_strutil.is_string(strAst, &strValue_tmp); + std::string strValue(strValue_tmp); + // TODO integer theory interaction + /* + int x_len = getLenValue(t, x); + int y_len = getLenValue(t, y); + int str_len = getLenValue(t, strAst); + int n_len = getLenValue(t, n); + */ + int x_len = -1; + int y_len = -1; + int str_len = -1; + int n_len = -1; + + expr_ref xorFlag(mgr); + expr_ref temp1(mgr); + std::pair key1(concatAst1, concatAst2); + std::pair key2(concatAst2, concatAst1); + if (varForBreakConcat.find(key1) == varForBreakConcat.end() && varForBreakConcat.find(key2) == varForBreakConcat.end()) { + temp1 = mk_nonempty_str_var(); + xorFlag = mk_internal_xor_var(); + + varForBreakConcat[key1][0] = temp1; + varForBreakConcat[key1][1] = xorFlag; + } else { + if (varForBreakConcat.find(key1) != varForBreakConcat.end()) { + temp1 = varForBreakConcat[key1][0]; + xorFlag = varForBreakConcat[key1][1]; + } else if (varForBreakConcat.find(key2) != varForBreakConcat.end()) { + temp1 = varForBreakConcat[key2][0]; + xorFlag = varForBreakConcat[key2][1]; + } + } + + + + int splitType = -1; + if (x_len != -1) { + if (x_len < str_len) + splitType = 0; + else if (x_len == str_len) + splitType = 1; + else + splitType = 2; + } + if (splitType == -1 && y_len != -1 && n_len != -1) { + if (y_len > n_len) + splitType = 0; + else if (y_len == n_len) + splitType = 1; + else + splitType = 2; + } + + TRACE("t_str_detail", tout << "Split type " << splitType << std::endl;); + + // Provide fewer split options when length information is available. + if (splitType == 0) { + NOT_IMPLEMENTED_YET(); // TODO + } + else if (splitType == 1) { + NOT_IMPLEMENTED_YET(); // TODO + } + else if (splitType == 2) { + NOT_IMPLEMENTED_YET(); // TODO + } + else { + // Split type -1. We know nothing about the length... + + int optionTotal = 2 + strValue.length(); + expr ** or_item = alloc_svect(expr*, optionTotal); + int option = 0; + expr ** and_item = alloc_svect(expr*, (2 + 4 * optionTotal)); + int pos = 1; + for (int i = 0; i <= (int) strValue.size(); i++) { + std::string part1Str = strValue.substr(0, i); + std::string part2Str = strValue.substr(i, strValue.size() - i); + expr_ref cropStr(m_strutil.mk_string(part1Str), mgr); + expr_ref suffixStr(m_strutil.mk_string(part2Str), mgr); + expr_ref y_concat(mk_concat(suffixStr, n), mgr); // TODO concat axioms? + + if (can_two_nodes_eq(x, cropStr) && can_two_nodes_eq(y, y_concat)) { + // break down option 3-1 + expr_ref x_eq_str(ctx.mk_eq_atom(x, cropStr), mgr); + or_item[option] = ctx.mk_eq_atom(xorFlag, mk_int(option)); + and_item[pos++] = ctx.mk_eq_atom(or_item[option], x_eq_str); + and_item[pos++] = ctx.mk_eq_atom(or_item[option], ctx.mk_eq_atom(y, y_concat)); + + and_item[pos++] = ctx.mk_eq_atom(or_item[option], ctx.mk_eq_atom(mk_strlen(x), mk_strlen(cropStr))); + // and_item[pos++] = Z3_mk_eq(ctx, or_item[option], Z3_mk_eq(ctx, mk_length(t, y), mk_length(t, y_concat))); + + // adding length constraint for _ = constStr seems slowing things down. + option++; + } + } + + expr_ref strAst_temp1(mk_concat(strAst, temp1), mgr); + + + //-------------------------------------------------------- + // x cut n + //-------------------------------------------------------- + if (can_two_nodes_eq(x, strAst_temp1)) { + if (!avoidLoopCut || !(has_self_cut(x, n))) { + // break down option 3-2 + or_item[option] = ctx.mk_eq_atom(xorFlag, mk_int(option)); + + expr_ref temp1_y(mk_concat(temp1, y), mgr); + and_item[pos++] = ctx.mk_eq_atom(or_item[option], ctx.mk_eq_atom(x, strAst_temp1)); + and_item[pos++] = ctx.mk_eq_atom(or_item[option], ctx.mk_eq_atom(n, temp1_y)); + + and_item[pos++] = ctx.mk_eq_atom(or_item[option], ctx.mk_eq_atom(mk_strlen(x), + m_autil.mk_add(mk_strlen(strAst), mk_strlen(temp1)) )); + option++; + + add_cut_info_merge(temp1, ctx.get_scope_level(), x); + add_cut_info_merge(temp1, ctx.get_scope_level(), n); + } else { + loopDetected = true; + TRACE("t_str", tout << "AVOID LOOP: SKIPPED." << std::endl;); + // TODO printCutVAR(x, n) + } + } + + + if (option > 0) { + if (option == 1) { + and_item[0] = or_item[0]; + } else { + and_item[0] = mgr.mk_or(option, or_item); + } + expr_ref implyR(mgr.mk_and(pos, and_item), mgr); + assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); + } else { + TRACE("t_str", tout << "STOP: should not split two eq. concats" << std::endl;); + } + } } From f7bc785a56f1bd4dade92a5812a843b634500fc5 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sat, 3 Oct 2015 12:19:55 -0400 Subject: [PATCH 046/410] process_concat_eq_type4, still WIP not tested --- src/smt/theory_str.cpp | 82 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 80 insertions(+), 2 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 7b555d6bb..5033ca978 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -1491,13 +1491,91 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { } +/************************************************************* + * Type 4: concat("str1", y) = concat("str2", n) + *************************************************************/ bool theory_str::is_concat_eq_type4(expr * concatAst1, expr * concatAst2) { - // TODO - return false; + expr * v1_arg0 = to_app(concatAst1)->get_arg(0); + expr * v1_arg1 = to_app(concatAst1)->get_arg(1); + expr * v2_arg0 = to_app(concatAst2)->get_arg(0); + expr * v2_arg1 = to_app(concatAst2)->get_arg(1); + + if (m_strutil.is_string(v1_arg0) && (!m_strutil.is_string(v1_arg1)) + && m_strutil.is_string(v2_arg0) && (!m_strutil.is_string(v2_arg1))) { + return true; + } else { + return false; + } } void theory_str::process_concat_eq_type4(expr * concatAst1, expr * concatAst2) { + ast_manager & mgr = get_manager(); + context & ctx = get_context(); + TRACE("t_str_detail", tout << "process_concat_eq TYPE 4" << std::endl + << "concatAst1 = " << mk_ismt2_pp(concatAst1, mgr) << std::endl + << "concatAst2 = " << mk_ismt2_pp(concatAst2, mgr) << std::endl; + ); + if (!is_concat(to_app(concatAst1))) { + TRACE("t_str_detail", tout << "concatAst1 is not a concat function" << std::endl;); + return; + } + if (!is_concat(to_app(concatAst2))) { + TRACE("t_str_detail", tout << "concatAst2 is not a concat function" << std::endl;); + return; + } + + expr * v1_arg0 = to_app(concatAst1)->get_arg(0); + expr * v1_arg1 = to_app(concatAst1)->get_arg(1); + expr * v2_arg0 = to_app(concatAst2)->get_arg(0); + expr * v2_arg1 = to_app(concatAst2)->get_arg(1); + + expr * str1Ast = v1_arg0; + expr * y = v1_arg1; + expr * str2Ast = v2_arg0; + expr * n = v2_arg1; + + const char *tmp = 0; + m_strutil.is_string(str1Ast, &tmp); + std::string str1Value(tmp); + m_strutil.is_string(str2Ast, &tmp); + std::string str2Value(tmp); + + int str1Len = str1Value.length(); + int str2Len = str2Value.length(); + + int commonLen = (str1Len > str2Len) ? str2Len : str1Len; + if (str1Value.substr(0, commonLen) != str2Value.substr(0, commonLen)) { + TRACE("t_str_detail", tout << "Conflict: " << mk_ismt2_pp(concatAst1, mgr) + << " has no common prefix with " << mk_ismt2_pp(concatAst2, mgr) << std::endl;); + expr_ref toNegate(mgr.mk_not(ctx.mk_eq_atom(concatAst1, concatAst2)), mgr); + assert_axiom(toNegate); + return; + } else { + if (str1Len > str2Len) { + std::string deltaStr = str1Value.substr(str2Len, str1Len - str2Len); + expr_ref tmpAst(mk_concat(m_strutil.mk_string(deltaStr), y), mgr); + if (!in_same_eqc(tmpAst, n)) { + // break down option 4-1 + expr_ref implyR(ctx.mk_eq_atom(n, tmpAst), mgr); + assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); + } + } else if (str1Len == str2Len) { + if (!in_same_eqc(n, y)) { + //break down option 4-2 + expr_ref implyR(ctx.mk_eq_atom(n, y), mgr); + assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); + } + } else { + std::string deltaStr = str2Value.substr(str1Len, str2Len - str1Len); + expr_ref tmpAst(mk_concat(m_strutil.mk_string(deltaStr), n), mgr); + if (!in_same_eqc(y, tmpAst)) { + //break down option 4-3 + expr_ref implyR(ctx.mk_eq_atom(y, tmpAst), mgr); + assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); + } + } + } } bool theory_str::is_concat_eq_type5(expr * concatAst1, expr * concatAst2) { From be7972338224e149356632e96171cc18b74112c4 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sat, 3 Oct 2015 12:26:30 -0400 Subject: [PATCH 047/410] process_concat_eq_type5 wip --- src/smt/theory_str.cpp | 80 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 78 insertions(+), 2 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 5033ca978..f052d293a 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -1578,13 +1578,89 @@ void theory_str::process_concat_eq_type4(expr * concatAst1, expr * concatAst2) { } } +/************************************************************* + * case 5: concat(x, "str1") = concat(m, "str2") + *************************************************************/ bool theory_str::is_concat_eq_type5(expr * concatAst1, expr * concatAst2) { - // TODO - return false; + expr * v1_arg0 = to_app(concatAst1)->get_arg(0); + expr * v1_arg1 = to_app(concatAst1)->get_arg(1); + expr * v2_arg0 = to_app(concatAst2)->get_arg(0); + expr * v2_arg1 = to_app(concatAst2)->get_arg(1); + + if ((!m_strutil.is_string(v1_arg0)) && m_strutil.is_string(v1_arg1) + && (!m_strutil.is_string(v2_arg0)) && m_strutil.is_string(v2_arg1)) { + return true; + } else { + return false; + } } void theory_str::process_concat_eq_type5(expr * concatAst1, expr * concatAst2) { + ast_manager & mgr = get_manager(); + context & ctx = get_context(); + TRACE("t_str_detail", tout << "process_concat_eq TYPE 5" << std::endl + << "concatAst1 = " << mk_ismt2_pp(concatAst1, mgr) << std::endl + << "concatAst2 = " << mk_ismt2_pp(concatAst2, mgr) << std::endl; + ); + if (!is_concat(to_app(concatAst1))) { + TRACE("t_str_detail", tout << "concatAst1 is not a concat function" << std::endl;); + return; + } + if (!is_concat(to_app(concatAst2))) { + TRACE("t_str_detail", tout << "concatAst2 is not a concat function" << std::endl;); + return; + } + + expr * v1_arg0 = to_app(concatAst1)->get_arg(0); + expr * v1_arg1 = to_app(concatAst1)->get_arg(1); + expr * v2_arg0 = to_app(concatAst2)->get_arg(0); + expr * v2_arg1 = to_app(concatAst2)->get_arg(1); + + expr * x = v1_arg0; + expr * str1Ast = v1_arg1; + expr * m = v2_arg0; + expr * str2Ast = v2_arg1; + + const char *tmp = 0; + m_strutil.is_string(str1Ast, &tmp); + std::string str1Value(tmp); + m_strutil.is_string(str2Ast, &tmp); + std::string str2Value(tmp); + + int str1Len = str1Value.length(); + int str2Len = str2Value.length(); + + int cLen = (str1Len > str2Len) ? str2Len : str1Len; + if (str1Value.substr(str1Len - cLen, cLen) != str2Value.substr(str2Len - cLen, cLen)) { + TRACE("t_str_detail", tout << "Conflict: " << mk_ismt2_pp(concatAst1, mgr) + << " has no common suffix with " << mk_ismt2_pp(concatAst2, mgr) << std::endl;); + expr_ref toNegate(mgr.mk_not(ctx.mk_eq_atom(concatAst1, concatAst2)), mgr); + assert_axiom(toNegate); + return; + } else { + if (str1Len > str2Len) { + std::string deltaStr = str1Value.substr(0, str1Len - str2Len); + expr_ref x_deltaStr(mk_concat(x, m_strutil.mk_string(deltaStr)), mgr); + if (!in_same_eqc(m, x_deltaStr)) { + expr_ref implyR(ctx.mk_eq_atom(m, x_deltaStr), mgr); + assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); + } + } else if (str1Len == str2Len) { + // test + if (!in_same_eqc(x, m)) { + expr_ref implyR(ctx.mk_eq_atom(x, m), mgr); + assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); + } + } else { + std::string deltaStr = str2Value.substr(0, str2Len - str1Len); + expr_ref m_deltaStr(mk_concat(m, m_strutil.mk_string(deltaStr)), mgr); + if (!in_same_eqc(x, m_deltaStr)) { + expr_ref implyR(ctx.mk_eq_atom(x, m_deltaStr), mgr); + assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); + } + } + } } bool theory_str::is_concat_eq_type6(expr * concatAst1, expr * concatAst2) { From 6791db64c01ac7dfbac7c0c6a883be9692e513da Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sat, 3 Oct 2015 13:34:42 -0400 Subject: [PATCH 048/410] process_concat_eq_type6 that's the last one! --- src/smt/theory_str.cpp | 162 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 160 insertions(+), 2 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index f052d293a..aebaec572 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -20,6 +20,7 @@ Revision History: #include"smt_model_generator.h" #include"ast_pp.h" #include"ast_ll_pp.h" +#include namespace smt { @@ -1663,13 +1664,170 @@ void theory_str::process_concat_eq_type5(expr * concatAst1, expr * concatAst2) { } } +/************************************************************* + * case 6: concat("str1", y) = concat(m, "str2") + *************************************************************/ bool theory_str::is_concat_eq_type6(expr * concatAst1, expr * concatAst2) { - // TODO - return false; + expr * v1_arg0 = to_app(concatAst1)->get_arg(0); + expr * v1_arg1 = to_app(concatAst1)->get_arg(1); + expr * v2_arg0 = to_app(concatAst2)->get_arg(0); + expr * v2_arg1 = to_app(concatAst2)->get_arg(1); + + if (m_strutil.is_string(v1_arg0) && (!m_strutil.is_string(v1_arg1)) + && (!m_strutil.is_string(v2_arg0)) && m_strutil.is_string(v2_arg1)) { + return true; + } else if (m_strutil.is_string(v2_arg0) && (!m_strutil.is_string(v2_arg1)) + && (!m_strutil.is_string(v1_arg0)) && m_strutil.is_string(v1_arg1)) { + return true; + } else { + return false; + } } void theory_str::process_concat_eq_type6(expr * concatAst1, expr * concatAst2) { + ast_manager & mgr = get_manager(); + context & ctx = get_context(); + TRACE("t_str_detail", tout << "process_concat_eq TYPE 6" << std::endl + << "concatAst1 = " << mk_ismt2_pp(concatAst1, mgr) << std::endl + << "concatAst2 = " << mk_ismt2_pp(concatAst2, mgr) << std::endl; + ); + if (!is_concat(to_app(concatAst1))) { + TRACE("t_str_detail", tout << "concatAst1 is not a concat function" << std::endl;); + return; + } + if (!is_concat(to_app(concatAst2))) { + TRACE("t_str_detail", tout << "concatAst2 is not a concat function" << std::endl;); + return; + } + + expr * v1_arg0 = to_app(concatAst1)->get_arg(0); + expr * v1_arg1 = to_app(concatAst1)->get_arg(1); + expr * v2_arg0 = to_app(concatAst2)->get_arg(0); + expr * v2_arg1 = to_app(concatAst2)->get_arg(1); + + + expr * str1Ast = NULL; + expr * y = NULL; + expr * m = NULL; + expr * str2Ast = NULL; + + if (m_strutil.is_string(v1_arg0)) { + str1Ast = v1_arg0; + y = v1_arg1; + m = v2_arg0; + str2Ast = v2_arg1; + } else { + str1Ast = v2_arg0; + y = v2_arg1; + m = v1_arg0; + str2Ast = v1_arg1; + } + + const char *tmp = 0; + m_strutil.is_string(str1Ast, &tmp); + std::string str1Value(tmp); + m_strutil.is_string(str2Ast, &tmp); + std::string str2Value(tmp); + + int str1Len = str1Value.length(); + int str2Len = str2Value.length(); + + //---------------------------------------- + //(a) |---str1---|----y----| + // |--m--|-----str2-----| + // + //(b) |---str1---|----y----| + // |-----m----|--str2---| + // + //(c) |---str1---|----y----| + // |------m------|-str2-| + //---------------------------------------- + + std::list overlapLen; + overlapLen.push_back(0); + + for (int i = 1; i <= str1Len && i <= str2Len; i++) { + if (str1Value.substr(str1Len - i, i) == str2Value.substr(0, i)) + overlapLen.push_back(i); + } + + //---------------------------------------------------------------- + expr * commonVar = NULL; + expr * xorFlag = NULL; + std::pair key1(concatAst1, concatAst2); + std::pair key2(concatAst2, concatAst1); + if (varForBreakConcat.find(key1) == varForBreakConcat.end() && varForBreakConcat.find(key2) == varForBreakConcat.end()) { + commonVar = mk_nonempty_str_var(); + xorFlag = mk_internal_xor_var(); + varForBreakConcat[key1][0] = commonVar; + varForBreakConcat[key1][1] = xorFlag; + } else { + if (varForBreakConcat.find(key1) != varForBreakConcat.end()) { + commonVar = varForBreakConcat[key1][0]; + xorFlag = varForBreakConcat[key1][1]; + } else { + commonVar = varForBreakConcat[key2][0]; + xorFlag = varForBreakConcat[key2][1]; + } + } + + expr ** or_item = alloc_svect(expr*, (overlapLen.size() + 1)); + int option = 0; + expr ** and_item = alloc_svect(expr*, (1 + 4 * (overlapLen.size() + 1))); + int pos = 1; + + if (!avoidLoopCut || !has_self_cut(m, y)) { + or_item[option] = ctx.mk_eq_atom(xorFlag, mk_int(option)); + + expr_ref str1_commonVar(mk_concat(str1Ast, commonVar), mgr); + and_item[pos++] = ctx.mk_eq_atom(or_item[option], ctx.mk_eq_atom(m, str1_commonVar)); + + expr_ref commonVar_str2(mk_concat(commonVar, str2Ast), mgr); + and_item[pos++] = ctx.mk_eq_atom(or_item[option], ctx.mk_eq_atom(y, commonVar_str2)); + + and_item[pos++] = ctx.mk_eq_atom(or_item[option], ctx.mk_eq_atom(mk_strlen(m), + m_autil.mk_add(mk_strlen(str1Ast), mk_strlen(commonVar)) )); + + // addItems[0] = mk_length(t, commonVar); + // addItems[1] = mk_length(t, str2Ast); + // and_item[pos++] = Z3_mk_eq(ctx, or_item[option], Z3_mk_eq(ctx, mk_length(t, y), Z3_mk_add(ctx, 2, addItems))); + + option++; + } else { + loopDetected = true; + TRACE("t_str", tout << "AVOID LOOP: SKIPPED." << std::endl;); + // TODO printCutVAR(m, y) + } + + for (std::list::iterator itor = overlapLen.begin(); itor != overlapLen.end(); itor++) { + int overLen = *itor; + std::string prefix = str1Value.substr(0, str1Len - overLen); + std::string suffix = str2Value.substr(overLen, str2Len - overLen); + or_item[option] = ctx.mk_eq_atom(xorFlag, mk_int(option)); + + expr_ref prefixAst(m_strutil.mk_string(prefix), mgr); + expr_ref x_eq_prefix(ctx.mk_eq_atom(m, prefixAst), mgr); + and_item[pos++] = ctx.mk_eq_atom(or_item[option], x_eq_prefix); + + and_item[pos++] = ctx.mk_eq_atom(or_item[option], + ctx.mk_eq_atom(mk_strlen(m), mk_strlen(prefixAst))); + + // adding length constraint for _ = constStr seems slowing things down. + + expr_ref suffixAst(m_strutil.mk_string(suffix), mgr); + expr_ref y_eq_suffix(ctx.mk_eq_atom(y, suffixAst), mgr); + and_item[pos++] = ctx.mk_eq_atom(or_item[option], y_eq_suffix); + + and_item[pos++] = ctx.mk_eq_atom(or_item[option], ctx.mk_eq_atom(mk_strlen(y), mk_strlen(suffixAst))); + + option++; + } + + // case 6: concat("str1", y) = concat(m, "str2") + and_item[0] = mgr.mk_or(option, or_item); + expr_ref implyR(mgr.mk_and(pos, and_item), mgr); + assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); } /* From b494804c9c75b083767dd1a63d75f1abf8ebc717 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 6 Oct 2015 19:31:26 -0400 Subject: [PATCH 049/410] ignore tests dir --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 97ca67cf4..b4a69f69e 100644 --- a/.gitignore +++ b/.gitignore @@ -77,4 +77,7 @@ doc/code # reference code for z3str2 Z3-str Z3-str/** +# test cases +tests +tests/** From e521ab2c3af04c7c4b4082a2ffc81b0c6caf864a Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sun, 18 Oct 2015 19:39:55 -0400 Subject: [PATCH 050/410] fix concat_axiom loop in propagate(): compare against size()...... --- src/smt/theory_str.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index aebaec572..47165997d 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -376,7 +376,7 @@ void theory_str::propagate() { } m_str_eq_todo.reset(); - for (unsigned i = 0; i < m_concat_axiom_todo.empty(); ++i) { + for (unsigned i = 0; i < m_concat_axiom_todo.size(); ++i) { instantiate_concat_axiom(m_concat_axiom_todo[i]); } m_concat_axiom_todo.reset(); From 3ee8f27588bcb88220f0ff515526ee0003716c43 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sun, 18 Oct 2015 20:20:09 -0400 Subject: [PATCH 051/410] possibly fix internalization bug mentioned in #2 (this leads to a not-implemented-yet in final_check_eh() due to missing code surrounding free variable production) --- src/smt/theory_str.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 47165997d..15253bcfd 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -1837,6 +1837,8 @@ void theory_str::process_concat_eq_type6(expr * concatAst1, expr * concatAst2) { */ expr * theory_str::get_eqc_value(expr * n, bool & hasEqcValue) { context & ctx = get_context(); + // I hope this works + ctx.internalize(n, false); enode * nNode = ctx.get_enode(n); enode * eqcNode = nNode; do { From c08f4371f4b9b0f98001eae4c111dbe91cf53d8f Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 21 Oct 2015 21:32:38 -0400 Subject: [PATCH 052/410] begin model generation, wip --- src/smt/theory_str.cpp | 97 ++++++++++++++++++++++++++++++++++++++++++ src/smt/theory_str.h | 5 +++ 2 files changed, 102 insertions(+) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 15253bcfd..3e6b637d1 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -2525,6 +2525,88 @@ void theory_str::dump_assignments() { ); } +// NOTE: this function used to take an argument `Z3_ast node`; +// it was not used and so was removed from the signature +void theory_str::classify_ast_by_type_in_positive_context(std::map & varMap, + std::map & concatMap, std::map & unrollMap) { + + context & ctx = get_context(); + ast_manager & m = get_manager(); + expr_ref_vector assignments(m); + ctx.get_assignments(assignments); + + for (expr_ref_vector::iterator it = assignments.begin(); it != assignments.end(); ++it) { + expr * argAst = *it; + // TODO + NOT_IMPLEMENTED_YET(); + /* + * according to getNodeType(), the following things are considered "functions": + * Contains, StartsWith, EndsWith, RegexIn + * Length, Indexof, Indexof2, LastIndexof + * Concat, SubString, Replace, Unroll, CharAt + * RegexConcat, RegexStar, RegexPlus, RegexCharRange, RegexUnion, Str2Reg + * something about Z3_ARRAY_SORT? + * Z3 native functions that aren't considered "uninterpreted" + * "real" uninterpreted functions declared in the input (domainSize != 0) + */ + + /* + if (getNodeType(t, argAst) == my_Z3_Func) { + Z3_app func_app = Z3_to_app(ctx, argAst); + Z3_decl_kind func_decl = Z3_get_decl_kind(ctx, Z3_get_app_decl(ctx, func_app)); + + if (isInterestingFuncKind(func_decl)) { + classifyAstByType(t, argAst, varMap, concatMap, unrollMap); + } + } + */ + } +} + +/* + * Dependence analysis from current context assignment + * - "freeVarMap" contains a set of variables that doesn't constrained by Concats. + * But it's possible that it's bounded by unrolls + * For the case of + * (1) var1 = unroll(r1, t1) + * var1 is in the freeVarMap + * > should unroll r1 for var1 + * (2) var1 = unroll(r1, t1) /\ var1 = Concat(var2, var3) + * var2, var3 are all in freeVar + * > should split the unroll function so that var2 and var3 are bounded by new unrolls + */ +int theory_str::ctx_dep_analysis(std::map & strVarMap, std::map & freeVarMap, + std::map > & unrollGroupMap) { + std::map concatMap; + std::map unrollMap; + std::map aliasIndexMap; + std::map var_eq_constStr_map; + std::map concat_eq_constStr_map; + std::map > var_eq_concat_map; + std::map > var_eq_unroll_map; + std::map > concat_eq_concat_map; + std::map > depMap; + + context & ctx = get_context(); + ast_manager & m = get_manager(); + + // note that the old API concatenated these assignments into + // a massive conjunction; we may have the opportunity to avoid that here + expr_ref_vector assignments(m); + ctx.get_assignments(assignments); + + // Step 1: get variables / concat AST appearing in the context + // TODO build this map; see strTheory::checkInputVar() + // it should just be variable_set - internal_variable_set? + for(std::map::iterator it = inputVarMap.begin(); it != inputVarMap.end(); ++it) { + strVarMap[it->first] = 1; + } + classify_ast_by_type_in_positive_context(assignments, strVarMap, concatMap, unrollMap); + + // TODO the rest + NOT_IMPLEMENTED_YET(); +} + final_check_status theory_str::final_check_eh() { ast_manager & m = get_manager(); context & ctx = get_context(); @@ -2532,6 +2614,20 @@ final_check_status theory_str::final_check_eh() { TRACE("t_str", tout << "final check" << std::endl;); TRACE("t_str_detail", dump_assignments();); + // run dependence analysis to find free string variables + std::map varAppearInAssign; + std::map freeVar_map; + std::map > unrollGroup_map; + int conflictInDep = ctx_dep_analysis(varAppearInAssign, freeVar_map, unrollGroup_map); + if (conflictInDep == -1) { + // return Z3_TRUE; + return FC_DONE; + } + + // TODO the rest... + NOT_IMPLEMENTED_YET(); + + /* // Check every variable to see if it's eq. to some string constant. // If not, mark it as free. bool needToAssignFreeVars = false; @@ -2561,6 +2657,7 @@ final_check_status theory_str::final_check_eh() { } } return FC_CONTINUE; + */ } void theory_str::init_model(model_generator & mg) { diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 1c2e2fbee..80e321729 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -144,6 +144,11 @@ namespace smt { bool new_eq_check(expr * lhs, expr * rhs); void group_terms_by_eqc(expr * n, std::set & concats, std::set & vars, std::set & consts); + int ctx_dep_analysis(std::map & strVarMap, std::map & freeVarMap, + std::map > & unrollGroupMap); + void classify_ast_by_type_in_positive_context(std::map & varMap, + std::map & concatMap, std::map & unrollMap) + void dump_assignments(); public: theory_str(ast_manager & m); From 1f3c5cebbf660a52aebae291846a65a021c30520 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Mon, 26 Oct 2015 15:43:31 -0400 Subject: [PATCH 053/410] variable classification (WIP) --- src/smt/theory_str.cpp | 82 ++++++++++++++++++++++++++++++------------ src/smt/theory_str.h | 6 +++- 2 files changed, 64 insertions(+), 24 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 3e6b637d1..86aaaaf44 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -2525,6 +2525,57 @@ void theory_str::dump_assignments() { ); } +void theory_str::classify_ast_by_type(expr * node, std::map & varMap, + std::map & concatMap, std::map & unrollMap) { + + context & ctx = get_context(); + ast_manager & m = get_manager(); + + // check whether the node is a non-internal string variable; + // testing set membership here bypasses several expensive checks + if (variable_set.find(node) != variable_set.end() + && internal_variable_set.find(node) == internal_variable_set.end()) { + varMap[node] = 1; + } + // check whether the node is a function that we want to inspect + else if (is_app(node)) { // TODO + app * aNode = to_app(node); + if (is_strlen(aNode)) { + // Length + return; + } else if (is_concat(aNode)) { + expr * arg0 = aNode->get_arg(0); + expr * arg1 = aNode->get_arg(1); + bool arg0HasEq = false; + bool arg1HasEq = false; + expr * arg0Val = get_eqc_value(arg0, arg0HasEq); + expr * arg1Val = get_eqc_value(arg1, arg1HasEq); + + int canskip = 0; + if (arg0HasEq && arg0Val == m_strutil.mk_string("")) { + canskip = 1; + } + if (canskip == 0 && arg1HasEq && arg1Val == m_strutil.mk_string("")) { + canskip = 1; + } + if (canskip == 0 && concatMap.find(node) == concatMap.end()) { + concatMap[node] = 1; + } + } else if (false) { // TODO is_unroll() + // Unroll + if (unrollMap.find(node) == unrollMap.end()) { + unrollMap[node] = 1; + } + } + // recursively visit all arguments + app * aNode = to_app(node); + for (unsigned i = 0; i < aNode->get_num_args(); ++i) { + expr * arg = aNode->get_arg(i); + classify_ast_by_type(arg, varMap, concatMap, unrollMap); + } + } +} + // NOTE: this function used to take an argument `Z3_ast node`; // it was not used and so was removed from the signature void theory_str::classify_ast_by_type_in_positive_context(std::map & varMap, @@ -2537,29 +2588,14 @@ void theory_str::classify_ast_by_type_in_positive_context(std::map & for (expr_ref_vector::iterator it = assignments.begin(); it != assignments.end(); ++it) { expr * argAst = *it; - // TODO - NOT_IMPLEMENTED_YET(); - /* - * according to getNodeType(), the following things are considered "functions": - * Contains, StartsWith, EndsWith, RegexIn - * Length, Indexof, Indexof2, LastIndexof - * Concat, SubString, Replace, Unroll, CharAt - * RegexConcat, RegexStar, RegexPlus, RegexCharRange, RegexUnion, Str2Reg - * something about Z3_ARRAY_SORT? - * Z3 native functions that aren't considered "uninterpreted" - * "real" uninterpreted functions declared in the input (domainSize != 0) - */ + // the original code jumped through some hoops to check whether the AST node + // is a function, then checked whether that function is "interesting". + // however, the only thing that's considered "interesting" is an equality predicate. + // so we bypass a huge amount of work by doing the following... - /* - if (getNodeType(t, argAst) == my_Z3_Func) { - Z3_app func_app = Z3_to_app(ctx, argAst); - Z3_decl_kind func_decl = Z3_get_decl_kind(ctx, Z3_get_app_decl(ctx, func_app)); - - if (isInterestingFuncKind(func_decl)) { - classifyAstByType(t, argAst, varMap, concatMap, unrollMap); - } + if (m.is_eq(argAst)) { + classify_ast_by_type(argAst, varMap, concatMap, unrollMap); } - */ } } @@ -2598,10 +2634,10 @@ int theory_str::ctx_dep_analysis(std::map & strVarMap, std::map::iterator it = inputVarMap.begin(); it != inputVarMap.end(); ++it) { + for(std::map::iterator it = input_var_map.begin(); it != input_var_map.end(); ++it) { strVarMap[it->first] = 1; } - classify_ast_by_type_in_positive_context(assignments, strVarMap, concatMap, unrollMap); + classify_ast_by_type_in_positive_context(strVarMap, concatMap, unrollMap); // TODO the rest NOT_IMPLEMENTED_YET(); diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 80e321729..684526602 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -104,6 +104,8 @@ namespace smt { bool is_concat(enode const * n) const { return is_concat(n->get_owner()); } bool is_string(app const * a) const { return a->is_app_of(get_id(), OP_STR); } bool is_string(enode const * n) const { return is_string(n->get_owner()); } + bool is_strlen(app const * a) const { return a->is_app_of(get_id(), OP_STRLEN); } + bool is_strlen(enode const * n) const { return is_strlen(n->get_owner()); } void instantiate_concat_axiom(enode * cat); void instantiate_basic_string_axioms(enode * str); void instantiate_str_eq_length_axiom(enode * lhs, enode * rhs); @@ -146,8 +148,10 @@ namespace smt { int ctx_dep_analysis(std::map & strVarMap, std::map & freeVarMap, std::map > & unrollGroupMap); + void classify_ast_by_type(expr * node, std::map & varMap, + std::map & concatMap, std::map & unrollMap); void classify_ast_by_type_in_positive_context(std::map & varMap, - std::map & concatMap, std::map & unrollMap) + std::map & concatMap, std::map & unrollMap); void dump_assignments(); public: From 9f01b9dc92203f8cdefa7cd9befe43da6c6565f6 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 4 Nov 2015 16:22:06 -0500 Subject: [PATCH 054/410] more progress on model gen (WIP) --- src/smt/theory_str.cpp | 203 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 199 insertions(+), 4 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 86aaaaf44..c5ca630f9 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -2632,13 +2632,208 @@ int theory_str::ctx_dep_analysis(std::map & strVarMap, std::map::iterator it = input_var_map.begin(); it != input_var_map.end(); ++it) { - strVarMap[it->first] = 1; + // the thing we iterate over should just be variable_set - internal_variable_set + // so we avoid computing the set difference (but this might be slower) + for(std::set::iterator it = variable_set.begin(); it != variable_set.end(); ++it) { + expr* var = *it; + if (internal_variable_set.find(var) == internal_variable_set.end()) { + strVarMap[*it] = 1; + } } classify_ast_by_type_in_positive_context(strVarMap, concatMap, unrollMap); + // TODO unroll() + /* + std::map aliasUnrollSet; + std::map::iterator unrollItor = unrollMap.begin(); + for (; unrollItor != unrollMap.end(); unrollItor++) { + if (aliasUnrollSet.find(unrollItor->first) != aliasUnrollSet.end()) + continue; + Z3_ast aRoot = NULL; + Z3_ast curr = unrollItor->first; + do { + if (isUnrollFunc(t, curr)) { + if (aRoot == NULL) { + aRoot = curr; + } + aliasUnrollSet[curr] = aRoot; + } + curr = Z3_theory_get_eqc_next(t, curr); + } while (curr != unrollItor->first); + } + + for (unrollItor = unrollMap.begin(); unrollItor != unrollMap.end(); unrollItor++) { + Z3_ast unrFunc = unrollItor->first; + Z3_ast urKey = aliasUnrollSet[unrFunc]; + unrollGroupMap[urKey].insert(unrFunc); + } + */ + + // Step 2: collect alias relation + // e.g. suppose we have the equivalence class {x, y, z}; + // then we set aliasIndexMap[y] = x + // and aliasIndexMap[z] = x + + std::map::iterator varItor = strVarMap.begin(); + for (; varItor != strVarMap.end(); ++varItor) { + if (aliasIndexMap.find(varItor->first) != aliasIndexMap.end()) { + continue; + } + expr * aRoot = NULL; + expr * curr = varItor->first; + do { + if (variable_set.find(curr) != variable_set.end()) { // TODO internal_variable_set? + if (aRoot == NULL) { + aRoot = curr; + } else { + aliasIndexMap[curr] = aRoot; + } + } + // curr = get_eqc_next(curr); + enode * eqcNode = ctx.get_enode(curr); + eqcNode = eqcNode->get_next(); + curr = eqcNode->get_owner(); + } while (curr != varItor->first); + } + + // Step 3: Collect interested cases + + varItor = strVarMap.begin(); + for (; varItor != strVarMap.end(); ++varItor) { + expr * deAliasNode = get_alias_index_ast(aliasIndexMap, varItor->first); + // Case 1: variable = string constant + // e.g. z = "str1" ::= var_eq_constStr_map[z] = "str1" + + if (var_eq_constStr_map.find(deAliasNode) == var_eq_constStr_map.end()) { + bool nodeHasEqcValue = false; + expr * nodeValue = get_eqc_value(deAliasNode, nodeHasEqcValue); + if (nodeHasEqcValue) { + var_eq_constStr_map[deAliasNode] = nodeValue; + } + } + + // Case 2: var_eq_concat + // e.g. z = concat("str1", b) ::= var_eq_concat[z][concat(c, "str2")] = 1 + // var_eq_unroll + // e.g. z = unroll(...) ::= var_eq_unroll[z][unroll(...)] = 1 + + if (var_eq_concat_map.find(deAliasNode) == var_eq_concat_map.end()) { + enode * e_curr = ctx.get_enode(deAliasNode); + expr * curr = e_curr->get_next()->get_owner(); + while (curr != deAliasNode) { + app * aCurr = to_app(curr); + // collect concat + if (is_concat(aCurr)) { + expr * arg0 = aCurr->get_arg(0); + expr * arg1 = aCurr->get_arg(1); + bool arg0HasEqcValue = false; + bool arg1HasEqcValue = false; + expr * arg0_value = get_eqc_value(arg0, arg0HasEqcValue); + expr * arg1_value = get_eqc_value(arg1, arg1HasEqcValue); + + bool is_arg0_emptyStr = false; + if (arg0HasEqcValue) { + const char * strval = 0; + m_strutil.is_string(arg0_value, &strval); + if (strcmp(strval, "") == 0) { + is_arg0_emptyStr = true; + } + } + + bool is_arg1_emptyStr = false; + if (arg1HasEqcValue) { + const char * strval = 0; + m_strutil.is_string(arg1_value, &strval); + if (strcmp(strval, "") == 0) { + is_arg1_emptyStr = true; + } + } + + if (!is_arg0_emptyStr && !is_arg1_emptyStr) { + var_eq_concat_map[deAliasNode][curr] = 1; + } + } + // TODO: collect unroll functions + /* + else if (isUnrollFunc(t, curr)) { + var_eq_unroll_map[deAliasNode][curr] = 1; + } + */ + + // curr = get_eqc_next(curr) + e_curr = ctx.get_enode(curr); + curr = e_curr->get_next()->get_owner(); + } + } + + } // for(varItor in strVarMap) + + // -------------------------------------------------- + // * collect aliasing relation among eq concats + // e.g EQC={concat1, concat2, concat3} + // concats_eq_Index_map[concat2] = concat1 + // concats_eq_Index_map[concat3] = concat1 + // -------------------------------------------------- + + /* + std::map concats_eq_Index_map; + std::map::iterator concatItor = concatMap.begin(); + for (; concatItor != concatMap.end(); concatItor++) { + // simplifyConcatToConst(t, concatItor->first); + + if (concats_eq_Index_map.find(concatItor->first) != concats_eq_Index_map.end()) + continue; + + Z3_ast aRoot = NULL; + Z3_ast curr = concatItor->first; + do { + if (isConcatFunc(t, curr)) { + if (aRoot == NULL) + aRoot = curr; + else + concats_eq_Index_map[curr] = aRoot; + } + curr = Z3_theory_get_eqc_next(t, curr); + } while (curr != concatItor->first); + } + + concatItor = concatMap.begin(); + for (; concatItor != concatMap.end(); concatItor++) { + Z3_ast deAliasConcat = NULL; + if (concats_eq_Index_map.find(concatItor->first) != concats_eq_Index_map.end()) + deAliasConcat = concats_eq_Index_map[concatItor->first]; + else + deAliasConcat = concatItor->first; + + // -------------------------------------------------- + // (3) concat_eq_constStr: + // e.g, concat(a,b) = "str1" + // -------------------------------------------------- + if (concat_eq_constStr_map.find(deAliasConcat) == concat_eq_constStr_map.end()) { + bool nodeHasEqcValue = false; + Z3_ast nodeValue = get_eqc_value(t, deAliasConcat, nodeHasEqcValue); + if (nodeHasEqcValue) + concat_eq_constStr_map[deAliasConcat] = nodeValue; + } + // -------------------------------------------------- + // (4) concat_eq_concat: + // e.g, concat(a,b) = concat("str1", c) /\ z = concat(a, b) /\ z = concat(e, f) + // -------------------------------------------------- + if (concat_eq_concat_map.find(deAliasConcat) == concat_eq_concat_map.end()) { + Z3_ast curr = deAliasConcat; + do { + if (isConcatFunc(t, curr)) { + // curr is not a concat that can be reduced + if (concatMap.find(curr) != concatMap.end()) { + concat_eq_concat_map[deAliasConcat][curr] = 1; + } + } + curr = Z3_theory_get_eqc_next(t, curr); + } while (curr != deAliasConcat); + } + } + */ + // TODO the rest NOT_IMPLEMENTED_YET(); } From 4a8ee88461ab27233c15a9af389bdea3743fc5bd Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Fri, 6 Nov 2015 13:43:54 -0500 Subject: [PATCH 055/410] ctx_dep_analysis() done, final_check() WIP --- src/smt/theory_str.cpp | 517 +++++++++++++++++++++++++++++++++++------ src/smt/theory_str.h | 4 + 2 files changed, 451 insertions(+), 70 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index c5ca630f9..7d9aaad7d 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -2599,6 +2599,33 @@ void theory_str::classify_ast_by_type_in_positive_context(std::map & } } +inline expr * theory_str::get_alias_index_ast(std::map & aliasIndexMap, expr * node) { + if (aliasIndexMap.find(node) != aliasIndexMap.end()) + return aliasIndexMap[node]; + else + return node; +} + +inline expr * theory_str::getMostLeftNodeInConcat(expr * node) { + app * aNode = to_app(node); + if (!is_concat(aNode)) { + return node; + } else { + expr * concatArgL = aNode->get_arg(0); + return getMostLeftNodeInConcat(concatArgL); + } +} + +inline expr * theory_str::getMostRightNodeInConcat(expr * node) { + app * aNode = to_app(node); + if (!is_concat(aNode)) { + return node; + } else { + expr * concatArgR = aNode->get_arg(1); + return getMostRightNodeInConcat(concatArgR); + } +} + /* * Dependence analysis from current context assignment * - "freeVarMap" contains a set of variables that doesn't constrained by Concats. @@ -2775,67 +2802,429 @@ int theory_str::ctx_dep_analysis(std::map & strVarMap, std::map concats_eq_Index_map; - std::map::iterator concatItor = concatMap.begin(); - for (; concatItor != concatMap.end(); concatItor++) { - // simplifyConcatToConst(t, concatItor->first); - - if (concats_eq_Index_map.find(concatItor->first) != concats_eq_Index_map.end()) - continue; - - Z3_ast aRoot = NULL; - Z3_ast curr = concatItor->first; - do { - if (isConcatFunc(t, curr)) { - if (aRoot == NULL) - aRoot = curr; - else - concats_eq_Index_map[curr] = aRoot; - } - curr = Z3_theory_get_eqc_next(t, curr); - } while (curr != concatItor->first); + std::map concats_eq_index_map; + std::map::iterator concatItor = concatMap.begin(); + for(; concatItor != concatMap.end(); ++concatItor) { + if (concats_eq_index_map.find(concatItor->first) != concats_eq_index_map.end()) { + continue; + } + expr * aRoot = NULL; + expr * curr = concatItor->first; + do { + if (is_concat(to_app(curr))) { + if (aRoot == NULL) { + aRoot = curr; + } else { + concats_eq_index_map[curr] = aRoot; + } + } + // curr = get_eqc_next(curr); + enode * e_curr = ctx.get_enode(curr); + curr = e_curr->get_next()->get_owner(); + } while (curr != concatItor->first); } concatItor = concatMap.begin(); - for (; concatItor != concatMap.end(); concatItor++) { - Z3_ast deAliasConcat = NULL; - if (concats_eq_Index_map.find(concatItor->first) != concats_eq_Index_map.end()) - deAliasConcat = concats_eq_Index_map[concatItor->first]; - else - deAliasConcat = concatItor->first; + for(; concatItor != concatMap.end(); ++concatItor) { + expr * deAliasConcat = NULL; + if (concats_eq_index_map.find(concatItor->first) != concats_eq_index_map.end()) { + deAliasConcat = concats_eq_index_map[concatItor->first]; + } else { + deAliasConcat = concatItor->first; + } - // -------------------------------------------------- - // (3) concat_eq_constStr: - // e.g, concat(a,b) = "str1" - // -------------------------------------------------- - if (concat_eq_constStr_map.find(deAliasConcat) == concat_eq_constStr_map.end()) { - bool nodeHasEqcValue = false; - Z3_ast nodeValue = get_eqc_value(t, deAliasConcat, nodeHasEqcValue); - if (nodeHasEqcValue) - concat_eq_constStr_map[deAliasConcat] = nodeValue; - } - // -------------------------------------------------- - // (4) concat_eq_concat: - // e.g, concat(a,b) = concat("str1", c) /\ z = concat(a, b) /\ z = concat(e, f) - // -------------------------------------------------- - if (concat_eq_concat_map.find(deAliasConcat) == concat_eq_concat_map.end()) { - Z3_ast curr = deAliasConcat; - do { - if (isConcatFunc(t, curr)) { - // curr is not a concat that can be reduced - if (concatMap.find(curr) != concatMap.end()) { - concat_eq_concat_map[deAliasConcat][curr] = 1; - } - } - curr = Z3_theory_get_eqc_next(t, curr); - } while (curr != deAliasConcat); - } + // (3) concat_eq_conststr, e.g. concat(a,b) = "str1" + if (concat_eq_constStr_map.find(deAliasConcat) == concat_eq_constStr_map.end()) { + bool nodeHasEqcValue = false; + expr * nodeValue = get_eqc_value(deAliasConcat, nodeHasEqcValue); + if (nodeHasEqcValue) { + concat_eq_constStr_map[deAliasConcat] = nodeValue; + } + } + + // (4) concat_eq_concat, e.g. + // concat(a,b) = concat("str1", c) AND z = concat(a,b) AND z = concat(e,f) + if (concat_eq_concat_map.find(deAliasConcat) == concat_eq_concat_map.end()) { + expr * curr = deAliasConcat; + do { + if (is_concat(to_app(curr))) { + // curr cannot be reduced + if (concatMap.find(curr) != concatMap.end()) { + concat_eq_concat_map[deAliasConcat][curr] = 1; + } + } + // curr = get_eqc_next(curr); + enode * e_curr = ctx.get_enode(curr); + curr = e_curr->get_next()->get_owner(); + } while (curr != deAliasConcat); + } + } + + // TODO this would be a great place to print some debugging information + + // TODO compute Contains + /* + if (containPairBoolMap.size() > 0) { + computeContains(t, aliasIndexMap, concats_eq_Index_map, var_eq_constStr_map, concat_eq_constStr_map, var_eq_concat_map); } */ - // TODO the rest - NOT_IMPLEMENTED_YET(); + // step 4: dependence analysis + + // (1) var = string constant + for (std::map::iterator itor = var_eq_constStr_map.begin(); + itor != var_eq_constStr_map.end(); ++itor) { + expr * var = get_alias_index_ast(aliasIndexMap, itor->first); + expr * strAst = itor->second; + depMap[var][strAst] = 1; + } + + // (2) var = concat + for (std::map >::iterator itor = var_eq_concat_map.begin(); + itor != var_eq_concat_map.end(); ++itor) { + expr * var = get_alias_index_ast(aliasIndexMap, itor->first); + for (std::map::iterator itor1 = itor->second.begin(); itor1 != itor->second.end(); ++itor1) { + expr * concat = itor1->first; + std::map inVarMap; + std::map inConcatMap; + std::map inUnrollMap; + classify_ast_by_type(concat, inVarMap, inConcatMap, inUnrollMap); + for (std::map::iterator itor2 = inVarMap.begin(); itor2 != inVarMap.end(); ++itor2) { + expr * varInConcat = get_alias_index_ast(aliasIndexMap, itor2->first); + if (!(depMap[var].find(varInConcat) != depMap[var].end() && depMap[var][varInConcat] == 1)) { + depMap[var][varInConcat] = 2; + } + } + } + } + + for (std::map >::iterator itor = var_eq_unroll_map.begin(); + itor != var_eq_unroll_map.end(); itor++) { + expr * var = get_alias_index_ast(aliasIndexMap, itor->first); + for (std::map::iterator itor1 = itor->second.begin(); itor1 != itor->second.end(); itor1++) { + expr * unrollFunc = itor1->first; + std::map inVarMap; + std::map inConcatMap; + std::map inUnrollMap; + classify_ast_by_type(unrollFunc, inVarMap, inConcatMap, inUnrollMap); + for (std::map::iterator itor2 = inVarMap.begin(); itor2 != inVarMap.end(); itor2++) { + expr * varInFunc = get_alias_index_ast(aliasIndexMap, itor2->first); + + STRACE("t_str_detail", tout << "var in unroll = " << + mk_ismt2_pp(itor2->first, m) << std::endl + << "dealiased var = " << mk_ismt2_pp(varInFunc) << std::endl;); + + // it's possible that we have both (Unroll $$_regVar_0 $$_unr_0) /\ (Unroll abcd $$_unr_0), + // while $$_regVar_0 = "abcd" + // have to exclude such cases + bool varHasValue = false; + get_eqc_value(varInFunc, varHasValue); + if (varHasValue) + continue; + + if (depMap[var].find(varInFunc) == depMap[var].end()) { + depMap[var][varInFunc] = 6; + } + } + } + } + + // (3) concat = string constant + for (std::map::iterator itor = concat_eq_constStr_map.begin(); + itor != concat_eq_constStr_map.end(); itor++) { + expr * concatAst = itor->first; + expr * constStr = itor->second; + std::map inVarMap; + std::map inConcatMap; + std::map inUnrollMap; + classify_ast_by_type(concatAst, inVarMap, inConcatMap, inUnrollMap); + for (std::map::iterator itor2 = inVarMap.begin(); itor2 != inVarMap.end(); itor2++) { + expr * varInConcat = get_alias_index_ast(aliasIndexMap, itor2->first); + if (!(depMap[varInConcat].find(constStr) != depMap[varInConcat].end() && depMap[varInConcat][constStr] == 1)) + depMap[varInConcat][constStr] = 3; + } + } + + // (4) equivalent concats + // - possibility 1 : concat("str", v1) = concat(concat(v2, v3), v4) = concat(v5, v6) + // ==> v2, v5 are constrained by "str" + // - possibliity 2 : concat(v1, "str") = concat(v2, v3) = concat(v4, v5) + // ==> v2, v4 are constrained by "str" + //-------------------------------------------------------------- + + std::map mostLeftNodes; + std::map mostRightNodes; + + std::map mLIdxMap; + std::map > mLMap; + std::map mRIdxMap; + std::map > mRMap; + std::set nSet; + + for (std::map >::iterator itor = concat_eq_concat_map.begin(); + itor != concat_eq_concat_map.end(); itor++) { + mostLeftNodes.clear(); + mostRightNodes.clear(); + + expr * mLConst = NULL; + expr * mRConst = NULL; + + for (std::map::iterator itor1 = itor->second.begin(); itor1 != itor->second.end(); itor1++) { + expr * concatNode = itor1->first; + expr * mLNode = getMostLeftNodeInConcat(concatNode); + const char * strval; + if (m_strutil.is_string(to_app(mLNode), & strval)) { + if (mLConst == NULL && strcmp(strval, "") != 0) { + mLConst = mLNode; + } + } else { + mostLeftNodes[mLNode] = concatNode; + } + + expr * mRNode = getMostRightNodeInConcat(concatNode); + if (m_strutil.is_string(to_app(mRNode), & strval)) { + if (mRConst == NULL && strcmp(strval, "") != 0) { + mRConst = mRNode; + } + } else { + mostRightNodes[mRNode] = concatNode; + } + } + + if (mLConst != NULL) { + // ------------------------------------------------------------------------------------- + // The left most variable in a concat is constrained by a constant string in eqc concat + // ------------------------------------------------------------------------------------- + // e.g. Concat(x, ...) = Concat("abc", ...) + // ------------------------------------------------------------------------------------- + for (std::map::iterator itor1 = mostLeftNodes.begin(); + itor1 != mostLeftNodes.end(); itor1++) { + expr * deVar = get_alias_index_ast(aliasIndexMap, itor1->first); + if (depMap[deVar].find(mLConst) == depMap[deVar].end() || depMap[deVar][mLConst] != 1) { + depMap[deVar][mLConst] = 4; + } + } + } + + { + // ------------------------------------------------------------------------------------- + // The left most variables in eqc concats are constrained by each other + // ------------------------------------------------------------------------------------- + // e.g. concat(x, ...) = concat(u, ...) = ... + // x and u are constrained by each other + // ------------------------------------------------------------------------------------- + nSet.clear(); + std::map::iterator itl = mostLeftNodes.begin(); + for (; itl != mostLeftNodes.end(); itl++) { + bool lfHasEqcValue = false; + get_eqc_value(itl->first, lfHasEqcValue); + if (lfHasEqcValue) + continue; + expr * deVar = get_alias_index_ast(aliasIndexMap, itl->first); + nSet.insert(deVar); + } + + if (nSet.size() > 1) { + int lId = -1; + for (std::set::iterator itor2 = nSet.begin(); itor2 != nSet.end(); itor2++) { + if (mLIdxMap.find(*itor2) != mLIdxMap.end()) { + lId = mLIdxMap[*itor2]; + break; + } + } + if (lId == -1) + lId = mLMap.size(); + for (std::set::iterator itor2 = nSet.begin(); itor2 != nSet.end(); itor2++) { + bool itorHasEqcValue = false; + get_eqc_value(*itor2, itorHasEqcValue); + if (itorHasEqcValue) + continue; + mLIdxMap[*itor2] = lId; + mLMap[lId].insert(*itor2); + } + } + } + + if (mRConst != NULL) { + for (std::map::iterator itor1 = mostRightNodes.begin(); + itor1 != mostRightNodes.end(); itor1++) { + expr * deVar = get_alias_index_ast(aliasIndexMap, itor1->first); + if (depMap[deVar].find(mRConst) == depMap[deVar].end() || depMap[deVar][mRConst] != 1) { + depMap[deVar][mRConst] = 5; + } + } + } + + { + nSet.clear(); + std::map::iterator itr = mostRightNodes.begin(); + for (; itr != mostRightNodes.end(); itr++) { + expr * deVar = get_alias_index_ast(aliasIndexMap, itr->first); + nSet.insert(deVar); + } + if (nSet.size() > 1) { + int rId = -1; + std::set::iterator itor2 = nSet.begin(); + for (; itor2 != nSet.end(); itor2++) { + if (mRIdxMap.find(*itor2) != mRIdxMap.end()) { + rId = mRIdxMap[*itor2]; + break; + } + } + if (rId == -1) + rId = mRMap.size(); + for (itor2 = nSet.begin(); itor2 != nSet.end(); itor2++) { + bool rHasEqcValue = false; + get_eqc_value(*itor2, rHasEqcValue); + if (rHasEqcValue) + continue; + mRIdxMap[*itor2] = rId; + mRMap[rId].insert(*itor2); + } + } + } + } + + // TODO this would be a great place to print the dependence map + + // step, errr, 5: compute free variables based on the dependence map + + // the case dependence map is empty, every var in VarMap is free + //--------------------------------------------------------------- + // remove L/R most var in eq concat since they are constrained with each other + std::map > lrConstrainedMap; + for (std::map >::iterator itor = mLMap.begin(); itor != mLMap.end(); itor++) { + for (std::set::iterator it1 = itor->second.begin(); it1 != itor->second.end(); it1++) { + std::set::iterator it2 = it1; + it2++; + for (; it2 != itor->second.end(); it2++) { + expr * n1 = *it1; + expr * n2 = *it2; + lrConstrainedMap[n1][n2] = 1; + lrConstrainedMap[n2][n1] = 1; + } + } + } + for (std::map >::iterator itor = mRMap.begin(); itor != mRMap.end(); itor++) { + for (std::set::iterator it1 = itor->second.begin(); it1 != itor->second.end(); it1++) { + std::set::iterator it2 = it1; + it2++; + for (; it2 != itor->second.end(); it2++) { + expr * n1 = *it1; + expr * n2 = *it2; + lrConstrainedMap[n1][n2] = 1; + lrConstrainedMap[n2][n1] = 1; + } + } + } + + if (depMap.size() == 0) { + std::map::iterator itor = strVarMap.begin(); + for (; itor != strVarMap.end(); itor++) { + expr * var = get_alias_index_ast(aliasIndexMap, itor->first); + if (lrConstrainedMap.find(var) == lrConstrainedMap.end()) { + freeVarMap[var] = 1; + } else { + int lrConstainted = 0; + std::map::iterator lrit = freeVarMap.begin(); + for (; lrit != freeVarMap.end(); lrit++) { + if (lrConstrainedMap[var].find(lrit->first) != lrConstrainedMap[var].end()) { + lrConstainted = 1; + break; + } + } + if (lrConstainted == 0) { + freeVarMap[var] = 1; + } + } + } + } else { + // if the keys in aliasIndexMap are not contained in keys in depMap, they are free + // e.g., x= y /\ x = z /\ t = "abc" + // aliasIndexMap[y]= x, aliasIndexMap[z] = x + // depMap t ~ "abc"(1) + // x should be free + std::map::iterator itor2 = strVarMap.begin(); + for (; itor2 != strVarMap.end(); itor2++) { + if (aliasIndexMap.find(itor2->first) != aliasIndexMap.end()) { + expr * var = aliasIndexMap[itor2->first]; + if (depMap.find(var) == depMap.end()) { + if (lrConstrainedMap.find(var) == lrConstrainedMap.end()) { + freeVarMap[var] = 1; + } else { + int lrConstainted = 0; + std::map::iterator lrit = freeVarMap.begin(); + for (; lrit != freeVarMap.end(); lrit++) { + if (lrConstrainedMap[var].find(lrit->first) != lrConstrainedMap[var].end()) { + lrConstainted = 1; + break; + } + } + if (lrConstainted == 0) { + freeVarMap[var] = 1; + } + } + } + } else if (aliasIndexMap.find(itor2->first) == aliasIndexMap.end()) { + // if a variable is not in aliasIndexMap and not in depMap, it's free + if (depMap.find(itor2->first) == depMap.end()) { + expr * var = itor2->first; + if (lrConstrainedMap.find(var) == lrConstrainedMap.end()) { + freeVarMap[var] = 1; + } else { + int lrConstainted = 0; + std::map::iterator lrit = freeVarMap.begin(); + for (; lrit != freeVarMap.end(); lrit++) { + if (lrConstrainedMap[var].find(lrit->first) != lrConstrainedMap[var].end()) { + lrConstainted = 1; + break; + } + } + if (lrConstainted == 0) { + freeVarMap[var] = 1; + } + } + } + } + } + + std::map >::iterator itor = depMap.begin(); + for (; itor != depMap.end(); itor++) { + for (std::map::iterator itor1 = itor->second.begin(); itor1 != itor->second.end(); itor1++) { + if (variable_set.find(itor1->first) != variable_set.end()) { // expr type = var + expr * var = get_alias_index_ast(aliasIndexMap, itor1->first); + // if a var is dep on itself and all dependence are type 2, it's a free variable + // e.g {y --> x(2), y(2), m --> m(2), n(2)} y,m are free + { + if (depMap.find(var) == depMap.end()) { + if (freeVarMap.find(var) == freeVarMap.end()) { + if (lrConstrainedMap.find(var) == lrConstrainedMap.end()) { + freeVarMap[var] = 1; + } else { + int lrConstainted = 0; + std::map::iterator lrit = freeVarMap.begin(); + for (; lrit != freeVarMap.end(); lrit++) { + if (lrConstrainedMap[var].find(lrit->first) != lrConstrainedMap[var].end()) { + lrConstainted = 1; + break; + } + } + if (lrConstainted == 0) { + freeVarMap[var] = 1; + } + } + + } else { + freeVarMap[var] = freeVarMap[var] + 1; + } + } + } + } + } + } + } + + return 0; } final_check_status theory_str::final_check_eh() { @@ -2855,10 +3244,6 @@ final_check_status theory_str::final_check_eh() { return FC_DONE; } - // TODO the rest... - NOT_IMPLEMENTED_YET(); - - /* // Check every variable to see if it's eq. to some string constant. // If not, mark it as free. bool needToAssignFreeVars = false; @@ -2877,18 +3262,10 @@ final_check_status theory_str::final_check_eh() { return FC_DONE; } - for (std::set::iterator it = free_variables.begin(); it != free_variables.end(); ++it) { - expr * var = *it; - if (internal_variable_set.find(var) != internal_variable_set.end()) { - TRACE("t_str", tout << "assigning arbitrary string to internal variable " << mk_ismt2_pp(var, m) << std::endl;); - app * val = m_strutil.mk_string("**unused**"); - assert_axiom(ctx.mk_eq_atom(var, val)); - } else { - NOT_IMPLEMENTED_YET(); // TODO free variable assignment from strTheory::cb_final_check() - } - } - return FC_CONTINUE; - */ + + + // TODO the rest... + NOT_IMPLEMENTED_YET(); } void theory_str::init_model(model_generator & mg) { diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 684526602..3d0f14ca7 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -153,6 +153,10 @@ namespace smt { void classify_ast_by_type_in_positive_context(std::map & varMap, std::map & concatMap, std::map & unrollMap); + expr * get_alias_index_ast(std::map & aliasIndexMap, expr * node); + expr * getMostLeftNodeInConcat(expr * node); + expr * getMostRightNodeInConcat(expr * node); + void dump_assignments(); public: theory_str(ast_manager & m); From ac8b5e6eae5cf97146fafd4f1cc450cda7dc4a27 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Fri, 6 Nov 2015 14:10:18 -0500 Subject: [PATCH 056/410] free variable WIP --- src/smt/theory_str.cpp | 160 ++++++++++++++++++++++++++++++++++++++++- src/smt/theory_str.h | 3 + 2 files changed, 161 insertions(+), 2 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 7d9aaad7d..cb31aedde 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -3262,10 +3262,166 @@ final_check_status theory_str::final_check_eh() { return FC_DONE; } + // ----------------------------------------------------------- + // variables in freeVar are those not bouned by Concats + // classify variables in freeVarMap: + // (1) freeVar = unroll(r1, t1) + // (2) vars are not bounded by either concat or unroll + // ----------------------------------------------------------- + std::map > fv_unrolls_map; + std::set tmpSet; + expr * constValue = NULL; + for (std::map::iterator fvIt2 = freeVar_map.begin(); fvIt2 != freeVar_map.end(); fvIt2++) { + expr * var = fvIt2->first; + tmpSet.clear(); + get_eqc_allUnroll(var, constValue, tmpSet); + if (tmpSet.size() > 0) { + fv_unrolls_map[var] = tmpSet; + } + } + // erase var bounded by an unroll function from freeVar_map + for (std::map >::iterator fvIt3 = fv_unrolls_map.begin(); + fvIt3 != fv_unrolls_map.end(); fvIt3++) { + expr * var = fvIt3->first; + freeVar_map.erase(var); + } + // collect the case: + // * Concat(X, Y) = unroll(r1, t1) /\ Concat(X, Y) = unroll(r2, t2) + // concatEqUnrollsMap[Concat(X, Y)] = {unroll(r1, t1), unroll(r2, t2)} - // TODO the rest... - NOT_IMPLEMENTED_YET(); + std::map > concatEqUnrollsMap; + for (std::map >::iterator urItor = unrollGroup_map.begin(); + urItor != unrollGroup_map.end(); urItor++) { + expr * unroll = urItor->first; + expr * curr = unroll; + do { + if (is_concat(to_app(curr))) { + concatEqUnrollsMap[curr].insert(unroll); + concatEqUnrollsMap[curr].insert(unrollGroup_map[unroll].begin(), unrollGroup_map[unroll].end()); + } + enode * e_curr = ctx.get_enode(curr); + curr = e_curr->get_next()->get_owner(); + // curr = get_eqc_next(curr); + } while (curr != unroll); + } + + std::map > concatFreeArgsEqUnrollsMap; + std::set fvUnrollSet; + for (std::map >::iterator concatItor = concatEqUnrollsMap.begin(); + concatItor != concatEqUnrollsMap.end(); concatItor++) { + expr * concat = concatItor->first; + expr * concatArg1 = to_app(concat)->get_arg(0); + expr * concatArg2 = to_app(concat)->get_arg(1); + bool arg1Bounded = false; + bool arg2Bounded = false; + // arg1 + if (variable_set.find(concatArg1) != variable_set.end()) { + if (freeVar_map.find(concatArg1) == freeVar_map.end()) { + arg1Bounded = true; + } else { + fvUnrollSet.insert(concatArg1); + } + } else if (is_concat(to_app(concatArg1))) { + if (concatEqUnrollsMap.find(concatArg1) == concatEqUnrollsMap.end()) { + arg1Bounded = true; + } + } + // arg2 + if (variable_set.find(concatArg2) != variable_set.end()) { + if (freeVar_map.find(concatArg2) == freeVar_map.end()) { + arg2Bounded = true; + } else { + fvUnrollSet.insert(concatArg2); + } + } else if (is_concat(to_app(concatArg2))) { + if (concatEqUnrollsMap.find(concatArg2) == concatEqUnrollsMap.end()) { + arg2Bounded = true; + } + } + if (!arg1Bounded && !arg2Bounded) { + concatFreeArgsEqUnrollsMap[concat].insert( + concatEqUnrollsMap[concat].begin(), + concatEqUnrollsMap[concat].end()); + } + } + for (std::set::iterator vItor = fvUnrollSet.begin(); vItor != fvUnrollSet.end(); vItor++) { + freeVar_map.erase(*vItor); + } + + // Assign free variables + std::set fSimpUnroll; + + constValue = NULL; + + // TODO this would be a great place to print debugging information + + // TODO process_concat_eq_unroll() + /* + for (std::map >::iterator fvIt2 = concatFreeArgsEqUnrollsMap.begin(); + fvIt2 != concatFreeArgsEqUnrollsMap.end(); fvIt2++) { + expr * concat = fvIt2->first; + for (std::set::iterator urItor = fvIt2->second.begin(); urItor != fvIt2->second.end(); urItor++) { + Z3_ast unroll = *urItor; + processConcatEqUnroll(concat, unroll); + } + } + */ + + // -------- + // experimental free variable assignment - begin + // * special handling for variables that are not used in concat + // -------- + bool testAssign = true; + if (!testAssign) { + for (std::map::iterator fvIt = freeVar_map.begin(); fvIt != freeVar_map.end(); fvIt++) { + expr * freeVar = fvIt->first; + /* + std::string vName = std::string(Z3_ast_to_string(ctx, freeVar)); + if (vName.length() >= 9 && vName.substr(0, 9) == "$$_regVar") { + continue; + } + */ + // TODO if this variable represents a regular expression, continue + expr * toAssert = gen_len_val_options_for_free_var(freeVar, NULL, ""); + if (toAssert != NULL) { + assert_axiom(toAssert); + } + } + } else { + process_free_var(freeVar_map); + } + // experimental free variable assignment - end + + // TODO more unroll stuff + /* + for (std::map >::iterator fvIt1 = fv_unrolls_map.begin(); + fvIt1 != fv_unrolls_map.end(); fvIt1++) { + Z3_ast var = fvIt1->first; + fSimpUnroll.clear(); + get_eqc_simpleUnroll(t, var, constValue, fSimpUnroll); + if (fSimpUnroll.size() == 0) { + genAssignUnrollReg(t, fv_unrolls_map[var]); + } else { + Z3_ast toAssert = genAssignUnrollStr2Reg(t, var, fSimpUnroll); + if (toAssert != NULL) { + addAxiom(t, toAssert, __LINE__); + } + } + } + */ + + return FC_CONTINUE; // since by this point we've added axioms +} + +expr * theory_str::gen_len_val_options_for_free_var(expr * freeVar, expr * lenTesterInCbEq, std::string lenTesterValue) { + // TODO + NOT_IMPLEMENTED_YET(); +} + +void theory_str::process_free_var(std::map & freeVar_map) { + // TODO this one first + NOT_IMPLEMENTED_YET(); } void theory_str::init_model(model_generator & mg) { diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 3d0f14ca7..60c2c3a8e 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -153,6 +153,9 @@ namespace smt { void classify_ast_by_type_in_positive_context(std::map & varMap, std::map & concatMap, std::map & unrollMap); + expr * gen_len_val_options_for_free_var(expr * freeVar, expr * lenTesterInCbEq, std::string lenTesterValue); + void process_free_var(std::map & freeVar_map); + expr * get_alias_index_ast(std::map & aliasIndexMap, expr * node); expr * getMostLeftNodeInConcat(expr * node); expr * getMostRightNodeInConcat(expr * node); From e9b31f29954ccc2252a5d122bcbe25b9f1601bc1 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Fri, 6 Nov 2015 14:13:38 -0500 Subject: [PATCH 057/410] temporarily patched in a get_eqc_allUnroll() implementation --- src/smt/theory_str.cpp | 26 +++++++++++++++++++++++++- src/smt/theory_str.h | 4 ++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index cb31aedde..0965e7873 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -3263,7 +3263,7 @@ final_check_status theory_str::final_check_eh() { } // ----------------------------------------------------------- - // variables in freeVar are those not bouned by Concats + // variables in freeVar are those not bounded by Concats // classify variables in freeVarMap: // (1) freeVar = unroll(r1, t1) // (2) vars are not bounded by either concat or unroll @@ -3424,6 +3424,30 @@ void theory_str::process_free_var(std::map & freeVar_map) { NOT_IMPLEMENTED_YET(); } +/* + * Collect all unroll functions + * and constant string in eqc of node n + */ +void theory_str::get_eqc_allUnroll(expr * n, expr * &constStr, std::set & unrollFuncSet) { + constStr = NULL; + unrollFuncSet.clear(); + context & ctx = get_context(); + + expr * curr = n; + do { + if (is_string(to_app(curr))) { + constStr = curr; + } else if (false) /*(td->Unroll == Z3_get_app_decl(ctx, Z3_to_app(ctx, curr)))*/ { // TODO + if (unrollFuncSet.find(curr) == unrollFuncSet.end()) { + unrollFuncSet.insert(curr); + } + } + enode * e_curr = ctx.get_enode(curr); + curr = e_curr->get_next()->get_owner(); + // curr = get_eqc_next(t, curr); + } while (curr != n); +} + void theory_str::init_model(model_generator & mg) { TRACE("t_str", tout << "initializing model" << std::endl; display(tout);); m_factory = alloc(str_value_factory, get_manager(), get_family_id()); diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 60c2c3a8e..86f45aea0 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -160,6 +160,10 @@ namespace smt { expr * getMostLeftNodeInConcat(expr * node); expr * getMostRightNodeInConcat(expr * node); + // strRegex + + void get_eqc_allUnroll(expr * n, expr * &constStr, std::set & unrollFuncSet); + void dump_assignments(); public: theory_str(ast_manager & m); From a9b8707d48fb8b474a6b49ba68d1f7e9fdf7d250 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Mon, 9 Nov 2015 15:14:34 -0500 Subject: [PATCH 058/410] possibly found a way to do get_parents() --- src/smt/theory_str.cpp | 76 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 0965e7873..da5c858b8 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -3420,7 +3420,81 @@ expr * theory_str::gen_len_val_options_for_free_var(expr * freeVar, expr * lenTe } void theory_str::process_free_var(std::map & freeVar_map) { - // TODO this one first + context & ctx = get_context(); + ast_manager & m = get_manager(); + + std::set eqcRepSet; + std::set leafVarSet; + std::map > aloneVars; + + for (std::map::iterator fvIt = freeVar_map.begin(); fvIt != freeVar_map.end(); fvIt++) { + expr * freeVar = fvIt->first; + /* + std::string vName = std::string(Z3_ast_to_string(ctx, freeVar)); + if (vName.length() >= 9 && vName.substr(0, 9) == "$$_regVar") { + continue; + } + */ + // TODO skip all regular expression vars + + // Iterate the EQC of freeVar, its eqc variable should not be in the eqcRepSet. + // If found, have to filter it out + std::set eqVarSet; + get_var_in_eqc(freeVar, eqVarSet); + bool duplicated = false; + expr * dupVar = NULL; + for (std::set::iterator itorEqv = eqVarSet.begin(); itorEqv != eqVarSet.end(); itorEqv++) { + if (eqcRepSet.find(*itorEqv) != eqcRepSet.end()) { + duplicated = true; + dupVar = *itorEqv; + break; + } + } + if (duplicated && dupVar != NULL) { + STRACE("t_str_detail", tout << "Duplicated free variable found:" << mk_ismt2_pp(freeVar, m) + << " = " << mk_ismt2_pp(dupVar, m) << " (SKIP)" << std::endl;); + continue; + } else { + eqcRepSet.insert(freeVar); + } + } + + for (std::set::iterator fvIt = eqcRepSet.begin(); fvIt != eqcRepSet.end(); fvIt++) { + bool standAlone = true; + expr * freeVar = *fvIt; + // has length constraint initially + if (input_var_in_len.find(freeVar) != input_var_in_len.end()) { + standAlone = false; + } + // iterate parents + if (standAlone) { + // I hope this works! + enode * e_freeVar = ctx.get_enode(freeVar); + enode_vector::iterator it = e_freeVar->begin_parents(); + for (; it != e_freeVar->end_parents(); ++it) { + expr * parentAst = (*it)->get_owner(); + if (is_concat(to_app(parentAst))) { + standAlone = false; + break; + } + } + } + + if (standAlone) { + // TODO + // int lenValue = getLenValue(freeVar); + int lenValue = -1; + if (lenValue != -1) { + leafVarSet.insert(freeVar); + } else { + aloneVars[lenValue].insert(freeVar); + } + } else { + leafVarSet.insert(freeVar); + } + } + + // TODO the rest NOT_IMPLEMENTED_YET(); } From 0178872a19c0ad0cec339c2b9ebbe99453aa2c74 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Mon, 9 Nov 2015 15:33:52 -0500 Subject: [PATCH 059/410] completed process_free_var(), still WIP working on gen_len_val_options_for_free_var() --- src/smt/theory_str.cpp | 40 ++++++++++++++++++++++++++++++++++++++-- src/smt/theory_str.h | 3 +++ 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index da5c858b8..a235936d6 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -2385,6 +2385,14 @@ void theory_str::set_up_axioms(expr * ex) { if (is_concat(ap)) { // if ex is a concat, set up concat axioms later m_concat_axiom_todo.push_back(n); + } else if (is_strlen(ap)) { + // if the argument is a variable, + // keep track of this for later, we'll need it during model gen + expr * var = ap->get_arg(0); + app * aVar = to_app(var); + if (aVar->get_num_args() == 0 && !is_string(aVar)) { + input_var_in_len.insert(var); + } } else if (ap->get_num_args() == 0 && !is_string(ap)) { // if ex is a variable, add it to our list of variables TRACE("t_str_detail", tout << "tracking variable" << std::endl;); @@ -3419,6 +3427,20 @@ expr * theory_str::gen_len_val_options_for_free_var(expr * freeVar, expr * lenTe NOT_IMPLEMENTED_YET(); } +void theory_str::get_var_in_eqc(expr * n, std::set & varSet) { + context & ctx = get_context(); + + expr * eqcNode = n; + do { + if (variable_set.find(eqcNode) != variable_set.end()) { + varSet.insert(eqcNode); + } + enode * e_eqc = ctx.get_enode(eqcNode); + eqcNode = e_eqc->get_next()->get_owner(); + // eqcNode = Z3_theory_get_eqc_next(t, eqcNode); + } while (eqcNode != n); +} + void theory_str::process_free_var(std::map & freeVar_map) { context & ctx = get_context(); ast_manager & m = get_manager(); @@ -3494,8 +3516,22 @@ void theory_str::process_free_var(std::map & freeVar_map) { } } - // TODO the rest - NOT_IMPLEMENTED_YET(); + // TODO here's a great place for debugging info + + for(std::set::iterator itor1 = leafVarSet.begin(); + itor1 != leafVarSet.end(); ++itor1) { + expr * toAssert = gen_len_val_options_for_free_var(*itor1, NULL, ""); + assert_axiom(toAssert); + } + + for (std::map >::iterator mItor = aloneVars.begin(); + mItor != aloneVars.end(); ++mItor) { + std::set::iterator itor2 = mItor->second.begin(); + for(; itor2 != mItor->second.end(); ++itor2) { + expr * toAssert = gen_len_val_options_for_free_var(*itor2, NULL, ""); + assert_axiom(toAssert); + } + } } /* diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 86f45aea0..a4a89f947 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -82,6 +82,8 @@ namespace smt { std::set variable_set; std::set internal_variable_set; + + std::set input_var_in_len; protected: void assert_axiom(expr * e); void assert_implication(expr * premise, expr * conclusion); @@ -159,6 +161,7 @@ namespace smt { expr * get_alias_index_ast(std::map & aliasIndexMap, expr * node); expr * getMostLeftNodeInConcat(expr * node); expr * getMostRightNodeInConcat(expr * node); + void get_var_in_eqc(expr * n, std::set & varSet); // strRegex From 6374d6316017ff772b2930c104215a39142917bc Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Mon, 9 Nov 2015 16:11:00 -0500 Subject: [PATCH 060/410] gen_len_val_options_for_free_var() WIP --- src/smt/theory_str.cpp | 224 ++++++++++++++++++++++++++++++++++++++--- src/smt/theory_str.h | 8 ++ 2 files changed, 219 insertions(+), 13 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index a235936d6..c485c40ff 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -221,6 +221,24 @@ app * theory_str::mk_int(int n) { return m_autil.mk_numeral(rational(n), true); } +expr * theory_str::mk_internal_lenTest_var(expr * node, int lTries) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + std::stringstream ss; + ss << "$$_len_" << mk_ismt2_pp(node, m) << "_" << lTries; + std::string name = ss.str(); + return mk_str_var(name); + + /* + Z3_context ctx = Z3_theory_get_context(t); + std::stringstream ss; + ss << "$$_len_" << Z3_ast_to_string(ctx, node) << "_" << lTries; + std::string name = ss.str(); + return my_mk_str_var(t, name.c_str()); + */ +} + app * theory_str::mk_internal_xor_var() { context & ctx = get_context(); ast_manager & m = get_manager(); @@ -239,17 +257,26 @@ app * theory_str::mk_internal_xor_var() { return a; } -/* - Z3_context ctx = Z3_theory_get_context(t); - PATheoryData * td = (PATheoryData *) Z3_theory_get_ext_data(t); - std::stringstream ss; - ss << tmpStringVarCount; - tmpStringVarCount++; - std::string name = "$$_str" + ss.str(); - Z3_ast varAst = mk_var(ctx, name.c_str(), td->String); - nonEmptyStrVarAxiom(t, varAst, __LINE__); - return varAst; -*/ +app * theory_str::mk_str_var(std::string name) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + sort * string_sort = m.mk_sort(m_strutil.get_family_id(), STRING_SORT); + char * new_buffer = alloc_svect(char, name.length() + 1); + strcpy(new_buffer, name.c_str()); + symbol sym(new_buffer); + + app * a = m.mk_const(m.mk_const_decl(sym, string_sort)); + + // I have a hunch that this may not get internalized for free... + SASSERT(ctx.get_enode(a) != NULL); + m_basicstr_axiom_todo.push_back(ctx.get_enode(a)); + + variable_set.insert(a); + internal_variable_set.insert(a); + + return a; +} app * theory_str::mk_nonempty_str_var() { context & ctx = get_context(); @@ -3422,9 +3449,180 @@ final_check_status theory_str::final_check_eh() { return FC_CONTINUE; // since by this point we've added axioms } +inline std::string int_to_string(int i) { + std::stringstream ss; + ss << i; + return ss.str(); +} + +expr * theory_str::gen_len_test_options(expr * freeVar, expr * indicator, int tries) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + expr * freeVarLen = mk_strlen(freeVar); + + ptr_vector orList; + ptr_vector andList; + + int distance = 3; + int l = (tries - 1) * distance; + int h = tries * distance; + + for (int i = l; i < h; ++i) { + orList.push_back(m.mk_eq(indicator, m_strutil.mk_string(int_to_string(i).c_str()))); + andList.push_back(m.mk_eq(orList[orList.size() - 1], m.mk_eq(freeVarLen, mk_int(i)))); + } + + orList.push_back(m.mk_eq(indicator, m_strutil.mk_string("more"))); + andList.push_back(m.mk_eq(orList[orList.size() - 1], m_autil.mk_ge(freeVarLen, mk_int(h)))); + + expr ** or_items = alloc_svect(expr*, orList.size()); + expr ** and_items = alloc_svect(expr*, andList.size() + 1); + + for (int i = 0; i < orList.size(); ++i) { + or_items[i] = orList[i]; + } + + and_items[0] = m.mk_or(orList.size(), or_items); + for(int i = 0; i < andList.size(); ++i) { + and_items[i+1] = andList[i]; + } + expr * lenTestAssert = m.mk_and(andList.size() + 1, and_items); + + expr * assertL = NULL; + int testerCount = tries - 1; + if (testerCount > 0) { + expr ** and_items_LHS = alloc_svect(expr*, testerCount); + expr * moreAst = m_strutil.mk_string("more"); + for (int i = 0; i < testerCount; ++i) { + and_items_LHS[i] = m.mk_eq(fvar_lenTester_map[freeVar][i], moreAst); + } + if (testerCount == 1) { + assertL = and_items_LHS[0]; + } else { + assertL = m.mk_and(testerCount, and_items_LHS); + } + } + + if (assertL != NULL) { + // return the axiom (assertL -> lenTestAssert) + // would like to use mk_implies() here but... + expr_ref lenTestAssert(m.mk_or(m.mk_not(assertL), lenTestAssert), m); + } + + return lenTestAssert; + +} + +// ----------------------------------------------------------------------------------------------------- +// True branch will be taken in final_check: +// - When we discover a variable is "free" for the first time +// lenTesterInCbEq = NULL +// lenTesterValue = "" +// False branch will be taken when invoked by new_eq_eh(). +// - After we set up length tester for a "free" var in final_check, +// when the tester is assigned to some value (e.g. "more" or "4"), +// lenTesterInCbEq != NULL, and its value will be passed by lenTesterValue +// The difference is that in new_eq_eh(), lenTesterInCbEq and its value have NOT been put into a same eqc +// ----------------------------------------------------------------------------------------------------- expr * theory_str::gen_len_val_options_for_free_var(expr * freeVar, expr * lenTesterInCbEq, std::string lenTesterValue) { - // TODO - NOT_IMPLEMENTED_YET(); + + context & ctx = get_context(); + ast_manager & m = get_manager(); + + STRACE("t_str_detail", tout << "gen for free var " << mk_ismt2_pp(freeVar, m) << std::endl;); + // no length assertions for this free variable have ever been added. + if (fvar_len_count_map.find(freeVar) == fvar_len_count_map.end()) { + fvar_len_count_map[freeVar] = 1; + unsigned int testNum = fvar_len_count_map[freeVar]; + expr * indicator = mk_internal_lenTest_var(freeVar, testNum); + fvar_lenTester_map[freeVar].push_back(indicator); + lenTester_fvar_map[indicator] = freeVar; + + expr * lenTestAssert = gen_len_test_options(freeVar, indicator, testNum); + return lenTestAssert; + } else { + /* + Z3_ast effectiveLenInd = NULL; + std::string effectiveLenIndiStr = ""; + int lenTesterCount = (int) fvarLenTesterMap[freeVar].size(); + + int i = 0; + for (; i < lenTesterCount; i++) { + Z3_ast len_indicator_pre = fvarLenTesterMap[freeVar][i]; + bool indicatorHasEqcValue = false; + Z3_ast len_indicator_value = get_eqc_value(t, len_indicator_pre, indicatorHasEqcValue); +#ifdef DEBUGLOG + __debugPrint(logFile, "* length indicator "); + printZ3Node(t, len_indicator_pre); + __debugPrint(logFile, " = "); + printZ3Node(t, len_indicator_value); + __debugPrint(logFile, "\n"); +#endif + if (indicatorHasEqcValue) { + std::string len_pIndiStr = getConstStrValue(t, len_indicator_value); + if (len_pIndiStr != "more") { + effectiveLenInd = len_indicator_pre; + effectiveLenIndiStr = len_pIndiStr; + break; + } + } else { + if (lenTesterInCbEq != len_indicator_pre) { +#ifdef DEBUGLOG + __debugPrint(logFile, "\n>> *Warning*: length indicator: "); + printZ3Node(t, len_indicator_pre); + __debugPrint(logFile, " doesn't have an EQC value. i = %d, lenTesterCount = %d\n", i , lenTesterCount); +#endif + if (i > 0) { + effectiveLenInd = fvarLenTesterMap[freeVar][i - 1]; + if (effectiveLenInd == lenTesterInCbEq) { + effectiveLenIndiStr = lenTesterValue; + } else { + bool effectiveHasEqcValue = false; + effectiveLenIndiStr = getConstStrValue(t, get_eqc_value(t, effectiveLenInd, effectiveHasEqcValue)); + } + } + break; + } + // lenTesterInCbEq == len_indicator_pre + else { + if (lenTesterValue != "more") { + effectiveLenInd = len_indicator_pre; + effectiveLenIndiStr = lenTesterValue; + break; + } + } + } + } + + if (effectiveLenIndiStr == "more" || effectiveLenIndiStr == "") { + Z3_ast indicator = NULL; + unsigned int testNum = 0; + + __debugPrint(logFile, "\n>> effectiveLenIndiStr = %s, i = %d, lenTesterCount = %d\n", effectiveLenIndiStr.c_str(), i, lenTesterCount); + + if (i == lenTesterCount) { + fvarLenCountMap[freeVar] = fvarLenCountMap[freeVar] + 1; + testNum = fvarLenCountMap[freeVar]; + indicator = my_mk_internal_lenTest_var(t, freeVar, testNum); + fvarLenTesterMap[freeVar].push_back(indicator); + lenTesterFvarMap[indicator] = freeVar; + } else { + indicator = fvarLenTesterMap[freeVar][i]; + testNum = i + 1; + } + Z3_ast lenTestAssert = genLenTestOptions(t, freeVar, indicator, testNum); + return lenTestAssert; + } else { + // length is fixed + Z3_ast valueAssert = genFreeVarOptions(t, freeVar, effectiveLenInd, effectiveLenIndiStr, NULL, ""); + return valueAssert; + } + */ + + // TODO + NOT_IMPLEMENTED_YET(); + } // fVarLenCountMap.find(...) } void theory_str::get_var_in_eqc(expr * n, std::set & varSet) { diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index a4a89f947..b7d93ef54 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -84,6 +84,11 @@ namespace smt { std::set internal_variable_set; std::set input_var_in_len; + + std::map fvar_len_count_map; + std::map > fvar_lenTester_map; + std::map lenTester_fvar_map; + protected: void assert_axiom(expr * e); void assert_implication(expr * premise, expr * conclusion); @@ -99,6 +104,7 @@ namespace smt { void add_cut_info_merge(expr * destNode, int slevel, expr * srcNode); bool has_self_cut(expr * n1, expr * n2); + app * mk_str_var(std::string name); app * mk_nonempty_str_var(); app * mk_internal_xor_var(); @@ -155,8 +161,10 @@ namespace smt { void classify_ast_by_type_in_positive_context(std::map & varMap, std::map & concatMap, std::map & unrollMap); + expr * mk_internal_lenTest_var(expr * node, int lTries); expr * gen_len_val_options_for_free_var(expr * freeVar, expr * lenTesterInCbEq, std::string lenTesterValue); void process_free_var(std::map & freeVar_map); + expr * gen_len_test_options(expr * freeVar, expr * indicator, int tries); expr * get_alias_index_ast(std::map & aliasIndexMap, expr * node); expr * getMostLeftNodeInConcat(expr * node); From 3a404c248d49a700ff50ed09db42859f4c660763 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 10 Nov 2015 12:40:01 -0500 Subject: [PATCH 061/410] gen_free_var_options() WIP --- src/smt/theory_str.cpp | 124 +++++++++++++++++++++++++++++------------ src/smt/theory_str.h | 2 + 2 files changed, 90 insertions(+), 36 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index c485c40ff..2101fe7a5 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -3455,6 +3455,65 @@ inline std::string int_to_string(int i) { return ss.str(); } +expr * theory_str::gen_free_var_options(expr * freeVar, expr * len_indicator, + std::string len_valueStr, expr * valTesterInCbEq, std::string valTesterValueStr) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + int len = atoi(len_valueStr.c_str()); + + if (fvar_valueTester_map[freeVar].find(len) == fvar_valueTester_map[freeVar].end()) { + int tries = 0; + expr * val_indicator = mk_internal_valTest_var(freeVar, len, tries); + valueTester_fvar_map[val_indicator] = freeVar; + fvar_valueTester_map[freeVar][len].push_back(std::make_pair(sLevel, val_indicator)); + print_value_tester_list(fvar_valueTester_map[freeVar][len]); + return gen_val_options(freeVar, len_indicator, val_indicator, len_valueStr, tries); + } else { + // go through all previous value testers + // If some doesn't have an eqc value, add its assertion again. + int testerTotal = fvar_valueTester_map[freeVar][len].size(); + int i = 0; + for (; i < testerTotal; i++) { + expr * aTester = fvarValueTesterMap[freeVar][len][i].second; + + if (aTester == valTesterInCbEq) { + break; + } + + bool anEqcHasValue = false; + // Z3_ast anEqc = get_eqc_value(t, aTester, anEqcHasValue); + get_eqc_value(aTester, anEqcHasValue); + if (!anEqcHasValue) { + STRACE("t_str_detail", "value tester " << mk_ismt2_pp(aTester, m) + << "doesn't have an equivalence class value." << std::endl;); + + expr_ref makeupAssert(gen_val_options(freeVar, len_indicator, aTester, len_valueStr, i), m); + + STRACE("t_str_detail", "var: " << mk_ismt2_pp(freeVar, m) << std::endl + << mk_ismt2_pp(makeupAssert, m) << std::endl;); + assert_axiom(makeupAssert); + } + } + + if (valTesterValueStr == "more") { + expr * valTester = NULL; + if (i + 1 < testerTotal) { + valTester = fvar_valueTester_map[freeVar][len][i + 1].second; + } else { + valTester = mk_internal_valTest_var(freeVar, len, i + 1); + valueTester_fvar_map[valTester] = freeVar; + fvar_valueTester_map[freeVar][len].push_back(std::make_pair(sLevel, valTester)); + print_value_tester_list(fvar_valueTester_map[freeVar][len]); + } + expr_ref nextAssert(gen_val_options(freeVar, len_indicator, valTester, len_valueStr, i + 1), m); + return nextAssert; + } + + return NULL; + } +} + expr * theory_str::gen_len_test_options(expr * freeVar, expr * indicator, int tries) { context & ctx = get_context(); ast_manager & m = get_manager(); @@ -3542,25 +3601,22 @@ expr * theory_str::gen_len_val_options_for_free_var(expr * freeVar, expr * lenTe expr * lenTestAssert = gen_len_test_options(freeVar, indicator, testNum); return lenTestAssert; } else { - /* - Z3_ast effectiveLenInd = NULL; + + expr * effectiveLenInd = NULL; std::string effectiveLenIndiStr = ""; - int lenTesterCount = (int) fvarLenTesterMap[freeVar].size(); + int lenTesterCount = (int) fvar_lenTester_map[freeVar].size(); int i = 0; - for (; i < lenTesterCount; i++) { - Z3_ast len_indicator_pre = fvarLenTesterMap[freeVar][i]; + for (; i < lenTesterCount; ++i) { + expr * len_indicator_pre = fvar_lenTester_map[freeVar][i]; bool indicatorHasEqcValue = false; - Z3_ast len_indicator_value = get_eqc_value(t, len_indicator_pre, indicatorHasEqcValue); -#ifdef DEBUGLOG - __debugPrint(logFile, "* length indicator "); - printZ3Node(t, len_indicator_pre); - __debugPrint(logFile, " = "); - printZ3Node(t, len_indicator_value); - __debugPrint(logFile, "\n"); -#endif + expr * len_indicator_value = get_eqc_value(len_indicator_pre, indicatorHasEqcValue); + STRACE("t_str_detail", tout << "length indicator " << mk_ismt2_pp(len_indicator_pre, m) << + " = " << mk_ismt2_pp(len_indicator_value, m) << std::endl;); if (indicatorHasEqcValue) { - std::string len_pIndiStr = getConstStrValue(t, len_indicator_value); + const char * val = 0; + m_strutil.is_string(len_indicator_value, & val); + std::string len_pIndiStr(val); if (len_pIndiStr != "more") { effectiveLenInd = len_indicator_pre; effectiveLenIndiStr = len_pIndiStr; @@ -3568,18 +3624,18 @@ expr * theory_str::gen_len_val_options_for_free_var(expr * freeVar, expr * lenTe } } else { if (lenTesterInCbEq != len_indicator_pre) { -#ifdef DEBUGLOG - __debugPrint(logFile, "\n>> *Warning*: length indicator: "); - printZ3Node(t, len_indicator_pre); - __debugPrint(logFile, " doesn't have an EQC value. i = %d, lenTesterCount = %d\n", i , lenTesterCount); -#endif + STRACE("t_str", tout << "WARNING: length indicator " << mk_ismt2_pp(len_indicator_pre, m) + << " does not have an equivalence class value." + << " i = " << i << ", lenTesterCount = " << lenTesterCount << std::endl;); if (i > 0) { effectiveLenInd = fvarLenTesterMap[freeVar][i - 1]; if (effectiveLenInd == lenTesterInCbEq) { effectiveLenIndiStr = lenTesterValue; } else { bool effectiveHasEqcValue = false; - effectiveLenIndiStr = getConstStrValue(t, get_eqc_value(t, effectiveLenInd, effectiveHasEqcValue)); + const char * val = 0; + m_strutil.is_string(get_eqc_value(effectiveLenInd, effectiveHasEqcValue), & val); + effectiveLenIndiStr = val; } } break; @@ -3592,36 +3648,32 @@ expr * theory_str::gen_len_val_options_for_free_var(expr * freeVar, expr * lenTe break; } } - } - } - + } // !indicatorHasEqcValue + } // for (i : [0..lenTesterCount-1]) if (effectiveLenIndiStr == "more" || effectiveLenIndiStr == "") { - Z3_ast indicator = NULL; + expr * indicator = NULL; unsigned int testNum = 0; - __debugPrint(logFile, "\n>> effectiveLenIndiStr = %s, i = %d, lenTesterCount = %d\n", effectiveLenIndiStr.c_str(), i, lenTesterCount); + STRACE("t_str", tout << "effectiveLenIndiStr = " << effectiveLenIndiStr + << ", i = " << i << ", lenTesterCount = " << lenTesterCount << std::endl;); if (i == lenTesterCount) { - fvarLenCountMap[freeVar] = fvarLenCountMap[freeVar] + 1; - testNum = fvarLenCountMap[freeVar]; - indicator = my_mk_internal_lenTest_var(t, freeVar, testNum); - fvarLenTesterMap[freeVar].push_back(indicator); - lenTesterFvarMap[indicator] = freeVar; + fvar_len_count_map[freeVar] = fvar_len_count_map[freeVar] + 1; + testNum = fvar_len_count_map[freeVar]; + indicator = mk_internal_lenTest_var(freeVar, testNum); + fvar_lenTester_map[freeVar].push_back(indicator); + lenTester_fvar_map[indicator] = freeVar; } else { indicator = fvarLenTesterMap[freeVar][i]; testNum = i + 1; } - Z3_ast lenTestAssert = genLenTestOptions(t, freeVar, indicator, testNum); + expr_ref lenTestAssert(gen_len_test_options(freeVar, indicator, testNum), m); return lenTestAssert; } else { // length is fixed - Z3_ast valueAssert = genFreeVarOptions(t, freeVar, effectiveLenInd, effectiveLenIndiStr, NULL, ""); + expr_ref valueAssert(gen_free_var_options(freeVar, effectiveLenInd, effectiveLenIndiStr, NULL, ""), m); return valueAssert; } - */ - - // TODO - NOT_IMPLEMENTED_YET(); } // fVarLenCountMap.find(...) } diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index b7d93ef54..bd26f2564 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -165,6 +165,8 @@ namespace smt { expr * gen_len_val_options_for_free_var(expr * freeVar, expr * lenTesterInCbEq, std::string lenTesterValue); void process_free_var(std::map & freeVar_map); expr * gen_len_test_options(expr * freeVar, expr * indicator, int tries); + expr * gen_free_var_options(expr * freeVar, expr * len_indicator, + std::string len_valueStr, expr * valTesterInCbEq, std::string valTesterValueStr); expr * get_alias_index_ast(std::map & aliasIndexMap, expr * node); expr * getMostLeftNodeInConcat(expr * node); From 8b538f584031907a2802551d36bcbfaf256b9c4a Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 11 Nov 2015 15:34:11 -0500 Subject: [PATCH 062/410] started gen_val_options() WIP --- src/smt/theory_str.cpp | 146 ++++++++++++++++++++++++++++++++++++++++- src/smt/theory_str.h | 7 ++ 2 files changed, 152 insertions(+), 1 deletion(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 2101fe7a5..f86f921de 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -239,6 +239,23 @@ expr * theory_str::mk_internal_lenTest_var(expr * node, int lTries) { */ } +expr * theory_str::mk_internal_valTest_var(expr * node, int len, int vTries) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + std::stringstream ss; + ss << "$$_val_" << mk_ismt2_pp(node, m) << "_" << len << "_" << vTries; + std::string name = ss.str(); + return mk_str_var(name); + + /* + Z3_context ctx = Z3_theory_get_context(t); + std::stringstream ss; + ss << "$$_val_" << Z3_ast_to_string(ctx, node) << "_" << len << "_" << vTries; + std::string name = ss.str(); + return my_mk_str_var(t, name.c_str()); + */ +} + app * theory_str::mk_internal_xor_var() { context & ctx = get_context(); ast_manager & m = get_manager(); @@ -3455,6 +3472,133 @@ inline std::string int_to_string(int i) { return ss.str(); } +inline std::string longlong_to_string(long long i) { + std::stringstream ss; + ss << i; + return ss.str(); +} + +void theory_str::print_value_tester_list(std::vector > & testerList) { + ast_manager & m = get_manager(); + STRACE("t_str_detail", + int ss = testerList.size(); + tout << "valueTesterList = {"; + for (int i = 0; i < ss; ++i) { + if (i % 4 == 0) { + tout << std::endl; + } + tout << "(" << testerList[i].first << ", "; + tout << mk_ismt2_pp(testerList[i].second, m); + tout << "), "; + } + tout << std::endl << "}" << std::endl; + ); +} + +expr * theory_str::gen_val_options(expr * freeVar, expr * len_indicator, expr * val_indicator, + std::string lenStr, int tries) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + int distance = 32; + + // ---------------------------------------------------------------------------------------- + // generate value options encoding + // encoding is a vector of size (len + 1) + // e.g, len = 2, + // encoding {1, 2, 0} means the value option is "charSet[2]"."charSet[1]" + // the last item in the encoding indicates whether the whole space is covered + // for example, if the charSet = {a, b}. All valid encodings are + // {0, 0, 0}, {1, 0, 0}, {0, 1, 0}, {1, 1, 0} + // if add 1 to the last one, we get + // {0, 0, 1} + // the last item "1" shows this is not a valid encoding, and we have covered all space + // ---------------------------------------------------------------------------------------- + int len = atoi(lenStr.c_str()); + bool coverAll = false; + std::vector > options; + std::vector base; + + if (tries == 0) { + base = std::vector(len + 1, 0); + coverAll = false; + } else { + expr * lastestValIndi = fvar_valueTester_map[freeVar][len][tries - 1].second; + STRACE("t_str_detail", tout << "last value tester = " << mk_ismt2_pp(lastestValIndi, m) << std::endl;); + coverAll = get_next_val_encode(valRangeMap[lastestValIndi], base); + } + + long long l = (tries) * distance; + long long h = l; + for (int i = 0; i < distance; i++) { + if (coverAll) + break; + options.push_back(base); + h++; + coverAll = getNextValEncode(options[options.size() - 1], base); + } + valRangeMap[val_indicator] = options[options.size() - 1]; + + STRACE("t_str_detail", tout << "value tester encoding " << printVectorInt(valRangeMap[val_indicator]) << std::endl;); + + // ---------------------------------------------------------------------------------------- + + std::vector orList; + std::vector andList; + + for (long long i = l; i < h; i++) { + orList.push_back(m.mk_eq(val_indicator, m_strutil.mk_string(longlong_to_string(i).c_str()) )); + std::string aStr = gen_val_string(len, options[i - l]); + expr_ref strAst(m_strutil.mk_string(aStr), m); + andList.push_back(m.mk_eq(orList[orList.size() - 1], m.mk_eq(freeVar, strAst))); + } + if (!coverAll) { + orList.push_back(m.mk_eq(val_indicator, m_strutil.mk_string("more"))); + } + + Z3_ast * or_items = new Z3_ast[orList.size()]; + Z3_ast * and_items = new Z3_ast[andList.size() + 1]; + for (int i = 0; i < (int) orList.size(); i++) { + or_items[i] = orList[i]; + } + if (orList.size() > 1) + and_items[0] = Z3_mk_or(ctx, orList.size(), or_items); + else + and_items[0] = or_items[0]; + + for (int i = 0; i < (int) andList.size(); i++) { + and_items[i + 1] = andList[i]; + } + Z3_ast valTestAssert = Z3_mk_and(ctx, andList.size() + 1, and_items); + delete[] or_items; + delete[] and_items; + + // --------------------------------------- + // IF the new value tester is $$_val_x_16_i + // Should add ($$_len_x_j = 16) /\ ($$_val_x_16_i = "more") + // --------------------------------------- + andList.clear(); + andList.push_back(Z3_mk_eq(ctx, len_indicator, my_mk_str_value(t, lenStr.c_str()))); + for (int i = 0; i < tries; i++) { + Z3_ast vTester = fvarValueTesterMap[freeVar][len][i].second; + if (vTester != val_indicator) + andList.push_back(Z3_mk_eq(ctx, vTester, my_mk_str_value(t, "more"))); + } + Z3_ast assertL = NULL; + if (andList.size() == 1) { + assertL = andList[0]; + } else { + Z3_ast * and_items = new Z3_ast[andList.size()]; + for (int i = 0; i < (int) andList.size(); i++) { + and_items[i] = andList[i]; + } + assertL = Z3_mk_and(ctx, andList.size(), and_items); + } + + valTestAssert = Z3_mk_implies(ctx, assertL, valTestAssert); + return valTestAssert; +} + expr * theory_str::gen_free_var_options(expr * freeVar, expr * len_indicator, std::string len_valueStr, expr * valTesterInCbEq, std::string valTesterValueStr) { context & ctx = get_context(); @@ -3475,7 +3619,7 @@ expr * theory_str::gen_free_var_options(expr * freeVar, expr * len_indicator, int testerTotal = fvar_valueTester_map[freeVar][len].size(); int i = 0; for (; i < testerTotal; i++) { - expr * aTester = fvarValueTesterMap[freeVar][len][i].second; + expr * aTester = fvar_valueTester_map[freeVar][len][i].second; if (aTester == valTesterInCbEq) { break; diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index bd26f2564..c9432921a 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -89,6 +89,9 @@ namespace smt { std::map > fvar_lenTester_map; std::map lenTester_fvar_map; + std::map > > > fvar_valueTester_map; + std::map valueTester_fvar_map; + protected: void assert_axiom(expr * e); void assert_implication(expr * premise, expr * conclusion); @@ -107,6 +110,7 @@ namespace smt { app * mk_str_var(std::string name); app * mk_nonempty_str_var(); app * mk_internal_xor_var(); + expr * mk_internal_valTest_var(expr * node, int len, int vTries); bool is_concat(app const * a) const { return a->is_app_of(get_id(), OP_STRCAT); } bool is_concat(enode const * n) const { return is_concat(n->get_owner()); } @@ -167,6 +171,9 @@ namespace smt { expr * gen_len_test_options(expr * freeVar, expr * indicator, int tries); expr * gen_free_var_options(expr * freeVar, expr * len_indicator, std::string len_valueStr, expr * valTesterInCbEq, std::string valTesterValueStr); + expr * gen_val_options(expr * freeVar, expr * len_indicator, expr * val_indicator, + std::string lenStr, int tries); + void print_value_tester_list(std::vector > & testerList); expr * get_alias_index_ast(std::map & aliasIndexMap, expr * node); expr * getMostLeftNodeInConcat(expr * node); From 9beeb09acf44b8a5d0dc442cf27a1132205f33e9 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sun, 15 Nov 2015 15:18:14 -0500 Subject: [PATCH 063/410] model gen possibly done, but I doubt it works so WIP --- src/smt/theory_str.cpp | 76 ++++++++++++++++++++++++++++++++---------- src/smt/theory_str.h | 6 ++++ 2 files changed, 65 insertions(+), 17 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index f86f921de..1961a3e36 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -3495,6 +3495,48 @@ void theory_str::print_value_tester_list(std::vector > & t ); } +std::string theory_str::gen_val_string(int len, std::vector & encoding) { + SASSERT(charSetSize > 0); + + std::string re = std::string(len, charSet[0]); + for (int i = 0; i < (int) encoding.size() - 1; i++) { + int idx = encoding[i]; + re[len - 1 - i] = charSet[idx]; + } + return re; +} + +/* + * The return value indicates whether we covered the search space. + * - If the next encoding is valid, return false + * - Otherwise, return true + */ +bool theory_str::get_next_val_encode(std::vector & base, std::vector & next) { + int s = 0; + int carry = 0; + next.clear(); + + for (int i = 0; i < (int) base.size(); i++) { + if (i == 0) { + s = base[i] + 1; + carry = s / charSetSize; + s = s % charSetSize; + next.push_back(s); + } else { + s = base[i] + carry; + carry = s / charSetSize; + s = s % charSetSize; + next.push_back(s); + } + } + if (next[next.size() - 1] > 0) { + next.clear(); + return true; + } else { + return false; + } +} + expr * theory_str::gen_val_options(expr * freeVar, expr * len_indicator, expr * val_indicator, std::string lenStr, int tries) { context & ctx = get_context(); @@ -3525,7 +3567,7 @@ expr * theory_str::gen_val_options(expr * freeVar, expr * len_indicator, expr * } else { expr * lastestValIndi = fvar_valueTester_map[freeVar][len][tries - 1].second; STRACE("t_str_detail", tout << "last value tester = " << mk_ismt2_pp(lastestValIndi, m) << std::endl;); - coverAll = get_next_val_encode(valRangeMap[lastestValIndi], base); + coverAll = get_next_val_encode(val_range_map[lastestValIndi], base); } long long l = (tries) * distance; @@ -3535,9 +3577,9 @@ expr * theory_str::gen_val_options(expr * freeVar, expr * len_indicator, expr * break; options.push_back(base); h++; - coverAll = getNextValEncode(options[options.size() - 1], base); + coverAll = get_next_val_encode(options[options.size() - 1], base); } - valRangeMap[val_indicator] = options[options.size() - 1]; + val_range_map[val_indicator] = options[options.size() - 1]; STRACE("t_str_detail", tout << "value tester encoding " << printVectorInt(valRangeMap[val_indicator]) << std::endl;); @@ -3556,46 +3598,46 @@ expr * theory_str::gen_val_options(expr * freeVar, expr * len_indicator, expr * orList.push_back(m.mk_eq(val_indicator, m_strutil.mk_string("more"))); } - Z3_ast * or_items = new Z3_ast[orList.size()]; - Z3_ast * and_items = new Z3_ast[andList.size() + 1]; + expr ** or_items = alloc_svect(expr*, orList.size()); + expr ** and_items = alloc_svect(expr*, andList.size() + 1); + for (int i = 0; i < (int) orList.size(); i++) { or_items[i] = orList[i]; } if (orList.size() > 1) - and_items[0] = Z3_mk_or(ctx, orList.size(), or_items); + and_items[0] = m.mk_or(orList.size(), or_items); else and_items[0] = or_items[0]; for (int i = 0; i < (int) andList.size(); i++) { and_items[i + 1] = andList[i]; } - Z3_ast valTestAssert = Z3_mk_and(ctx, andList.size() + 1, and_items); - delete[] or_items; - delete[] and_items; + expr * valTestAssert = m.mk_and(andList.size() + 1, and_items); // --------------------------------------- - // IF the new value tester is $$_val_x_16_i + // If the new value tester is $$_val_x_16_i // Should add ($$_len_x_j = 16) /\ ($$_val_x_16_i = "more") // --------------------------------------- andList.clear(); - andList.push_back(Z3_mk_eq(ctx, len_indicator, my_mk_str_value(t, lenStr.c_str()))); + andList.push_back(m.mk_eq(len_indicator, m_strutil.mk_string(lenStr.c_str()))); for (int i = 0; i < tries; i++) { - Z3_ast vTester = fvarValueTesterMap[freeVar][len][i].second; + expr * vTester = fvar_valueTester_map[freeVar][len][i].second; if (vTester != val_indicator) - andList.push_back(Z3_mk_eq(ctx, vTester, my_mk_str_value(t, "more"))); + andList.push_back(m.mk_eq(vTester, m_strutil.mk_string("more"))); } - Z3_ast assertL = NULL; + expr * assertL = NULL; if (andList.size() == 1) { assertL = andList[0]; } else { - Z3_ast * and_items = new Z3_ast[andList.size()]; + expr ** and_items = alloc_svect(expr*, andList.size()); for (int i = 0; i < (int) andList.size(); i++) { and_items[i] = andList[i]; } - assertL = Z3_mk_and(ctx, andList.size(), and_items); + assertL = m.mk_and(andList.size(), and_items); } - valTestAssert = Z3_mk_implies(ctx, assertL, valTestAssert); + // (assertL => valTestAssert) <=> (!assertL OR valTestAssert) + valTestAssert = m.mk_or(m.mk_not(assertL), valTestAssert); return valTestAssert; } diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index c9432921a..b7a63edb3 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -92,6 +92,10 @@ namespace smt { std::map > > > fvar_valueTester_map; std::map valueTester_fvar_map; + std::map > val_range_map; + + int charSetSize = 0; + protected: void assert_axiom(expr * e); void assert_implication(expr * premise, expr * conclusion); @@ -174,6 +178,8 @@ namespace smt { expr * gen_val_options(expr * freeVar, expr * len_indicator, expr * val_indicator, std::string lenStr, int tries); void print_value_tester_list(std::vector > & testerList); + bool get_next_val_encode(std::vector & base, std::vector & next); + std::string gen_val_string(int len, std::vector & encoding); expr * get_alias_index_ast(std::map & aliasIndexMap, expr * node); expr * getMostLeftNodeInConcat(expr * node); From b34fc06fe95645db09b8b3eb1778c9607e0c1e94 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Fri, 20 Nov 2015 12:24:23 -0500 Subject: [PATCH 064/410] fix all compilation errors, now to test it --- src/smt/theory_str.cpp | 71 +++++++++++++++++++++--------------------- src/smt/theory_str.h | 13 ++++---- 2 files changed, 42 insertions(+), 42 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 1961a3e36..068745d94 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -32,7 +32,9 @@ theory_str::theory_str(ast_manager & m): tmpStringVarCount(0), tmpXorVarCount(0), avoidLoopCut(true), - loopDetected(false) + loopDetected(false), + char_set(NULL), + charSetSize(0) { } @@ -222,7 +224,6 @@ app * theory_str::mk_int(int n) { } expr * theory_str::mk_internal_lenTest_var(expr * node, int lTries) { - context & ctx = get_context(); ast_manager & m = get_manager(); std::stringstream ss; @@ -240,7 +241,6 @@ expr * theory_str::mk_internal_lenTest_var(expr * node, int lTries) { } expr * theory_str::mk_internal_valTest_var(expr * node, int len, int vTries) { - context & ctx = get_context(); ast_manager & m = get_manager(); std::stringstream ss; ss << "$$_val_" << mk_ismt2_pp(node, m) << "_" << len << "_" << vTries; @@ -257,7 +257,6 @@ expr * theory_str::mk_internal_valTest_var(expr * node, int len, int vTries) { } app * theory_str::mk_internal_xor_var() { - context & ctx = get_context(); ast_manager & m = get_manager(); std::stringstream ss; ss << tmpXorVarCount; @@ -296,7 +295,6 @@ app * theory_str::mk_str_var(std::string name) { } app * theory_str::mk_nonempty_str_var() { - context & ctx = get_context(); ast_manager & m = get_manager(); std::stringstream ss; ss << tmpStringVarCount; @@ -435,12 +433,10 @@ void theory_str::instantiate_concat_axiom(enode * cat) { SASSERT(is_concat(cat)); app * a_cat = cat->get_owner(); - context & ctx = get_context(); ast_manager & m = get_manager(); // build LHS expr_ref len_xy(m); - // TODO re-use ASTs for length subexpressions, like in old Z3-str? // TODO should we use str_util for these and other expressions? len_xy = mk_strlen(a_cat); SASSERT(len_xy); @@ -2580,9 +2576,6 @@ void theory_str::dump_assignments() { void theory_str::classify_ast_by_type(expr * node, std::map & varMap, std::map & concatMap, std::map & unrollMap) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - // check whether the node is a non-internal string variable; // testing set membership here bypasses several expensive checks if (variable_set.find(node) != variable_set.end() @@ -2620,7 +2613,6 @@ void theory_str::classify_ast_by_type(expr * node, std::map & varMap } } // recursively visit all arguments - app * aNode = to_app(node); for (unsigned i = 0; i < aNode->get_num_args(); ++i) { expr * arg = aNode->get_arg(i); classify_ast_by_type(arg, varMap, concatMap, unrollMap); @@ -2964,7 +2956,7 @@ int theory_str::ctx_dep_analysis(std::map & strVarMap, std::map & strVarMap, std::map > & testerList) { +void theory_str::print_value_tester_list(svector > & testerList) { ast_manager & m = get_manager(); STRACE("t_str_detail", int ss = testerList.size(); @@ -3495,13 +3486,14 @@ void theory_str::print_value_tester_list(std::vector > & t ); } -std::string theory_str::gen_val_string(int len, std::vector & encoding) { +std::string theory_str::gen_val_string(int len, int_vector & encoding) { SASSERT(charSetSize > 0); + SASSERT(char_set != NULL); - std::string re = std::string(len, charSet[0]); + std::string re = std::string(len, char_set[0]); for (int i = 0; i < (int) encoding.size() - 1; i++) { int idx = encoding[i]; - re[len - 1 - i] = charSet[idx]; + re[len - 1 - i] = char_set[idx]; } return re; } @@ -3511,10 +3503,10 @@ std::string theory_str::gen_val_string(int len, std::vector & encoding) { * - If the next encoding is valid, return false * - Otherwise, return true */ -bool theory_str::get_next_val_encode(std::vector & base, std::vector & next) { +bool theory_str::get_next_val_encode(int_vector & base, int_vector & next) { int s = 0; int carry = 0; - next.clear(); + next.reset(); for (int i = 0; i < (int) base.size(); i++) { if (i == 0) { @@ -3530,7 +3522,7 @@ bool theory_str::get_next_val_encode(std::vector & base, std::vector & } } if (next[next.size() - 1] > 0) { - next.clear(); + next.reset(); return true; } else { return false; @@ -3539,7 +3531,6 @@ bool theory_str::get_next_val_encode(std::vector & base, std::vector & expr * theory_str::gen_val_options(expr * freeVar, expr * len_indicator, expr * val_indicator, std::string lenStr, int tries) { - context & ctx = get_context(); ast_manager & m = get_manager(); int distance = 32; @@ -3558,11 +3549,11 @@ expr * theory_str::gen_val_options(expr * freeVar, expr * len_indicator, expr * // ---------------------------------------------------------------------------------------- int len = atoi(lenStr.c_str()); bool coverAll = false; - std::vector > options; - std::vector base; + svector options; + int_vector base; if (tries == 0) { - base = std::vector(len + 1, 0); + base = int_vector(len + 1, 0); coverAll = false; } else { expr * lastestValIndi = fvar_valueTester_map[freeVar][len][tries - 1].second; @@ -3581,12 +3572,20 @@ expr * theory_str::gen_val_options(expr * freeVar, expr * len_indicator, expr * } val_range_map[val_indicator] = options[options.size() - 1]; - STRACE("t_str_detail", tout << "value tester encoding " << printVectorInt(valRangeMap[val_indicator]) << std::endl;); + STRACE("t_str_detail", + tout << "value tester encoding " << "{" << std::endl; + int_vector vec = val_range_map[val_indicator]; + + for (int_vector::iterator it = vec.begin(); it != vec.end(); ++it) { + tout << *it << std::endl; + } + tout << "}" << std::endl; + ); // ---------------------------------------------------------------------------------------- - std::vector orList; - std::vector andList; + ptr_vector orList; + ptr_vector andList; for (long long i = l; i < h; i++) { orList.push_back(m.mk_eq(val_indicator, m_strutil.mk_string(longlong_to_string(i).c_str()) )); @@ -3618,7 +3617,7 @@ expr * theory_str::gen_val_options(expr * freeVar, expr * len_indicator, expr * // If the new value tester is $$_val_x_16_i // Should add ($$_len_x_j = 16) /\ ($$_val_x_16_i = "more") // --------------------------------------- - andList.clear(); + andList.reset(); andList.push_back(m.mk_eq(len_indicator, m_strutil.mk_string(lenStr.c_str()))); for (int i = 0; i < tries; i++) { expr * vTester = fvar_valueTester_map[freeVar][len][i].second; @@ -3646,6 +3645,8 @@ expr * theory_str::gen_free_var_options(expr * freeVar, expr * len_indicator, context & ctx = get_context(); ast_manager & m = get_manager(); + int sLevel = ctx.get_scope_level(); + int len = atoi(len_valueStr.c_str()); if (fvar_valueTester_map[freeVar].find(len) == fvar_valueTester_map[freeVar].end()) { @@ -3671,12 +3672,12 @@ expr * theory_str::gen_free_var_options(expr * freeVar, expr * len_indicator, // Z3_ast anEqc = get_eqc_value(t, aTester, anEqcHasValue); get_eqc_value(aTester, anEqcHasValue); if (!anEqcHasValue) { - STRACE("t_str_detail", "value tester " << mk_ismt2_pp(aTester, m) + STRACE("t_str_detail", tout << "value tester " << mk_ismt2_pp(aTester, m) << "doesn't have an equivalence class value." << std::endl;); expr_ref makeupAssert(gen_val_options(freeVar, len_indicator, aTester, len_valueStr, i), m); - STRACE("t_str_detail", "var: " << mk_ismt2_pp(freeVar, m) << std::endl + STRACE("t_str_detail", tout << "var: " << mk_ismt2_pp(freeVar, m) << std::endl << mk_ismt2_pp(makeupAssert, m) << std::endl;); assert_axiom(makeupAssert); } @@ -3701,7 +3702,6 @@ expr * theory_str::gen_free_var_options(expr * freeVar, expr * len_indicator, } expr * theory_str::gen_len_test_options(expr * freeVar, expr * indicator, int tries) { - context & ctx = get_context(); ast_manager & m = get_manager(); expr * freeVarLen = mk_strlen(freeVar); @@ -3724,12 +3724,12 @@ expr * theory_str::gen_len_test_options(expr * freeVar, expr * indicator, int tr expr ** or_items = alloc_svect(expr*, orList.size()); expr ** and_items = alloc_svect(expr*, andList.size() + 1); - for (int i = 0; i < orList.size(); ++i) { + for (unsigned i = 0; i < orList.size(); ++i) { or_items[i] = orList[i]; } and_items[0] = m.mk_or(orList.size(), or_items); - for(int i = 0; i < andList.size(); ++i) { + for(unsigned i = 0; i < andList.size(); ++i) { and_items[i+1] = andList[i]; } expr * lenTestAssert = m.mk_and(andList.size() + 1, and_items); @@ -3772,7 +3772,6 @@ expr * theory_str::gen_len_test_options(expr * freeVar, expr * indicator, int tr // ----------------------------------------------------------------------------------------------------- expr * theory_str::gen_len_val_options_for_free_var(expr * freeVar, expr * lenTesterInCbEq, std::string lenTesterValue) { - context & ctx = get_context(); ast_manager & m = get_manager(); STRACE("t_str_detail", tout << "gen for free var " << mk_ismt2_pp(freeVar, m) << std::endl;); @@ -3814,7 +3813,7 @@ expr * theory_str::gen_len_val_options_for_free_var(expr * freeVar, expr * lenTe << " does not have an equivalence class value." << " i = " << i << ", lenTesterCount = " << lenTesterCount << std::endl;); if (i > 0) { - effectiveLenInd = fvarLenTesterMap[freeVar][i - 1]; + effectiveLenInd = fvar_lenTester_map[freeVar][i - 1]; if (effectiveLenInd == lenTesterInCbEq) { effectiveLenIndiStr = lenTesterValue; } else { @@ -3850,7 +3849,7 @@ expr * theory_str::gen_len_val_options_for_free_var(expr * freeVar, expr * lenTe fvar_lenTester_map[freeVar].push_back(indicator); lenTester_fvar_map[indicator] = freeVar; } else { - indicator = fvarLenTesterMap[freeVar][i]; + indicator = fvar_lenTester_map[freeVar][i]; testNum = i + 1; } expr_ref lenTestAssert(gen_len_test_options(freeVar, indicator, testNum), m); diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index b7a63edb3..2f23ce43e 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -89,12 +89,13 @@ namespace smt { std::map > fvar_lenTester_map; std::map lenTester_fvar_map; - std::map > > > fvar_valueTester_map; + std::map > > > fvar_valueTester_map; std::map valueTester_fvar_map; - std::map > val_range_map; + std::map val_range_map; - int charSetSize = 0; + char * char_set; + int charSetSize; protected: void assert_axiom(expr * e); @@ -177,9 +178,9 @@ namespace smt { std::string len_valueStr, expr * valTesterInCbEq, std::string valTesterValueStr); expr * gen_val_options(expr * freeVar, expr * len_indicator, expr * val_indicator, std::string lenStr, int tries); - void print_value_tester_list(std::vector > & testerList); - bool get_next_val_encode(std::vector & base, std::vector & next); - std::string gen_val_string(int len, std::vector & encoding); + void print_value_tester_list(svector > & testerList); + bool get_next_val_encode(int_vector & base, int_vector & next); + std::string gen_val_string(int len, int_vector & encoding); expr * get_alias_index_ast(std::map & aliasIndexMap, expr * node); expr * getMostLeftNodeInConcat(expr * node); From bf27d41b0824cab5c80280278d6032c4e8c97c30 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Fri, 20 Nov 2015 12:27:29 -0500 Subject: [PATCH 065/410] use TRACE instead of STRACE... --- src/smt/theory_str.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 068745d94..429c87e62 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -2954,7 +2954,7 @@ int theory_str::ctx_dep_analysis(std::map & strVarMap, std::map::iterator itor2 = inVarMap.begin(); itor2 != inVarMap.end(); itor2++) { expr * varInFunc = get_alias_index_ast(aliasIndexMap, itor2->first); - STRACE("t_str_detail", tout << "var in unroll = " << + TRACE("t_str_detail", tout << "var in unroll = " << mk_ismt2_pp(itor2->first, m) << std::endl << "dealiased var = " << mk_ismt2_pp(varInFunc, m) << std::endl;); @@ -3471,7 +3471,7 @@ inline std::string longlong_to_string(long long i) { void theory_str::print_value_tester_list(svector > & testerList) { ast_manager & m = get_manager(); - STRACE("t_str_detail", + TRACE("t_str_detail", int ss = testerList.size(); tout << "valueTesterList = {"; for (int i = 0; i < ss; ++i) { @@ -3557,7 +3557,7 @@ expr * theory_str::gen_val_options(expr * freeVar, expr * len_indicator, expr * coverAll = false; } else { expr * lastestValIndi = fvar_valueTester_map[freeVar][len][tries - 1].second; - STRACE("t_str_detail", tout << "last value tester = " << mk_ismt2_pp(lastestValIndi, m) << std::endl;); + TRACE("t_str_detail", tout << "last value tester = " << mk_ismt2_pp(lastestValIndi, m) << std::endl;); coverAll = get_next_val_encode(val_range_map[lastestValIndi], base); } @@ -3572,7 +3572,7 @@ expr * theory_str::gen_val_options(expr * freeVar, expr * len_indicator, expr * } val_range_map[val_indicator] = options[options.size() - 1]; - STRACE("t_str_detail", + TRACE("t_str_detail", tout << "value tester encoding " << "{" << std::endl; int_vector vec = val_range_map[val_indicator]; @@ -3672,12 +3672,12 @@ expr * theory_str::gen_free_var_options(expr * freeVar, expr * len_indicator, // Z3_ast anEqc = get_eqc_value(t, aTester, anEqcHasValue); get_eqc_value(aTester, anEqcHasValue); if (!anEqcHasValue) { - STRACE("t_str_detail", tout << "value tester " << mk_ismt2_pp(aTester, m) + TRACE("t_str_detail", tout << "value tester " << mk_ismt2_pp(aTester, m) << "doesn't have an equivalence class value." << std::endl;); expr_ref makeupAssert(gen_val_options(freeVar, len_indicator, aTester, len_valueStr, i), m); - STRACE("t_str_detail", tout << "var: " << mk_ismt2_pp(freeVar, m) << std::endl + TRACE("t_str_detail", tout << "var: " << mk_ismt2_pp(freeVar, m) << std::endl << mk_ismt2_pp(makeupAssert, m) << std::endl;); assert_axiom(makeupAssert); } @@ -3774,7 +3774,7 @@ expr * theory_str::gen_len_val_options_for_free_var(expr * freeVar, expr * lenTe ast_manager & m = get_manager(); - STRACE("t_str_detail", tout << "gen for free var " << mk_ismt2_pp(freeVar, m) << std::endl;); + TRACE("t_str_detail", tout << "gen for free var " << mk_ismt2_pp(freeVar, m) << std::endl;); // no length assertions for this free variable have ever been added. if (fvar_len_count_map.find(freeVar) == fvar_len_count_map.end()) { fvar_len_count_map[freeVar] = 1; @@ -3796,7 +3796,7 @@ expr * theory_str::gen_len_val_options_for_free_var(expr * freeVar, expr * lenTe expr * len_indicator_pre = fvar_lenTester_map[freeVar][i]; bool indicatorHasEqcValue = false; expr * len_indicator_value = get_eqc_value(len_indicator_pre, indicatorHasEqcValue); - STRACE("t_str_detail", tout << "length indicator " << mk_ismt2_pp(len_indicator_pre, m) << + TRACE("t_str_detail", tout << "length indicator " << mk_ismt2_pp(len_indicator_pre, m) << " = " << mk_ismt2_pp(len_indicator_value, m) << std::endl;); if (indicatorHasEqcValue) { const char * val = 0; @@ -3809,7 +3809,7 @@ expr * theory_str::gen_len_val_options_for_free_var(expr * freeVar, expr * lenTe } } else { if (lenTesterInCbEq != len_indicator_pre) { - STRACE("t_str", tout << "WARNING: length indicator " << mk_ismt2_pp(len_indicator_pre, m) + TRACE("t_str", tout << "WARNING: length indicator " << mk_ismt2_pp(len_indicator_pre, m) << " does not have an equivalence class value." << " i = " << i << ", lenTesterCount = " << lenTesterCount << std::endl;); if (i > 0) { @@ -3839,7 +3839,7 @@ expr * theory_str::gen_len_val_options_for_free_var(expr * freeVar, expr * lenTe expr * indicator = NULL; unsigned int testNum = 0; - STRACE("t_str", tout << "effectiveLenIndiStr = " << effectiveLenIndiStr + TRACE("t_str", tout << "effectiveLenIndiStr = " << effectiveLenIndiStr << ", i = " << i << ", lenTesterCount = " << lenTesterCount << std::endl;); if (i == lenTesterCount) { @@ -3908,7 +3908,7 @@ void theory_str::process_free_var(std::map & freeVar_map) { } } if (duplicated && dupVar != NULL) { - STRACE("t_str_detail", tout << "Duplicated free variable found:" << mk_ismt2_pp(freeVar, m) + TRACE("t_str_detail", tout << "Duplicated free variable found:" << mk_ismt2_pp(freeVar, m) << " = " << mk_ismt2_pp(dupVar, m) << " (SKIP)" << std::endl;); continue; } else { From 24148bafa3df7df22afedda3a6aac1175f9fc155 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Fri, 20 Nov 2015 15:48:06 -0500 Subject: [PATCH 066/410] fixed several AST bugs; need to load charSet now --- src/smt/theory_str.cpp | 55 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 49 insertions(+), 6 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 429c87e62..b2f79b7bc 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -285,6 +285,7 @@ app * theory_str::mk_str_var(std::string name) { app * a = m.mk_const(m.mk_const_decl(sym, string_sort)); // I have a hunch that this may not get internalized for free... + ctx.internalize(a, false); SASSERT(ctx.get_enode(a) != NULL); m_basicstr_axiom_todo.push_back(ctx.get_enode(a)); @@ -3504,6 +3505,8 @@ std::string theory_str::gen_val_string(int len, int_vector & encoding) { * - Otherwise, return true */ bool theory_str::get_next_val_encode(int_vector & base, int_vector & next) { + SASSERT(charSetSize > 0); + int s = 0; int carry = 0; next.reset(); @@ -3552,6 +3555,14 @@ expr * theory_str::gen_val_options(expr * freeVar, expr * len_indicator, expr * svector options; int_vector base; + TRACE("t_str_detail", tout + << "freeVar = " << mk_ismt2_pp(freeVar, m) << std::endl + << "len_indicator = " << mk_ismt2_pp(len_indicator, m) << std::endl + << "val_indicator = " << mk_ismt2_pp(val_indicator, m) << std::endl + << "lenstr = " << lenStr << std::endl + << "tries = " << tries << std::endl + ;); + if (tries == 0) { base = int_vector(len + 1, 0); coverAll = false; @@ -3650,6 +3661,7 @@ expr * theory_str::gen_free_var_options(expr * freeVar, expr * len_indicator, int len = atoi(len_valueStr.c_str()); if (fvar_valueTester_map[freeVar].find(len) == fvar_valueTester_map[freeVar].end()) { + TRACE("t_str_detail", tout << "no previous value testers" << std::endl;); int tries = 0; expr * val_indicator = mk_internal_valTest_var(freeVar, len, tries); valueTester_fvar_map[val_indicator] = freeVar; @@ -3657,6 +3669,7 @@ expr * theory_str::gen_free_var_options(expr * freeVar, expr * len_indicator, print_value_tester_list(fvar_valueTester_map[freeVar][len]); return gen_val_options(freeVar, len_indicator, val_indicator, len_valueStr, tries); } else { + TRACE("t_str_detail", tout << "checking previous value testers" << std::endl;); // go through all previous value testers // If some doesn't have an eqc value, add its assertion again. int testerTotal = fvar_valueTester_map[freeVar][len].size(); @@ -3704,7 +3717,10 @@ expr * theory_str::gen_free_var_options(expr * freeVar, expr * len_indicator, expr * theory_str::gen_len_test_options(expr * freeVar, expr * indicator, int tries) { ast_manager & m = get_manager(); - expr * freeVarLen = mk_strlen(freeVar); + TRACE("t_str_detail", tout << "entry" << std::endl;); + + expr_ref freeVarLen(mk_strlen(freeVar), m); + SASSERT(freeVarLen); ptr_vector orList; ptr_vector andList; @@ -3713,9 +3729,16 @@ expr * theory_str::gen_len_test_options(expr * freeVar, expr * indicator, int tr int l = (tries - 1) * distance; int h = tries * distance; + TRACE("t_str_detail", tout << "building andList and orList" << std::endl;); + for (int i = l; i < h; ++i) { - orList.push_back(m.mk_eq(indicator, m_strutil.mk_string(int_to_string(i).c_str()))); - andList.push_back(m.mk_eq(orList[orList.size() - 1], m.mk_eq(freeVarLen, mk_int(i)))); + expr * or_expr = m.mk_eq(indicator, m_strutil.mk_string(int_to_string(i).c_str())); + TRACE("t_str_detail", tout << "or_expr = " << mk_ismt2_pp(or_expr, m) << std::endl;); + orList.push_back(or_expr); + + expr * and_expr = m.mk_eq(orList[orList.size() - 1], m.mk_eq(freeVarLen, mk_int(i))); + TRACE("t_str_detail", tout << "and_expr = " << mk_ismt2_pp(and_expr, m) << std::endl;); + andList.push_back(and_expr); } orList.push_back(m.mk_eq(indicator, m_strutil.mk_string("more"))); @@ -3725,14 +3748,20 @@ expr * theory_str::gen_len_test_options(expr * freeVar, expr * indicator, int tr expr ** and_items = alloc_svect(expr*, andList.size() + 1); for (unsigned i = 0; i < orList.size(); ++i) { + SASSERT(orList[i] != NULL); or_items[i] = orList[i]; } and_items[0] = m.mk_or(orList.size(), or_items); + SASSERT(and_items[0] != NULL); for(unsigned i = 0; i < andList.size(); ++i) { + SASSERT(andList[i] != NULL); and_items[i+1] = andList[i]; } expr * lenTestAssert = m.mk_and(andList.size() + 1, and_items); + SASSERT(lenTestAssert != NULL); + + TRACE("t_str_detail", tout << "lenTestAssert = " << mk_ismt2_pp(lenTestAssert, m) << std::endl;); expr * assertL = NULL; int testerCount = tries - 1; @@ -3750,11 +3779,14 @@ expr * theory_str::gen_len_test_options(expr * freeVar, expr * indicator, int tr } if (assertL != NULL) { + TRACE("t_str_detail", tout << "assertL = " << mk_ismt2_pp(assertL, m) << std::endl;); // return the axiom (assertL -> lenTestAssert) // would like to use mk_implies() here but... expr_ref lenTestAssert(m.mk_or(m.mk_not(assertL), lenTestAssert), m); } + TRACE("t_str_detail", tout << "exit" << std::endl;); + return lenTestAssert; } @@ -3777,16 +3809,23 @@ expr * theory_str::gen_len_val_options_for_free_var(expr * freeVar, expr * lenTe TRACE("t_str_detail", tout << "gen for free var " << mk_ismt2_pp(freeVar, m) << std::endl;); // no length assertions for this free variable have ever been added. if (fvar_len_count_map.find(freeVar) == fvar_len_count_map.end()) { + + TRACE("t_str_detail", tout << "no length assertions yet" << std::endl;); + fvar_len_count_map[freeVar] = 1; unsigned int testNum = fvar_len_count_map[freeVar]; + expr * indicator = mk_internal_lenTest_var(freeVar, testNum); + SASSERT(indicator != NULL); + fvar_lenTester_map[freeVar].push_back(indicator); lenTester_fvar_map[indicator] = freeVar; expr * lenTestAssert = gen_len_test_options(freeVar, indicator, testNum); + SASSERT(lenTestAssert != NULL); return lenTestAssert; } else { - + TRACE("t_str_detail", tout << "found previous length assertions" << std::endl;); expr * effectiveLenInd = NULL; std::string effectiveLenIndiStr = ""; int lenTesterCount = (int) fvar_lenTester_map[freeVar].size(); @@ -3836,6 +3875,7 @@ expr * theory_str::gen_len_val_options_for_free_var(expr * freeVar, expr * lenTe } // !indicatorHasEqcValue } // for (i : [0..lenTesterCount-1]) if (effectiveLenIndiStr == "more" || effectiveLenIndiStr == "") { + TRACE("t_str", tout << "length is not fixed; generating length tester options for free var" << std::endl;); expr * indicator = NULL; unsigned int testNum = 0; @@ -3852,11 +3892,12 @@ expr * theory_str::gen_len_val_options_for_free_var(expr * freeVar, expr * lenTe indicator = fvar_lenTester_map[freeVar][i]; testNum = i + 1; } - expr_ref lenTestAssert(gen_len_test_options(freeVar, indicator, testNum), m); + expr * lenTestAssert = gen_len_test_options(freeVar, indicator, testNum); return lenTestAssert; } else { + TRACE("t_str", tout << "length is fixed; generating models for free var" << std::endl;); // length is fixed - expr_ref valueAssert(gen_free_var_options(freeVar, effectiveLenInd, effectiveLenIndiStr, NULL, ""), m); + expr * valueAssert = gen_free_var_options(freeVar, effectiveLenInd, effectiveLenIndiStr, NULL, ""); return valueAssert; } } // fVarLenCountMap.find(...) @@ -3956,6 +3997,7 @@ void theory_str::process_free_var(std::map & freeVar_map) { for(std::set::iterator itor1 = leafVarSet.begin(); itor1 != leafVarSet.end(); ++itor1) { expr * toAssert = gen_len_val_options_for_free_var(*itor1, NULL, ""); + SASSERT(toAssert != NULL); assert_axiom(toAssert); } @@ -3964,6 +4006,7 @@ void theory_str::process_free_var(std::map & freeVar_map) { std::set::iterator itor2 = mItor->second.begin(); for(; itor2 != mItor->second.end(); ++itor2) { expr * toAssert = gen_len_val_options_for_free_var(*itor2, NULL, ""); + SASSERT(toAssert != NULL); assert_axiom(toAssert); } } From 9010a5c4cf08764988113b3cf221c47e73d5963a Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Fri, 20 Nov 2015 16:05:43 -0500 Subject: [PATCH 067/410] honest-to-goodness working model gen, i.e. it didn't crash. more testing needed --- src/smt/theory_str.cpp | 79 ++++++++++++++++++++++++++++++++++++++++-- src/smt/theory_str.h | 2 ++ 2 files changed, 78 insertions(+), 3 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index b2f79b7bc..61763b693 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -32,15 +32,88 @@ theory_str::theory_str(ast_manager & m): tmpStringVarCount(0), tmpXorVarCount(0), avoidLoopCut(true), - loopDetected(false), - char_set(NULL), - charSetSize(0) + loopDetected(false) { + initialize_charset(); } theory_str::~theory_str() { } +void theory_str::initialize_charset() { + bool defaultCharset = true; + if (defaultCharset) { + // valid C strings can't contain the null byte ('\0') + charSetSize = 255; + char_set = alloc_svect(char, charSetSize); + int idx = 0; + // small letters + for (int i = 97; i < 123; i++) { + char_set[idx] = (char) i; + charSetLookupTable[char_set[idx]] = idx; + idx++; + } + // caps + for (int i = 65; i < 91; i++) { + char_set[idx] = (char) i; + charSetLookupTable[char_set[idx]] = idx; + idx++; + } + // numbers + for (int i = 48; i < 58; i++) { + char_set[idx] = (char) i; + charSetLookupTable[char_set[idx]] = idx; + idx++; + } + // printable marks - 1 + for (int i = 32; i < 48; i++) { + char_set[idx] = (char) i; + charSetLookupTable[char_set[idx]] = idx; + idx++; + } + // printable marks - 2 + for (int i = 58; i < 65; i++) { + char_set[idx] = (char) i; + charSetLookupTable[char_set[idx]] = idx; + idx++; + } + // printable marks - 3 + for (int i = 91; i < 97; i++) { + char_set[idx] = (char) i; + charSetLookupTable[char_set[idx]] = idx; + idx++; + } + // printable marks - 4 + for (int i = 123; i < 127; i++) { + char_set[idx] = (char) i; + charSetLookupTable[char_set[idx]] = idx; + idx++; + } + // non-printable - 1 + for (int i = 1; i < 32; i++) { + char_set[idx] = (char) i; + charSetLookupTable[char_set[idx]] = idx; + idx++; + } + // non-printable - 2 + for (int i = 127; i < 256; i++) { + char_set[idx] = (char) i; + charSetLookupTable[char_set[idx]] = idx; + idx++; + } + } else { + const char setset[] = { 'a', 'b', 'c' }; + int fSize = sizeof(setset) / sizeof(char); + + char_set = alloc_svect(char, fSize); + charSetSize = fSize; + for (int i = 0; i < charSetSize; i++) { + char_set[i] = setset[i]; + charSetLookupTable[setset[i]] = i; + } + } +} + void theory_str::assert_axiom(expr * e) { if (get_manager().is_true(e)) return; TRACE("t_str_detail", tout << "asserting " << mk_ismt2_pp(e, get_manager()) << std::endl;); diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 2f23ce43e..12898d458 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -95,6 +95,7 @@ namespace smt { std::map val_range_map; char * char_set; + std::map charSetLookupTable; int charSetSize; protected: @@ -192,6 +193,7 @@ namespace smt { void get_eqc_allUnroll(expr * n, expr * &constStr, std::set & unrollFuncSet); void dump_assignments(); + void initialize_charset(); public: theory_str(ast_manager & m); virtual ~theory_str(); From 07626a1e030427c9a612c3778e67ee5fda6382d8 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sat, 28 Nov 2015 23:56:30 -0500 Subject: [PATCH 068/410] remove expr_ref stuff, start tracking variables more closely --- src/smt/theory_str.cpp | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 61763b693..9cdc53329 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -350,6 +350,8 @@ app * theory_str::mk_str_var(std::string name) { context & ctx = get_context(); ast_manager & m = get_manager(); + TRACE("t_str_detail", tout << "creating string variable " << name << std::endl;); + sort * string_sort = m.mk_sort(m_strutil.get_family_id(), STRING_SORT); char * new_buffer = alloc_svect(char, name.length() + 1); strcpy(new_buffer, name.c_str()); @@ -360,6 +362,7 @@ app * theory_str::mk_str_var(std::string name) { // I have a hunch that this may not get internalized for free... ctx.internalize(a, false); SASSERT(ctx.get_enode(a) != NULL); + SASSERT(ctx.e_internalized(a)); m_basicstr_axiom_todo.push_back(ctx.get_enode(a)); variable_set.insert(a); @@ -369,26 +372,31 @@ app * theory_str::mk_str_var(std::string name) { } app * theory_str::mk_nonempty_str_var() { + context & ctx = get_context(); ast_manager & m = get_manager(); + std::stringstream ss; ss << tmpStringVarCount; tmpStringVarCount++; std::string name = "$$_str" + ss.str(); + + TRACE("t_str_detail", tout << "creating nonempty string variable " << name << std::endl;); + sort * string_sort = m.mk_sort(m_strutil.get_family_id(), STRING_SORT); char * new_buffer = alloc_svect(char, name.length() + 1); strcpy(new_buffer, name.c_str()); symbol sym(new_buffer); app* a = m.mk_const(m.mk_const_decl(sym, string_sort)); + ctx.internalize(a, false); + SASSERT(ctx.get_enode(a) != NULL); // assert a variation of the basic string axioms that ensures this string is nonempty { // build LHS - expr_ref len_str(m); - len_str = mk_strlen(a); + expr * len_str = mk_strlen(a); SASSERT(len_str); // build RHS - expr_ref zero(m); - zero = m_autil.mk_numeral(rational(0), true); + expr * zero = m_autil.mk_numeral(rational(0), true); SASSERT(zero); // build LHS > RHS and assert // we have to build !(LHS <= RHS) instead @@ -2509,7 +2517,7 @@ void theory_str::set_up_axioms(expr * ex) { } } else if (ap->get_num_args() == 0 && !is_string(ap)) { // if ex is a variable, add it to our list of variables - TRACE("t_str_detail", tout << "tracking variable" << std::endl;); + TRACE("t_str_detail", tout << "tracking variable " << mk_ismt2_pp(ap, get_manager()) << std::endl;); variable_set.insert(ex); } } @@ -3347,6 +3355,7 @@ int theory_str::ctx_dep_analysis(std::map & strVarMap, std::map free_variables; + TRACE("t_str_detail", tout << variable_set.size() << " variables in variable_set" << std::endl;); for (std::set::iterator it = variable_set.begin(); it != variable_set.end(); ++it) { + TRACE("t_str_detail", tout << "checking eqc of variable " << mk_ismt2_pp(*it, m) << std::endl;); bool has_eqc_value = false; get_eqc_value(*it, has_eqc_value); if (!has_eqc_value) { @@ -3674,7 +3685,7 @@ expr * theory_str::gen_val_options(expr * freeVar, expr * len_indicator, expr * for (long long i = l; i < h; i++) { orList.push_back(m.mk_eq(val_indicator, m_strutil.mk_string(longlong_to_string(i).c_str()) )); std::string aStr = gen_val_string(len, options[i - l]); - expr_ref strAst(m_strutil.mk_string(aStr), m); + expr * strAst = m_strutil.mk_string(aStr); andList.push_back(m.mk_eq(orList[orList.size() - 1], m.mk_eq(freeVar, strAst))); } if (!coverAll) { @@ -3761,7 +3772,7 @@ expr * theory_str::gen_free_var_options(expr * freeVar, expr * len_indicator, TRACE("t_str_detail", tout << "value tester " << mk_ismt2_pp(aTester, m) << "doesn't have an equivalence class value." << std::endl;); - expr_ref makeupAssert(gen_val_options(freeVar, len_indicator, aTester, len_valueStr, i), m); + expr * makeupAssert = gen_val_options(freeVar, len_indicator, aTester, len_valueStr, i); TRACE("t_str_detail", tout << "var: " << mk_ismt2_pp(freeVar, m) << std::endl << mk_ismt2_pp(makeupAssert, m) << std::endl;); @@ -3779,7 +3790,7 @@ expr * theory_str::gen_free_var_options(expr * freeVar, expr * len_indicator, fvar_valueTester_map[freeVar][len].push_back(std::make_pair(sLevel, valTester)); print_value_tester_list(fvar_valueTester_map[freeVar][len]); } - expr_ref nextAssert(gen_val_options(freeVar, len_indicator, valTester, len_valueStr, i + 1), m); + expr * nextAssert = gen_val_options(freeVar, len_indicator, valTester, len_valueStr, i + 1); return nextAssert; } @@ -3792,7 +3803,7 @@ expr * theory_str::gen_len_test_options(expr * freeVar, expr * indicator, int tr TRACE("t_str_detail", tout << "entry" << std::endl;); - expr_ref freeVarLen(mk_strlen(freeVar), m); + expr * freeVarLen = mk_strlen(freeVar); SASSERT(freeVarLen); ptr_vector orList; @@ -3855,7 +3866,7 @@ expr * theory_str::gen_len_test_options(expr * freeVar, expr * indicator, int tr TRACE("t_str_detail", tout << "assertL = " << mk_ismt2_pp(assertL, m) << std::endl;); // return the axiom (assertL -> lenTestAssert) // would like to use mk_implies() here but... - expr_ref lenTestAssert(m.mk_or(m.mk_not(assertL), lenTestAssert), m); + lenTestAssert = m.mk_or(m.mk_not(assertL), lenTestAssert); } TRACE("t_str_detail", tout << "exit" << std::endl;); From dd0bc13be720fbb3abf976d6d6c134900db74058 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Mon, 30 Nov 2015 19:22:01 -0500 Subject: [PATCH 069/410] attempt to track popped variables, still segfaults, WIP --- src/smt/theory_str.cpp | 45 ++++++++++++++++++++++++++++++++++++++---- src/smt/theory_str.h | 2 ++ 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 9cdc53329..8056864a4 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -329,8 +329,18 @@ expr * theory_str::mk_internal_valTest_var(expr * node, int len, int vTries) { */ } +void theory_str::track_variable_scope(expr * var) { + context & ctx = get_context(); + int sLevel = ctx.get_scope_level(); + if (internal_variable_scope_levels.find(sLevel) == internal_variable_scope_levels.end()) { + internal_variable_scope_levels[sLevel] = std::set(); + } + internal_variable_scope_levels[sLevel].insert(var); +} + app * theory_str::mk_internal_xor_var() { ast_manager & m = get_manager(); + context & ctx = get_context(); std::stringstream ss; ss << tmpXorVarCount; tmpXorVarCount++; @@ -342,6 +352,7 @@ app * theory_str::mk_internal_xor_var() { symbol sym(new_buffer); app* a = m.mk_const(m.mk_const_decl(sym, int_sort)); + // TODO ctx.save_ast_trail(a)? return a; } @@ -350,7 +361,8 @@ app * theory_str::mk_str_var(std::string name) { context & ctx = get_context(); ast_manager & m = get_manager(); - TRACE("t_str_detail", tout << "creating string variable " << name << std::endl;); + int sLevel = ctx.get_scope_level(); + TRACE("t_str_detail", tout << "creating string variable " << name << " at scope level " << sLevel << std::endl;); sort * string_sort = m.mk_sort(m_strutil.get_family_id(), STRING_SORT); char * new_buffer = alloc_svect(char, name.length() + 1); @@ -367,6 +379,7 @@ app * theory_str::mk_str_var(std::string name) { variable_set.insert(a); internal_variable_set.insert(a); + track_variable_scope(a); return a; } @@ -380,7 +393,9 @@ app * theory_str::mk_nonempty_str_var() { tmpStringVarCount++; std::string name = "$$_str" + ss.str(); - TRACE("t_str_detail", tout << "creating nonempty string variable " << name << std::endl;); + int sLevel = ctx.get_scope_level(); + + TRACE("t_str_detail", tout << "creating nonempty string variable " << name << " at scope level " << sLevel << std::endl;); sort * string_sort = m.mk_sort(m_strutil.get_family_id(), STRING_SORT); char * new_buffer = alloc_svect(char, name.length() + 1); @@ -408,6 +423,7 @@ app * theory_str::mk_nonempty_str_var() { // add 'a' to variable sets, so we can keep track of it variable_set.insert(a); internal_variable_set.insert(a); + track_variable_scope(a); return a; } @@ -2620,13 +2636,16 @@ void theory_str::assign_eh(bool_var v, bool is_true) { } void theory_str::push_scope_eh() { - TRACE("t_str", tout << "push" << std::endl;); + context & ctx = get_context(); + int sLevel = ctx.get_scope_level(); + TRACE("t_str", tout << "push to " << sLevel << std::endl;); } void theory_str::pop_scope_eh(unsigned num_scopes) { - TRACE("t_str", tout << "pop " << num_scopes << std::endl;); context & ctx = get_context(); int sLevel = ctx.get_scope_level(); + TRACE("t_str", tout << "pop " << num_scopes << " to " << sLevel << std::endl;); + std::map >::iterator varItor = cut_var_map.begin(); while (varItor != cut_var_map.end()) { while ((varItor->second.size() > 0) && (varItor->second.top()->level != 0) && (varItor->second.top()->level >= sLevel)) { @@ -2639,6 +2658,24 @@ void theory_str::pop_scope_eh(unsigned num_scopes) { } ++varItor; } + + // see if any internal variables went out of scope + for (int check_level = sLevel + num_scopes ; check_level > sLevel; --check_level) { + TRACE("t_str_detail", tout << "cleaning up internal variables at scope level " << check_level << std::endl;); + std::map >::iterator it = internal_variable_scope_levels.find(check_level); + if (it != internal_variable_scope_levels.end()) { + unsigned count = 0; + std::set vars = it->second; + for (std::set::iterator var_it = vars.begin(); var_it != vars.end(); ++var_it) { + variable_set.erase(*var_it); + internal_variable_set.erase(*var_it); + count += 1; + } + TRACE("t_str_detail", tout << "cleaned up " << count << " variables" << std::endl;); + vars.clear(); + } + } + } void theory_str::dump_assignments() { diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 12898d458..fe2ff4625 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -82,6 +82,7 @@ namespace smt { std::set variable_set; std::set internal_variable_set; + std::map > internal_variable_scope_levels; std::set input_var_in_len; @@ -113,6 +114,7 @@ namespace smt { void add_cut_info_merge(expr * destNode, int slevel, expr * srcNode); bool has_self_cut(expr * n1, expr * n2); + void track_variable_scope(expr * var); app * mk_str_var(std::string name); app * mk_nonempty_str_var(); app * mk_internal_xor_var(); From c44d49b625661065d8bb3a6da1a0a6015100b3e4 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 1 Dec 2015 14:41:11 -0500 Subject: [PATCH 070/410] keep track of search level ourselves --- src/smt/theory_str.cpp | 11 +++-------- src/smt/theory_str.h | 1 + 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 8056864a4..b69bb4ac7 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -29,6 +29,7 @@ theory_str::theory_str(ast_manager & m): search_started(false), m_autil(m), m_strutil(m), + sLevel(0), tmpStringVarCount(0), tmpXorVarCount(0), avoidLoopCut(true), @@ -331,7 +332,6 @@ expr * theory_str::mk_internal_valTest_var(expr * node, int len, int vTries) { void theory_str::track_variable_scope(expr * var) { context & ctx = get_context(); - int sLevel = ctx.get_scope_level(); if (internal_variable_scope_levels.find(sLevel) == internal_variable_scope_levels.end()) { internal_variable_scope_levels[sLevel] = std::set(); } @@ -361,7 +361,6 @@ app * theory_str::mk_str_var(std::string name) { context & ctx = get_context(); ast_manager & m = get_manager(); - int sLevel = ctx.get_scope_level(); TRACE("t_str_detail", tout << "creating string variable " << name << " at scope level " << sLevel << std::endl;); sort * string_sort = m.mk_sort(m_strutil.get_family_id(), STRING_SORT); @@ -393,8 +392,6 @@ app * theory_str::mk_nonempty_str_var() { tmpStringVarCount++; std::string name = "$$_str" + ss.str(); - int sLevel = ctx.get_scope_level(); - TRACE("t_str_detail", tout << "creating nonempty string variable " << name << " at scope level " << sLevel << std::endl;); sort * string_sort = m.mk_sort(m_strutil.get_family_id(), STRING_SORT); @@ -2637,13 +2634,13 @@ void theory_str::assign_eh(bool_var v, bool is_true) { void theory_str::push_scope_eh() { context & ctx = get_context(); - int sLevel = ctx.get_scope_level(); + sLevel += 1; TRACE("t_str", tout << "push to " << sLevel << std::endl;); } void theory_str::pop_scope_eh(unsigned num_scopes) { context & ctx = get_context(); - int sLevel = ctx.get_scope_level(); + sLevel -= num_scopes; TRACE("t_str", tout << "pop " << num_scopes << " to " << sLevel << std::endl;); std::map >::iterator varItor = cut_var_map.begin(); @@ -3777,8 +3774,6 @@ expr * theory_str::gen_free_var_options(expr * freeVar, expr * len_indicator, context & ctx = get_context(); ast_manager & m = get_manager(); - int sLevel = ctx.get_scope_level(); - int len = atoi(len_valueStr.c_str()); if (fvar_valueTester_map[freeVar].find(len) == fvar_valueTester_map[freeVar].end()) { diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index fe2ff4625..ce4a0cfc9 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -63,6 +63,7 @@ namespace smt { }; protected: bool search_started; + int sLevel; arith_util m_autil; str_util m_strutil; From 52f0277c99439419336bbcfb46f08d82d32e7041 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 1 Dec 2015 19:19:00 -0500 Subject: [PATCH 071/410] attempt to clean up out-of-scope variables more, still crashing --- src/smt/theory_str.cpp | 50 +++++++++++++++++++++++++++++++++++++----- src/smt/theory_str.h | 4 +++- 2 files changed, 48 insertions(+), 6 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index b69bb4ac7..3c6261243 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -32,6 +32,8 @@ theory_str::theory_str(ast_manager & m): sLevel(0), tmpStringVarCount(0), tmpXorVarCount(0), + tmpLenTestVarCount(0), + tmpValTestVarCount(0), avoidLoopCut(true), loopDetected(false) { @@ -297,11 +299,14 @@ app * theory_str::mk_int(int n) { return m_autil.mk_numeral(rational(n), true); } +// We have to work a little bit harder to ensure that all variables we create here are always fresh. + expr * theory_str::mk_internal_lenTest_var(expr * node, int lTries) { ast_manager & m = get_manager(); std::stringstream ss; - ss << "$$_len_" << mk_ismt2_pp(node, m) << "_" << lTries; + ss << "$$_len_" << mk_ismt2_pp(node, m) << "_" << lTries << "_" << tmpLenTestVarCount; + tmpLenTestVarCount += 1; std::string name = ss.str(); return mk_str_var(name); @@ -317,7 +322,8 @@ expr * theory_str::mk_internal_lenTest_var(expr * node, int lTries) { expr * theory_str::mk_internal_valTest_var(expr * node, int len, int vTries) { ast_manager & m = get_manager(); std::stringstream ss; - ss << "$$_val_" << mk_ismt2_pp(node, m) << "_" << len << "_" << vTries; + ss << "$$_val_" << mk_ismt2_pp(node, m) << "_" << len << "_" << vTries << "_" << tmpValTestVarCount; + tmpValTestVarCount += 1; std::string name = ss.str(); return mk_str_var(name); @@ -3923,9 +3929,35 @@ expr * theory_str::gen_len_val_options_for_free_var(expr * freeVar, expr * lenTe ast_manager & m = get_manager(); TRACE("t_str_detail", tout << "gen for free var " << mk_ismt2_pp(freeVar, m) << std::endl;); - // no length assertions for this free variable have ever been added. - if (fvar_len_count_map.find(freeVar) == fvar_len_count_map.end()) { + bool map_effectively_empty = false; + if (fvar_len_count_map.find(freeVar) == fvar_len_count_map.end()) { + TRACE("t_str_detail", tout << "fvar_len_count_map is empty" << std::endl;); + map_effectively_empty = true; + } + + if (!map_effectively_empty) { + // check whether any entries correspond to variables that went out of scope; + // if every entry is out of scope then the map counts as being empty + // TODO: maybe remove them from the map instead? either here or in pop_scope_eh() + + // assume empty and find a counterexample + map_effectively_empty = true; + ptr_vector indicator_set = fvar_lenTester_map[freeVar]; + for (ptr_vector::iterator it = indicator_set.begin(); it != indicator_set.end(); ++it) { + expr * indicator = *it; + if (internal_variable_set.find(indicator) != internal_variable_set.end()) { + TRACE("t_str_detail", tout <<"found active internal variable " << mk_ismt2_pp(indicator, m) + << " in fvar_lenTester_map[freeVar]" << std::endl;); + map_effectively_empty = false; + break; + } + } + CTRACE("t_str_detail", map_effectively_empty, tout << "all variables in fvar_lenTester_map[freeVar] out of scope" << std::endl;); + } + + if (map_effectively_empty) { + // no length assertions for this free variable have ever been added. TRACE("t_str_detail", tout << "no length assertions yet" << std::endl;); fvar_len_count_map[freeVar] = 1; @@ -3934,6 +3966,8 @@ expr * theory_str::gen_len_val_options_for_free_var(expr * freeVar, expr * lenTe expr * indicator = mk_internal_lenTest_var(freeVar, testNum); SASSERT(indicator != NULL); + // since the map is "effectively empty", we can remove those variables that have left scope... + fvar_lenTester_map[freeVar].shrink(0); fvar_lenTester_map[freeVar].push_back(indicator); lenTester_fvar_map[indicator] = freeVar; @@ -3941,7 +3975,8 @@ expr * theory_str::gen_len_val_options_for_free_var(expr * freeVar, expr * lenTe SASSERT(lenTestAssert != NULL); return lenTestAssert; } else { - TRACE("t_str_detail", tout << "found previous length assertions" << std::endl;); + TRACE("t_str_detail", tout << "found previous in-scope length assertions" << std::endl;); + expr * effectiveLenInd = NULL; std::string effectiveLenIndiStr = ""; int lenTesterCount = (int) fvar_lenTester_map[freeVar].size(); @@ -3949,6 +3984,11 @@ expr * theory_str::gen_len_val_options_for_free_var(expr * freeVar, expr * lenTe int i = 0; for (; i < lenTesterCount; ++i) { expr * len_indicator_pre = fvar_lenTester_map[freeVar][i]; + // check whether this is in scope as well + if (internal_variable_set.find(len_indicator_pre) == internal_variable_set.end()) { + continue; + } + bool indicatorHasEqcValue = false; expr * len_indicator_value = get_eqc_value(len_indicator_pre, indicatorHasEqcValue); TRACE("t_str_detail", tout << "length indicator " << mk_ismt2_pp(len_indicator_pre, m) << diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index ce4a0cfc9..f126ca019 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -63,9 +63,9 @@ namespace smt { }; protected: bool search_started; - int sLevel; arith_util m_autil; str_util m_strutil; + int sLevel; str_value_factory * m_factory; @@ -75,6 +75,8 @@ namespace smt { int tmpStringVarCount; int tmpXorVarCount; + int tmpLenTestVarCount; + int tmpValTestVarCount; std::map, std::map > varForBreakConcat; bool avoidLoopCut; From 953a4c5437c5a5d1a2be60e68991f0f15a0b49a2 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 2 Dec 2015 20:48:15 -0500 Subject: [PATCH 072/410] add temporary variables to m_trail --- src/smt/theory_str.cpp | 8 +++++--- src/smt/theory_str.h | 3 +++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 3c6261243..9d0f9d689 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -30,6 +30,7 @@ theory_str::theory_str(ast_manager & m): m_autil(m), m_strutil(m), sLevel(0), + m_trail(m), tmpStringVarCount(0), tmpXorVarCount(0), tmpLenTestVarCount(0), @@ -357,9 +358,8 @@ app * theory_str::mk_internal_xor_var() { strcpy(new_buffer, name.c_str()); symbol sym(new_buffer); - app* a = m.mk_const(m.mk_const_decl(sym, int_sort)); - - // TODO ctx.save_ast_trail(a)? + app * a = m.mk_const(m.mk_const_decl(sym, int_sort)); + m_trail.push_back(a); return a; } @@ -382,6 +382,7 @@ app * theory_str::mk_str_var(std::string name) { SASSERT(ctx.e_internalized(a)); m_basicstr_axiom_todo.push_back(ctx.get_enode(a)); + m_trail.push_back(a); variable_set.insert(a); internal_variable_set.insert(a); track_variable_scope(a); @@ -424,6 +425,7 @@ app * theory_str::mk_nonempty_str_var() { } // add 'a' to variable sets, so we can keep track of it + m_trail.push_back(a); variable_set.insert(a); internal_variable_set.insert(a); track_variable_scope(a); diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index f126ca019..ca985cb8f 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -67,6 +67,9 @@ namespace smt { str_util m_strutil; int sLevel; + // TODO make sure that all generated expressions are saved into the trail + expr_ref_vector m_trail; // trail for generated terms + str_value_factory * m_factory; ptr_vector m_basicstr_axiom_todo; From 23150d3b5e81064f7717835457ef2689b28aebe2 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 2 Dec 2015 22:03:12 -0500 Subject: [PATCH 073/410] never ever ever reuse constants in mk_string(). this gets us MUCH farther --- src/ast/str_decl_plugin.cpp | 3 ++- src/smt/theory_str.cpp | 30 ++++++++++++++---------------- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/ast/str_decl_plugin.cpp b/src/ast/str_decl_plugin.cpp index 5589db56c..550789065 100644 --- a/src/ast/str_decl_plugin.cpp +++ b/src/ast/str_decl_plugin.cpp @@ -101,7 +101,8 @@ func_decl * str_decl_plugin::mk_func_decl(decl_kind k, unsigned num_parameters, app * str_decl_plugin::mk_string(std::string & val) { std::map::iterator it = string_cache.find(val); - if (it == string_cache.end()) { + //if (it == string_cache.end()) { + if (true) { char * new_buffer = alloc_svect(char, (val.length() + 1)); strcpy(new_buffer, val.c_str()); parameter p[1] = {parameter(new_buffer)}; diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 9d0f9d689..9b9cc8fd9 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -300,7 +300,8 @@ app * theory_str::mk_int(int n) { return m_autil.mk_numeral(rational(n), true); } -// We have to work a little bit harder to ensure that all variables we create here are always fresh. + +// TODO refactor all of these so that they don't use variable counters, but use ast_manager::mk_fresh_const instead expr * theory_str::mk_internal_lenTest_var(expr * node, int lTries) { ast_manager & m = get_manager(); @@ -369,12 +370,8 @@ app * theory_str::mk_str_var(std::string name) { TRACE("t_str_detail", tout << "creating string variable " << name << " at scope level " << sLevel << std::endl;); - sort * string_sort = m.mk_sort(m_strutil.get_family_id(), STRING_SORT); - char * new_buffer = alloc_svect(char, name.length() + 1); - strcpy(new_buffer, name.c_str()); - symbol sym(new_buffer); - - app * a = m.mk_const(m.mk_const_decl(sym, string_sort)); + sort * string_sort = m.mk_sort(get_family_id(), STRING_SORT); + app * a = m.mk_fresh_const(name.c_str(), string_sort); // I have a hunch that this may not get internalized for free... ctx.internalize(a, false); @@ -401,12 +398,9 @@ app * theory_str::mk_nonempty_str_var() { TRACE("t_str_detail", tout << "creating nonempty string variable " << name << " at scope level " << sLevel << std::endl;); - sort * string_sort = m.mk_sort(m_strutil.get_family_id(), STRING_SORT); - char * new_buffer = alloc_svect(char, name.length() + 1); - strcpy(new_buffer, name.c_str()); - symbol sym(new_buffer); + sort * string_sort = m.mk_sort(get_family_id(), STRING_SORT); + app * a = m.mk_fresh_const(name.c_str(), string_sort); - app* a = m.mk_const(m.mk_const_decl(sym, string_sort)); ctx.internalize(a, false); SASSERT(ctx.get_enode(a) != NULL); // assert a variation of the basic string axioms that ensures this string is nonempty @@ -3843,7 +3837,7 @@ expr * theory_str::gen_len_test_options(expr * freeVar, expr * indicator, int tr TRACE("t_str_detail", tout << "entry" << std::endl;); - expr * freeVarLen = mk_strlen(freeVar); + expr_ref freeVarLen(mk_strlen(freeVar), m); SASSERT(freeVarLen); ptr_vector orList; @@ -3856,7 +3850,10 @@ expr * theory_str::gen_len_test_options(expr * freeVar, expr * indicator, int tr TRACE("t_str_detail", tout << "building andList and orList" << std::endl;); for (int i = l; i < h; ++i) { - expr * or_expr = m.mk_eq(indicator, m_strutil.mk_string(int_to_string(i).c_str())); + std::string i_str = int_to_string(i); + expr_ref str_indicator(m_strutil.mk_string(i_str), m); + TRACE("t_str_detail", tout << "just created a string term: " << mk_ismt2_pp(str_indicator, m) << std::endl;); + expr * or_expr = m.mk_eq(indicator, str_indicator); // ARGUMENT 2 IS BOGUS! WRONG SORT TRACE("t_str_detail", tout << "or_expr = " << mk_ismt2_pp(or_expr, m) << std::endl;); orList.push_back(or_expr); @@ -3868,6 +3865,7 @@ expr * theory_str::gen_len_test_options(expr * freeVar, expr * indicator, int tr orList.push_back(m.mk_eq(indicator, m_strutil.mk_string("more"))); andList.push_back(m.mk_eq(orList[orList.size() - 1], m_autil.mk_ge(freeVarLen, mk_int(h)))); + // TODO refactor this to use expr_ref_vector/svector/buffer instead expr ** or_items = alloc_svect(expr*, orList.size()); expr ** and_items = alloc_svect(expr*, andList.size() + 1); @@ -3965,8 +3963,8 @@ expr * theory_str::gen_len_val_options_for_free_var(expr * freeVar, expr * lenTe fvar_len_count_map[freeVar] = 1; unsigned int testNum = fvar_len_count_map[freeVar]; - expr * indicator = mk_internal_lenTest_var(freeVar, testNum); - SASSERT(indicator != NULL); + expr_ref indicator(mk_internal_lenTest_var(freeVar, testNum), m); + SASSERT(indicator); // since the map is "effectively empty", we can remove those variables that have left scope... fvar_lenTester_map[freeVar].shrink(0); From 1a15b3937deb367adac5c64dae48e9e1298a6b5d Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 2 Dec 2015 22:09:30 -0500 Subject: [PATCH 074/410] in_same_eqc() now checks to ensure both terms are internalized before doing anything else --- src/smt/theory_str.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 9b9cc8fd9..62100cfcd 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -1999,6 +1999,20 @@ expr * theory_str::get_eqc_value(expr * n, bool & hasEqcValue) { bool theory_str::in_same_eqc(expr * n1, expr * n2) { if (n1 == n2) return true; context & ctx = get_context(); + ast_manager & m = get_manager(); + + // similar to get_eqc_value(), make absolutely sure + // that we've set this up properly for the context + + if (!ctx.e_internalized(n1)) { + TRACE("t_str_detail", tout << "WARNING: expression " << mk_ismt2_pp(n1, m) << " was not internalized" << std::endl;); + ctx.internalize(n1, false); + } + if (!ctx.e_internalized(n2)) { + TRACE("t_str_detail", tout << "WARNING: expression " << mk_ismt2_pp(n2, m) << " was not internalized" << std::endl;); + ctx.internalize(n2, false); + } + enode * n1Node = ctx.get_enode(n1); enode * n2Node = ctx.get_enode(n2); From f5e94af784b19e021e367c688d295298abf214b8 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 2 Dec 2015 22:15:04 -0500 Subject: [PATCH 075/410] check that both simplified expressions are concats in simplify_concat_equality() this seems to fix all the crashes but the solver takes forever to solve a really simple instance with easy model generation, so I think something is still wrong probably next I will go through and change std::map to obj_map, etc. --- src/smt/theory_str.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 62100cfcd..f6187c7c1 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -922,10 +922,6 @@ void theory_str::simplify_concat_equality(expr * nn1, expr * nn2) { expr * new_nn2 = simplify_concat(nn2); app * a_new_nn1 = to_app(new_nn1); app * a_new_nn2 = to_app(new_nn2); - expr * v1_arg0 = a_new_nn1->get_arg(0); - expr * v1_arg1 = a_new_nn1->get_arg(1); - expr * v2_arg0 = a_new_nn2->get_arg(0); - expr * v2_arg1 = a_new_nn2->get_arg(1); TRACE("t_str_detail", tout << "new_nn1 = " << mk_ismt2_pp(new_nn1, m) << std::endl << "new_nn2 = " << mk_ismt2_pp(new_nn2, m) << std::endl;); @@ -960,6 +956,13 @@ void theory_str::simplify_concat_equality(expr * nn1, expr * nn2) { return; } + // TODO what happens if BOTH of these are simplified into non-concat terms? + + expr * v1_arg0 = a_new_nn1->get_arg(0); + expr * v1_arg1 = a_new_nn1->get_arg(1); + expr * v2_arg0 = a_new_nn2->get_arg(0); + expr * v2_arg1 = a_new_nn2->get_arg(1); + if (!in_same_eqc(new_nn1, new_nn2) && (nn1 != new_nn1 || nn2 != new_nn2)) { int ii4 = 0; expr* item[3]; From e010e7c0d606a4059bf8d4fd56777720679402f9 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 2 Dec 2015 23:35:26 -0500 Subject: [PATCH 076/410] add trace message to indicate which free variables are giving us trouble I think I'm onto the issue though --- src/smt/theory_str.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index f6187c7c1..a5584efd8 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -532,6 +532,8 @@ void theory_str::instantiate_concat_axiom(enode * cat) { ast_manager & m = get_manager(); + TRACE("t_str_detail", tout << "instantiating concat axiom for " << mk_ismt2_pp(a_cat, m) << std::endl;); + // build LHS expr_ref len_xy(m); // TODO should we use str_util for these and other expressions? @@ -560,7 +562,6 @@ void theory_str::instantiate_concat_axiom(enode * cat) { // finally assert equality between the two subexpressions app * eq = m.mk_eq(len_xy, len_x_plus_len_y); SASSERT(eq); - TRACE("t_str", tout << mk_ismt2_pp(eq, m) << std::endl;); assert_axiom(eq); } @@ -3443,6 +3444,13 @@ final_check_status theory_str::final_check_eh() { return FC_DONE; } + CTRACE("t_str", needToAssignFreeVars, + tout << "Need to assign values to the following free variables:" << std::endl; + for (std::set::iterator itx = free_variables.begin(); itx != free_variables.end(); ++itx) { + tout << mk_ismt2_pp(*itx, m) << std::endl; + } + ); + // ----------------------------------------------------------- // variables in freeVar are those not bounded by Concats // classify variables in freeVarMap: From cf5eacbf332d175bad5b46b9739016814a3991f3 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Thu, 3 Dec 2015 20:58:54 -0500 Subject: [PATCH 077/410] successful run of model generation test case, after assigning all internal variables a bogus value if they are unused --- src/smt/theory_str.cpp | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index a5584efd8..fb74f4c40 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -3428,20 +3428,37 @@ final_check_status theory_str::final_check_eh() { // If not, mark it as free. bool needToAssignFreeVars = false; std::set free_variables; + std::set unused_internal_variables; TRACE("t_str_detail", tout << variable_set.size() << " variables in variable_set" << std::endl;); for (std::set::iterator it = variable_set.begin(); it != variable_set.end(); ++it) { TRACE("t_str_detail", tout << "checking eqc of variable " << mk_ismt2_pp(*it, m) << std::endl;); bool has_eqc_value = false; get_eqc_value(*it, has_eqc_value); if (!has_eqc_value) { - needToAssignFreeVars = true; - free_variables.insert(*it); + // if this is an internal variable, it can be ignored...I think + if (internal_variable_set.find(*it) != internal_variable_set.end()) { + TRACE("t_str_detail", tout << "WARNING: free internal variable " << mk_ismt2_pp(*it, m) << std::endl;); + unused_internal_variables.insert(*it); + } else { + needToAssignFreeVars = true; + free_variables.insert(*it); + } } } if (!needToAssignFreeVars) { - TRACE("t_str", tout << "All variables are assigned. Done!" << std::endl;); - return FC_DONE; + if (unused_internal_variables.empty()) { + TRACE("t_str", tout << "All variables are assigned. Done!" << std::endl;); + return FC_DONE; + } else { + TRACE("t_str", tout << "Assigning decoy values to free internal variables." << std::endl;); + for (std::set::iterator it = unused_internal_variables.begin(); it != unused_internal_variables.end(); ++it) { + expr * var = *it; + expr_ref assignment(m.mk_eq(var, m_strutil.mk_string("**unused**")), m); + assert_axiom(assignment); + } + return FC_CONTINUE; + } } CTRACE("t_str", needToAssignFreeVars, @@ -4218,7 +4235,7 @@ void theory_str::get_eqc_allUnroll(expr * n, expr * &constStr, std::set & } void theory_str::init_model(model_generator & mg) { - TRACE("t_str", tout << "initializing model" << std::endl; display(tout);); + //TRACE("t_str", tout << "initializing model" << std::endl; display(tout);); m_factory = alloc(str_value_factory, get_manager(), get_family_id()); mg.register_factory(m_factory); } @@ -4287,7 +4304,7 @@ model_value_proc * theory_str::mk_value(enode * n, model_generator & mg) { TRACE("t_str", tout << "WARNING: failed to find a concrete value, falling back" << std::endl;); // TODO make absolutely sure the reason we can't find a concrete value is because of an unassigned temporary // e.g. for an expression like (Concat X $$_str0) - //return alloc(expr_wrapper_proc, m_strutil.mk_string("")); + //return alloc(expr_wrapper_proc, m_strutil.mk_string("**UNUSED**")); NOT_IMPLEMENTED_YET(); } } From a2d0299621c2c7856329ea32c26aa264ed8ff2c3 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sat, 7 May 2016 14:19:12 -0400 Subject: [PATCH 078/410] call super in push and pop --- src/smt/theory_str.cpp | 26 ++++---------------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index fb74f4c40..61488638c 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -191,26 +191,6 @@ static void cut_vars_map_copy(std::map & dest, std::map } } -/* -bool hasSelfCut(Z3_ast n1, Z3_ast n2) { - if (cut_VARMap.find(n1) == cut_VARMap.end()) - return false; - - if (cut_VARMap.find(n2) == cut_VARMap.end()) - return false; - - if (cut_VARMap[n1].empty() || cut_VARMap[n2].empty()) - return false; - - std::map::iterator itor = cut_VARMap[n1].top()->vars.begin(); - for (; itor != cut_VARMap[n1].top()->vars.end(); itor++) { - if (cut_VARMap[n2].top()->vars.find(itor->first) != cut_VARMap[n2].top()->vars.end()) - return true; - } - return false; -} -*/ - bool theory_str::has_self_cut(expr * n1, expr * n2) { if (cut_var_map.find(n1) == cut_var_map.end()) { return false; @@ -2653,9 +2633,11 @@ void theory_str::assign_eh(bool_var v, bool is_true) { } void theory_str::push_scope_eh() { + theory::push_scope_eh(); context & ctx = get_context(); sLevel += 1; TRACE("t_str", tout << "push to " << sLevel << std::endl;); + TRACE("t_str_dump_assign", dump_assignments();); } void theory_str::pop_scope_eh(unsigned num_scopes) { @@ -2692,7 +2674,7 @@ void theory_str::pop_scope_eh(unsigned num_scopes) { vars.clear(); } } - + theory::pop_scope_eh(num_scopes); } void theory_str::dump_assignments() { @@ -3128,7 +3110,7 @@ int theory_str::ctx_dep_analysis(std::map & strVarMap, std::map v2, v5 are constrained by "str" - // - possibliity 2 : concat(v1, "str") = concat(v2, v3) = concat(v4, v5) + // - possibility 2 : concat(v1, "str") = concat(v2, v3) = concat(v4, v5) // ==> v2, v4 are constrained by "str" //-------------------------------------------------------------- From 1d324877cdf16e6547ef0a909a491e9e99ff3cef Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sat, 7 May 2016 15:40:39 -0400 Subject: [PATCH 079/410] use theory_seq's internalize_term --- src/smt/theory_str.cpp | 67 +++++++++++++++++++++++++++++++++++++++--- src/smt/theory_str.h | 4 ++- 2 files changed, 66 insertions(+), 5 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 61488638c..fc9a7f3d5 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -139,6 +139,7 @@ void theory_str::assert_implication(expr * premise, expr * conclusion) { } bool theory_str::internalize_atom(app * atom, bool gate_ctx) { + /* TRACE("t_str", tout << "internalizing atom: " << mk_ismt2_pp(atom, get_manager()) << std::endl;); SASSERT(atom->get_family_id() == get_family_id()); @@ -152,15 +153,21 @@ bool theory_str::internalize_atom(app * atom, bool gate_ctx) { ctx.internalize(atom->get_arg(i), false); literal l(ctx.mk_bool_var(atom)); + ctx.set_var_theory(l.var(), get_id()); return true; + */ + return internalize_term(atom); } bool theory_str::internalize_term(app * term) { context & ctx = get_context(); + ast_manager & m = get_manager(); TRACE("t_str", tout << "internalizing term: " << mk_ismt2_pp(term, get_manager()) << std::endl;); SASSERT(term->get_family_id() == get_family_id()); + + /* // what I had before SASSERT(!ctx.e_internalized(term)); unsigned num_args = term->get_num_args(); @@ -175,15 +182,67 @@ bool theory_str::internalize_term(app * term) { attach_new_th_var(e); - /* - if (is_concat(term)) { - instantiate_concat_axiom(e); - } + //if (is_concat(term)) { + // instantiate_concat_axiom(e); + //} */ + // from theory_seq::internalize_term() + if (ctx.e_internalized(term)) { + enode* e = ctx.get_enode(term); + mk_var(e); + return true; + } + unsigned num_args = term->get_num_args(); + expr* arg; + for (unsigned i = 0; i < num_args; i++) { + arg = term->get_arg(i); + mk_var(ensure_enode(arg)); + } + 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* e = 0; + if (ctx.e_internalized(term)) { + e = ctx.get_enode(term); + } + else { + e = ctx.mk_enode(term, false, m.is_bool(term), true); + } + mk_var(e); + return true; } +enode* theory_str::ensure_enode(expr* e) { + context& ctx = get_context(); + if (!ctx.e_internalized(e)) { + ctx.internalize(e, false); + } + enode* n = ctx.get_enode(e); + ctx.mark_as_relevant(n); + return n; +} + +theory_var theory_str::mk_var(enode* n) { + if (!m_strutil.is_string(n->get_owner())) { + return null_theory_var; + } + if (is_attached_to_var(n)) { + return n->get_th_var(get_id()); + } + else { + theory_var v = theory::mk_var(n); + // m_find.mk_var(); + get_context().attach_th_var(n, this, v); + get_context().mark_as_relevant(n); + return v; + } +} + static void cut_vars_map_copy(std::map & dest, std::map & src) { std::map::iterator itor = src.begin(); for (; itor != src.end(); itor++) { diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index ca985cb8f..af2ea1db6 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -208,6 +208,8 @@ namespace smt { protected: virtual bool internalize_atom(app * atom, bool gate_ctx); virtual bool internalize_term(app * term); + virtual enode* ensure_enode(expr* e); + virtual theory_var mk_var(enode * n); virtual void new_eq_eh(theory_var, theory_var); virtual void new_diseq_eh(theory_var, theory_var); @@ -224,7 +226,7 @@ namespace smt { virtual void propagate(); virtual final_check_status final_check_eh(); - void attach_new_th_var(enode * n); + virtual void attach_new_th_var(enode * n); virtual void init_model(model_generator & m); virtual model_value_proc * mk_value(enode * n, model_generator & mg); From 6dfc2dd9100a39f705000c54f80930f19d65a08c Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sat, 7 May 2016 17:16:31 -0400 Subject: [PATCH 080/410] variables of sort String should now correctly be identified as Very Relevant to the string solver --- src/smt/theory_str.cpp | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index fc9a7f3d5..37b68e48c 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -212,7 +212,8 @@ bool theory_str::internalize_term(app * term) { else { e = ctx.mk_enode(term, false, m.is_bool(term), true); } - mk_var(e); + theory_var v = mk_var(e); + TRACE("t_str_detail", tout << "term " << mk_ismt2_pp(term, get_manager()) << " = v#" << v << std::endl;); return true; } @@ -228,9 +229,16 @@ enode* theory_str::ensure_enode(expr* e) { } theory_var theory_str::mk_var(enode* n) { + /* if (!m_strutil.is_string(n->get_owner())) { return null_theory_var; } + */ + // TODO this may require an overhaul of m_strutil.is_string() if things suddenly start working after the following change: + ast_manager & m = get_manager(); + if (!(is_sort_of(m.get_sort(n->get_owner()), m_strutil.get_fid(), STRING_SORT))) { + return null_theory_var; + } if (is_attached_to_var(n)) { return n->get_th_var(get_id()); } @@ -416,6 +424,8 @@ app * theory_str::mk_str_var(std::string name) { ctx.internalize(a, false); SASSERT(ctx.get_enode(a) != NULL); SASSERT(ctx.e_internalized(a)); + // this might help?? + mk_var(ctx.get_enode(a)); m_basicstr_axiom_todo.push_back(ctx.get_enode(a)); m_trail.push_back(a); @@ -442,6 +452,9 @@ app * theory_str::mk_nonempty_str_var() { ctx.internalize(a, false); SASSERT(ctx.get_enode(a) != NULL); + // this might help?? + mk_var(ctx.get_enode(a)); + // assert a variation of the basic string axioms that ensures this string is nonempty { // build LHS @@ -2496,11 +2509,11 @@ void theory_str::handle_equality(expr * lhs, expr * rhs) { ); // step 1: Concat == Concat - // I'm disabling this entire code block for now. It may no longer be useful. + // This code block may no longer be useful. // Z3 seems to be putting LHS and RHS into the same equivalence class extremely early. // As a result, simplify_concat_equality() is never getting called, // and if it were called, it would probably get called with the same element on both sides. - /* + bool hasCommon = false; if (eqc_lhs_concat.size() != 0 && eqc_rhs_concat.size() != 0) { std::set::iterator itor1 = eqc_lhs_concat.begin(); @@ -2521,7 +2534,7 @@ void theory_str::handle_equality(expr * lhs, expr * rhs) { simplify_concat_equality(*(eqc_lhs_concat.begin()), *(eqc_rhs_concat.begin())); } } - */ + if (eqc_lhs_concat.size() != 0 && eqc_rhs_concat.size() != 0) { // let's pick the first concat in the LHS's eqc // and find some concat in the RHS's eqc that is @@ -2591,6 +2604,10 @@ void theory_str::set_up_axioms(expr * ex) { // if ex is a variable, add it to our list of variables TRACE("t_str_detail", tout << "tracking variable " << mk_ismt2_pp(ap, get_manager()) << std::endl;); variable_set.insert(ex); + ctx.mark_as_relevant(ex); + // this might help?? + theory_var v = mk_var(n); + TRACE("t_str_detail", tout << "variable " << mk_ismt2_pp(ap, get_manager()) << " is #" << v << std::endl;); } } } else { @@ -2637,7 +2654,7 @@ void theory_str::init_search_eh() { * This is done to find equalities between terms, etc. that we otherwise * might not get a chance to see. */ - /* + expr_ref_vector assignments(m); ctx.get_assignments(assignments); for (expr_ref_vector::iterator i = assignments.begin(); i != assignments.end(); ++i) { @@ -2659,7 +2676,6 @@ void theory_str::init_search_eh() { << ": expr ignored" << std::endl;); } } - */ TRACE("t_str", tout << "search started" << std::endl;); search_started = true; From bcaad06061b2981669c84770462192d0886791e3 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sat, 7 May 2016 17:47:50 -0400 Subject: [PATCH 081/410] add theory name; add debug info for freeVar_map --- src/smt/theory_str.cpp | 11 +++++++---- src/smt/theory_str.h | 2 ++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 37b68e48c..3903508ea 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -2655,6 +2655,7 @@ void theory_str::init_search_eh() { * might not get a chance to see. */ + /* expr_ref_vector assignments(m); ctx.get_assignments(assignments); for (expr_ref_vector::iterator i = assignments.begin(); i != assignments.end(); ++i) { @@ -2676,6 +2677,7 @@ void theory_str::init_search_eh() { << ": expr ignored" << std::endl;); } } + */ TRACE("t_str", tout << "search started" << std::endl;); search_started = true; @@ -2686,16 +2688,12 @@ void theory_str::new_eq_eh(theory_var x, theory_var y) { TRACE("t_str", tout << "new eq: " << mk_ismt2_pp(get_enode(x)->get_owner(), get_manager()) << " = " << mk_ismt2_pp(get_enode(y)->get_owner(), get_manager()) << std::endl;); handle_equality(get_enode(x)->get_owner(), get_enode(y)->get_owner()); - - TRACE("t_str_dump_assign", dump_assignments();); } void theory_str::new_diseq_eh(theory_var x, theory_var y) { //TRACE("t_str_detail", tout << "new diseq: v#" << x << " != v#" << y << std::endl;); TRACE("t_str", tout << "new diseq: " << mk_ismt2_pp(get_enode(x)->get_owner(), get_manager()) << " != " << mk_ismt2_pp(get_enode(y)->get_owner(), get_manager()) << std::endl;); - - TRACE("t_str_dump_assign", dump_assignments();); } void theory_str::relevant_eh(app * n) { @@ -3523,6 +3521,11 @@ final_check_status theory_str::final_check_eh() { for (std::set::iterator itx = free_variables.begin(); itx != free_variables.end(); ++itx) { tout << mk_ismt2_pp(*itx, m) << std::endl; } + tout << "freeVar_map has the following entries:" << std::endl; + for (std::map::iterator fvIt = freeVar_map.begin(); fvIt != freeVar_map.end(); fvIt++) { + expr * var = fvIt->first; + tout << mk_ismt2_pp(var, m) << std::endl; + } ); // ----------------------------------------------------------- diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index af2ea1db6..9d56c01fe 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -205,6 +205,8 @@ namespace smt { public: theory_str(ast_manager & m); virtual ~theory_str(); + + virtual char const * get_name() const { return "strings"; } protected: virtual bool internalize_atom(app * atom, bool gate_ctx); virtual bool internalize_term(app * term); From f9e1ed4496a859a771bdb100a34dc2c0cf533f8a Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Mon, 9 May 2016 18:12:21 -0400 Subject: [PATCH 082/410] add simplify_parent() --- src/ast/str_decl_plugin.cpp | 8 + src/ast/str_decl_plugin.h | 2 + src/smt/theory_str.cpp | 359 +++++++++++++++++++++++++++++++++++- src/smt/theory_str.h | 1 + 4 files changed, 368 insertions(+), 2 deletions(-) diff --git a/src/ast/str_decl_plugin.cpp b/src/ast/str_decl_plugin.cpp index 550789065..cd9cae5a5 100644 --- a/src/ast/str_decl_plugin.cpp +++ b/src/ast/str_decl_plugin.cpp @@ -169,6 +169,14 @@ bool str_recognizers::is_string(expr const * n) const { return is_string(n, & tmp); } +std::string str_recognizers::get_string_constant_value(expr const *n) const { + const char * cstr = 0; + bool isString = is_string(n, & cstr); + SASSERT(isString); + std::string strval(cstr); + return strval; +} + str_util::str_util(ast_manager &m) : str_recognizers(m.mk_family_id(symbol("str"))), m_manager(m) { diff --git a/src/ast/str_decl_plugin.h b/src/ast/str_decl_plugin.h index f1978ab8b..4f46fa5ac 100644 --- a/src/ast/str_decl_plugin.h +++ b/src/ast/str_decl_plugin.h @@ -81,6 +81,8 @@ public: bool is_string(expr const * n, const char ** val) const; bool is_string(expr const * n) const; + + std::string get_string_constant_value(expr const *n) const; // TODO }; diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 3903508ea..254d32141 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -797,6 +797,34 @@ void theory_str::get_nodes_in_concat(expr * node, ptr_vector & nodeList) { } } +// previously Concat() in strTheory.cpp +// Evaluates the concatenation (n1 . n2) with respect to +// the current equivalence classes of n1 and n2. +// Returns a constant string expression representing this concatenation +// if one can be determined, or NULL if this is not possible. +expr * theory_str::eval_concat(expr * n1, expr * n2) { + bool n1HasEqcValue = false; + bool n2HasEqcValue = false; + expr * v1 = get_eqc_value(n1, n1HasEqcValue); + expr * v2 = get_eqc_value(n2, n2HasEqcValue); + if (n1HasEqcValue && n2HasEqcValue) { + std::string n1_str = m_strutil.get_string_constant_value(v1); + std::string n2_str = m_strutil.get_string_constant_value(v2); + std::string result = n1_str + n2_str; + return m_strutil.mk_string(result); + } else if (n1HasEqcValue && !n2HasEqcValue) { + if (m_strutil.get_string_constant_value(v1) == "") { + return n2; + } + } else if (n2HasEqcValue && !n1HasEqcValue) { + if (m_strutil.get_string_constant_value(v2) == "") { + return n1; + } + } + // give up + return NULL; +} + /* * The inputs: * ~ nn: non const node @@ -806,8 +834,298 @@ void theory_str::get_nodes_in_concat(expr * node, ptr_vector & nodeList) { * to see whether some concat nodes can be simplified. */ +// TODO NEXT complete this method! void theory_str::simplify_parent(expr * nn, expr * eq_str) { - // TODO strTheory::simplifyParent() + ast_manager & m = get_manager(); + context & ctx = get_context(); + + TRACE("t_str", tout << "simplifying parents of " << mk_ismt2_pp(nn, m) + << " with respect to " << mk_ismt2_pp(eq_str, m) << std::endl;); + + ctx.internalize(nn, false); + enode * n_eq_enode = ctx.get_enode(nn); + enode * nn_enode = n_eq_enode; + + const char * tmp = 0; + m_strutil.is_string(eq_str, & tmp); + std::string eq_strValue(tmp); + + do { + app * n_eqNode = n_eq_enode->get_owner(); + for (enode_vector::iterator parent_it = n_eq_enode->begin_parents(); parent_it != n_eq_enode->end_parents(); parent_it++) { + enode * e_parent = *parent_it; + app * a_parent = e_parent->get_owner(); + TRACE("t_str_detail", tout << "considering parent " << mk_ismt2_pp(a_parent, m) << std::endl;); + + if (is_concat(a_parent)) { + expr * arg0 = a_parent->get_arg(0); + expr * arg1 = a_parent->get_arg(1); + + // TODO getLenValue() + // int parentLen = getLenValue(a_parent) + int parentLen = -1; + if (arg0 == n_eq_enode->get_owner()) { + // TODO getLenValue() + // int arg0Len = getLenValue(eq_str); + // int arg1Len = getLenValue(arg1); + int arg0Len = -1; + int arg1Len = -1; + + TRACE("t_str_detail", + tout << "simplify_parent #1:" << std::endl + << "* parent = " << mk_ismt2_pp(a_parent, m) << std::endl + << "* |parent| = " << parentLen << std::endl + << "* |arg0| = " << arg0Len << std::endl + << "* |arg1| = " << arg1Len << std::endl; + ); + + if (parentLen != -1 && arg1Len == -1) { + // TODO after getLenValue() above + /* + Z3_ast implyL11 = mk_2_and(t, Z3_mk_eq(ctx, mk_length(t, parent), mk_int(ctx, parentLen)), + Z3_mk_eq(ctx, mk_length(t, arg0), mk_int(ctx, arg0Len))); + int makeUpLenArg1 = parentLen - arg0Len; + Z3_ast lenAss = NULL; + if (makeUpLenArg1 >= 0) { + Z3_ast implyR11 = Z3_mk_eq(ctx, mk_length(t, arg1), mk_int(ctx, makeUpLenArg1)); + lenAss = Z3_mk_implies(ctx, implyL11, implyR11); + } else { + lenAss = Z3_mk_not(ctx, implyL11); + } + addAxiom(t, lenAss, __LINE__); + */ + } + + // (Concat n_eqNode arg1) /\ arg1 has eq const + + expr * concatResult = eval_concat(eq_str, arg1); + if (concatResult != NULL) { + bool arg1HasEqcValue = false; + expr * arg1Value = get_eqc_value(arg1, arg1HasEqcValue); + expr_ref implyL(m); + if (arg1 != arg1Value) { + expr_ref eq_ast1(m); + eq_ast1 = ctx.mk_eq_atom(n_eqNode, eq_str); + SASSERT(eq_ast1); + + expr_ref eq_ast2(m); + eq_ast2 = ctx.mk_eq_atom(arg1, arg1Value); + SASSERT(eq_ast2); + + implyL = m.mk_and(eq_ast1, eq_ast2); + } else { + implyL = ctx.mk_eq_atom(n_eqNode, eq_str); + } + + + if (!in_same_eqc(a_parent, concatResult)) { + expr_ref implyR(m); + implyR = ctx.mk_eq_atom(a_parent, concatResult); + SASSERT(implyR); + + assert_implication(implyL, implyR); + } + } else if (is_concat(n_eqNode)) { + expr_ref simpleConcat(m); + simpleConcat = mk_concat(eq_str, arg1); + if (!in_same_eqc(a_parent, simpleConcat)) { + expr_ref implyL(m); + implyL = ctx.mk_eq_atom(n_eqNode, eq_str); + SASSERT(implyL); + + expr_ref implyR(m); + implyR = ctx.mk_eq_atom(a_parent, simpleConcat); + SASSERT(implyR); + assert_implication(implyL, implyR); + } + } + } // if (arg0 == n_eq_enode->get_owner()) + + if (arg1 == n_eq_enode->get_owner()) { + // TODO getLenValue() + // int arg0Len = getLenValue(arg0); + // int arg1Len = getLenValue(eq_str); + int arg0Len = -1; + int arg1Len = -1; + + TRACE("t_str_detail", + tout << "simplify_parent #2:" << std::endl + << "* parent = " << mk_ismt2_pp(a_parent, m) << std::endl + << "* |parent| = " << parentLen << std::endl + << "* |arg0| = " << arg0Len << std::endl + << "* |arg1| = " << arg1Len << std::endl; + ); + + if (parentLen != -1 && arg0Len == -1) { + // TODO after getLenValue() above + /* + Z3_ast implyL11 = mk_2_and(t, Z3_mk_eq(ctx, mk_length(t, parent), mk_int(ctx, parentLen)), + Z3_mk_eq(ctx, mk_length(t, arg1), mk_int(ctx, arg1Len))); + int makeUpLenArg0 = parentLen - arg1Len; + Z3_ast lenAss = NULL; + if (makeUpLenArg0 >= 0) { + Z3_ast implyR11 = Z3_mk_eq(ctx, mk_length(t, arg0), mk_int(ctx, makeUpLenArg0)); + lenAss = Z3_mk_implies(ctx, implyL11, implyR11); + } else { + lenAss = Z3_mk_not(ctx, implyL11); + } + addAxiom(t, lenAss, __LINE__); + */ + } + + // (Concat arg0 n_eqNode) /\ arg0 has eq const + + expr * concatResult = eval_concat(eq_str, arg1); + if (concatResult != NULL) { + bool arg0HasEqcValue = false; + expr * arg0Value = get_eqc_value(arg0, arg0HasEqcValue); + expr_ref implyL(m); + if (arg0 != arg0Value) { + expr_ref eq_ast1(m); + eq_ast1 = ctx.mk_eq_atom(n_eqNode, eq_str); + SASSERT(eq_ast1); + + expr_ref eq_ast2(m); + eq_ast2 = ctx.mk_eq_atom(arg0, arg0Value); + SASSERT(eq_ast2); + + implyL = m.mk_and(eq_ast1, eq_ast2); + } else { + implyL = ctx.mk_eq_atom(n_eqNode, eq_str); + } + + if (!in_same_eqc(a_parent, concatResult)) { + expr_ref implyR(m); + implyR = ctx.mk_eq_atom(a_parent, concatResult); + SASSERT(implyR); + + assert_implication(implyL, implyR); + } + } else if (is_concat(n_eqNode)) { + expr_ref simpleConcat(m); + simpleConcat = mk_concat(arg0, eq_str); + if (!in_same_eqc(a_parent, simpleConcat)) { + expr_ref implyL(m); + implyL = ctx.mk_eq_atom(n_eqNode, eq_str); + SASSERT(implyL); + + expr_ref implyR(m); + implyR = ctx.mk_eq_atom(a_parent, simpleConcat); + SASSERT(implyR); + assert_implication(implyL, implyR); + } + } + } // if (arg1 == n_eq_enode->get_owner + + + //--------------------------------------------------------- + // Case (2-1) begin: (Concat n_eqNode (Concat str var)) + if (arg0 == n_eqNode && is_concat(to_app(arg1))) { + app * a_arg1 = to_app(arg1); + TRACE("t_str_detail", tout << "simplify_parent #3" << std::endl;); + expr * r_concat_arg0 = a_arg1->get_arg(0); + if (m_strutil.is_string(r_concat_arg0)) { + expr * combined_str = eval_concat(eq_str, r_concat_arg0); + SASSERT(combined_str); + expr * r_concat_arg1 = a_arg1->get_arg(1); + expr_ref implyL(m); + implyL = ctx.mk_eq_atom(n_eqNode, eq_str); + expr * simplifiedAst = mk_concat(combined_str, r_concat_arg1); + if (!in_same_eqc(a_parent, simplifiedAst)) { + expr_ref implyR(m); + implyR = ctx.mk_eq_atom(a_parent, simplifiedAst); + assert_implication(implyL, implyR); + } + } + } + // Case (2-1) end: (Concat n_eqNode (Concat str var)) + //--------------------------------------------------------- + + + //--------------------------------------------------------- + // Case (2-2) begin: (Concat (Concat var str) n_eqNode) + if (is_concat(to_app(arg0)) && arg1 == n_eqNode) { + app * a_arg0 = to_app(arg0); + TRACE("t_str_detail", tout << "simplify_parent #4" << std::endl;); + expr * l_concat_arg1 = a_arg0->get_arg(1); + if (m_strutil.is_string(l_concat_arg1)) { + expr * combined_str = eval_concat(l_concat_arg1, eq_str); + SASSERT(combined_str); + expr * l_concat_arg0 = a_arg0->get_arg(0); + expr_ref implyL(m); + implyL = ctx.mk_eq_atom(n_eqNode, eq_str); + expr * simplifiedAst = mk_concat(l_concat_arg0, combined_str); + if (!in_same_eqc(a_parent, simplifiedAst)) { + expr_ref implyR(m); + implyR = ctx.mk_eq_atom(a_parent, simplifiedAst); + assert_implication(implyL, implyR); + } + } + } + // Case (2-2) end: (Concat (Concat var str) n_eqNode) + //--------------------------------------------------------- + + // Have to look up one more layer: if the parent of the concat is another concat + //------------------------------------------------- + // Case (3-1) begin: (Concat (Concat var n_eqNode) str ) + if (arg1 == n_eqNode) { + for (enode_vector::iterator concat_parent_it = e_parent->begin_parents(); + concat_parent_it != e_parent->end_parents(); concat_parent_it++) { + enode * e_concat_parent = *concat_parent_it; + app * concat_parent = e_concat_parent->get_owner(); + if (is_concat(concat_parent)) { + expr * concat_parent_arg0 = concat_parent->get_arg(0); + expr * concat_parent_arg1 = concat_parent->get_arg(1); + if (concat_parent_arg0 == a_parent && m_strutil.is_string(concat_parent_arg1)) { + TRACE("t_str_detail", tout << "simplify_parent #5" << std::endl;); + expr * combinedStr = eval_concat(eq_str, concat_parent_arg1); + SASSERT(combinedStr); + expr_ref implyL(m); + implyL = ctx.mk_eq_atom(n_eqNode, eq_str); + expr * simplifiedAst = mk_concat(arg0, combinedStr); + if (!in_same_eqc(concat_parent, simplifiedAst)) { + expr_ref implyR(m); + implyR = ctx.mk_eq_atom(concat_parent, simplifiedAst); + assert_implication(implyL, implyR); + } + } + } + } + } + // Case (3-1) end: (Concat (Concat var n_eqNode) str ) + // Case (3-2) begin: (Concat str (Concat n_eqNode var) ) + if (arg0 == n_eqNode) { + for (enode_vector::iterator concat_parent_it = e_parent->begin_parents(); + concat_parent_it != e_parent->end_parents(); concat_parent_it++) { + enode * e_concat_parent = *concat_parent_it; + app * concat_parent = e_concat_parent->get_owner(); + if (is_concat(concat_parent)) { + expr * concat_parent_arg0 = concat_parent->get_arg(0); + expr * concat_parent_arg1 = concat_parent->get_arg(1); + if (concat_parent_arg1 == a_parent && m_strutil.is_string(concat_parent_arg0)) { + TRACE("t_str_detail", tout << "simplify_parent #6" << std::endl;); + expr * combinedStr = eval_concat(concat_parent_arg0, eq_str); + SASSERT(combinedStr); + expr_ref implyL(m); + implyL = ctx.mk_eq_atom(n_eqNode, eq_str); + expr * simplifiedAst = mk_concat(combinedStr, arg1); + if (!in_same_eqc(concat_parent, simplifiedAst)) { + expr_ref implyR(m); + implyR = ctx.mk_eq_atom(concat_parent, simplifiedAst); + assert_implication(implyL, implyR); + } + } + } + } + } + // Case (3-2) end: (Concat str (Concat n_eqNode var) ) + } // if is_concat(a_parent) + } // for parent_it : n_eq_enode->begin_parents() + + + // check next EQC member + n_eq_enode = n_eq_enode->get_next(); + } while (n_eq_enode != nn_enode); } expr * theory_str::simplify_concat(expr * node) { @@ -2565,7 +2883,44 @@ void theory_str::handle_equality(expr * lhs, expr * rhs) { } } - // TODO simplify_parent over eqc + // simplify parents wrt. the equivalence class of both sides + // TODO this is slightly broken, re-enable it once some semantics have been fixed + // Briefly, Z3str2 expects that as this function is entered, + // lhs and rhs are NOT in the same equivalence class yet. + // However, newer versions of Z3 appear to behave differently, + // putting lhs and rhs into the same equivalence class + // *before* this function is called. + // Instead we do something possibly more aggressive here. + /* + bool lhs_has_eqc_value = false; + bool rhs_has_eqc_value = false; + expr * lhs_value = get_eqc_value(lhs, lhs_has_eqc_value); + expr * rhs_value = get_eqc_value(rhs, rhs_has_eqc_value); + if (lhs_has_eqc_value && !rhs_has_eqc_value) { + simplify_parent(rhs, lhs_value); + } + if (!lhs_has_eqc_value && rhs_has_eqc_value) { + simplify_parent(lhs, rhs_value); + } + */ + + bool lhs_has_eqc_value = false; + bool rhs_has_eqc_value = false; + expr * lhs_value = get_eqc_value(lhs, lhs_has_eqc_value); + expr * rhs_value = get_eqc_value(rhs, rhs_has_eqc_value); + + // TODO this depends on the old, possibly broken, semantics of is_string(). + // we explicitly want to test whether lhs/rhs is actually a string constant. + bool lhs_is_string_constant = m_strutil.is_string(lhs); + bool rhs_is_string_constant = m_strutil.is_string(rhs); + + + if (lhs_has_eqc_value && !rhs_is_string_constant) { + simplify_parent(rhs, lhs_value); + } + if (rhs_has_eqc_value && !lhs_is_string_constant) { + simplify_parent(lhs, rhs_value); + } // TODO regex unroll? (much later) } diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 9d56c01fe..e167beb18 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -195,6 +195,7 @@ namespace smt { expr * getMostLeftNodeInConcat(expr * node); expr * getMostRightNodeInConcat(expr * node); void get_var_in_eqc(expr * n, std::set & varSet); + expr * eval_concat(expr * n1, expr * n2); // strRegex From 9fc1410495bb6184606d603ba2f2eda104e23a9e Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 17 May 2016 14:53:17 -0400 Subject: [PATCH 083/410] remove incorrect not-null assertions for model gen --- src/smt/theory_str.cpp | 54 +++++++++++++++++++++++++++++++++++------- src/smt/theory_str.h | 2 ++ 2 files changed, 48 insertions(+), 8 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 254d32141..eace51dcb 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -2748,6 +2748,35 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { } } +bool theory_str::free_var_attempt(expr * nn1, expr * nn2) { + /* + Z3_context ctx = Z3_theory_get_context(t); + if (getNodeType(t, nn1) == my_Z3_Str_Var) { + std::string vName = std::string(Z3_ast_to_string(ctx, nn1)); + if (vName.length() >= 6) { + std::string vPrefix = vName.substr(0, 6); + // length attempts + if (vPrefix == "$$_len") { + if (getNodeType(t, nn2) == my_Z3_ConstStr) { + moreLenTests(t, nn1, getConstStrValue(t, nn2)); + } + return 1; + } + // value attempts + else if (vPrefix == "$$_val") { + if (getNodeType(t, nn2) == my_Z3_ConstStr && "more" == getConstStrValue(t, nn2)) { + moreValueTests(t, nn1, getConstStrValue(t, nn2)); + } + return 1; + } else if (vPrefix == "$$_uRt") { + return 1; + } + } + } + return 0; + */ +} + void theory_str::handle_equality(expr * lhs, expr * rhs) { ast_manager & m = get_manager(); context & ctx = get_context(); @@ -2761,8 +2790,6 @@ void theory_str::handle_equality(expr * lhs, expr * rhs) { return; } - // TODO freeVarAttempt()? - // TODO simplify concat? // newEqCheck() -- check consistency wrt. existing equivalence classes @@ -4245,6 +4272,8 @@ expr * theory_str::gen_free_var_options(expr * freeVar, expr * len_indicator, return gen_val_options(freeVar, len_indicator, val_indicator, len_valueStr, tries); } else { TRACE("t_str_detail", tout << "checking previous value testers" << std::endl;); + print_value_tester_list(fvar_valueTester_map[freeVar][len]); + // go through all previous value testers // If some doesn't have an eqc value, add its assertion again. int testerTotal = fvar_valueTester_map[freeVar][len].size(); @@ -4258,16 +4287,19 @@ expr * theory_str::gen_free_var_options(expr * freeVar, expr * len_indicator, bool anEqcHasValue = false; // Z3_ast anEqc = get_eqc_value(t, aTester, anEqcHasValue); - get_eqc_value(aTester, anEqcHasValue); + expr * aTester_eqc_value = get_eqc_value(aTester, anEqcHasValue); if (!anEqcHasValue) { TRACE("t_str_detail", tout << "value tester " << mk_ismt2_pp(aTester, m) - << "doesn't have an equivalence class value." << std::endl;); + << " doesn't have an equivalence class value." << std::endl;); expr * makeupAssert = gen_val_options(freeVar, len_indicator, aTester, len_valueStr, i); TRACE("t_str_detail", tout << "var: " << mk_ismt2_pp(freeVar, m) << std::endl << mk_ismt2_pp(makeupAssert, m) << std::endl;); assert_axiom(makeupAssert); + } else { + TRACE("t_str_detail", tout << "value tester " << mk_ismt2_pp(aTester, m) + << " == " << mk_ismt2_pp(aTester_eqc_value, m) << std::endl;); } } @@ -4506,6 +4538,7 @@ expr * theory_str::gen_len_val_options_for_free_var(expr * freeVar, expr * lenTe testNum = i + 1; } expr * lenTestAssert = gen_len_test_options(freeVar, indicator, testNum); + SASSERT(lenTestAssert != NULL); return lenTestAssert; } else { TRACE("t_str", tout << "length is fixed; generating models for free var" << std::endl;); @@ -4610,8 +4643,11 @@ void theory_str::process_free_var(std::map & freeVar_map) { for(std::set::iterator itor1 = leafVarSet.begin(); itor1 != leafVarSet.end(); ++itor1) { expr * toAssert = gen_len_val_options_for_free_var(*itor1, NULL, ""); - SASSERT(toAssert != NULL); - assert_axiom(toAssert); + // gen_len_val_options_for_free_var() can legally return NULL, + // as methods that it calls may assert their own axioms instead. + if (toAssert != NULL) { + assert_axiom(toAssert); + } } for (std::map >::iterator mItor = aloneVars.begin(); @@ -4619,8 +4655,10 @@ void theory_str::process_free_var(std::map & freeVar_map) { std::set::iterator itor2 = mItor->second.begin(); for(; itor2 != mItor->second.end(); ++itor2) { expr * toAssert = gen_len_val_options_for_free_var(*itor2, NULL, ""); - SASSERT(toAssert != NULL); - assert_axiom(toAssert); + // same deal with returning a NULL axiom here + if(toAssert != NULL) { + assert_axiom(toAssert); + } } } } diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index e167beb18..242b37747 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -191,6 +191,8 @@ namespace smt { bool get_next_val_encode(int_vector & base, int_vector & next); std::string gen_val_string(int len, int_vector & encoding); + bool free_var_attempt(expr * nn1, expr * nn2); + expr * get_alias_index_ast(std::map & aliasIndexMap, expr * node); expr * getMostLeftNodeInConcat(expr * node); expr * getMostRightNodeInConcat(expr * node); From 2f80a9d4aecd78e5c483c6f9410f2dd60d81398e Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 17 May 2016 16:31:08 -0400 Subject: [PATCH 084/410] add more_len_tests, more_value_tests --- src/smt/theory_str.cpp | 110 +++++++++++++++++++++++++---------------- src/smt/theory_str.h | 5 ++ 2 files changed, 72 insertions(+), 43 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index eace51dcb..ff0aacfa1 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -357,15 +357,9 @@ expr * theory_str::mk_internal_lenTest_var(expr * node, int lTries) { ss << "$$_len_" << mk_ismt2_pp(node, m) << "_" << lTries << "_" << tmpLenTestVarCount; tmpLenTestVarCount += 1; std::string name = ss.str(); - return mk_str_var(name); - - /* - Z3_context ctx = Z3_theory_get_context(t); - std::stringstream ss; - ss << "$$_len_" << Z3_ast_to_string(ctx, node) << "_" << lTries; - std::string name = ss.str(); - return my_mk_str_var(t, name.c_str()); - */ + app * var = mk_str_var(name); + internal_lenTest_vars.insert(var); + return var; } expr * theory_str::mk_internal_valTest_var(expr * node, int len, int vTries) { @@ -374,15 +368,9 @@ expr * theory_str::mk_internal_valTest_var(expr * node, int len, int vTries) { ss << "$$_val_" << mk_ismt2_pp(node, m) << "_" << len << "_" << vTries << "_" << tmpValTestVarCount; tmpValTestVarCount += 1; std::string name = ss.str(); - return mk_str_var(name); - - /* - Z3_context ctx = Z3_theory_get_context(t); - std::stringstream ss; - ss << "$$_val_" << Z3_ast_to_string(ctx, node) << "_" << len << "_" << vTries; - std::string name = ss.str(); - return my_mk_str_var(t, name.c_str()); - */ + app * var = mk_str_var(name); + internal_valTest_vars.insert(var); + return var; } void theory_str::track_variable_scope(expr * var) { @@ -2748,33 +2736,65 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { } } -bool theory_str::free_var_attempt(expr * nn1, expr * nn2) { - /* - Z3_context ctx = Z3_theory_get_context(t); - if (getNodeType(t, nn1) == my_Z3_Str_Var) { - std::string vName = std::string(Z3_ast_to_string(ctx, nn1)); - if (vName.length() >= 6) { - std::string vPrefix = vName.substr(0, 6); - // length attempts - if (vPrefix == "$$_len") { - if (getNodeType(t, nn2) == my_Z3_ConstStr) { - moreLenTests(t, nn1, getConstStrValue(t, nn2)); - } - return 1; +void theory_str::more_len_tests(expr * lenTester, std::string lenTesterValue) { + ast_manager & m = get_manager(); + if (lenTester_fvar_map.find(lenTester) != lenTester_fvar_map.end()) { + expr * fVar = lenTester_fvar_map[lenTester]; + expr * toAssert = gen_len_val_options_for_free_var(fVar, lenTester, lenTesterValue); + TRACE("t_str_detail", tout << "asserting more length tests for free variable " << mk_ismt2_pp(fVar, m) << std::endl;); + if (toAssert != NULL) { + assert_axiom(toAssert); } - // value attempts - else if (vPrefix == "$$_val") { - if (getNodeType(t, nn2) == my_Z3_ConstStr && "more" == getConstStrValue(t, nn2)) { - moreValueTests(t, nn1, getConstStrValue(t, nn2)); - } - return 1; - } else if (vPrefix == "$$_uRt") { - return 1; - } - } } - return 0; - */ +} + +void theory_str::more_value_tests(expr * valTester, std::string valTesterValue) { + ast_manager & m = get_manager(); + + expr * fVar = valueTester_fvar_map[valTester]; + int lenTesterCount = fvar_lenTester_map[fVar].size(); + + expr * effectiveLenInd = NULL; + std::string effectiveLenIndiStr = ""; + for (int i = 0; i < lenTesterCount; ++i) { + expr * len_indicator_pre = fvar_lenTester_map[fVar][i]; + bool indicatorHasEqcValue = false; + expr * len_indicator_value = get_eqc_value(len_indicator_pre, indicatorHasEqcValue); + if (indicatorHasEqcValue) { + std::string len_pIndiStr = m_strutil.get_string_constant_value(len_indicator_value); + if (len_pIndiStr != "more") { + effectiveLenInd = len_indicator_pre; + effectiveLenIndiStr = len_pIndiStr; + break; + } + } + } + expr * valueAssert = gen_free_var_options(fVar, effectiveLenInd, effectiveLenIndiStr, valTester, valTesterValue); + TRACE("t_str_detail", tout << "asserting more value tests for free variable " << mk_ismt2_pp(fVar, m) << std::endl;); + if (valueAssert != NULL) { + assert_axiom(valueAssert); + } +} + +bool theory_str::free_var_attempt(expr * nn1, expr * nn2) { + ast_manager & m = get_manager(); + + if (internal_lenTest_vars.contains(nn1) && m_strutil.is_string(nn2)) { + TRACE("t_str", tout << "acting on equivalence between length tester var " << mk_ismt2_pp(nn1, m) + << " and constant " << mk_ismt2_pp(nn2, m) << std::endl;); + more_len_tests(nn1, m_strutil.get_string_constant_value(nn2)); + return true; + } else if (internal_valTest_vars.contains(nn1) && m_strutil.is_string(nn2)) { + std::string nn2_str = m_strutil.get_string_constant_value(nn2); + if (nn2_str == "more") { + TRACE("t_str", tout << "acting on equivalence between value var " << mk_ismt2_pp(nn1, m) + << " and constant " << mk_ismt2_pp(nn2, m) << std::endl;); + more_value_tests(nn1, nn2_str); + } + return true; + } else { + return false; + } } void theory_str::handle_equality(expr * lhs, expr * rhs) { @@ -2790,6 +2810,10 @@ void theory_str::handle_equality(expr * lhs, expr * rhs) { return; } + if (free_var_attempt(lhs, rhs) || free_var_attempt(rhs, lhs)) { + return; + } + // TODO simplify concat? // newEqCheck() -- check consistency wrt. existing equivalence classes diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 242b37747..3bb3940b6 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -90,6 +90,9 @@ namespace smt { std::set internal_variable_set; std::map > internal_variable_scope_levels; + obj_hashtable internal_lenTest_vars; + obj_hashtable internal_valTest_vars; + std::set input_var_in_len; std::map fvar_len_count_map; @@ -192,6 +195,8 @@ namespace smt { std::string gen_val_string(int len, int_vector & encoding); bool free_var_attempt(expr * nn1, expr * nn2); + void more_len_tests(expr * lenTester, std::string lenTesterValue); + void more_value_tests(expr * valTester, std::string valTesterValue); expr * get_alias_index_ast(std::map & aliasIndexMap, expr * node); expr * getMostLeftNodeInConcat(expr * node); From 866d97f768b0ed0217e66b30762bf6e6aa4ff5ca Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 17 May 2016 16:45:53 -0400 Subject: [PATCH 085/410] fix eval_concat copy-and-paste error in simplify_parent; concat-eq-concat-case3_sat now passing --- src/smt/theory_str.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index ff0aacfa1..4c936dea5 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -963,7 +963,7 @@ void theory_str::simplify_parent(expr * nn, expr * eq_str) { // (Concat arg0 n_eqNode) /\ arg0 has eq const - expr * concatResult = eval_concat(eq_str, arg1); + expr * concatResult = eval_concat(arg0, eq_str); if (concatResult != NULL) { bool arg0HasEqcValue = false; expr * arg0Value = get_eqc_value(arg0, arg0HasEqcValue); From c8522c5b78a51e537bb36bd5468073be9f588c6b Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Thu, 19 May 2016 16:51:43 -0400 Subject: [PATCH 086/410] cleanup before attempting to fix the null enode parent bug --- src/smt/theory_str.cpp | 38 ++++++++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 4c936dea5..7581baf8d 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -821,8 +821,6 @@ expr * theory_str::eval_concat(expr * n1, expr * n2) { * ~ concat node * to see whether some concat nodes can be simplified. */ - -// TODO NEXT complete this method! void theory_str::simplify_parent(expr * nn, expr * eq_str) { ast_manager & m = get_manager(); context & ctx = get_context(); @@ -834,14 +832,17 @@ void theory_str::simplify_parent(expr * nn, expr * eq_str) { enode * n_eq_enode = ctx.get_enode(nn); enode * nn_enode = n_eq_enode; - const char * tmp = 0; - m_strutil.is_string(eq_str, & tmp); - std::string eq_strValue(tmp); + std::string eq_strValue = m_strutil.get_string_constant_value(eq_str); do { app * n_eqNode = n_eq_enode->get_owner(); + TRACE("t_str_detail", tout << "considering all parents of " << mk_ismt2_pp(n_eqNode, m) << std::endl + << "associated n_eq_enode has " << n_eq_enode->get_num_parents() << " parents" << std::endl;); for (enode_vector::iterator parent_it = n_eq_enode->begin_parents(); parent_it != n_eq_enode->end_parents(); parent_it++) { enode * e_parent = *parent_it; + // TODO deeper bug hiding here + SASSERT(e_parent != NULL); + app * a_parent = e_parent->get_owner(); TRACE("t_str_detail", tout << "considering parent " << mk_ismt2_pp(a_parent, m) << std::endl;); @@ -2814,7 +2815,32 @@ void theory_str::handle_equality(expr * lhs, expr * rhs) { return; } - // TODO simplify concat? + if (is_concat(to_app(lhs)) && is_concat(to_app(rhs))) { + bool nn1HasEqcValue = false; + bool nn2HasEqcValue = false; + expr * nn1_value = get_eqc_value(lhs, nn1HasEqcValue); + expr * nn2_value = get_eqc_value(rhs, nn2HasEqcValue); + if (nn1HasEqcValue && !nn2HasEqcValue) { + simplify_parent(rhs, nn1_value); + } + if (!nn1HasEqcValue && nn2HasEqcValue) { + simplify_parent(lhs, nn2_value); + } + + expr * nn1_arg0 = to_app(lhs)->get_arg(0); + expr * nn1_arg1 = to_app(lhs)->get_arg(1); + expr * nn2_arg0 = to_app(rhs)->get_arg(0); + expr * nn2_arg1 = to_app(rhs)->get_arg(1); + if (nn1_arg0 == nn2_arg0 && in_same_eqc(nn1_arg1, nn2_arg1)) { + TRACE("t_str_detail", tout << "skip: lhs arg0 == rhs arg0" << std::endl;); + return; + } + + if (nn1_arg1 == nn2_arg1 && in_same_eqc(nn1_arg0, nn2_arg0)) { + TRACE("t_str_detail", tout << "skip: lhs arg1 == rhs arg1" << std::endl;); + return; + } + } // newEqCheck() -- check consistency wrt. existing equivalence classes if (!new_eq_check(lhs, rhs)) { From 2f494a96119732443e4d41321a6f97508a16a4ce Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Thu, 19 May 2016 16:57:01 -0400 Subject: [PATCH 087/410] fix null parent bug by making a copy of n_eq_enode->m_parents in simplify_parent --- src/smt/theory_str.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 7581baf8d..bc32e14eb 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -838,9 +838,18 @@ void theory_str::simplify_parent(expr * nn, expr * eq_str) { app * n_eqNode = n_eq_enode->get_owner(); TRACE("t_str_detail", tout << "considering all parents of " << mk_ismt2_pp(n_eqNode, m) << std::endl << "associated n_eq_enode has " << n_eq_enode->get_num_parents() << " parents" << std::endl;); - for (enode_vector::iterator parent_it = n_eq_enode->begin_parents(); parent_it != n_eq_enode->end_parents(); parent_it++) { + + // the goal of this next bit is to avoid dereferencing a bogus e_parent in the following loop. + // what I image is causing this bug is that, for example, we examine some parent, we add an axiom that involves it, + // and the parent_it iterator becomes invalidated, because we indirectly modified the container that we're iterating over. + + enode_vector current_parents; + for (enode_vector::const_iterator parent_it = n_eq_enode->begin_parents(); parent_it != n_eq_enode->end_parents(); parent_it++) { + current_parents.insert(*parent_it); + } + + for (enode_vector::iterator parent_it = current_parents.begin(); parent_it != current_parents.end(); ++parent_it) { enode * e_parent = *parent_it; - // TODO deeper bug hiding here SASSERT(e_parent != NULL); app * a_parent = e_parent->get_owner(); From 2522e35c5e90d2a47f5bf4b2a4ad16f300678c6d Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Fri, 20 May 2016 10:22:19 -0400 Subject: [PATCH 088/410] start work on string-integer integration --- src/smt/theory_str.cpp | 33 +++++++++++++++++++++++++++++++++ src/smt/theory_str.h | 2 ++ 2 files changed, 35 insertions(+) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index bc32e14eb..05425c61b 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -2363,6 +2363,39 @@ expr * theory_str::get_eqc_value(expr * n, bool & hasEqcValue) { return n; } +/* + * Look through the equivalence class of n to find an integer constant. + * Return that constant if it is found. Otherwise, return -1. + * Note that a return value of -1 should not normally be possible, as + * string length cannot be negative. + */ + +rational theory_str::get_len_value(expr * n) { + ast_manager & m = get_manager(); + context & ctx = get_context(); + ctx.internalize(n, false); + + TRACE("t_str_detail", tout << "checking eqc of " << mk_ismt2_pp(n, m) << " for an integer constant" << std::endl;); + + enode * nNode = ctx.get_enode(n); + enode * eqcNode = nNode; + do { + app * ast = eqcNode->get_owner(); + rational val; + bool is_int; + if (m_autil.is_numeral(n, val, is_int)) { + if (is_int) { + TRACE("t_str_detail", tout << "eqc contains integer constant " << val << std::endl;); + SASSERT(!val.is_neg()); + return val; + } + } + } while (eqcNode != nNode); + // not found + TRACE("t_str_detail", tout << "eqc contains no integer constants" << std::endl;); + return rational(-1); +} + /* * Decide whether n1 and n2 are already in the same equivalence class. * This only checks whether the core considers them to be equal; diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 3bb3940b6..cf7ef0060 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -146,6 +146,8 @@ namespace smt { expr * get_eqc_value(expr * n, bool & hasEqcValue); bool in_same_eqc(expr * n1, expr * n2); + rational get_len_value(expr * n); + bool can_two_nodes_eq(expr * n1, expr * n2); bool can_concat_eq_str(expr * concat, std::string str); bool can_concat_eq_concat(expr * concat1, expr * concat2); From ecb069b7018f52b7621a8ac7445d4ce7968db770 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Fri, 20 May 2016 16:34:11 -0400 Subject: [PATCH 089/410] non-fixes to string length code, plus the get_length() code from new Z3 --- src/smt/theory_str.cpp | 135 ++++++++++++++++++++++++++++++++++------- src/smt/theory_str.h | 2 +- 2 files changed, 115 insertions(+), 22 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 05425c61b..d3e842fed 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -1440,37 +1440,39 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { expr * m = to_app(concatAst2)->get_arg(0); expr * n = to_app(concatAst2)->get_arg(1); - /* TODO query the integer theory: - int x_len = getLenValue(t, x); - int y_len = getLenValue(t, y); - int m_len = getLenValue(t, m); - int n_len = getLenValue(t, n); - */ - int x_len = -1; - int y_len = -1; - int m_len = -1; - int n_len = -1; + rational x_len = get_len_value(x); + rational y_len = get_len_value(y); + rational m_len = get_len_value(m); + rational n_len = get_len_value(n); int splitType = -1; - if (x_len != -1 && m_len != -1) { - if (x_len < m_len) + if (x_len != rational(-1) && m_len != rational(-1)) { + if (x_len < m_len) { splitType = 0; - else if (x_len == m_len) + } else if (x_len == m_len) { splitType = 1; - else + } else { splitType = 2; + } } - if (splitType == -1 && y_len != -1 && n_len != -1) { - if (y_len > n_len) + if (splitType == -1 && y_len != rational(-1) && n_len != rational(-1)) { + if (y_len > n_len) { splitType = 0; - else if (y_len == n_len) + } else if (y_len == n_len) { splitType = 1; - else + } else { splitType = 2; + } } - TRACE("t_str_detail", tout << "split type " << splitType << std::endl;); + TRACE("t_str_detail", tout + << "len(x) = " << x_len << std::endl + << "len(y) = " << y_len << std::endl + << "len(m) = " << m_len << std::endl + << "len(n) = " << n_len << std::endl + << "split type " << splitType << std::endl; + ); expr * t1 = NULL; expr * t2 = NULL; @@ -2363,6 +2365,93 @@ expr * theory_str::get_eqc_value(expr * n, bool & hasEqcValue) { return n; } +// from Z3: theory_seq.cpp + +/* +static theory_mi_arith* get_th_arith(context& ctx, theory_id afid, expr* e) { + theory* th = ctx.get_theory(afid); + if (th && ctx.e_internalized(e)) { + return dynamic_cast(th); + } + else { + return 0; + } +} + +bool theory_seq::get_value(expr* e, rational& val) const { + context& ctx = get_context(); + theory_mi_arith* tha = get_th_arith(ctx, m_autil.get_family_id(), e); + expr_ref _val(m); + if (!tha || !tha->get_value(ctx.get_enode(e), _val)) return false; + return m_autil.is_numeral(_val, val) && val.is_int(); +} + +bool theory_seq::lower_bound(expr* _e, rational& lo) const { + context& ctx = get_context(); + expr_ref e(m_util.str.mk_length(_e), m); + theory_mi_arith* tha = get_th_arith(ctx, m_autil.get_family_id(), e); + expr_ref _lo(m); + if (!tha || !tha->get_lower(ctx.get_enode(e), _lo)) return false; + return m_autil.is_numeral(_lo, lo) && lo.is_int(); +} + +bool theory_seq::upper_bound(expr* _e, rational& hi) const { + context& ctx = get_context(); + expr_ref e(m_util.str.mk_length(_e), m); + theory_mi_arith* tha = get_th_arith(ctx, m_autil.get_family_id(), e); + expr_ref _hi(m); + if (!tha || !tha->get_upper(ctx.get_enode(e), _hi)) return false; + return m_autil.is_numeral(_hi, hi) && hi.is_int(); +} + +bool theory_seq::get_length(expr* e, rational& val) const { + context& ctx = get_context(); + theory* th = ctx.get_theory(m_autil.get_family_id()); + if (!th) return false; + theory_mi_arith* tha = dynamic_cast(th); + if (!tha) return false; + rational val1; + expr_ref len(m), len_val(m); + expr* e1, *e2; + ptr_vector todo; + todo.push_back(e); + val.reset(); + zstring s; + while (!todo.empty()) { + expr* c = todo.back(); + todo.pop_back(); + if (m_util.str.is_concat(c, e1, e2)) { + todo.push_back(e1); + todo.push_back(e2); + } + else if (m_util.str.is_unit(c)) { + val += rational(1); + } + else if (m_util.str.is_empty(c)) { + continue; + } + else if (m_util.str.is_string(c, s)) { + val += rational(s.length()); + } + else if (!has_length(c)) { + return false; + } + else { + len = m_util.str.mk_length(c); + if (ctx.e_internalized(len) && + tha->get_value(ctx.get_enode(len), len_val) && + m_autil.is_numeral(len_val, val1)) { + val += val1; + } + else { + return false; + } + } + } + return val.is_int(); +} +*/ + /* * Look through the equivalence class of n to find an integer constant. * Return that constant if it is found. Otherwise, return -1. @@ -2370,9 +2459,11 @@ expr * theory_str::get_eqc_value(expr * n, bool & hasEqcValue) { * string length cannot be negative. */ -rational theory_str::get_len_value(expr * n) { +rational theory_str::get_len_value(expr * x) { ast_manager & m = get_manager(); context & ctx = get_context(); + ctx.internalize(x, false); + expr * n = mk_strlen(x); ctx.internalize(n, false); TRACE("t_str_detail", tout << "checking eqc of " << mk_ismt2_pp(n, m) << " for an integer constant" << std::endl;); @@ -2383,13 +2474,15 @@ rational theory_str::get_len_value(expr * n) { app * ast = eqcNode->get_owner(); rational val; bool is_int; - if (m_autil.is_numeral(n, val, is_int)) { + TRACE("t_str_detail", tout << "eqc member: " << mk_ismt2_pp(ast, m) << std::endl;); + if (m_autil.is_numeral(ast, val, is_int)) { if (is_int) { TRACE("t_str_detail", tout << "eqc contains integer constant " << val << std::endl;); SASSERT(!val.is_neg()); return val; } } + eqcNode = eqcNode->get_next(); } while (eqcNode != nNode); // not found TRACE("t_str_detail", tout << "eqc contains no integer constants" << std::endl;); diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index cf7ef0060..946340366 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -146,7 +146,7 @@ namespace smt { expr * get_eqc_value(expr * n, bool & hasEqcValue); bool in_same_eqc(expr * n1, expr * n2); - rational get_len_value(expr * n); + rational get_len_value(expr * x); bool can_two_nodes_eq(expr * n1, expr * n2); bool can_concat_eq_str(expr * concat, std::string str); From f8f7014a1855d40931a3b2b9202f8c23ca617bc3 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 1 Jun 2016 16:34:48 -0400 Subject: [PATCH 090/410] use LRA instead of LIA in strings setup, so that the theory_seq integer value code works --- src/smt/smt_setup.cpp | 2 +- src/smt/theory_str.cpp | 68 +++++++++++++++++++++++++----------------- src/smt/theory_str.h | 3 +- 3 files changed, 43 insertions(+), 30 deletions(-) diff --git a/src/smt/smt_setup.cpp b/src/smt/smt_setup.cpp index 5e4af91fd..acb03a954 100644 --- a/src/smt/smt_setup.cpp +++ b/src/smt/smt_setup.cpp @@ -700,7 +700,7 @@ namespace smt { } void setup::setup_QF_S() { - setup_QF_LIA(); + setup_QF_LRA(); m_context.register_plugin(alloc(smt::theory_str, m_manager)); } diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index d3e842fed..e2e2f55d1 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -21,6 +21,7 @@ Revision History: #include"ast_pp.h" #include"ast_ll_pp.h" #include +#include"theory_arith.h" namespace smt { @@ -1440,13 +1441,14 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { expr * m = to_app(concatAst2)->get_arg(0); expr * n = to_app(concatAst2)->get_arg(1); - rational x_len = get_len_value(x); - rational y_len = get_len_value(y); - rational m_len = get_len_value(m); - rational n_len = get_len_value(n); + rational x_len, y_len, m_len, n_len; + bool x_len_exists = get_len_value(x, x_len); + bool y_len_exists = get_len_value(y, y_len); + bool m_len_exists = get_len_value(m, m_len); + bool n_len_exists = get_len_value(n, n_len); int splitType = -1; - if (x_len != rational(-1) && m_len != rational(-1)) { + if (x_len_exists && m_len_exists) { if (x_len < m_len) { splitType = 0; } else if (x_len == m_len) { @@ -1456,7 +1458,7 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { } } - if (splitType == -1 && y_len != rational(-1) && n_len != rational(-1)) { + if (splitType == -1 && y_len_exists && n_len_exists) { if (y_len > n_len) { splitType = 0; } else if (y_len == n_len) { @@ -2367,7 +2369,6 @@ expr * theory_str::get_eqc_value(expr * n, bool & hasEqcValue) { // from Z3: theory_seq.cpp -/* static theory_mi_arith* get_th_arith(context& ctx, theory_id afid, expr* e) { theory* th = ctx.get_theory(afid); if (th && ctx.e_internalized(e)) { @@ -2378,15 +2379,18 @@ static theory_mi_arith* get_th_arith(context& ctx, theory_id afid, expr* e) { } } -bool theory_seq::get_value(expr* e, rational& val) const { +bool theory_str::get_value(expr* e, rational& val) const { context& ctx = get_context(); + ast_manager & m = get_manager(); theory_mi_arith* tha = get_th_arith(ctx, m_autil.get_family_id(), e); expr_ref _val(m); if (!tha || !tha->get_value(ctx.get_enode(e), _val)) return false; return m_autil.is_numeral(_val, val) && val.is_int(); } -bool theory_seq::lower_bound(expr* _e, rational& lo) const { +// TODO bring these in as well +/* +bool theory_str::lower_bound(expr* _e, rational& lo) const { context& ctx = get_context(); expr_ref e(m_util.str.mk_length(_e), m); theory_mi_arith* tha = get_th_arith(ctx, m_autil.get_family_id(), e); @@ -2395,7 +2399,7 @@ bool theory_seq::lower_bound(expr* _e, rational& lo) const { return m_autil.is_numeral(_lo, lo) && lo.is_int(); } -bool theory_seq::upper_bound(expr* _e, rational& hi) const { +bool theory_str::upper_bound(expr* _e, rational& hi) const { context& ctx = get_context(); expr_ref e(m_util.str.mk_length(_e), m); theory_mi_arith* tha = get_th_arith(ctx, m_autil.get_family_id(), e); @@ -2403,54 +2407,60 @@ bool theory_seq::upper_bound(expr* _e, rational& hi) const { if (!tha || !tha->get_upper(ctx.get_enode(e), _hi)) return false; return m_autil.is_numeral(_hi, hi) && hi.is_int(); } +*/ -bool theory_seq::get_length(expr* e, rational& val) const { +bool theory_str::get_len_value(expr* e, rational& val) { context& ctx = get_context(); + ast_manager & m = get_manager(); theory* th = ctx.get_theory(m_autil.get_family_id()); - if (!th) return false; + if (!th) { + TRACE("t_str_int", tout << "oops, can't get m_autil's theory" << std::endl;); + return false; + } theory_mi_arith* tha = dynamic_cast(th); - if (!tha) return false; + if (!tha) { + TRACE("t_str_int", tout << "oops, can't cast to theory_mi_arith" << std::endl;); + return false; + } + + TRACE("t_str_int", tout << "checking len value of " << mk_ismt2_pp(e, m) << std::endl;); + rational val1; expr_ref len(m), len_val(m); expr* e1, *e2; ptr_vector todo; todo.push_back(e); val.reset(); - zstring s; while (!todo.empty()) { expr* c = todo.back(); todo.pop_back(); - if (m_util.str.is_concat(c, e1, e2)) { + if (is_concat(to_app(c))) { + e1 = to_app(c)->get_arg(0); + e2 = to_app(c)->get_arg(1); todo.push_back(e1); todo.push_back(e2); } - else if (m_util.str.is_unit(c)) { - val += rational(1); - } - else if (m_util.str.is_empty(c)) { - continue; - } - else if (m_util.str.is_string(c, s)) { - val += rational(s.length()); - } - else if (!has_length(c)) { - return false; + else if (is_string(to_app(c))) { + int sl = m_strutil.get_string_constant_value(c).length(); + val += rational(sl); } else { - len = m_util.str.mk_length(c); + len = mk_strlen(c); if (ctx.e_internalized(len) && tha->get_value(ctx.get_enode(len), len_val) && m_autil.is_numeral(len_val, val1)) { val += val1; + TRACE("t_str_int", tout << "subexpression " << mk_ismt2_pp(len, m) << " has length " << val1 << std::endl;); } else { + TRACE("t_str_int", tout << "subexpression " << mk_ismt2_pp(len, m) << " has no length assignment; bailing out" << std::endl;); return false; } } } + TRACE("t_str_int", tout << "length of " << mk_ismt2_pp(e, m) << " is " << val << std::endl;); return val.is_int(); } -*/ /* * Look through the equivalence class of n to find an integer constant. @@ -2459,6 +2469,7 @@ bool theory_seq::get_length(expr* e, rational& val) const { * string length cannot be negative. */ +/* rational theory_str::get_len_value(expr * x) { ast_manager & m = get_manager(); context & ctx = get_context(); @@ -2488,6 +2499,7 @@ rational theory_str::get_len_value(expr * x) { TRACE("t_str_detail", tout << "eqc contains no integer constants" << std::endl;); return rational(-1); } +*/ /* * Decide whether n1 and n2 are already in the same equivalence class. diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 946340366..da950713f 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -146,7 +146,8 @@ namespace smt { expr * get_eqc_value(expr * n, bool & hasEqcValue); bool in_same_eqc(expr * n1, expr * n2); - rational get_len_value(expr * x); + bool get_value(expr* e, rational& val) const; + bool get_len_value(expr* e, rational& val); bool can_two_nodes_eq(expr * n1, expr * n2); bool can_concat_eq_str(expr * concat, std::string str); From bc79a73779f0b28e10bb98ca22e266362c0c2687 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 1 Jun 2016 17:23:47 -0400 Subject: [PATCH 091/410] lower/upper bound WIP --- src/smt/theory_str.cpp | 21 +++++++++++---------- src/smt/theory_str.h | 2 ++ 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index e2e2f55d1..f7d31a80b 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -165,7 +165,6 @@ bool theory_str::internalize_atom(app * atom, bool gate_ctx) { bool theory_str::internalize_term(app * term) { context & ctx = get_context(); ast_manager & m = get_manager(); - TRACE("t_str", tout << "internalizing term: " << mk_ismt2_pp(term, get_manager()) << std::endl;); SASSERT(term->get_family_id() == get_family_id()); /* // what I had before @@ -194,6 +193,7 @@ bool theory_str::internalize_term(app * term) { mk_var(e); return true; } + TRACE("t_str", tout << "internalizing term: " << mk_ismt2_pp(term, get_manager()) << std::endl;); unsigned num_args = term->get_num_args(); expr* arg; for (unsigned i = 0; i < num_args; i++) { @@ -1447,6 +1447,8 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { bool m_len_exists = get_len_value(m, m_len); bool n_len_exists = get_len_value(n, n_len); + // debugging + int splitType = -1; if (x_len_exists && m_len_exists) { if (x_len < m_len) { @@ -2388,26 +2390,25 @@ bool theory_str::get_value(expr* e, rational& val) const { return m_autil.is_numeral(_val, val) && val.is_int(); } -// TODO bring these in as well -/* -bool theory_str::lower_bound(expr* _e, rational& lo) const { +bool theory_str::lower_bound(expr* _e, rational& lo) { context& ctx = get_context(); - expr_ref e(m_util.str.mk_length(_e), m); + ast_manager & m = get_manager(); + expr_ref e(mk_strlen(_e), m); theory_mi_arith* tha = get_th_arith(ctx, m_autil.get_family_id(), e); expr_ref _lo(m); if (!tha || !tha->get_lower(ctx.get_enode(e), _lo)) return false; return m_autil.is_numeral(_lo, lo) && lo.is_int(); } -bool theory_str::upper_bound(expr* _e, rational& hi) const { +bool theory_str::upper_bound(expr* _e, rational& hi) { context& ctx = get_context(); - expr_ref e(m_util.str.mk_length(_e), m); + ast_manager & m = get_manager(); + expr_ref e(mk_strlen(_e), m); theory_mi_arith* tha = get_th_arith(ctx, m_autil.get_family_id(), e); expr_ref _hi(m); if (!tha || !tha->get_upper(ctx.get_enode(e), _hi)) return false; return m_autil.is_numeral(_hi, hi) && hi.is_int(); } -*/ bool theory_str::get_len_value(expr* e, rational& val) { context& ctx = get_context(); @@ -2450,10 +2451,10 @@ bool theory_str::get_len_value(expr* e, rational& val) { tha->get_value(ctx.get_enode(len), len_val) && m_autil.is_numeral(len_val, val1)) { val += val1; - TRACE("t_str_int", tout << "subexpression " << mk_ismt2_pp(len, m) << " has length " << val1 << std::endl;); + TRACE("t_str_int", tout << "integer theory: subexpression " << mk_ismt2_pp(len, m) << " has length " << val1 << std::endl;); } else { - TRACE("t_str_int", tout << "subexpression " << mk_ismt2_pp(len, m) << " has no length assignment; bailing out" << std::endl;); + TRACE("t_str_int", tout << "integer theory: subexpression " << mk_ismt2_pp(len, m) << " has no length assignment; bailing out" << std::endl;); return false; } } diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index da950713f..45c5f3e06 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -148,6 +148,8 @@ namespace smt { bool get_value(expr* e, rational& val) const; bool get_len_value(expr* e, rational& val); + bool lower_bound(expr* _e, rational& lo); + bool upper_bound(expr* _e, rational& hi); bool can_two_nodes_eq(expr * n1, expr * n2); bool can_concat_eq_str(expr * concat, std::string str); From b5fe473c3ac99b012d6cc945bd5ca0109e9f7f3b Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 1 Jun 2016 17:50:45 -0400 Subject: [PATCH 092/410] fix compilation errors after merge --- src/parsers/smt2/smt2parser.cpp | 3 --- src/smt/theory_str.cpp | 4 ++++ src/smt/theory_str.h | 1 + 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/parsers/smt2/smt2parser.cpp b/src/parsers/smt2/smt2parser.cpp index c203b2faa..c8e9a78b6 100644 --- a/src/parsers/smt2/smt2parser.cpp +++ b/src/parsers/smt2/smt2parser.cpp @@ -1767,9 +1767,6 @@ namespace smt2 { case scanner::BV_TOKEN: parse_bv_numeral(); break; - case scanner::STRING_TOKEN: - parse_string(); - break; case scanner::LEFT_PAREN: push_expr_frame(m_num_expr_frames == 0 ? 0 : static_cast(m_stack.top())); break; diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index f7d31a80b..b65b799b1 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -4962,4 +4962,8 @@ model_value_proc * theory_str::mk_value(enode * n, model_generator & mg) { void theory_str::finalize_model(model_generator & mg) {} +void theory_str::display(std::ostream & out) const { + out << "TODO: theory_str display" << std::endl; +} + }; /* namespace smt */ diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 45c5f3e06..ecd7e443f 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -220,6 +220,7 @@ namespace smt { virtual ~theory_str(); virtual char const * get_name() const { return "strings"; } + virtual void display(std::ostream & out) const; protected: virtual bool internalize_atom(app * atom, bool gate_ctx); virtual bool internalize_term(app * term); From 33205cea712041ce5ba6cc3c6bc465e4c3e84d54 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 1 Jun 2016 17:57:00 -0400 Subject: [PATCH 093/410] completely bypass theory_seq; sorry! I'll put it back when I'm done --- src/ast/seq_decl_plugin.cpp | 6 +++--- src/cmd_context/check_logic.cpp | 7 ++++++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/ast/seq_decl_plugin.cpp b/src/ast/seq_decl_plugin.cpp index 21af0773a..787648e19 100644 --- a/src/ast/seq_decl_plugin.cpp +++ b/src/ast/seq_decl_plugin.cpp @@ -256,7 +256,7 @@ std::ostream& zstring::operator<<(std::ostream& out) const { seq_decl_plugin::seq_decl_plugin(): m_init(false), - m_stringc_sym("String"), + m_stringc_sym("StringSequence"), m_charc_sym("Char"), m_string(0), m_char(0), @@ -490,7 +490,7 @@ void seq_decl_plugin::set_manager(ast_manager* m, family_id id) { m_char = bv.mk_sort(8); m->inc_ref(m_char); parameter param(m_char); - m_string = m->mk_sort(symbol("String"), sort_info(m_family_id, SEQ_SORT, 1, ¶m)); + m_string = m->mk_sort(symbol("StringSequence"), sort_info(m_family_id, SEQ_SORT, 1, ¶m)); m->inc_ref(m_string); parameter paramS(m_string); m_re = m->mk_sort(m_family_id, RE_SORT, 1, ¶mS); @@ -745,7 +745,7 @@ void seq_decl_plugin::get_sort_names(svector & sort_names, symbol init(); sort_names.push_back(builtin_name("Seq", SEQ_SORT)); sort_names.push_back(builtin_name("RegEx", RE_SORT)); - sort_names.push_back(builtin_name("String", _STRING_SORT)); + sort_names.push_back(builtin_name("StringSequence", _STRING_SORT)); } app* seq_decl_plugin::mk_string(symbol const& s) { diff --git a/src/cmd_context/check_logic.cpp b/src/cmd_context/check_logic.cpp index 733689ac9..a547ab616 100644 --- a/src/cmd_context/check_logic.cpp +++ b/src/cmd_context/check_logic.cpp @@ -21,6 +21,7 @@ Revision History: #include"array_decl_plugin.h" #include"bv_decl_plugin.h" #include"seq_decl_plugin.h" +#include"str_decl_plugin.h" #include"ast_pp.h" #include"for_each_expr.h" @@ -31,6 +32,7 @@ struct check_logic::imp { bv_util m_bv_util; array_util m_ar_util; seq_util m_seq_util; + str_util m_str_util; bool m_uf; // true if the logic supports uninterpreted functions bool m_arrays; // true if the logic supports arbitrary arrays bool m_bv_arrays; // true if the logic supports only bv arrays @@ -42,7 +44,7 @@ struct check_logic::imp { bool m_quantifiers; // true if the logic supports quantifiers bool m_unknown_logic; - imp(ast_manager & _m):m(_m), m_a_util(m), m_bv_util(m), m_ar_util(m), m_seq_util(m) { + imp(ast_manager & _m):m(_m), m_a_util(m), m_bv_util(m), m_ar_util(m), m_seq_util(m), m_str_util(m) { reset(); } @@ -432,6 +434,9 @@ struct check_logic::imp { else if (fid == m_seq_util.get_family_id()) { // nothing to check } + else if (fid == m_str_util.get_family_id()) { + // nothing to check + } else { fail("logic does not support theory"); } From e0df5bc2edf3b68f2d8c6403332c93d0664afe48 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sat, 4 Jun 2016 16:29:10 -0400 Subject: [PATCH 094/410] fixups for string-integer --- src/smt/theory_str.cpp | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index b65b799b1..eb64aae5d 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -1447,10 +1447,9 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { bool m_len_exists = get_len_value(m, m_len); bool n_len_exists = get_len_value(n, n_len); - // debugging - int splitType = -1; if (x_len_exists && m_len_exists) { + TRACE("t_str_int", tout << "length values found: x/m" << std::endl;); if (x_len < m_len) { splitType = 0; } else if (x_len == m_len) { @@ -1461,6 +1460,7 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { } if (splitType == -1 && y_len_exists && n_len_exists) { + TRACE("t_str_int", tout << "length values found: y/n" << std::endl;); if (y_len > n_len) { splitType = 0; } else if (y_len == n_len) { @@ -1471,10 +1471,10 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { } TRACE("t_str_detail", tout - << "len(x) = " << x_len << std::endl - << "len(y) = " << y_len << std::endl - << "len(m) = " << m_len << std::endl - << "len(n) = " << n_len << std::endl + << "len(x) = " << (x_len_exists ? x_len.to_string() : "?") << std::endl + << "len(y) = " << (y_len_exists ? y_len.to_string() : "?") << std::endl + << "len(m) = " << (m_len_exists ? m_len.to_string() : "?") << std::endl + << "len(n) = " << (n_len_exists ? n_len.to_string() : "?") << std::endl << "split type " << splitType << std::endl; ); @@ -2385,11 +2385,24 @@ bool theory_str::get_value(expr* e, rational& val) const { context& ctx = get_context(); ast_manager & m = get_manager(); theory_mi_arith* tha = get_th_arith(ctx, m_autil.get_family_id(), e); + if (!tha) { + return false; + } expr_ref _val(m); - if (!tha || !tha->get_value(ctx.get_enode(e), _val)) return false; - return m_autil.is_numeral(_val, val) && val.is_int(); + enode * en_e = ctx.get_enode(e); + enode * it = en_e; + do { + if (tha->get_value(it, _val)) { + // found an arithmetic term + return m_autil.is_numeral(_val, val) && val.is_int(); + } + it = it->get_next(); + } while (it != en_e); + return false; } +// TODO these methods currently crash the solver, find out why + bool theory_str::lower_bound(expr* _e, rational& lo) { context& ctx = get_context(); ast_manager & m = get_manager(); @@ -2447,9 +2460,7 @@ bool theory_str::get_len_value(expr* e, rational& val) { } else { len = mk_strlen(c); - if (ctx.e_internalized(len) && - tha->get_value(ctx.get_enode(len), len_val) && - m_autil.is_numeral(len_val, val1)) { + if (ctx.e_internalized(len) && get_value(len, val1)) { val += val1; TRACE("t_str_int", tout << "integer theory: subexpression " << mk_ismt2_pp(len, m) << " has length " << val1 << std::endl;); } From 62aeff90c53ae03962b907a46ae2de07ca4ae511 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 7 Jun 2016 17:38:57 -0400 Subject: [PATCH 095/410] fix string theory setup so that string-integer integration actually works --- src/smt/smt_setup.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/smt/smt_setup.cpp b/src/smt/smt_setup.cpp index 5a7547609..117b606fd 100644 --- a/src/smt/smt_setup.cpp +++ b/src/smt/smt_setup.cpp @@ -706,7 +706,7 @@ namespace smt { } void setup::setup_QF_S() { - setup_QF_LRA(); + m_context.register_plugin(alloc(smt::theory_mi_arith, m_manager, m_params)); m_context.register_plugin(alloc(smt::theory_str, m_manager)); } From 513b4922eee8ac4b576d8588e31606c99645524e Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 7 Jun 2016 17:40:59 -0400 Subject: [PATCH 096/410] tracing code for string-integer integration --- src/smt/theory_arith.h | 14 ++++++++++++- src/smt/theory_arith_core.h | 41 +++++++++++++++++++++++++++++++++++-- src/smt/theory_str.cpp | 31 +++++++++++++++++++++++++--- 3 files changed, 80 insertions(+), 6 deletions(-) diff --git a/src/smt/theory_arith.h b/src/smt/theory_arith.h index 39f991c72..7e594abe5 100644 --- a/src/smt/theory_arith.h +++ b/src/smt/theory_arith.h @@ -576,7 +576,19 @@ namespace smt { return is_free(get_context().get_enode(n)->get_th_var(get_id())); } bool is_fixed(theory_var v) const; - void set_bound_core(theory_var v, bound * new_bound, bool upper) { m_bounds[static_cast(upper)][v] = new_bound; } + void set_bound_core(theory_var v, bound * new_bound, bool upper) { + TRACE("t_str_int", + tout << "setting " << (upper ? "upper" : "lower") << " bound "; + if (new_bound) { + tout << new_bound->get_value(); + } else { + tout << "(NULL)"; + } + tout << " for theory var v#" << v; + tout << std::endl; + ); + m_bounds[static_cast(upper)][v] = new_bound; + } void restore_bound(theory_var v, bound * new_bound, bool upper) { set_bound_core(v, new_bound, upper); } void restore_nl_propagated_flag(unsigned old_trail_size); void set_bound(bound * new_bound, bool upper); diff --git a/src/smt/theory_arith_core.h b/src/smt/theory_arith_core.h index 95c7fdfad..1ce56ffe8 100644 --- a/src/smt/theory_arith_core.h +++ b/src/smt/theory_arith_core.h @@ -3223,13 +3223,50 @@ namespace smt { bool theory_arith::get_value(enode * n, expr_ref & r) { theory_var v = n->get_th_var(get_id()); inf_numeral val; - return v != null_theory_var && (val = get_value(v), (!is_int(v) || val.is_int())) && to_expr(val, is_int(v), r); + // rewrites for tracing purposes + if (v == null_theory_var) { + TRACE("t_str_int", tout << "WARNING: enode " << mk_pp(n->get_owner(), get_manager()) + << " attached to null theory var" << std::endl; + ); + return false; + } else { + val = get_value(v); + TRACE("t_str_int", tout << "enode " << mk_pp(n->get_owner(), get_manager()) + << " attached to theory var v#" << v + << ", has val = " << val + << std::endl; + ); + if (!is_int(v) || val.is_int()) { + return to_expr(val, is_int(v), r); + } else { + return false; + } + } + // return v != null_theory_var && (val = get_value(v), (!is_int(v) || val.is_int())) && to_expr(val, is_int(v), r); } template bool theory_arith::get_lower(enode * n, expr_ref & r) { theory_var v = n->get_th_var(get_id()); - bound* b = (v == null_theory_var) ? 0 : lower(v); + bound * b; + if (v == null_theory_var) { + TRACE("t_str_int", tout << "WARNING: enode " << mk_pp(n->get_owner(), get_manager()) + << " attached to null theory var" << std::endl; + ); + b = 0; + } else { + b = lower(v); + TRACE("t_str_int", + tout << "enode " << mk_pp(n->get_owner(), get_manager()) + << " attached to theory var v#" << v + << std::endl; + if (b) { + tout << "lower bound = " << b->get_value() << std::endl; + } else { + tout << "WARNING: b = NULL" << std::endl; + } + ); + } return b && to_expr(b->get_value(), is_int(v), r); } diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index eb64aae5d..3b59961a3 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -1470,6 +1470,14 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { } } + { + rational x_lb, x_ub; + bool x_lb_p = lower_bound(x, x_lb); + bool x_ub_p = upper_bound(x, x_ub); + TRACE("t_str_detail", tout << "X [" << x_lb << ":" << x_ub << "]" << std::endl + << "lb? " << (x_lb_p?"yes":"no") << " ub? " << (x_ub_p?"yes":"no") << std::endl;); + } + TRACE("t_str_detail", tout << "len(x) = " << (x_len_exists ? x_len.to_string() : "?") << std::endl << "len(y) = " << (y_len_exists ? y_len.to_string() : "?") << std::endl @@ -2388,21 +2396,25 @@ bool theory_str::get_value(expr* e, rational& val) const { if (!tha) { return false; } + TRACE("t_str_int", tout << "checking eqc of " << mk_pp(e, m) << " for arithmetic value" << std::endl;); expr_ref _val(m); enode * en_e = ctx.get_enode(e); enode * it = en_e; do { if (tha->get_value(it, _val)) { // found an arithmetic term + TRACE("t_str_int", tout << "get_value[" << mk_pp(it->get_owner(), m) << "] = " << mk_pp(_val, m) + << std::endl;); return m_autil.is_numeral(_val, val) && val.is_int(); + } else { + TRACE("t_str_int", tout << "get_value[" << mk_pp(it->get_owner(), m) << "] not found" << std::endl;); } it = it->get_next(); } while (it != en_e); + TRACE("t_str_int", tout << "no arithmetic values found in eqc" << std::endl;); return false; } -// TODO these methods currently crash the solver, find out why - bool theory_str::lower_bound(expr* _e, rational& lo) { context& ctx = get_context(); ast_manager & m = get_manager(); @@ -2460,6 +2472,19 @@ bool theory_str::get_len_value(expr* e, rational& val) { } else { len = mk_strlen(c); + + // debugging + TRACE("t_str_int", { + tout << mk_pp(len, m) << ":" << std::endl + << (ctx.is_relevant(len.get()) ? "relevant" : "not relevant") << std::endl + << (ctx.e_internalized(len) ? "internalized" : "not internalized") << std::endl + ; + if (ctx.e_internalized(len)) { + enode * e_len = ctx.get_enode(len); + tout << "has " << e_len->get_num_th_vars() << " theory vars" << std::endl; + } + }); + if (ctx.e_internalized(len) && get_value(len, val1)) { val += val1; TRACE("t_str_int", tout << "integer theory: subexpression " << mk_ismt2_pp(len, m) << " has length " << val1 << std::endl;); @@ -3225,7 +3250,7 @@ void theory_str::init_search_eh() { unsigned nFormulas = ctx.get_num_asserted_formulas(); for (unsigned i = 0; i < nFormulas; ++i) { expr * ex = ctx.get_asserted_formula(i); - tout << mk_ismt2_pp(ex, m) << std::endl; + tout << mk_ismt2_pp(ex, m) << (ctx.is_relevant(ex) ? " (rel)" : " (NOT REL)") << std::endl; } ); /* From 04fe8f66df6fc10722dade93db0d5f103982dd0e Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 8 Jun 2016 16:22:31 -0400 Subject: [PATCH 097/410] concat-eq-concat type 1 split 0 --- src/smt/theory_str.cpp | 54 +++++++++++++++++++++++++++++++++++------- src/smt/theory_str.h | 1 + 2 files changed, 46 insertions(+), 9 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 3b59961a3..cc164ec3b 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -348,6 +348,10 @@ app * theory_str::mk_int(int n) { return m_autil.mk_numeral(rational(n), true); } +app * theory_str::mk_int(rational & q) { + return m_autil.mk_numeral(q, true); +} + // TODO refactor all of these so that they don't use variable counters, but use ast_manager::mk_fresh_const instead @@ -1470,14 +1474,6 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { } } - { - rational x_lb, x_ub; - bool x_lb_p = lower_bound(x, x_lb); - bool x_ub_p = upper_bound(x, x_ub); - TRACE("t_str_detail", tout << "X [" << x_lb << ":" << x_ub << "]" << std::endl - << "lb? " << (x_lb_p?"yes":"no") << " ub? " << (x_ub_p?"yes":"no") << std::endl;); - } - TRACE("t_str_detail", tout << "len(x) = " << (x_len_exists ? x_len.to_string() : "?") << std::endl << "len(y) = " << (y_len_exists ? y_len.to_string() : "?") << std::endl @@ -1518,7 +1514,47 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { // For split types 0 through 2, we can get away with providing // fewer split options since more length information is available. if (splitType == 0) { - NOT_IMPLEMENTED_YET(); // TODO + //-------------------------------------- + // Type 0: M cuts Y. + // len(x) < len(m) || len(y) > len(n) + //-------------------------------------- + if (!has_self_cut(m, y)) { + expr ** ax_l_items = alloc_svect(expr*, 3); + expr ** ax_r_items = alloc_svect(expr*, 3); + + ax_l_items[0] = ctx.mk_eq_atom(concatAst1, concatAst2); + + expr_ref x_t1(mk_concat(x, t1), mgr); + expr_ref t1_n(mk_concat(t1, n), mgr); + + ax_r_items[0] = ctx.mk_eq_atom(m, x_t1); + ax_r_items[1] = ctx.mk_eq_atom(y, t1_n); + + if (m_len_exists && x_len_exists) { + ax_l_items[1] = ctx.mk_eq_atom(mk_strlen(x), mk_int(x_len)); + ax_l_items[2] = ctx.mk_eq_atom(mk_strlen(m), mk_int(m_len)); + rational m_sub_x = m_len - x_len; + ax_r_items[2] = ctx.mk_eq_atom(mk_strlen(t1), mk_int(m_sub_x)); + } else { + ax_l_items[1] = ctx.mk_eq_atom(mk_strlen(y), mk_int(y_len)); + ax_l_items[2] = ctx.mk_eq_atom(mk_strlen(n), mk_int(n_len)); + rational y_sub_n = y_len - n_len; + ax_r_items[2] = ctx.mk_eq_atom(mk_strlen(t1), mk_int(y_sub_n)); + } + + expr_ref ax_l(mgr.mk_and(3, ax_l_items), mgr); + expr_ref ax_r(mgr.mk_and(3, ax_r_items), mgr); + + // Cut Info + add_cut_info_merge(t1, sLevel, m); + add_cut_info_merge(t1, sLevel, y); + + assert_implication(ax_l, ax_r); + } else { + loopDetected = true; + TRACE("t_str", tout << "AVOID LOOP: SKIPPED" << std::endl;); + // TODO printCutVar(m, y); + } } else if (splitType == 1) { NOT_IMPLEMENTED_YET(); // TODO } else if (splitType == 2) { diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index ecd7e443f..e8dc6909e 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -117,6 +117,7 @@ namespace smt { expr * mk_concat_const_str(expr * n1, expr * n2); app * mk_int(int n); + app * mk_int(rational & q); void check_and_init_cut_var(expr * node); void add_cut_info_one_node(expr * baseNode, int slevel, expr * node); From bd2b014008483ac42c53338007d5dab59b704880 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 8 Jun 2016 19:32:25 -0400 Subject: [PATCH 098/410] debugging information for dependence analysis --- src/smt/theory_str.cpp | 209 ++++++++++++++++++++++++++++++++++++++++- src/smt/theory_str.h | 22 +++++ 2 files changed, 227 insertions(+), 4 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index cc164ec3b..4918c999b 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -27,6 +27,10 @@ namespace smt { theory_str::theory_str(ast_manager & m): theory(m.mk_family_id("str")), + /* Options */ + opt_AggressiveLengthTesting(true), + opt_AggressiveValueTesting(true), + /* Internal setup */ search_started(false), m_autil(m), m_strutil(m), @@ -344,6 +348,14 @@ void theory_str::check_and_init_cut_var(expr * node) { } } +literal theory_str::mk_literal(expr* _e) { + ast_manager & m = get_manager(); + expr_ref e(_e, m); + context& ctx = get_context(); + ensure_enode(e); + return ctx.get_literal(e); +} + app * theory_str::mk_int(int n) { return m_autil.mk_numeral(rational(n), true); } @@ -3513,6 +3525,160 @@ inline expr * theory_str::getMostRightNodeInConcat(expr * node) { } } +void theory_str::trace_ctx_dep(std::ofstream & tout, + std::map & aliasIndexMap, + std::map & var_eq_constStr_map, + std::map > & var_eq_concat_map, + std::map & concat_eq_constStr_map, + std::map > & concat_eq_concat_map) { +#ifdef _TRACE + ast_manager & mgr = get_manager(); + { + tout << "(0) alias: variables" << std::endl; + std::map > aliasSumMap; + std::map::iterator itor0 = aliasIndexMap.begin(); + for (; itor0 != aliasIndexMap.end(); itor0++) { + aliasSumMap[itor0->second][itor0->first] = 1; + } + std::map >::iterator keyItor = aliasSumMap.begin(); + for (; keyItor != aliasSumMap.end(); keyItor++) { + tout << " * "; + tout << mk_pp(keyItor->first, mgr); + tout << " : "; + std::map::iterator innerItor = keyItor->second.begin(); + for (; innerItor != keyItor->second.end(); innerItor++) { + tout << mk_pp(innerItor->first, mgr); + tout << ", "; + } + tout << std::endl; + } + tout << std::endl; + } + + { + tout << "(1) var = constStr:" << std::endl; + std::map::iterator itor1 = var_eq_constStr_map.begin(); + for (; itor1 != var_eq_constStr_map.end(); itor1++) { + tout << " * "; + tout << mk_pp(itor1->first, mgr); + tout << " = "; + tout << mk_pp(itor1->second, mgr); + if (!in_same_eqc(itor1->first, itor1->second)) { + tout << " (not true in ctx)"; + } + tout << std::endl; + } + tout << std::endl; + } + + { + tout << "(2) var = concat:" << std::endl; + std::map >::iterator itor2 = var_eq_concat_map.begin(); + for (; itor2 != var_eq_concat_map.end(); itor2++) { + tout << " * "; + tout << mk_pp(itor2->first, mgr); + tout << " = { "; + std::map::iterator i_itor = itor2->second.begin(); + for (; i_itor != itor2->second.end(); i_itor++) { + tout << mk_pp(i_itor->first, mgr); + tout << ", "; + } + tout << std::endl; + } + tout << std::endl; + } +/*// TODO + { + __debugPrint(logFile, "(3) var = unrollFunc:\n"); + std::map >::iterator itor2 = var_eq_unroll_map.begin(); + for (; itor2 != var_eq_unroll_map.end(); itor2++) { + __debugPrint(logFile, " * "); + printZ3Node(t, itor2->first); + __debugPrint(logFile, " = { "); + std::map::iterator i_itor = itor2->second.begin(); + for (; i_itor != itor2->second.end(); i_itor++) { + printZ3Node(t, i_itor->first); + __debugPrint(logFile, ", "); + } + __debugPrint(logFile, " }\n"); + } + __debugPrint(logFile, "\n"); + } +*/ + { + tout << "(4) concat = constStr:" << std::endl; + std::map::iterator itor3 = concat_eq_constStr_map.begin(); + for (; itor3 != concat_eq_constStr_map.end(); itor3++) { + tout << " * "; + tout << mk_pp(itor3->first, mgr); + tout << " = "; + tout << mk_pp(itor3->second, mgr); + tout << std::endl; + + } + tout << std::endl; + } + + { + tout << "(5) eq concats:" << std::endl; + std::map >::iterator itor4 = concat_eq_concat_map.begin(); + for (; itor4 != concat_eq_concat_map.end(); itor4++) { + if (itor4->second.size() > 1) { + std::map::iterator i_itor = itor4->second.begin(); + tout << " * "; + for (; i_itor != itor4->second.end(); i_itor++) { + tout << mk_pp(i_itor->first, mgr); + tout << " , "; + } + tout << std::endl; + } + } + tout << std::endl; + } +/*// TODO + { + __debugPrint(logFile, "(6) eq unrolls:\n"); + std::map >::iterator itor5 = unrollGroupMap.begin(); + for (; itor5 != unrollGroupMap.end(); itor5++) { + __debugPrint(logFile, " * "); + std::set::iterator i_itor = itor5->second.begin(); + for (; i_itor != itor5->second.end(); i_itor++) { + printZ3Node(t, *i_itor); + __debugPrint(logFile, ", "); + } + __debugPrint(logFile, "\n"); + } + __debugPrint(logFile, "\n"); + } + + { + __debugPrint(logFile, "(7) unroll = concats:\n"); + std::map >::iterator itor5 = unrollGroupMap.begin(); + for (; itor5 != unrollGroupMap.end(); itor5++) { + __debugPrint(logFile, " * "); + Z3_ast unroll = itor5->first; + printZ3Node(t, unroll); + __debugPrint(logFile, "\n"); + Z3_ast curr = unroll; + do { + if (isConcatFunc(t, curr)) { + __debugPrint(logFile, " >>> "); + printZ3Node(t, curr); + __debugPrint(logFile, "\n"); + } + curr = Z3_theory_get_eqc_next(t, curr); + }while (curr != unroll); + __debugPrint(logFile, "\n"); + } + __debugPrint(logFile, "\n"); + } + */ +#else + return; +#endif // _TRACE +} + + /* * Dependence analysis from current context assignment * - "freeVarMap" contains a set of variables that doesn't constrained by Concats. @@ -3747,7 +3913,9 @@ int theory_str::ctx_dep_analysis(std::map & strVarMap, std::map & strVarMap, std::map & freeVarMap, std::map > & unrollGroupMap); + void trace_ctx_dep(std::ofstream & tout, + std::map & aliasIndexMap, + std::map & var_eq_constStr_map, + std::map > & var_eq_concat_map, + std::map & concat_eq_constStr_map, + std::map > & concat_eq_concat_map); + void classify_ast_by_type(expr * node, std::map & varMap, std::map & concatMap, std::map & unrollMap); void classify_ast_by_type_in_positive_context(std::map & varMap, From 633237257390ce0e4aa4ebc490a318953adce856 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 8 Jun 2016 20:01:56 -0400 Subject: [PATCH 099/410] more debugging info in theory_str final check; fix variable classification bug --- src/smt/theory_str.cpp | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 4918c999b..8523fa29c 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -3431,10 +3431,12 @@ void theory_str::dump_assignments() { void theory_str::classify_ast_by_type(expr * node, std::map & varMap, std::map & concatMap, std::map & unrollMap) { - // check whether the node is a non-internal string variable; - // testing set membership here bypasses several expensive checks + // check whether the node is a string variable; + // testing set membership here bypasses several expensive checks. + // note that internal variables don't count if they're only length tester / value tester vars. if (variable_set.find(node) != variable_set.end() - && internal_variable_set.find(node) == internal_variable_set.end()) { + && internal_lenTest_vars.find(node) == internal_lenTest_vars.end() + && internal_valTest_vars.find(node) == internal_valTest_vars.end()) { varMap[node] = 1; } // check whether the node is a function that we want to inspect @@ -4140,7 +4142,20 @@ int theory_str::ctx_dep_analysis(std::map & strVarMap, std::map >::iterator itor = depMap.begin(); itor != depMap.end(); itor++) { + tout << mk_pp(itor->first, m); + rational nnLen; + bool nnLen_exists = get_len_value(itor->first, nnLen); + tout << " [len = " << (nnLen_exists ? nnLen.to_string() : "?") << "] \t-->\t"; + for (std::map::iterator itor1 = itor->second.begin(); itor1 != itor->second.end(); itor1++) { + tout << mk_pp(itor1->first, m) << "(" << itor1->second << "), "; + } + tout << std::endl; + } + ); // step, errr, 5: compute free variables based on the dependence map From ae74b47924984a7778b77152fc00c96c918e178c Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Thu, 9 Jun 2016 15:41:31 -0400 Subject: [PATCH 100/410] string concat-eq type 1 integer integration --- src/smt/theory_str.cpp | 49 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 8523fa29c..6de5a10b7 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -1568,9 +1568,52 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { // TODO printCutVar(m, y); } } else if (splitType == 1) { - NOT_IMPLEMENTED_YET(); // TODO + // Type 1: + // len(x) = len(m) || len(y) = len(n) + expr_ref ax_l1(ctx.mk_eq_atom(concatAst1, concatAst2), mgr); + expr_ref ax_l2(mgr.mk_or(ctx.mk_eq_atom(mk_strlen(x), mk_strlen(m)), ctx.mk_eq_atom(mk_strlen(y), mk_strlen(n))), mgr); + expr_ref ax_l(mgr.mk_and(ax_l1, ax_l2), mgr); + expr_ref ax_r(mgr.mk_and(ctx.mk_eq_atom(x,m), ctx.mk_eq_atom(y,n)), mgr); + assert_implication(ax_l, ax_r); } else if (splitType == 2) { - NOT_IMPLEMENTED_YET(); // TODO + // Type 2: X cuts N. + // len(x) > len(m) || len(y) < len(n) + if (!has_self_cut(x, n)) { + expr_ref m_t2(mk_concat(m, t2), mgr); + expr_ref t2_y(mk_concat(t2, y), mgr); + + expr ** ax_l_items = alloc_svect(expr*, 3); + ax_l_items[0] = ctx.mk_eq_atom(concatAst1, concatAst2); + + expr ** ax_r_items = alloc_svect(expr*, 3); + ax_r_items[0] = ctx.mk_eq_atom(x, m_t2); + ax_r_items[1] = ctx.mk_eq_atom(t2_y, n); + + if (m_len_exists && x_len_exists) { + ax_l_items[1] = ctx.mk_eq_atom(mk_strlen(x), mk_int(x_len)); + ax_l_items[2] = ctx.mk_eq_atom(mk_strlen(m), mk_int(m_len)); + rational x_sub_m = x_len - m_len; + ax_r_items[2] = ctx.mk_eq_atom(mk_strlen(t2), mk_int(x_sub_m)); + } else { + ax_l_items[1] = ctx.mk_eq_atom(mk_strlen(y), mk_int(y_len)); + ax_l_items[2] = ctx.mk_eq_atom(mk_strlen(n), mk_int(n_len)); + rational n_sub_y = n_len - y_len; + ax_r_items[2] = ctx.mk_eq_atom(mk_strlen(t2), mk_int(n_sub_y)); + } + + expr_ref ax_l(mgr.mk_and(3, ax_l_items), mgr); + expr_ref ax_r(mgr.mk_and(3, ax_r_items), mgr); + + // Cut Info + add_cut_info_merge(t2, sLevel, x); + add_cut_info_merge(t2, sLevel, n); + + assert_implication(ax_l, ax_r); + } else { + loopDetected = true; + TRACE("t_str", tout << "AVOID LOOP: SKIPPED" << std::endl;); + // TODO printCutVar(m, y); + } } else if (splitType == -1) { // Here we don't really have a choice. We have no length information at all... expr ** or_item = alloc_svect(expr*, 3); @@ -1590,7 +1633,7 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { expr_ref x_plus_t1(m_autil.mk_add(mk_strlen(x), mk_strlen(t1)), mgr); and_item[pos++] = ctx.mk_eq_atom(or_item[option], ctx.mk_eq_atom(mk_strlen(m), x_plus_t1)); - // TODO these are crashing the solvers because the integer theory + // These were crashing the solver because the integer theory // expects a constant on the right-hand side. // The things we want to assert here are len(m) > len(x) and len(y) > len(n). // We rewrite A > B as A-B > 0 and then as not(A-B <= 0), From 6f5ee2c3ce50ebe3dfe18a4c391aa5765142a4a5 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Thu, 9 Jun 2016 16:04:13 -0400 Subject: [PATCH 101/410] string concat-eq type 2 integer integration --- src/smt/theory_str.cpp | 115 ++++++++++++++++++++++++++++++++++------- 1 file changed, 96 insertions(+), 19 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 6de5a10b7..0418fefd7 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -1776,21 +1776,14 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { y = v1_arg1; } - const char * strValue_tmp = 0; - m_strutil.is_string(strAst, &strValue_tmp); - std::string strValue(strValue_tmp); - // TODO integer theory interaction - /* - int x_len = getLenValue(t, x); - int y_len = getLenValue(t, y); - int m_len = getLenValue(t, m); - int str_len = getLenValue(t, strAst); - */ + std::string strValue = m_strutil.get_string_constant_value(strAst); - int x_len = -1; - int y_len = -1; - int m_len = -1; - int str_len = -1; + rational x_len, y_len, m_len, str_len; + bool x_len_exists = get_len_value(x, x_len); + bool y_len_exists = get_len_value(y, y_len); + bool m_len_exists = get_len_value(m, m_len); + bool str_len_exists = true; + str_len = rational((unsigned)(strValue.length())); // setup @@ -1816,7 +1809,7 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { } int splitType = -1; - if (x_len != -1 && m_len != -1) { + if (x_len_exists && m_len_exists) { if (x_len < m_len) splitType = 0; else if (x_len == m_len) @@ -1824,7 +1817,7 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { else splitType = 2; } - if (splitType == -1 && y_len != -1 && str_len != -1) { + if (splitType == -1 && y_len_exists && str_len_exists) { if (y_len > str_len) splitType = 0; else if (y_len == str_len) @@ -1838,11 +1831,95 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { // Provide fewer split options when length information is available. if (splitType == 0) { - NOT_IMPLEMENTED_YET(); // TODO + // M cuts Y + // | x | y | + // | m | str | + expr_ref temp1_strAst(mk_concat(temp1, strAst), mgr); + if (can_two_nodes_eq(y, temp1_strAst)) { + if (!avoidLoopCut || !(has_self_cut(m, y))) { + // break down option 2-1 + expr ** l_items = alloc_svect(expr*, 3); + l_items[0] = ctx.mk_eq_atom(concatAst1, concatAst2); + + expr ** r_items = alloc_svect(expr*, 3); + expr_ref x_temp1(mk_concat(x, temp1), mgr); + r_items[0] = ctx.mk_eq_atom(m, x_temp1); + r_items[1] = ctx.mk_eq_atom(y, temp1_strAst); + + if (x_len_exists && m_len_exists) { + l_items[1] = ctx.mk_eq_atom(mk_strlen(x), mk_int(x_len)); + l_items[2] = ctx.mk_eq_atom(mk_strlen(m), mk_int(m_len)); + rational m_sub_x = (m_len - x_len); + r_items[2] = ctx.mk_eq_atom(mk_strlen(temp1), mk_int(m_sub_x)); + } else { + l_items[1] = ctx.mk_eq_atom(mk_strlen(y), mk_int(y_len)); + l_items[2] = ctx.mk_eq_atom(mk_strlen(strAst), mk_int(str_len)); + rational y_sub_str = (y_len - str_len); + r_items[2] = ctx.mk_eq_atom(mk_strlen(temp1), mk_int(y_sub_str)); + } + + expr_ref ax_l(mgr.mk_and(3, l_items), mgr); + expr_ref ax_r(mgr.mk_and(3, r_items), mgr); + + add_cut_info_merge(temp1, sLevel, y); + add_cut_info_merge(temp1, sLevel, m); + + assert_implication(ax_l, ax_r); + } else { + loopDetected = true; + TRACE("t_str", tout << "AVOID LOOP: SKIP" << std::endl;); + // TODO printCutVar(m, y); + } + } } else if (splitType == 1) { - NOT_IMPLEMENTED_YET(); // TODO + // | x | y | + // | m | str | + expr_ref ax_l1(ctx.mk_eq_atom(concatAst1, concatAst2), mgr); + expr_ref ax_l2(mgr.mk_or( + ctx.mk_eq_atom(mk_strlen(x), mk_strlen(m)), + ctx.mk_eq_atom(mk_strlen(y), mk_strlen(strAst))), mgr); + expr_ref ax_l(mgr.mk_and(ax_l1, ax_l2), mgr); + expr_ref ax_r(mgr.mk_and(ctx.mk_eq_atom(x, m), ctx.mk_eq_atom(y, strAst)), mgr); + assert_implication(ax_l, ax_r); } else if (splitType == 2) { - NOT_IMPLEMENTED_YET(); // TODO + // m cut y, + // | x | y | + // | m | str | + rational lenDelta; + expr ** l_items = alloc_svect(expr*, 3); + int l_count = 0; + l_items[0] = ctx.mk_eq_atom(concatAst1, concatAst2); + if (x_len_exists && m_len_exists) { + l_items[1] = ctx.mk_eq_atom(mk_strlen(x), mk_int(x_len)); + l_items[2] = ctx.mk_eq_atom(mk_strlen(m), mk_int(m_len)); + l_count = 3; + lenDelta = x_len - m_len; + } else { + l_items[1] = ctx.mk_eq_atom(mk_strlen(y), mk_int(y_len)); + l_count = 2; + lenDelta = str_len - y_len; + } + std::string part1Str = strValue.substr(0, lenDelta.get_unsigned()); + std::string part2Str = strValue.substr(lenDelta.get_unsigned(), strValue.length() - lenDelta.get_unsigned()); + + expr_ref prefixStr(m_strutil.mk_string(part1Str), mgr); + expr_ref x_concat(mk_concat(m, prefixStr), mgr); + expr_ref cropStr(m_strutil.mk_string(part2Str), mgr); + + if (can_two_nodes_eq(x, x_concat) && can_two_nodes_eq(y, cropStr)) { + expr ** r_items = alloc_svect(expr*, 2); + r_items[0] = ctx.mk_eq_atom(x, x_concat); + r_items[1] = ctx.mk_eq_atom(y, cropStr); + expr_ref ax_l(mgr.mk_and(l_count, l_items), mgr); + expr_ref ax_r(mgr.mk_and(2, r_items), mgr); + + assert_implication(ax_l, ax_r); + } else { + // negate! It's impossible to split str with these lengths + TRACE("t_str", tout << "CONFLICT: Impossible to split str with these lengths." << std::endl;); + expr_ref ax_l(mgr.mk_and(l_count, l_items), mgr); + assert_axiom(mgr.mk_not(ax_l)); + } } else { // Split type -1: no idea about the length... int optionTotal = 2 + strValue.length(); From 91d82956b29a91c744390981a1acfd4f5653eadb Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Thu, 9 Jun 2016 16:25:19 -0400 Subject: [PATCH 102/410] string concat-eq type 3 integer integration --- src/smt/theory_str.cpp | 107 ++++++++++++++++++++++++++++++++++------- 1 file changed, 89 insertions(+), 18 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 0418fefd7..ba9b503be 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -2042,20 +2042,14 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { y = v1_arg1; } - const char * strValue_tmp = 0; - m_strutil.is_string(strAst, &strValue_tmp); - std::string strValue(strValue_tmp); + std::string strValue = m_strutil.get_string_constant_value(strAst); + // TODO integer theory interaction - /* - int x_len = getLenValue(t, x); - int y_len = getLenValue(t, y); - int str_len = getLenValue(t, strAst); - int n_len = getLenValue(t, n); - */ - int x_len = -1; - int y_len = -1; - int str_len = -1; - int n_len = -1; + rational x_len, y_len, str_len, n_len; + bool x_len_exists = get_len_value(x, x_len); + bool y_len_exists = get_len_value(y, y_len); + str_len = rational((unsigned)(strValue.length())); + bool n_len_exists = get_len_value(n, n_len); expr_ref xorFlag(mgr); expr_ref temp1(mgr); @@ -2080,7 +2074,7 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { int splitType = -1; - if (x_len != -1) { + if (x_len_exists) { if (x_len < str_len) splitType = 0; else if (x_len == str_len) @@ -2088,7 +2082,7 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { else splitType = 2; } - if (splitType == -1 && y_len != -1 && n_len != -1) { + if (splitType == -1 && y_len_exists && n_len_exists) { if (y_len > n_len) splitType = 0; else if (y_len == n_len) @@ -2101,13 +2095,90 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { // Provide fewer split options when length information is available. if (splitType == 0) { - NOT_IMPLEMENTED_YET(); // TODO + // | x | y | + // | str | n | + expr_ref_vector litems(mgr); + litems.push_back(ctx.mk_eq_atom(concatAst1, concatAst2)); + rational prefixLen; + if (!x_len_exists) { + prefixLen = str_len - (y_len - n_len); + litems.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_int(y_len))); + litems.push_back(ctx.mk_eq_atom(mk_strlen(n), mk_int(n_len))); + } else { + prefixLen = x_len; + litems.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_int(x_len))); + } + std::string prefixStr = strValue.substr(0, prefixLen.get_unsigned()); + rational str_sub_prefix = str_len - prefixLen; + std::string suffixStr = strValue.substr(prefixLen.get_unsigned(), str_sub_prefix.get_unsigned()); + expr_ref prefixAst(m_strutil.mk_string(prefixStr), mgr); + expr_ref suffixAst(m_strutil.mk_string(suffixStr), mgr); + expr_ref ax_l(mgr.mk_and(litems.size(), litems.c_ptr()), mgr); + + expr_ref suf_n_concat(mk_concat(suffixAst, n), mgr); + if (can_two_nodes_eq(x, prefixAst) && can_two_nodes_eq(y, suf_n_concat)) { + expr ** r_items = alloc_svect(expr*, 2); + r_items[0] = ctx.mk_eq_atom(x, prefixAst); + r_items[1] = ctx.mk_eq_atom(y, suf_n_concat); + assert_implication(ax_l, mgr.mk_and(2, r_items)); + } else { + // negate! It's impossible to split str with these lengths + TRACE("t_str", tout << "CONFLICT: Impossible to split str with these lengths." << std::endl;); + assert_axiom(mgr.mk_not(ax_l)); + } } else if (splitType == 1) { - NOT_IMPLEMENTED_YET(); // TODO + expr_ref ax_l1(ctx.mk_eq_atom(concatAst1, concatAst2), mgr); + expr_ref ax_l2(mgr.mk_or( + ctx.mk_eq_atom(mk_strlen(x), mk_strlen(strAst)), + ctx.mk_eq_atom(mk_strlen(y), mk_strlen(n))), mgr); + expr_ref ax_l(mgr.mk_and(ax_l1, ax_l2), mgr); + expr_ref ax_r(mgr.mk_and(ctx.mk_eq_atom(x, strAst), ctx.mk_eq_atom(y, n)), mgr); + assert_implication(ax_l, ax_r); } else if (splitType == 2) { - NOT_IMPLEMENTED_YET(); // TODO + // | x | y | + // | str | n | + expr_ref_vector litems(mgr); + litems.push_back(ctx.mk_eq_atom(concatAst1, concatAst2)); + rational tmpLen; + if (!x_len_exists) { + tmpLen = n_len - y_len; + litems.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_int(y_len))); + litems.push_back(ctx.mk_eq_atom(mk_strlen(n), mk_int(n_len))); + } else { + tmpLen = x_len - str_len; + litems.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_int(x_len))); + } + expr_ref ax_l(mgr.mk_and(litems.size(), litems.c_ptr()), mgr); + + expr_ref str_temp1(mk_concat(strAst, temp1), mgr); + expr_ref temp1_y(mk_concat(temp1, y), mgr); + + if (can_two_nodes_eq(x, str_temp1)) { + if (!avoidLoopCut || !(has_self_cut(x, n))) { + expr ** r_items = alloc_svect(expr*, 3); + r_items[0] = ctx.mk_eq_atom(x, str_temp1); + r_items[1] = ctx.mk_eq_atom(n, temp1_y); + r_items[2] = ctx.mk_eq_atom(mk_strlen(temp1), mk_int(tmpLen)); + expr_ref ax_r(mgr.mk_and(3, r_items), mgr); + + //Cut Info + add_cut_info_merge(temp1, sLevel, x); + add_cut_info_merge(temp1, sLevel, n); + + assert_implication(ax_l, ax_r); + } else { + loopDetected = true; + TRACE("t_str", tout << "AVOID LOOP: SKIPPED" << std::endl;); + // TODO printCutVar(x, n); + } + } + // else { + // // negate! It's impossible to split str with these lengths + // __debugPrint(logFile, "[Conflict] Negate! It's impossible to split str with these lengths @ %d.\n", __LINE__); + // addAxiom(t, Z3_mk_not(ctx, ax_l), __LINE__); + // } } else { // Split type -1. We know nothing about the length... From 1520760a04de41827bb177d2d67427033c9d2286 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Thu, 9 Jun 2016 20:31:21 -0400 Subject: [PATCH 103/410] string-integer integration in free var gen --- src/smt/theory_str.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index ba9b503be..f8366ed07 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -5302,13 +5302,12 @@ void theory_str::process_free_var(std::map & freeVar_map) { } if (standAlone) { - // TODO - // int lenValue = getLenValue(freeVar); - int lenValue = -1; - if (lenValue != -1) { + rational len_value; + bool len_value_exists = get_len_value(freeVar, len_value); + if (len_value_exists) { leafVarSet.insert(freeVar); } else { - aloneVars[lenValue].insert(freeVar); + aloneVars[-1].insert(freeVar); } } else { leafVarSet.insert(freeVar); From fd968783a599961a67ccc3afb2b90c4a776f9f38 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Thu, 9 Jun 2016 20:35:26 -0400 Subject: [PATCH 104/410] fix model generation for theory_str --- src/smt/theory_str.h | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 6616e9ffa..99899b365 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -44,9 +44,7 @@ namespace smt { return true; } virtual expr * get_fresh_value(sort * s) { - // TODO this may be causing crashes in model gen? investigate - //return m_util.mk_fresh_string(); - NOT_IMPLEMENTED_YET(); + return m_util.mk_fresh_string(); } virtual void register_value(expr * n) { /* Ignore */ } }; From 08328c5614f26712946d57bd0d5594831608c292 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sun, 12 Jun 2016 17:16:14 -0400 Subject: [PATCH 105/410] add option in theory_str to assert string constant lengths more eagerly now passes z3str/concat-025 --- src/smt/theory_str.cpp | 11 ++++++++++- src/smt/theory_str.h | 10 ++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index f8366ed07..02db2132a 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -30,6 +30,7 @@ theory_str::theory_str(ast_manager & m): /* Options */ opt_AggressiveLengthTesting(true), opt_AggressiveValueTesting(true), + opt_EagerStringConstantLengthAssertions(true), /* Internal setup */ search_started(false), m_autil(m), @@ -191,7 +192,9 @@ bool theory_str::internalize_term(app * term) { //} */ - // from theory_seq::internalize_term() + // TODO do we still need to do instantiate_concat_axiom()? + + // partially from theory_seq::internalize_term() if (ctx.e_internalized(term)) { enode* e = ctx.get_enode(term); mk_var(e); @@ -217,6 +220,12 @@ bool theory_str::internalize_term(app * term) { else { e = ctx.mk_enode(term, false, m.is_bool(term), true); } + + if (opt_EagerStringConstantLengthAssertions && m_strutil.is_string(term)) { + TRACE("t_str", tout << "eagerly asserting length of string term " << mk_pp(term, m) << std::endl;); + m_basicstr_axiom_todo.insert(e); + } + theory_var v = mk_var(e); TRACE("t_str_detail", tout << "term " << mk_ismt2_pp(term, get_manager()) << " = v#" << v << std::endl;); diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 99899b365..2b8077a13 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -74,6 +74,15 @@ namespace smt { */ bool opt_AggressiveValueTesting; + /* + * Setting EagerStringConstantLengthAssertions to true allows some methods, + * in particular internalize_term(), to add + * length assertions about relevant string constants. + * Note that currently this should always be set to 'true', or else *no* length assertions + * will be made about string constants. + */ + bool opt_EagerStringConstantLengthAssertions; + bool search_started; arith_util m_autil; str_util m_strutil; @@ -87,6 +96,7 @@ namespace smt { ptr_vector m_basicstr_axiom_todo; svector > m_str_eq_todo; ptr_vector m_concat_axiom_todo; + ptr_vector m_string_constant_length_todo; int tmpStringVarCount; int tmpXorVarCount; From 18cd47dcd02c92a5805b1ccb04b4879d06273aa1 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sun, 12 Jun 2016 20:14:57 -0400 Subject: [PATCH 106/410] add flag for bailing out during a final check infinite loop in theory_str also adds more debugging to free variable gen --- src/smt/theory_str.cpp | 28 +++++++++++++++++++++++++++- src/smt/theory_str.h | 8 ++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 02db2132a..aaeb9ccce 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -31,11 +31,13 @@ theory_str::theory_str(ast_manager & m): opt_AggressiveLengthTesting(true), opt_AggressiveValueTesting(true), opt_EagerStringConstantLengthAssertions(true), + opt_VerifyFinalCheckProgress(true), /* Internal setup */ search_started(false), m_autil(m), m_strutil(m), sLevel(0), + finalCheckProgressIndicator(false), m_trail(m), tmpStringVarCount(0), tmpXorVarCount(0), @@ -125,6 +127,9 @@ void theory_str::initialize_charset() { } void theory_str::assert_axiom(expr * e) { + if (opt_VerifyFinalCheckProgress) { + finalCheckProgressIndicator = true; + } if (get_manager().is_true(e)) return; TRACE("t_str_detail", tout << "asserting " << mk_ismt2_pp(e, get_manager()) << std::endl;); context & ctx = get_context(); @@ -4501,6 +4506,10 @@ final_check_status theory_str::final_check_eh() { context & ctx = get_context(); ast_manager & m = get_manager(); + if (opt_VerifyFinalCheckProgress) { + finalCheckProgressIndicator = false; + } + TRACE("t_str", tout << "final check" << std::endl;); TRACE("t_str_detail", dump_assignments();); @@ -4655,7 +4664,19 @@ final_check_status theory_str::final_check_eh() { constValue = NULL; - // TODO this would be a great place to print debugging information + { + TRACE("t_str_detail", tout << "free var map (# " << freeVar_map.size() << "):" << std::endl; + for (std::map::iterator freeVarItor1 = freeVar_map.begin(); freeVarItor1 != freeVar_map.end(); freeVarItor1++) { + expr * freeVar = freeVarItor1->first; + rational lenValue; + bool lenValue_exists = get_len_value(freeVar, lenValue); + // TODO get_bound_strlen() + tout << mk_pp(freeVar, m) << " [depCnt = " << freeVarItor1->second << ", length = " + << (lenValue_exists ? lenValue.to_string() : "?") + << "]" << std::endl; + } + ); + } // TODO process_concat_eq_unroll() /* @@ -4712,6 +4733,11 @@ final_check_status theory_str::final_check_eh() { } */ + if (opt_VerifyFinalCheckProgress && !finalCheckProgressIndicator) { + TRACE("t_str", tout << "BUG: no progress in final check, giving up!!" << std::endl;); + m.raise_exception("no progress in theory_str final check"); + } + return FC_CONTINUE; // since by this point we've added axioms } diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 2b8077a13..562f49004 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -83,11 +83,19 @@ namespace smt { */ bool opt_EagerStringConstantLengthAssertions; + /* + * If VerifyFinalCheckProgress is set to true, continuing after final check is invoked + * without asserting any new axioms is considered a bug and will throw an exception. + */ + bool opt_VerifyFinalCheckProgress; + bool search_started; arith_util m_autil; str_util m_strutil; int sLevel; + bool finalCheckProgressIndicator; + // TODO make sure that all generated expressions are saved into the trail expr_ref_vector m_trail; // trail for generated terms From 7d09dbb8ec8685a0cb9b75bdf87733839fc179e1 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sun, 12 Jun 2016 20:46:52 -0400 Subject: [PATCH 107/410] basic infrastructure for string rewriting --- src/ast/rewriter/rewriter.txt | 2 + src/ast/rewriter/str_rewriter.cpp | 66 +++++++++++++++++++++++++++++++ src/ast/rewriter/str_rewriter.h | 46 +++++++++++++++++++++ src/ast/rewriter/th_rewriter.cpp | 8 ++++ 4 files changed, 122 insertions(+) create mode 100644 src/ast/rewriter/str_rewriter.cpp create mode 100644 src/ast/rewriter/str_rewriter.h diff --git a/src/ast/rewriter/rewriter.txt b/src/ast/rewriter/rewriter.txt index cdfba9f0f..a7a9e5eff 100644 --- a/src/ast/rewriter/rewriter.txt +++ b/src/ast/rewriter/rewriter.txt @@ -7,6 +7,8 @@ The following classes implement theory specific rewriting rules: - array_rewriter - datatype_rewriter - fpa_rewriter + - seq_rewriter + - str_rewriter Each of them provide the method br_status mk_app_core(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result) diff --git a/src/ast/rewriter/str_rewriter.cpp b/src/ast/rewriter/str_rewriter.cpp new file mode 100644 index 000000000..35a255871 --- /dev/null +++ b/src/ast/rewriter/str_rewriter.cpp @@ -0,0 +1,66 @@ +/*++ +Copyright (c) 2016 Microsoft Corporation + +Module Name: + + str_rewriter.cpp + +Abstract: + + AST rewriting rules for string terms. + +Author: + + Murphy Berzish + +Notes: + +--*/ + +#include"str_rewriter.h" +#include"arith_decl_plugin.h" +#include"ast_pp.h" +#include"ast_util.h" +#include"well_sorted.h" + +br_status str_rewriter::mk_app_core(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result) { + SASSERT(f->get_family_id() == get_fid()); + + TRACE("t_str_rw", tout << "rewrite app: " << f->get_name() << std::endl;); + + switch(f->get_decl_kind()) { + default: + return BR_FAILED; + } +} + +br_status str_rewriter::mk_eq_core(expr * l, expr * r, expr_ref & result) { + // from seq_rewriter + expr_ref_vector lhs(m()), rhs(m()), res(m()); + bool changed = false; + if (!reduce_eq(l, r, lhs, rhs, changed)) { + result = m().mk_false(); + return BR_DONE; + } + if (!changed) { + return BR_FAILED; + } + for (unsigned i = 0; i < lhs.size(); ++i) { + res.push_back(m().mk_eq(lhs[i].get(), rhs[i].get())); + } + result = mk_and(res); + return BR_REWRITE3; +} + +bool str_rewriter::reduce_eq(expr * l, expr * r, expr_ref_vector & lhs, expr_ref_vector & rhs, bool & change) { + // TODO inspect seq_rewriter::reduce_eq() + change = false; + return true; +} + +bool str_rewriter::reduce_eq(expr_ref_vector& ls, expr_ref_vector& rs, expr_ref_vector& lhs, expr_ref_vector& rhs, bool& change) { + // TODO inspect seq_rewriter::reduce_eq() + change = false; + return true; +} + diff --git a/src/ast/rewriter/str_rewriter.h b/src/ast/rewriter/str_rewriter.h new file mode 100644 index 000000000..fde36e92e --- /dev/null +++ b/src/ast/rewriter/str_rewriter.h @@ -0,0 +1,46 @@ +/*++ +Copyright (c) 2016 Microsoft Corporation + +Module Name: + + str_rewriter.h + +Abstract: + + AST rewriting rules for string terms. + +Author: + + Murphy Berzish + +Notes: + +--*/ + +#include"str_decl_plugin.h" +#include"arith_decl_plugin.h" +#include"rewriter_types.h" +#include"params.h" + +class str_rewriter { + str_util m_strutil; + arith_util m_autil; + +public: + str_rewriter(ast_manager & m, params_ref const & p = params_ref()) : + m_strutil(m), m_autil(m) { + } + + ast_manager & m() const { return m_strutil.get_manager(); } + family_id get_fid() const { return m_strutil.get_family_id(); } + + void updt_params(params_ref const & p) {} + static void get_param_descrs(param_descrs & r) {} + + br_status mk_app_core(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result); + br_status mk_eq_core(expr * lhs, expr * rhs, expr_ref & result); + + bool reduce_eq(expr * l, expr * r, expr_ref_vector & lhs, expr_ref_vector & rhs, bool & change); + bool reduce_eq(expr_ref_vector& ls, expr_ref_vector& rs, expr_ref_vector& lhs, expr_ref_vector& rhs, bool& change); + +}; diff --git a/src/ast/rewriter/th_rewriter.cpp b/src/ast/rewriter/th_rewriter.cpp index 6f6daf8df..a56ca91d8 100644 --- a/src/ast/rewriter/th_rewriter.cpp +++ b/src/ast/rewriter/th_rewriter.cpp @@ -27,6 +27,7 @@ Notes: #include"dl_rewriter.h" #include"pb_rewriter.h" #include"seq_rewriter.h" +#include"str_rewriter.h" #include"rewriter_def.h" #include"expr_substitution.h" #include"ast_smt2_pp.h" @@ -45,6 +46,7 @@ struct th_rewriter_cfg : public default_rewriter_cfg { dl_rewriter m_dl_rw; pb_rewriter m_pb_rw; seq_rewriter m_seq_rw; + str_rewriter m_str_rw; arith_util m_a_util; bv_util m_bv_util; unsigned long long m_max_memory; // in bytes @@ -79,6 +81,7 @@ struct th_rewriter_cfg : public default_rewriter_cfg { m_ar_rw.updt_params(p); m_f_rw.updt_params(p); m_seq_rw.updt_params(p); + m_str_rw.updt_params(p); updt_local_params(p); } @@ -179,6 +182,8 @@ struct th_rewriter_cfg : public default_rewriter_cfg { st = m_ar_rw.mk_eq_core(args[0], args[1], result); else if (s_fid == m_seq_rw.get_fid()) st = m_seq_rw.mk_eq_core(args[0], args[1], result); + else if (s_fid == m_str_rw.get_fid()) + st = m_str_rw.mk_eq_core(args[0], args[1], result); if (st != BR_FAILED) return st; @@ -207,6 +212,8 @@ struct th_rewriter_cfg : public default_rewriter_cfg { return m_pb_rw.mk_app_core(f, num, args, result); if (fid == m_seq_rw.get_fid()) return m_seq_rw.mk_app_core(f, num, args, result); + if (fid == m_str_rw.get_fid()) + return m_str_rw.mk_app_core(f, num, args, result); return BR_FAILED; } @@ -665,6 +672,7 @@ struct th_rewriter_cfg : public default_rewriter_cfg { m_dl_rw(m), m_pb_rw(m), m_seq_rw(m), + m_str_rw(m), m_a_util(m), m_bv_util(m), m_used_dependencies(m), From 389845180c0f03cc0f808ac7cd1cab3bac691e1d Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Mon, 13 Jun 2016 16:34:24 -0400 Subject: [PATCH 108/410] add CharAt to theory_str and basic rewrite rule for constant CharAt exprs --- src/ast/rewriter/str_rewriter.cpp | 44 +++++++++++++++++++++++++++++++ src/ast/rewriter/str_rewriter.h | 2 ++ src/ast/str_decl_plugin.cpp | 10 ++++++- src/ast/str_decl_plugin.h | 6 ++++- 4 files changed, 60 insertions(+), 2 deletions(-) diff --git a/src/ast/rewriter/str_rewriter.cpp b/src/ast/rewriter/str_rewriter.cpp index 35a255871..3967453d4 100644 --- a/src/ast/rewriter/str_rewriter.cpp +++ b/src/ast/rewriter/str_rewriter.cpp @@ -23,12 +23,56 @@ Notes: #include"ast_util.h" #include"well_sorted.h" +br_status str_rewriter::mk_str_CharAt(expr * arg0, expr * arg1, expr_ref & result) { + TRACE("t_str_rw", tout << "rewrite (CharAt " << mk_pp(arg0, m()) << " " << mk_pp(arg1, m()) << ")" << std::endl;); + // if arg0 is a string constant and arg1 is an integer constant, + // we can rewrite this by evaluating the expression + rational arg1Int; + if (m_strutil.is_string(arg0) && m_autil.is_numeral(arg1, arg1Int)) { + TRACE("t_str_rw", tout << "evaluating constant CharAt expression" << std::endl;); + std::string arg0Str = m_strutil.get_string_constant_value(arg0); + std::string resultStr; + if (arg1Int >= rational(0) && arg1Int <= rational((unsigned)arg0Str.length())) { + resultStr = arg0Str.at(arg1Int.get_unsigned()); + TRACE("t_str_rw", tout << "result is '" << resultStr << "'" << std::endl;); + } else { + resultStr = ""; + TRACE("t_str_rw", tout << "bogus length argument, result is empty string" << std::endl;); + } + result = m_strutil.mk_string(resultStr); + return BR_DONE; + } else { + // TODO NEXT + NOT_IMPLEMENTED_YET(); + /* + Z3_ast ts0 = my_mk_internal_string_var(t); + Z3_ast ts1 = my_mk_internal_string_var(t); + Z3_ast ts2 = my_mk_internal_string_var(t); + + Z3_ast cond = mk_2_and(t, Z3_mk_ge(ctx, args[1], mk_int(ctx, 0)), Z3_mk_lt(ctx, args[1], mk_length(t, args[0]))); + + Z3_ast and_item[3]; + and_item[0] = Z3_mk_eq(ctx, args[0], mk_concat(t, ts0, mk_concat(t, ts1, ts2))); + and_item[1] = Z3_mk_eq(ctx, args[1], mk_length(t, ts0)); + and_item[2] = Z3_mk_eq(ctx, mk_length(t, ts1), mk_int(ctx, 1)); + Z3_ast thenBranch = Z3_mk_and(ctx, 3, and_item); + Z3_ast elseBranch = Z3_mk_eq(ctx, ts1, my_mk_str_value(t, "")); + breakdownAssert = Z3_mk_ite(ctx, cond, thenBranch, elseBranch); + return ts1; + */ + } +} + br_status str_rewriter::mk_app_core(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result) { SASSERT(f->get_family_id() == get_fid()); TRACE("t_str_rw", tout << "rewrite app: " << f->get_name() << std::endl;); + // TODO more rewrites for really easy cases, e.g. (Concat "abc" "def")... switch(f->get_decl_kind()) { + case OP_STR_CHARAT: + SASSERT(num_args == 2); + return mk_str_CharAt(args[0], args[1], result); default: return BR_FAILED; } diff --git a/src/ast/rewriter/str_rewriter.h b/src/ast/rewriter/str_rewriter.h index fde36e92e..01ccde242 100644 --- a/src/ast/rewriter/str_rewriter.h +++ b/src/ast/rewriter/str_rewriter.h @@ -40,6 +40,8 @@ public: br_status mk_app_core(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result); br_status mk_eq_core(expr * lhs, expr * rhs, expr_ref & result); + br_status mk_str_CharAt(expr * arg0, expr * arg1, expr_ref & result); + bool reduce_eq(expr * l, expr * r, expr_ref_vector & lhs, expr_ref_vector & rhs, bool & change); bool reduce_eq(expr_ref_vector& ls, expr_ref_vector& rs, expr_ref_vector& lhs, expr_ref_vector& rhs, bool& change); diff --git a/src/ast/str_decl_plugin.cpp b/src/ast/str_decl_plugin.cpp index cd9cae5a5..03fde5aeb 100644 --- a/src/ast/str_decl_plugin.cpp +++ b/src/ast/str_decl_plugin.cpp @@ -26,6 +26,7 @@ str_decl_plugin::str_decl_plugin(): m_str_decl(0), m_concat_decl(0), m_length_decl(0), + m_charat_decl(0), m_arith_plugin(0), m_arith_fid(0), m_int_sort(0){ @@ -39,6 +40,7 @@ void str_decl_plugin::finalize(void) { DEC_REF(m_str_decl); DEC_REF(m_concat_decl); DEC_REF(m_length_decl); + DEC_REF(m_charat_decl); DEC_REF(m_int_sort); } @@ -64,7 +66,11 @@ void str_decl_plugin::set_manager(ast_manager * m, family_id id) { MK_OP(m_concat_decl, "Concat", OP_STRCAT, s); - m_length_decl = m->mk_func_decl(symbol("Length"), s, i, func_decl_info(id, OP_STRLEN)); m_manager->inc_ref(m_length_decl); + m_length_decl = m->mk_func_decl(symbol("Length"), s, i, func_decl_info(id, OP_STRLEN)); + m_manager->inc_ref(m_length_decl); + + m_charat_decl = m->mk_func_decl(symbol("CharAt"), s, i, s, func_decl_info(id, OP_STR_CHARAT)); + m_manager->inc_ref(m_charat_decl); } decl_plugin * str_decl_plugin::mk_fresh() { @@ -82,6 +88,7 @@ func_decl * str_decl_plugin::mk_func_decl(decl_kind k) { switch(k) { case OP_STRCAT: return m_concat_decl; case OP_STRLEN: return m_length_decl; + case OP_STR_CHARAT: return m_charat_decl; default: return 0; } } @@ -138,6 +145,7 @@ app * str_decl_plugin::mk_fresh_string() { void str_decl_plugin::get_op_names(svector & op_names, symbol const & logic) { op_names.push_back(builtin_name("Concat", OP_STRCAT)); op_names.push_back(builtin_name("Length", OP_STRLEN)); + op_names.push_back(builtin_name("CharAt", OP_STR_CHARAT)); } void str_decl_plugin::get_sort_names(svector & sort_names, symbol const & logic) { diff --git a/src/ast/str_decl_plugin.h b/src/ast/str_decl_plugin.h index 4f46fa5ac..049ef08ba 100644 --- a/src/ast/str_decl_plugin.h +++ b/src/ast/str_decl_plugin.h @@ -27,9 +27,12 @@ enum str_sort_kind { enum str_op_kind { OP_STR, /* string constants */ - // + // basic string operators OP_STRCAT, OP_STRLEN, + // higher-level string functions -- these are reduced to basic operations + OP_STR_CHARAT, + // end LAST_STR_OP }; @@ -40,6 +43,7 @@ protected: func_decl * m_concat_decl; func_decl * m_length_decl; + func_decl * m_charat_decl; arith_decl_plugin * m_arith_plugin; family_id m_arith_fid; From be5cc02a4594c33b76fc1ca01586486071e0f272 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Mon, 13 Jun 2016 21:57:08 -0400 Subject: [PATCH 109/410] working axiomatization for CharAt --- src/ast/rewriter/str_rewriter.cpp | 4 +-- src/smt/theory_str.cpp | 49 ++++++++++++++++++++++++++++++- src/smt/theory_str.h | 7 +++++ 3 files changed, 57 insertions(+), 3 deletions(-) diff --git a/src/ast/rewriter/str_rewriter.cpp b/src/ast/rewriter/str_rewriter.cpp index 3967453d4..76c0d25ae 100644 --- a/src/ast/rewriter/str_rewriter.cpp +++ b/src/ast/rewriter/str_rewriter.cpp @@ -42,8 +42,8 @@ br_status str_rewriter::mk_str_CharAt(expr * arg0, expr * arg1, expr_ref & resul result = m_strutil.mk_string(resultStr); return BR_DONE; } else { - // TODO NEXT - NOT_IMPLEMENTED_YET(); + // TODO if we ever figure out how to assert axioms in here, add this code + return BR_FAILED; /* Z3_ast ts0 = my_mk_internal_string_var(t); Z3_ast ts1 = my_mk_internal_string_var(t); diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index aaeb9ccce..fe8f12e81 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -567,7 +567,9 @@ expr * theory_str::mk_concat(expr * n1, expr * n2) { } bool theory_str::can_propagate() { - return !m_basicstr_axiom_todo.empty() || !m_str_eq_todo.empty() || !m_concat_axiom_todo.empty(); + return !m_basicstr_axiom_todo.empty() || !m_str_eq_todo.empty() || !m_concat_axiom_todo.empty() + || !m_axiom_CharAt_todo.empty() + ; } void theory_str::propagate() { @@ -590,6 +592,11 @@ void theory_str::propagate() { instantiate_concat_axiom(m_concat_axiom_todo[i]); } m_concat_axiom_todo.reset(); + + for (unsigned i = 0; i < m_axiom_CharAt_todo.size(); ++i) { + instantiate_axiom_CharAt(m_axiom_CharAt_todo[i]); + } + m_axiom_CharAt_todo.reset(); } } @@ -738,6 +745,44 @@ void theory_str::instantiate_str_eq_length_axiom(enode * lhs, enode * rhs) { assert_implication(premise, conclusion); } +void theory_str::instantiate_axiom_CharAt(enode * e) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + app * expr = e->get_owner(); + + TRACE("t_str_detail", tout << "instantiate CharAt axiom for " << mk_pp(expr, m) << std::endl;); + + expr_ref ts0(mk_str_var("ts0"), m); + expr_ref ts1(mk_str_var("ts1"), m); + expr_ref ts2(mk_str_var("ts2"), m); + + expr_ref cond(m.mk_and( + m_autil.mk_ge(expr->get_arg(1), mk_int(0)), + // REWRITE for arithmetic theory: + // m_autil.mk_lt(expr->get_arg(1), mk_strlen(expr->get_arg(0))) + m.mk_not(m_autil.mk_ge(m_autil.mk_add(expr->get_arg(1), m_autil.mk_mul(mk_int(-1), mk_strlen(expr->get_arg(0)))), mk_int(0))) + ), m); + + expr_ref_vector and_item(m); + and_item.push_back(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(ts0, mk_concat(ts1, ts2)))); + and_item.push_back(ctx.mk_eq_atom(expr->get_arg(1), mk_strlen(ts0))); + and_item.push_back(ctx.mk_eq_atom(mk_strlen(ts1), mk_int(1))); + + expr_ref thenBranch(m.mk_and(and_item.size(), and_item.c_ptr()), m); + expr_ref elseBranch(ctx.mk_eq_atom(ts1, m_strutil.mk_string("")), m); + + expr_ref axiom(m.mk_ite(cond, thenBranch, elseBranch), m); + expr_ref reductionVar(ctx.mk_eq_atom(expr, ts1), m); + + SASSERT(axiom); + SASSERT(reductionVar); + + expr_ref finalAxiom(m.mk_and(axiom, reductionVar), m); + SASSERT(finalAxiom); + assert_axiom(finalAxiom); +} + void theory_str::attach_new_th_var(enode * n) { context & ctx = get_context(); theory_var v = mk_var(n); @@ -3469,6 +3514,8 @@ void theory_str::set_up_axioms(expr * ex) { if (aVar->get_num_args() == 0 && !is_string(aVar)) { input_var_in_len.insert(var); } + } else if (is_CharAt(ap)) { + m_axiom_CharAt_todo.push_back(n); } else if (ap->get_num_args() == 0 && !is_string(ap)) { // if ex is a variable, add it to our list of variables TRACE("t_str_detail", tout << "tracking variable " << mk_ismt2_pp(ap, get_manager()) << std::endl;); diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 562f49004..c86328d30 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -106,6 +106,9 @@ namespace smt { ptr_vector m_concat_axiom_todo; ptr_vector m_string_constant_length_todo; + // enode lists for term-specific axioms + ptr_vector m_axiom_CharAt_todo; + int tmpStringVarCount; int tmpXorVarCount; int tmpLenTestVarCount; @@ -167,10 +170,14 @@ namespace smt { bool is_string(enode const * n) const { return is_string(n->get_owner()); } bool is_strlen(app const * a) const { return a->is_app_of(get_id(), OP_STRLEN); } bool is_strlen(enode const * n) const { return is_strlen(n->get_owner()); } + bool is_CharAt(app const * a) const { return a->is_app_of(get_id(), OP_STR_CHARAT); } + bool is_CharAt(enode const * n) const { return is_CharAt(n->get_owner()); } void instantiate_concat_axiom(enode * cat); void instantiate_basic_string_axioms(enode * str); void instantiate_str_eq_length_axiom(enode * lhs, enode * rhs); + void instantiate_axiom_CharAt(enode * e); + void set_up_axioms(expr * ex); void handle_equality(expr * lhs, expr * rhs); From 7d8e54c50f2de8655c737a64853d86eaa7633a12 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Mon, 13 Jun 2016 22:27:46 -0400 Subject: [PATCH 110/410] decl and rewriter for string StartsWith --- src/ast/rewriter/str_rewriter.cpp | 41 +++++++++++++++++++------------ src/ast/rewriter/str_rewriter.h | 1 + src/ast/str_decl_plugin.cpp | 9 +++++++ src/ast/str_decl_plugin.h | 3 +++ 4 files changed, 38 insertions(+), 16 deletions(-) diff --git a/src/ast/rewriter/str_rewriter.cpp b/src/ast/rewriter/str_rewriter.cpp index 76c0d25ae..4b7ff9057 100644 --- a/src/ast/rewriter/str_rewriter.cpp +++ b/src/ast/rewriter/str_rewriter.cpp @@ -42,24 +42,30 @@ br_status str_rewriter::mk_str_CharAt(expr * arg0, expr * arg1, expr_ref & resul result = m_strutil.mk_string(resultStr); return BR_DONE; } else { - // TODO if we ever figure out how to assert axioms in here, add this code + // TODO if we ever figure out how to assert axioms in here, add the axiom code from Z3str2's strAstReduce.cpp return BR_FAILED; - /* - Z3_ast ts0 = my_mk_internal_string_var(t); - Z3_ast ts1 = my_mk_internal_string_var(t); - Z3_ast ts2 = my_mk_internal_string_var(t); + } +} - Z3_ast cond = mk_2_and(t, Z3_mk_ge(ctx, args[1], mk_int(ctx, 0)), Z3_mk_lt(ctx, args[1], mk_length(t, args[0]))); - - Z3_ast and_item[3]; - and_item[0] = Z3_mk_eq(ctx, args[0], mk_concat(t, ts0, mk_concat(t, ts1, ts2))); - and_item[1] = Z3_mk_eq(ctx, args[1], mk_length(t, ts0)); - and_item[2] = Z3_mk_eq(ctx, mk_length(t, ts1), mk_int(ctx, 1)); - Z3_ast thenBranch = Z3_mk_and(ctx, 3, and_item); - Z3_ast elseBranch = Z3_mk_eq(ctx, ts1, my_mk_str_value(t, "")); - breakdownAssert = Z3_mk_ite(ctx, cond, thenBranch, elseBranch); - return ts1; - */ +br_status str_rewriter::mk_str_StartsWith(expr * haystack, expr * needle, expr_ref & result) { + TRACE("t_str_rw", tout << "rewrite (StartsWith " << mk_pp(haystack, m()) << " " << mk_pp(needle, m()) << ")" << std::endl;); + if (m_strutil.is_string(haystack) && m_strutil.is_string(needle)) { + TRACE("t_str_rw", tout << "evaluating constant StartsWith predicate" << std::endl;); + std::string haystackStr = m_strutil.get_string_constant_value(haystack); + std::string needleStr = m_strutil.get_string_constant_value(needle); + if (haystackStr.length() < needleStr.length()) { + result = m().mk_false(); + return BR_DONE; + } else { + if (haystackStr.substr(0, needleStr.length()) == needleStr) { + result = m().mk_true(); + } else { + result = m().mk_false(); + } + return BR_DONE; + } + } else { + return BR_FAILED; } } @@ -73,6 +79,9 @@ br_status str_rewriter::mk_app_core(func_decl * f, unsigned num_args, expr * con case OP_STR_CHARAT: SASSERT(num_args == 2); return mk_str_CharAt(args[0], args[1], result); + case OP_STR_STARTSWITH: + SASSERT(num_args == 2); + return mk_str_StartsWith(args[0], args[1], result); default: return BR_FAILED; } diff --git a/src/ast/rewriter/str_rewriter.h b/src/ast/rewriter/str_rewriter.h index 01ccde242..8f12a75db 100644 --- a/src/ast/rewriter/str_rewriter.h +++ b/src/ast/rewriter/str_rewriter.h @@ -41,6 +41,7 @@ public: br_status mk_eq_core(expr * lhs, expr * rhs, expr_ref & result); br_status mk_str_CharAt(expr * arg0, expr * arg1, expr_ref & result); + br_status mk_str_StartsWith(expr * haystack, expr * needle, expr_ref & result); bool reduce_eq(expr * l, expr * r, expr_ref_vector & lhs, expr_ref_vector & rhs, bool & change); bool reduce_eq(expr_ref_vector& ls, expr_ref_vector& rs, expr_ref_vector& lhs, expr_ref_vector& rhs, bool& change); diff --git a/src/ast/str_decl_plugin.cpp b/src/ast/str_decl_plugin.cpp index 03fde5aeb..c6328d592 100644 --- a/src/ast/str_decl_plugin.cpp +++ b/src/ast/str_decl_plugin.cpp @@ -27,6 +27,7 @@ str_decl_plugin::str_decl_plugin(): m_concat_decl(0), m_length_decl(0), m_charat_decl(0), + m_startswith_decl(0), m_arith_plugin(0), m_arith_fid(0), m_int_sort(0){ @@ -41,6 +42,7 @@ void str_decl_plugin::finalize(void) { DEC_REF(m_concat_decl); DEC_REF(m_length_decl); DEC_REF(m_charat_decl); + DEC_REF(m_startswith_decl); DEC_REF(m_int_sort); } @@ -60,6 +62,8 @@ void str_decl_plugin::set_manager(ast_manager * m, family_id id) { m_manager->inc_ref(m_int_sort); sort * i = m_int_sort; + sort* boolT = m_manager->mk_bool_sort(); + #define MK_OP(FIELD, NAME, KIND, SORT) \ FIELD = m->mk_func_decl(symbol(NAME), SORT, SORT, SORT, func_decl_info(id, KIND)); \ m->inc_ref(FIELD) @@ -71,6 +75,9 @@ void str_decl_plugin::set_manager(ast_manager * m, family_id id) { m_charat_decl = m->mk_func_decl(symbol("CharAt"), s, i, s, func_decl_info(id, OP_STR_CHARAT)); m_manager->inc_ref(m_charat_decl); + + m_startswith_decl = m->mk_func_decl(symbol("StartsWith"), s, s, boolT, func_decl_info(id, OP_STR_STARTSWITH)); + m_manager->inc_ref(m_startswith_decl); } decl_plugin * str_decl_plugin::mk_fresh() { @@ -89,6 +96,7 @@ func_decl * str_decl_plugin::mk_func_decl(decl_kind k) { case OP_STRCAT: return m_concat_decl; case OP_STRLEN: return m_length_decl; case OP_STR_CHARAT: return m_charat_decl; + case OP_STR_STARTSWITH: return m_startswith_decl; default: return 0; } } @@ -146,6 +154,7 @@ void str_decl_plugin::get_op_names(svector & op_names, symbol cons op_names.push_back(builtin_name("Concat", OP_STRCAT)); op_names.push_back(builtin_name("Length", OP_STRLEN)); op_names.push_back(builtin_name("CharAt", OP_STR_CHARAT)); + op_names.push_back(builtin_name("StartsWith", OP_STR_STARTSWITH)); } void str_decl_plugin::get_sort_names(svector & sort_names, symbol const & logic) { diff --git a/src/ast/str_decl_plugin.h b/src/ast/str_decl_plugin.h index 049ef08ba..d7bfcf172 100644 --- a/src/ast/str_decl_plugin.h +++ b/src/ast/str_decl_plugin.h @@ -32,6 +32,7 @@ enum str_op_kind { OP_STRLEN, // higher-level string functions -- these are reduced to basic operations OP_STR_CHARAT, + OP_STR_STARTSWITH, // end LAST_STR_OP }; @@ -43,7 +44,9 @@ protected: func_decl * m_concat_decl; func_decl * m_length_decl; + func_decl * m_charat_decl; + func_decl * m_startswith_decl; arith_decl_plugin * m_arith_plugin; family_id m_arith_fid; From c5ffb012dd3d69c768133221fb391855a9773581 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 14 Jun 2016 16:16:39 -0400 Subject: [PATCH 111/410] axioms for StartsWith; WIP as I need to fix an infinite recursion bug --- src/smt/theory_str.cpp | 53 +++++++++++++++++++++++++++++++++++++++++- src/smt/theory_str.h | 4 ++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index fe8f12e81..b87881ea6 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -568,7 +568,7 @@ expr * theory_str::mk_concat(expr * n1, expr * n2) { bool theory_str::can_propagate() { return !m_basicstr_axiom_todo.empty() || !m_str_eq_todo.empty() || !m_concat_axiom_todo.empty() - || !m_axiom_CharAt_todo.empty() + || !m_axiom_CharAt_todo.empty() || !m_axiom_StartsWith_todo.empty() ; } @@ -597,6 +597,10 @@ void theory_str::propagate() { instantiate_axiom_CharAt(m_axiom_CharAt_todo[i]); } m_axiom_CharAt_todo.reset(); + + for (unsigned i = 0; i < m_axiom_StartsWith_todo.size(); ++i) { + instantiate_axiom_StartsWith(m_axiom_StartsWith_todo[i]); + } } } @@ -783,6 +787,39 @@ void theory_str::instantiate_axiom_CharAt(enode * e) { assert_axiom(finalAxiom); } +void theory_str::instantiate_axiom_StartsWith(enode * e) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + app * expr = e->get_owner(); + + TRACE("t_str_detail", tout << "instantiate StartsWith axiom for " << mk_pp(expr, m) << std::endl;); + + expr_ref ts0(mk_str_var("ts0"), m); + expr_ref ts1(mk_str_var("ts1"), m); + + expr_ref_vector innerItems(m); + innerItems.push_back(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(ts0, ts1))); + innerItems.push_back(ctx.mk_eq_atom(mk_strlen(ts0), mk_strlen(expr->get_arg(1)))); + innerItems.push_back(m.mk_ite(ctx.mk_eq_atom(ts0, expr->get_arg(1)), expr, m.mk_not(expr))); + expr_ref then1(m.mk_and(innerItems.size(), innerItems.c_ptr()), m); + SASSERT(then1); + + // the top-level condition is Length(arg0) >= Length(arg1). + // of course, the integer theory is not so accommodating + expr_ref topLevelCond( + m_autil.mk_ge( + m_autil.mk_add( + mk_strlen(expr->get_arg(0)), m_autil.mk_mul(mk_int(-1), mk_strlen(expr->get_arg(1)))), + mk_int(0)) + , m); + SASSERT(topLevelCond); + + expr_ref finalAxiom(m.mk_ite(topLevelCond, then1, m.mk_not(expr)), m); + + SASSERT(finalAxiom); + assert_axiom(finalAxiom); +} + void theory_str::attach_new_th_var(enode * n) { context & ctx = get_context(); theory_var v = mk_var(n); @@ -3491,6 +3528,7 @@ void theory_str::set_up_axioms(expr * ex) { sort * ex_sort = m.get_sort(ex); sort * str_sort = m.mk_sort(get_family_id(), STRING_SORT); + sort * bool_sort = m.mk_bool_sort(); if (ex_sort == str_sort) { TRACE("t_str_detail", tout << "setting up axioms for " << mk_ismt2_pp(ex, get_manager()) << @@ -3526,6 +3564,19 @@ void theory_str::set_up_axioms(expr * ex) { TRACE("t_str_detail", tout << "variable " << mk_ismt2_pp(ap, get_manager()) << " is #" << v << std::endl;); } } + } else if (ex_sort == bool_sort) { + TRACE("t_str_detail", tout << "setting up axioms for " << mk_ismt2_pp(ex, get_manager()) << + ": expr is of sort Bool" << std::endl;); + // set up axioms for boolean terms + enode * n = ctx.get_enode(ex); + SASSERT(n); + + if (is_app(ex)) { + app * ap = to_app(ex); + if (is_StartsWith(ap)) { + m_axiom_StartsWith_todo.push_back(n); + } + } } else { TRACE("t_str_detail", tout << "setting up axioms for " << mk_ismt2_pp(ex, get_manager()) << ": expr is of wrong sort, ignoring" << std::endl;); diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index c86328d30..6c332dbd4 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -108,6 +108,7 @@ namespace smt { // enode lists for term-specific axioms ptr_vector m_axiom_CharAt_todo; + ptr_vector m_axiom_StartsWith_todo; int tmpStringVarCount; int tmpXorVarCount; @@ -172,11 +173,14 @@ namespace smt { bool is_strlen(enode const * n) const { return is_strlen(n->get_owner()); } bool is_CharAt(app const * a) const { return a->is_app_of(get_id(), OP_STR_CHARAT); } bool is_CharAt(enode const * n) const { return is_CharAt(n->get_owner()); } + bool is_StartsWith(app const * a) const { return a->is_app_of(get_id(), OP_STR_STARTSWITH); } + bool is_StartsWith(enode const * n) const { return is_StartsWith(n->get_owner()); } void instantiate_concat_axiom(enode * cat); void instantiate_basic_string_axioms(enode * str); void instantiate_str_eq_length_axiom(enode * lhs, enode * rhs); void instantiate_axiom_CharAt(enode * e); + void instantiate_axiom_StartsWith(enode * e); void set_up_axioms(expr * ex); void handle_equality(expr * lhs, expr * rhs); From 4f131ebba7f3dbd48abbe4c90d9e908aee3e728e Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 14 Jun 2016 16:42:46 -0400 Subject: [PATCH 112/410] prevent infinite loop of axiom generation. working StartsWith --- src/smt/theory_str.cpp | 12 ++++++++++++ src/smt/theory_str.h | 5 +++++ 2 files changed, 17 insertions(+) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index b87881ea6..7bdc9f197 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -601,6 +601,7 @@ void theory_str::propagate() { for (unsigned i = 0; i < m_axiom_StartsWith_todo.size(); ++i) { instantiate_axiom_StartsWith(m_axiom_StartsWith_todo[i]); } + m_axiom_StartsWith_todo.reset(); } } @@ -754,6 +755,11 @@ void theory_str::instantiate_axiom_CharAt(enode * e) { ast_manager & m = get_manager(); app * expr = e->get_owner(); + if (axiomatized_terms.contains(expr)) { + TRACE("t_str_detail", tout << "already set up CharAt axiom for " << mk_pp(expr, m) << std::endl;); + return; + } + axiomatized_terms.insert(expr); TRACE("t_str_detail", tout << "instantiate CharAt axiom for " << mk_pp(expr, m) << std::endl;); @@ -790,7 +796,13 @@ void theory_str::instantiate_axiom_CharAt(enode * e) { void theory_str::instantiate_axiom_StartsWith(enode * e) { context & ctx = get_context(); ast_manager & m = get_manager(); + app * expr = e->get_owner(); + if (axiomatized_terms.contains(expr)) { + TRACE("t_str_detail", tout << "already set up StartsWith axiom for " << mk_pp(expr, m) << std::endl;); + return; + } + axiomatized_terms.insert(expr); TRACE("t_str_detail", tout << "instantiate StartsWith axiom for " << mk_pp(expr, m) << std::endl;); diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 6c332dbd4..6debaad71 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -110,6 +110,11 @@ namespace smt { ptr_vector m_axiom_CharAt_todo; ptr_vector m_axiom_StartsWith_todo; + // hashtable of all exprs for which we've already set up term-specific axioms -- + // this prevents infinite recursive descent with respect to axioms that + // include an occurrence of the term for which axioms are being generated + obj_hashtable axiomatized_terms; + int tmpStringVarCount; int tmpXorVarCount; int tmpLenTestVarCount; From fd38b4c729c8f03acfb7362c4c5edd89f7f4a7a7 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 14 Jun 2016 17:55:46 -0400 Subject: [PATCH 113/410] EndsWith decl and rewriter, WIP --- src/ast/rewriter/str_rewriter.cpp | 25 +++++++++++++++++++ src/ast/rewriter/str_rewriter.h | 1 + src/ast/str_decl_plugin.cpp | 7 ++++++ src/ast/str_decl_plugin.h | 2 ++ src/smt/theory_str.cpp | 40 ++++++++++++++++++++++++++++++- src/smt/theory_str.h | 5 ++++ 6 files changed, 79 insertions(+), 1 deletion(-) diff --git a/src/ast/rewriter/str_rewriter.cpp b/src/ast/rewriter/str_rewriter.cpp index 4b7ff9057..d6419ba4f 100644 --- a/src/ast/rewriter/str_rewriter.cpp +++ b/src/ast/rewriter/str_rewriter.cpp @@ -69,6 +69,28 @@ br_status str_rewriter::mk_str_StartsWith(expr * haystack, expr * needle, expr_r } } +br_status str_rewriter::mk_str_EndsWith(expr * haystack, expr * needle, expr_ref & result) { + TRACE("t_str_rw", tout << "rewrite (EndsWith " << mk_pp(haystack, m()) << " " << mk_pp(needle, m()) << ")" << std::endl;); + if (m_strutil.is_string(haystack) && m_strutil.is_string(needle)) { + TRACE("t_str_rw", tout << "evaluating constant EndsWith predicate" << std::endl;); + std::string haystackStr = m_strutil.get_string_constant_value(haystack); + std::string needleStr = m_strutil.get_string_constant_value(needle); + if (haystackStr.length() < needleStr.length()) { + result = m().mk_false(); + return BR_DONE; + } else { + if (haystackStr.substr(haystackStr.length() - needleStr.length(), needleStr.length()) == needleStr) { + result = m().mk_true(); + } else { + result = m().mk_false(); + } + return BR_DONE; + } + } else { + return BR_FAILED; + } +} + br_status str_rewriter::mk_app_core(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result) { SASSERT(f->get_family_id() == get_fid()); @@ -82,6 +104,9 @@ br_status str_rewriter::mk_app_core(func_decl * f, unsigned num_args, expr * con case OP_STR_STARTSWITH: SASSERT(num_args == 2); return mk_str_StartsWith(args[0], args[1], result); + case OP_STR_ENDSWITH: + SASSERT(num_args == 2); + return mk_str_EndsWith(args[0], args[1], result); default: return BR_FAILED; } diff --git a/src/ast/rewriter/str_rewriter.h b/src/ast/rewriter/str_rewriter.h index 8f12a75db..b179934c7 100644 --- a/src/ast/rewriter/str_rewriter.h +++ b/src/ast/rewriter/str_rewriter.h @@ -42,6 +42,7 @@ public: br_status mk_str_CharAt(expr * arg0, expr * arg1, expr_ref & result); br_status mk_str_StartsWith(expr * haystack, expr * needle, expr_ref & result); + br_status mk_str_EndsWith(expr * haystack, expr * needle, expr_ref & result); bool reduce_eq(expr * l, expr * r, expr_ref_vector & lhs, expr_ref_vector & rhs, bool & change); bool reduce_eq(expr_ref_vector& ls, expr_ref_vector& rs, expr_ref_vector& lhs, expr_ref_vector& rhs, bool& change); diff --git a/src/ast/str_decl_plugin.cpp b/src/ast/str_decl_plugin.cpp index c6328d592..6453cb244 100644 --- a/src/ast/str_decl_plugin.cpp +++ b/src/ast/str_decl_plugin.cpp @@ -28,6 +28,7 @@ str_decl_plugin::str_decl_plugin(): m_length_decl(0), m_charat_decl(0), m_startswith_decl(0), + m_endswith_decl(0), m_arith_plugin(0), m_arith_fid(0), m_int_sort(0){ @@ -43,6 +44,7 @@ void str_decl_plugin::finalize(void) { DEC_REF(m_length_decl); DEC_REF(m_charat_decl); DEC_REF(m_startswith_decl); + DEC_REF(m_endswith_decl); DEC_REF(m_int_sort); } @@ -78,6 +80,9 @@ void str_decl_plugin::set_manager(ast_manager * m, family_id id) { m_startswith_decl = m->mk_func_decl(symbol("StartsWith"), s, s, boolT, func_decl_info(id, OP_STR_STARTSWITH)); m_manager->inc_ref(m_startswith_decl); + + m_endswith_decl = m->mk_func_decl(symbol("EndsWith"), s, s, boolT, func_decl_info(id, OP_STR_ENDSWITH)); + m_manager->inc_ref(m_endswith_decl); } decl_plugin * str_decl_plugin::mk_fresh() { @@ -97,6 +102,7 @@ func_decl * str_decl_plugin::mk_func_decl(decl_kind k) { case OP_STRLEN: return m_length_decl; case OP_STR_CHARAT: return m_charat_decl; case OP_STR_STARTSWITH: return m_startswith_decl; + case OP_STR_ENDSWITH: return m_endswith_decl; default: return 0; } } @@ -155,6 +161,7 @@ void str_decl_plugin::get_op_names(svector & op_names, symbol cons op_names.push_back(builtin_name("Length", OP_STRLEN)); op_names.push_back(builtin_name("CharAt", OP_STR_CHARAT)); op_names.push_back(builtin_name("StartsWith", OP_STR_STARTSWITH)); + op_names.push_back(builtin_name("EndsWith", OP_STR_ENDSWITH)); } void str_decl_plugin::get_sort_names(svector & sort_names, symbol const & logic) { diff --git a/src/ast/str_decl_plugin.h b/src/ast/str_decl_plugin.h index d7bfcf172..4ce258c60 100644 --- a/src/ast/str_decl_plugin.h +++ b/src/ast/str_decl_plugin.h @@ -33,6 +33,7 @@ enum str_op_kind { // higher-level string functions -- these are reduced to basic operations OP_STR_CHARAT, OP_STR_STARTSWITH, + OP_STR_ENDSWITH, // end LAST_STR_OP }; @@ -47,6 +48,7 @@ protected: func_decl * m_charat_decl; func_decl * m_startswith_decl; + func_decl * m_endswith_decl; arith_decl_plugin * m_arith_plugin; family_id m_arith_fid; diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 7bdc9f197..76835c560 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -568,7 +568,7 @@ expr * theory_str::mk_concat(expr * n1, expr * n2) { bool theory_str::can_propagate() { return !m_basicstr_axiom_todo.empty() || !m_str_eq_todo.empty() || !m_concat_axiom_todo.empty() - || !m_axiom_CharAt_todo.empty() || !m_axiom_StartsWith_todo.empty() + || !m_axiom_CharAt_todo.empty() || !m_axiom_StartsWith_todo.empty() || !m_axiom_EndsWith_todo.empty() ; } @@ -602,6 +602,11 @@ void theory_str::propagate() { instantiate_axiom_StartsWith(m_axiom_StartsWith_todo[i]); } m_axiom_StartsWith_todo.reset(); + + for (unsigned i = 0; i < m_axiom_EndsWith_todo.size(); ++i) { + instantiate_axiom_EndsWith(m_axiom_EndsWith_todo[i]); + } + m_axiom_EndsWith_todo.reset(); } } @@ -832,6 +837,37 @@ void theory_str::instantiate_axiom_StartsWith(enode * e) { assert_axiom(finalAxiom); } +void theory_str::instantiate_axiom_EndsWith(enode * e) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + app * expr = e->get_owner(); + if (axiomatized_terms.contains(expr)) { + TRACE("t_str_detail", tout << "already set up EndsWith axiom for " << mk_pp(expr, m) << std::endl;); + return; + } + axiomatized_terms.insert(expr); + + TRACE("t_str_detail", tout << "instantiate EndsWith axiom for " << mk_pp(expr, m) << std::endl;); + + // TODO NEXT + NOT_IMPLEMENTED_YET(); + /* + Z3_ast resBoolVar = my_mk_internal_bool_var(t); + Z3_ast ts0 = my_mk_internal_string_var(t); + Z3_ast ts1 = my_mk_internal_string_var(t); + // boolVar = endswith(arg[0], arg[1]) + // -------------------------------------------- + std::vector innerItems; + innerItems.push_back( Z3_mk_eq(ctx, args[0], mk_concat(t, ts0, ts1)) ); + innerItems.push_back( Z3_mk_eq(ctx, mk_length(t, ts1), mk_length(t, args[1])) ); + innerItems.push_back( Z3_mk_ite(ctx, Z3_mk_eq(ctx, ts1, args[1]), Z3_mk_eq(ctx, resBoolVar, Z3_mk_true(ctx)), Z3_mk_eq(ctx, resBoolVar, Z3_mk_false(ctx) ) ) ); + Z3_ast then1 = mk_and_fromVector(t, innerItems); + breakdownAssert = Z3_mk_ite(ctx, Z3_mk_ge(ctx, mk_length(t, args[0]), mk_length(t, args[1])), then1, Z3_mk_eq(ctx, resBoolVar, Z3_mk_false(ctx) ) ); + reduceAst = resBoolVar; + */ +} + void theory_str::attach_new_th_var(enode * n) { context & ctx = get_context(); theory_var v = mk_var(n); @@ -3587,6 +3623,8 @@ void theory_str::set_up_axioms(expr * ex) { app * ap = to_app(ex); if (is_StartsWith(ap)) { m_axiom_StartsWith_todo.push_back(n); + } else if (is_EndsWith(ap)) { + m_axiom_EndsWith_todo.push_back(n); } } } else { diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 6debaad71..ae3cc5d52 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -109,6 +109,7 @@ namespace smt { // enode lists for term-specific axioms ptr_vector m_axiom_CharAt_todo; ptr_vector m_axiom_StartsWith_todo; + ptr_vector m_axiom_EndsWith_todo; // hashtable of all exprs for which we've already set up term-specific axioms -- // this prevents infinite recursive descent with respect to axioms that @@ -180,12 +181,16 @@ namespace smt { bool is_CharAt(enode const * n) const { return is_CharAt(n->get_owner()); } bool is_StartsWith(app const * a) const { return a->is_app_of(get_id(), OP_STR_STARTSWITH); } bool is_StartsWith(enode const * n) const { return is_StartsWith(n->get_owner()); } + bool is_EndsWith(app const * a) const { return a->is_app_of(get_id(), OP_STR_ENDSWITH); } + bool is_EndsWith(enode const * n) const { return is_EndsWith(n->get_owner()); } + void instantiate_concat_axiom(enode * cat); void instantiate_basic_string_axioms(enode * str); void instantiate_str_eq_length_axiom(enode * lhs, enode * rhs); void instantiate_axiom_CharAt(enode * e); void instantiate_axiom_StartsWith(enode * e); + void instantiate_axiom_EndsWith(enode * e); void set_up_axioms(expr * ex); void handle_equality(expr * lhs, expr * rhs); From 989d6b577b457931574a0a9e376523f5869f2b88 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 14 Jun 2016 18:05:24 -0400 Subject: [PATCH 114/410] EndsWith axiomatization in theory_str --- src/smt/theory_str.cpp | 39 ++++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 76835c560..508f451a3 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -832,7 +832,6 @@ void theory_str::instantiate_axiom_StartsWith(enode * e) { SASSERT(topLevelCond); expr_ref finalAxiom(m.mk_ite(topLevelCond, then1, m.mk_not(expr)), m); - SASSERT(finalAxiom); assert_axiom(finalAxiom); } @@ -850,22 +849,28 @@ void theory_str::instantiate_axiom_EndsWith(enode * e) { TRACE("t_str_detail", tout << "instantiate EndsWith axiom for " << mk_pp(expr, m) << std::endl;); - // TODO NEXT - NOT_IMPLEMENTED_YET(); - /* - Z3_ast resBoolVar = my_mk_internal_bool_var(t); - Z3_ast ts0 = my_mk_internal_string_var(t); - Z3_ast ts1 = my_mk_internal_string_var(t); - // boolVar = endswith(arg[0], arg[1]) - // -------------------------------------------- - std::vector innerItems; - innerItems.push_back( Z3_mk_eq(ctx, args[0], mk_concat(t, ts0, ts1)) ); - innerItems.push_back( Z3_mk_eq(ctx, mk_length(t, ts1), mk_length(t, args[1])) ); - innerItems.push_back( Z3_mk_ite(ctx, Z3_mk_eq(ctx, ts1, args[1]), Z3_mk_eq(ctx, resBoolVar, Z3_mk_true(ctx)), Z3_mk_eq(ctx, resBoolVar, Z3_mk_false(ctx) ) ) ); - Z3_ast then1 = mk_and_fromVector(t, innerItems); - breakdownAssert = Z3_mk_ite(ctx, Z3_mk_ge(ctx, mk_length(t, args[0]), mk_length(t, args[1])), then1, Z3_mk_eq(ctx, resBoolVar, Z3_mk_false(ctx) ) ); - reduceAst = resBoolVar; - */ + expr_ref ts0(mk_str_var("ts0"), m); + expr_ref ts1(mk_str_var("ts1"), m); + + expr_ref_vector innerItems(m); + innerItems.push_back(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(ts0, ts1))); + innerItems.push_back(ctx.mk_eq_atom(mk_strlen(ts1), mk_strlen(expr->get_arg(1)))); + innerItems.push_back(m.mk_ite(ctx.mk_eq_atom(ts1, expr->get_arg(1)), expr, m.mk_not(expr))); + expr_ref then1(m.mk_and(innerItems.size(), innerItems.c_ptr()), m); + SASSERT(then1); + + // the top-level condition is Length(arg0) >= Length(arg1) + expr_ref topLevelCond( + m_autil.mk_ge( + m_autil.mk_add( + mk_strlen(expr->get_arg(0)), m_autil.mk_mul(mk_int(-1), mk_strlen(expr->get_arg(1)))), + mk_int(0)) + , m); + SASSERT(topLevelCond); + + expr_ref finalAxiom(m.mk_ite(topLevelCond, then1, m.mk_not(expr)), m); + SASSERT(finalAxiom); + assert_axiom(finalAxiom); } void theory_str::attach_new_th_var(enode * n) { From a3986d6d0e0ad90a62652b92f131dddb30115999 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 14 Jun 2016 18:36:43 -0400 Subject: [PATCH 115/410] decl and rewriter support for Contains (WIP) --- src/ast/rewriter/str_rewriter.cpp | 24 ++++++++++++++++++++++++ src/ast/rewriter/str_rewriter.h | 1 + src/ast/str_decl_plugin.cpp | 7 +++++++ src/ast/str_decl_plugin.h | 2 ++ 4 files changed, 34 insertions(+) diff --git a/src/ast/rewriter/str_rewriter.cpp b/src/ast/rewriter/str_rewriter.cpp index d6419ba4f..d33194748 100644 --- a/src/ast/rewriter/str_rewriter.cpp +++ b/src/ast/rewriter/str_rewriter.cpp @@ -91,6 +91,27 @@ br_status str_rewriter::mk_str_EndsWith(expr * haystack, expr * needle, expr_ref } } +br_status str_rewriter::mk_str_Contains(expr * haystack, expr * needle, expr_ref & result) { + TRACE("t_str_rw", tout << "rewrite (Contains " << mk_pp(haystack, m()) << " " << mk_pp(needle, m()) << ")" << std::endl;); + if (haystack == needle) { + TRACE("t_str_rw", tout << "eliminate (Contains) over identical terms" << std::endl;); + result = m().mk_true(); + return BR_DONE; + } else if (m_strutil.is_string(haystack) && m_strutil.is_string(needle)) { + TRACE("t_str_rw", tout << "evaluating constant Contains predicate" << std::endl;); + std::string haystackStr = m_strutil.get_string_constant_value(haystack); + std::string needleStr = m_strutil.get_string_constant_value(needle); + if (haystackStr.find(needleStr) != std::string::npos) { + result = m().mk_true(); + } else { + result = m().mk_false(); + } + return BR_DONE; + } else { + return BR_FAILED; + } +} + br_status str_rewriter::mk_app_core(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result) { SASSERT(f->get_family_id() == get_fid()); @@ -107,6 +128,9 @@ br_status str_rewriter::mk_app_core(func_decl * f, unsigned num_args, expr * con case OP_STR_ENDSWITH: SASSERT(num_args == 2); return mk_str_EndsWith(args[0], args[1], result); + case OP_STR_CONTAINS: + SASSERT(num_args == 2); + return mk_str_Contains(args[0], args[1], result); default: return BR_FAILED; } diff --git a/src/ast/rewriter/str_rewriter.h b/src/ast/rewriter/str_rewriter.h index b179934c7..f98f64cc4 100644 --- a/src/ast/rewriter/str_rewriter.h +++ b/src/ast/rewriter/str_rewriter.h @@ -43,6 +43,7 @@ public: br_status mk_str_CharAt(expr * arg0, expr * arg1, expr_ref & result); br_status mk_str_StartsWith(expr * haystack, expr * needle, expr_ref & result); br_status mk_str_EndsWith(expr * haystack, expr * needle, expr_ref & result); + br_status mk_str_Contains(expr * haystack, expr * needle, expr_ref & result); bool reduce_eq(expr * l, expr * r, expr_ref_vector & lhs, expr_ref_vector & rhs, bool & change); bool reduce_eq(expr_ref_vector& ls, expr_ref_vector& rs, expr_ref_vector& lhs, expr_ref_vector& rhs, bool& change); diff --git a/src/ast/str_decl_plugin.cpp b/src/ast/str_decl_plugin.cpp index 6453cb244..07e0d07a2 100644 --- a/src/ast/str_decl_plugin.cpp +++ b/src/ast/str_decl_plugin.cpp @@ -29,6 +29,7 @@ str_decl_plugin::str_decl_plugin(): m_charat_decl(0), m_startswith_decl(0), m_endswith_decl(0), + m_contains_decl(0), m_arith_plugin(0), m_arith_fid(0), m_int_sort(0){ @@ -45,6 +46,7 @@ void str_decl_plugin::finalize(void) { DEC_REF(m_charat_decl); DEC_REF(m_startswith_decl); DEC_REF(m_endswith_decl); + DEC_REF(m_contains_decl); DEC_REF(m_int_sort); } @@ -83,6 +85,9 @@ void str_decl_plugin::set_manager(ast_manager * m, family_id id) { m_endswith_decl = m->mk_func_decl(symbol("EndsWith"), s, s, boolT, func_decl_info(id, OP_STR_ENDSWITH)); m_manager->inc_ref(m_endswith_decl); + + m_contains_decl = m->mk_func_decl(symbol("Contains"), s, s, boolT, func_decl_info(id, OP_STR_CONTAINS)); + m_manager->inc_ref(m_contains_decl); } decl_plugin * str_decl_plugin::mk_fresh() { @@ -103,6 +108,7 @@ func_decl * str_decl_plugin::mk_func_decl(decl_kind k) { case OP_STR_CHARAT: return m_charat_decl; case OP_STR_STARTSWITH: return m_startswith_decl; case OP_STR_ENDSWITH: return m_endswith_decl; + case OP_STR_CONTAINS: return m_contains_decl; default: return 0; } } @@ -162,6 +168,7 @@ void str_decl_plugin::get_op_names(svector & op_names, symbol cons op_names.push_back(builtin_name("CharAt", OP_STR_CHARAT)); op_names.push_back(builtin_name("StartsWith", OP_STR_STARTSWITH)); op_names.push_back(builtin_name("EndsWith", OP_STR_ENDSWITH)); + op_names.push_back(builtin_name("Contains", OP_STR_CONTAINS)); } void str_decl_plugin::get_sort_names(svector & sort_names, symbol const & logic) { diff --git a/src/ast/str_decl_plugin.h b/src/ast/str_decl_plugin.h index 4ce258c60..c4605003d 100644 --- a/src/ast/str_decl_plugin.h +++ b/src/ast/str_decl_plugin.h @@ -34,6 +34,7 @@ enum str_op_kind { OP_STR_CHARAT, OP_STR_STARTSWITH, OP_STR_ENDSWITH, + OP_STR_CONTAINS, // end LAST_STR_OP }; @@ -49,6 +50,7 @@ protected: func_decl * m_charat_decl; func_decl * m_startswith_decl; func_decl * m_endswith_decl; + func_decl * m_contains_decl; arith_decl_plugin * m_arith_plugin; family_id m_arith_fid; From 7aeeb599ef6ed05ab8eb0f06fc4fb279585c981e Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 14 Jun 2016 18:43:51 -0400 Subject: [PATCH 116/410] very very basic Contains support in theory_str not included: the 1200 lines of code that make it very fast --- src/smt/theory_str.cpp | 29 +++++++++++++++++++++++++++++ src/smt/theory_str.h | 4 ++++ 2 files changed, 33 insertions(+) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 508f451a3..faaba596f 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -569,6 +569,7 @@ expr * theory_str::mk_concat(expr * n1, expr * n2) { bool theory_str::can_propagate() { return !m_basicstr_axiom_todo.empty() || !m_str_eq_todo.empty() || !m_concat_axiom_todo.empty() || !m_axiom_CharAt_todo.empty() || !m_axiom_StartsWith_todo.empty() || !m_axiom_EndsWith_todo.empty() + || !m_axiom_Contains_todo.empty() ; } @@ -607,6 +608,11 @@ void theory_str::propagate() { instantiate_axiom_EndsWith(m_axiom_EndsWith_todo[i]); } m_axiom_EndsWith_todo.reset(); + + for (unsigned i = 0; i < m_axiom_Contains_todo.size(); ++i) { + instantiate_axiom_Contains(m_axiom_Contains_todo[i]); + } + m_axiom_Contains_todo.reset(); } } @@ -873,6 +879,27 @@ void theory_str::instantiate_axiom_EndsWith(enode * e) { assert_axiom(finalAxiom); } +void theory_str::instantiate_axiom_Contains(enode * e) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + app * expr = e->get_owner(); + if (axiomatized_terms.contains(expr)) { + TRACE("t_str_detail", tout << "already set up Contains axiom for " << mk_pp(expr, m) << std::endl;); + return; + } + axiomatized_terms.insert(expr); + + TRACE("t_str_detail", tout << "instantiate Contains axiom for " << mk_pp(expr, m) << std::endl;); + + expr_ref ts0(mk_str_var("ts0"), m); + expr_ref ts1(mk_str_var("ts1"), m); + // TODO NEXT registerContain(expr); + expr_ref breakdownAssert(ctx.mk_eq_atom(expr, ctx.mk_eq_atom(expr->get_arg(0), mk_concat(ts0, mk_concat(expr->get_arg(1), ts1)))), m); + SASSERT(breakdownAssert); + assert_axiom(breakdownAssert); +} + void theory_str::attach_new_th_var(enode * n) { context & ctx = get_context(); theory_var v = mk_var(n); @@ -3630,6 +3657,8 @@ void theory_str::set_up_axioms(expr * ex) { m_axiom_StartsWith_todo.push_back(n); } else if (is_EndsWith(ap)) { m_axiom_EndsWith_todo.push_back(n); + } else if (is_Contains(ap)) { + m_axiom_Contains_todo.push_back(n); } } } else { diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index ae3cc5d52..6d1bd597f 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -110,6 +110,7 @@ namespace smt { ptr_vector m_axiom_CharAt_todo; ptr_vector m_axiom_StartsWith_todo; ptr_vector m_axiom_EndsWith_todo; + ptr_vector m_axiom_Contains_todo; // hashtable of all exprs for which we've already set up term-specific axioms -- // this prevents infinite recursive descent with respect to axioms that @@ -183,6 +184,8 @@ namespace smt { bool is_StartsWith(enode const * n) const { return is_StartsWith(n->get_owner()); } bool is_EndsWith(app const * a) const { return a->is_app_of(get_id(), OP_STR_ENDSWITH); } bool is_EndsWith(enode const * n) const { return is_EndsWith(n->get_owner()); } + bool is_Contains(app const * a) const { return a->is_app_of(get_id(), OP_STR_CONTAINS); } + bool is_Contains(enode const * n) const { return is_Contains(n->get_owner()); } void instantiate_concat_axiom(enode * cat); void instantiate_basic_string_axioms(enode * str); @@ -191,6 +194,7 @@ namespace smt { void instantiate_axiom_CharAt(enode * e); void instantiate_axiom_StartsWith(enode * e); void instantiate_axiom_EndsWith(enode * e); + void instantiate_axiom_Contains(enode * e); void set_up_axioms(expr * ex); void handle_equality(expr * lhs, expr * rhs); From db2a5854e9e21dffe51477a2d27f9711a2a85380 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 14 Jun 2016 20:10:06 -0400 Subject: [PATCH 117/410] decl and rewriter for Indexof (WIP) --- src/ast/rewriter/str_rewriter.cpp | 21 +++++++++++++++++++++ src/ast/rewriter/str_rewriter.h | 1 + src/ast/str_decl_plugin.cpp | 7 +++++++ src/ast/str_decl_plugin.h | 2 ++ 4 files changed, 31 insertions(+) diff --git a/src/ast/rewriter/str_rewriter.cpp b/src/ast/rewriter/str_rewriter.cpp index d33194748..5e61ee3a2 100644 --- a/src/ast/rewriter/str_rewriter.cpp +++ b/src/ast/rewriter/str_rewriter.cpp @@ -112,6 +112,24 @@ br_status str_rewriter::mk_str_Contains(expr * haystack, expr * needle, expr_ref } } +br_status str_rewriter::mk_str_Indexof(expr * haystack, expr * needle, expr_ref & result) { + TRACE("t_str_rw", tout << "rewrite (Indexof " << mk_pp(haystack, m()) << " " << mk_pp(needle, m()) << ")" << std::endl;); + if (m_strutil.is_string(haystack) && m_strutil.is_string(needle)) { + TRACE("t_str_rw", tout << "evaluating constant Indexof expression" << std::endl;); + std::string haystackStr = m_strutil.get_string_constant_value(haystack); + std::string needleStr = m_strutil.get_string_constant_value(needle); + if (haystackStr.find(needleStr) != std::string::npos) { + int index = haystackStr.find(needleStr); + result = m_autil.mk_numeral(rational(index), true); + } else { + result = m_autil.mk_numeral(rational(-1), true); + } + return BR_DONE; + } else { + return BR_FAILED; + } +} + br_status str_rewriter::mk_app_core(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result) { SASSERT(f->get_family_id() == get_fid()); @@ -131,6 +149,9 @@ br_status str_rewriter::mk_app_core(func_decl * f, unsigned num_args, expr * con case OP_STR_CONTAINS: SASSERT(num_args == 2); return mk_str_Contains(args[0], args[1], result); + case OP_STR_INDEXOF: + SASSERT(num_args == 2); + return mk_str_Indexof(args[0], args[1], result); default: return BR_FAILED; } diff --git a/src/ast/rewriter/str_rewriter.h b/src/ast/rewriter/str_rewriter.h index f98f64cc4..f22ac31a7 100644 --- a/src/ast/rewriter/str_rewriter.h +++ b/src/ast/rewriter/str_rewriter.h @@ -44,6 +44,7 @@ public: br_status mk_str_StartsWith(expr * haystack, expr * needle, expr_ref & result); br_status mk_str_EndsWith(expr * haystack, expr * needle, expr_ref & result); br_status mk_str_Contains(expr * haystack, expr * needle, expr_ref & result); + br_status mk_str_Indexof(expr * haystack, expr * needle, expr_ref & result); bool reduce_eq(expr * l, expr * r, expr_ref_vector & lhs, expr_ref_vector & rhs, bool & change); bool reduce_eq(expr_ref_vector& ls, expr_ref_vector& rs, expr_ref_vector& lhs, expr_ref_vector& rhs, bool& change); diff --git a/src/ast/str_decl_plugin.cpp b/src/ast/str_decl_plugin.cpp index 07e0d07a2..ea4b0c6d0 100644 --- a/src/ast/str_decl_plugin.cpp +++ b/src/ast/str_decl_plugin.cpp @@ -30,6 +30,7 @@ str_decl_plugin::str_decl_plugin(): m_startswith_decl(0), m_endswith_decl(0), m_contains_decl(0), + m_indexof_decl(0), m_arith_plugin(0), m_arith_fid(0), m_int_sort(0){ @@ -47,6 +48,7 @@ void str_decl_plugin::finalize(void) { DEC_REF(m_startswith_decl); DEC_REF(m_endswith_decl); DEC_REF(m_contains_decl); + DEC_REF(m_indexof_decl); DEC_REF(m_int_sort); } @@ -88,6 +90,9 @@ void str_decl_plugin::set_manager(ast_manager * m, family_id id) { m_contains_decl = m->mk_func_decl(symbol("Contains"), s, s, boolT, func_decl_info(id, OP_STR_CONTAINS)); m_manager->inc_ref(m_contains_decl); + + m_indexof_decl = m->mk_func_decl(symbol("Indexof"), s, s, i, func_decl_info(id, OP_STR_INDEXOF)); + m_manager->inc_ref(m_indexof_decl); } decl_plugin * str_decl_plugin::mk_fresh() { @@ -109,6 +114,7 @@ func_decl * str_decl_plugin::mk_func_decl(decl_kind k) { case OP_STR_STARTSWITH: return m_startswith_decl; case OP_STR_ENDSWITH: return m_endswith_decl; case OP_STR_CONTAINS: return m_contains_decl; + case OP_STR_INDEXOF: return m_indexof_decl; default: return 0; } } @@ -169,6 +175,7 @@ void str_decl_plugin::get_op_names(svector & op_names, symbol cons op_names.push_back(builtin_name("StartsWith", OP_STR_STARTSWITH)); op_names.push_back(builtin_name("EndsWith", OP_STR_ENDSWITH)); op_names.push_back(builtin_name("Contains", OP_STR_CONTAINS)); + op_names.push_back(builtin_name("Indexof", OP_STR_INDEXOF)); } void str_decl_plugin::get_sort_names(svector & sort_names, symbol const & logic) { diff --git a/src/ast/str_decl_plugin.h b/src/ast/str_decl_plugin.h index c4605003d..a2a355ba2 100644 --- a/src/ast/str_decl_plugin.h +++ b/src/ast/str_decl_plugin.h @@ -35,6 +35,7 @@ enum str_op_kind { OP_STR_STARTSWITH, OP_STR_ENDSWITH, OP_STR_CONTAINS, + OP_STR_INDEXOF, // end LAST_STR_OP }; @@ -51,6 +52,7 @@ protected: func_decl * m_startswith_decl; func_decl * m_endswith_decl; func_decl * m_contains_decl; + func_decl * m_indexof_decl; arith_decl_plugin * m_arith_plugin; family_id m_arith_fid; From 881e3056f3a039ec0551b29ed4065c607de54fdc Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 14 Jun 2016 21:28:31 -0400 Subject: [PATCH 118/410] support for IndexOf in theory_str --- src/smt/theory_str.cpp | 113 ++++++++++++++++++++++++++++++++++++++++- src/smt/theory_str.h | 6 +++ 2 files changed, 117 insertions(+), 2 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index faaba596f..df77018e9 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -430,6 +430,30 @@ app * theory_str::mk_internal_xor_var() { return a; } +app * theory_str::mk_int_var(std::string name) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + TRACE("t_str_detail", tout << "creating integer variable " << name << " at scope level " << sLevel << std::endl;); + + sort * int_sort = m.mk_sort(m_autil.get_family_id(), INT_SORT); + app * a = m.mk_fresh_const(name.c_str(), int_sort); + + ctx.internalize(a, false); + SASSERT(ctx.get_enode(a) != NULL); + SASSERT(ctx.e_internalized(a)); + ctx.mark_as_relevant(a); + // I'm assuming that this combination will do the correct thing in the integer theory. + + //mk_var(ctx.get_enode(a)); + m_trail.push_back(a); + //variable_set.insert(a); + //internal_variable_set.insert(a); + //track_variable_scope(a); + + return a; +} + app * theory_str::mk_str_var(std::string name) { context & ctx = get_context(); ast_manager & m = get_manager(); @@ -498,6 +522,15 @@ app * theory_str::mk_nonempty_str_var() { return a; } +app * theory_str::mk_contains(expr * haystack, expr * needle) { + expr * args[2] = {haystack, needle}; + app * contains = get_manager().mk_app(get_id(), OP_STR_CONTAINS, 0, 0, 2, args); + // immediately force internalization so that axiom setup does not fail + get_context().internalize(contains, false); + set_up_axioms(contains); + return contains; +} + app * theory_str::mk_strlen(expr * e) { /*if (m_strutil.is_string(e)) {*/ if (false) { const char * strval = 0; @@ -569,7 +602,7 @@ expr * theory_str::mk_concat(expr * n1, expr * n2) { bool theory_str::can_propagate() { return !m_basicstr_axiom_todo.empty() || !m_str_eq_todo.empty() || !m_concat_axiom_todo.empty() || !m_axiom_CharAt_todo.empty() || !m_axiom_StartsWith_todo.empty() || !m_axiom_EndsWith_todo.empty() - || !m_axiom_Contains_todo.empty() + || !m_axiom_Contains_todo.empty() || !m_axiom_Indexof_todo.empty() ; } @@ -613,6 +646,11 @@ void theory_str::propagate() { instantiate_axiom_Contains(m_axiom_Contains_todo[i]); } m_axiom_Contains_todo.reset(); + + for (unsigned i = 0; i < m_axiom_Indexof_todo.size(); ++i) { + instantiate_axiom_Indexof(m_axiom_Indexof_todo[i]); + } + m_axiom_Indexof_todo.reset(); } } @@ -900,6 +938,62 @@ void theory_str::instantiate_axiom_Contains(enode * e) { assert_axiom(breakdownAssert); } +void theory_str::instantiate_axiom_Indexof(enode * e) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + app * expr = e->get_owner(); + if (axiomatized_terms.contains(expr)) { + TRACE("t_str_detail", tout << "already set up Indexof axiom for " << mk_pp(expr, m) << std::endl;); + return; + } + axiomatized_terms.insert(expr); + + TRACE("t_str_detail", tout << "instantiate Indexof axiom for " << mk_pp(expr, m) << std::endl;); + + expr_ref x1(mk_str_var("x1"), m); + expr_ref x2(mk_str_var("x2"), m); + expr_ref indexAst(mk_int_var("index"), m); + + expr_ref condAst(mk_contains(expr->get_arg(0), expr->get_arg(1)), m); + SASSERT(condAst); + + // ----------------------- + // true branch + expr_ref_vector thenItems(m); + // args[0] = x1 . args[1] . x2 + thenItems.push_back(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(x1, mk_concat(expr->get_arg(1), x2)))); + // indexAst = |x1| + thenItems.push_back(ctx.mk_eq_atom(indexAst, mk_strlen(x1))); + // args[0] = x3 . x4 + // /\ |x3| = |x1| + |args[1]| - 1 + // /\ ! contains(x3, args[1]) + expr_ref x3(mk_str_var("x3"), m); + expr_ref x4(mk_str_var("x4"), m); + expr_ref tmpLen(m_autil.mk_add(indexAst, mk_strlen(expr->get_arg(1)), mk_int(-1)), m); + SASSERT(tmpLen); + thenItems.push_back(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(x3, x4))); + thenItems.push_back(ctx.mk_eq_atom(mk_strlen(x3), tmpLen)); + thenItems.push_back(m.mk_not(mk_contains(x3, expr->get_arg(1)))); + expr_ref thenBranch(m.mk_and(thenItems.size(), thenItems.c_ptr()), m); + SASSERT(thenBranch); + + // ----------------------- + // false branch + expr_ref elseBranch(ctx.mk_eq_atom(indexAst, mk_int(-1)), m); + SASSERT(elseBranch); + + expr_ref breakdownAssert(m.mk_ite(condAst, thenBranch, elseBranch), m); + SASSERT(breakdownAssert); + + expr_ref reduceToIndex(ctx.mk_eq_atom(expr, indexAst), m); + SASSERT(reduceToIndex); + + expr_ref finalAxiom(m.mk_and(breakdownAssert, reduceToIndex), m); + SASSERT(finalAxiom); + assert_axiom(finalAxiom); +} + void theory_str::attach_new_th_var(enode * n) { context & ctx = get_context(); theory_var v = mk_var(n); @@ -3602,7 +3696,6 @@ void theory_str::handle_equality(expr * lhs, expr * rhs) { } void theory_str::set_up_axioms(expr * ex) { - // TODO check to make sure we don't set up axioms on the same term twice ast_manager & m = get_manager(); context & ctx = get_context(); @@ -3610,6 +3703,9 @@ void theory_str::set_up_axioms(expr * ex) { sort * str_sort = m.mk_sort(get_family_id(), STRING_SORT); sort * bool_sort = m.mk_bool_sort(); + family_id m_arith_fid = m.mk_family_id("arith"); + sort * int_sort = m.mk_sort(m_arith_fid, INT_SORT); + if (ex_sort == str_sort) { TRACE("t_str_detail", tout << "setting up axioms for " << mk_ismt2_pp(ex, get_manager()) << ": expr is of sort String" << std::endl;); @@ -3661,6 +3757,19 @@ void theory_str::set_up_axioms(expr * ex) { m_axiom_Contains_todo.push_back(n); } } + } else if (ex_sort == int_sort) { + TRACE("t_str_detail", tout << "setting up axioms for " << mk_ismt2_pp(ex, get_manager()) << + ": expr is of sort Int" << std::endl;); + // set up axioms for boolean terms + enode * n = ctx.get_enode(ex); + SASSERT(n); + + if (is_app(ex)) { + app * ap = to_app(ex); + if (is_Indexof(ap)) { + m_axiom_Indexof_todo.push_back(n); + } + } } else { TRACE("t_str_detail", tout << "setting up axioms for " << mk_ismt2_pp(ex, get_manager()) << ": expr is of wrong sort, ignoring" << std::endl;); diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 6d1bd597f..bf0fef38b 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -111,6 +111,7 @@ namespace smt { ptr_vector m_axiom_StartsWith_todo; ptr_vector m_axiom_EndsWith_todo; ptr_vector m_axiom_Contains_todo; + ptr_vector m_axiom_Indexof_todo; // hashtable of all exprs for which we've already set up term-specific axioms -- // this prevents infinite recursive descent with respect to axioms that @@ -156,6 +157,7 @@ namespace smt { app * mk_strlen(expr * e); expr * mk_concat(expr * n1, expr * n2); expr * mk_concat_const_str(expr * n1, expr * n2); + app * mk_contains(expr * haystack, expr * needle); literal mk_literal(expr* _e); app * mk_int(int n); @@ -168,6 +170,7 @@ namespace smt { void track_variable_scope(expr * var); app * mk_str_var(std::string name); + app * mk_int_var(std::string name); app * mk_nonempty_str_var(); app * mk_internal_xor_var(); expr * mk_internal_valTest_var(expr * node, int len, int vTries); @@ -186,6 +189,8 @@ namespace smt { bool is_EndsWith(enode const * n) const { return is_EndsWith(n->get_owner()); } bool is_Contains(app const * a) const { return a->is_app_of(get_id(), OP_STR_CONTAINS); } bool is_Contains(enode const * n) const { return is_Contains(n->get_owner()); } + bool is_Indexof(app const * a) const { return a->is_app_of(get_id(), OP_STR_INDEXOF); } + bool is_Indexof(enode const * n) const { return is_Indexof(n->get_owner()); } void instantiate_concat_axiom(enode * cat); void instantiate_basic_string_axioms(enode * str); @@ -195,6 +200,7 @@ namespace smt { void instantiate_axiom_StartsWith(enode * e); void instantiate_axiom_EndsWith(enode * e); void instantiate_axiom_Contains(enode * e); + void instantiate_axiom_Indexof(enode * e); void set_up_axioms(expr * ex); void handle_equality(expr * lhs, expr * rhs); From dc5a334d429e1e2c16ac00a9cdc11c2f1e60a236 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 15 Jun 2016 17:37:17 -0400 Subject: [PATCH 119/410] support for Indexof2 in theory_str --- src/ast/rewriter/str_rewriter.cpp | 31 ++++++++++++ src/ast/rewriter/str_rewriter.h | 1 + src/ast/str_decl_plugin.cpp | 10 ++++ src/ast/str_decl_plugin.h | 2 + src/smt/theory_str.cpp | 84 ++++++++++++++++++++++++++++++- src/smt/theory_str.h | 6 +++ 6 files changed, 133 insertions(+), 1 deletion(-) diff --git a/src/ast/rewriter/str_rewriter.cpp b/src/ast/rewriter/str_rewriter.cpp index 5e61ee3a2..30dcb1d95 100644 --- a/src/ast/rewriter/str_rewriter.cpp +++ b/src/ast/rewriter/str_rewriter.cpp @@ -130,6 +130,34 @@ br_status str_rewriter::mk_str_Indexof(expr * haystack, expr * needle, expr_ref } } +br_status str_rewriter::mk_str_Indexof2(expr * arg0, expr * arg1, expr * arg2, expr_ref & result) { + TRACE("t_str_rw", tout << "rewrite (Indexof2 " << mk_pp(arg0, m()) << " " << mk_pp(arg1, m()) << " " << mk_pp(arg2, m()) << ")" << std::endl;); + //if (getNodeType(t, args[0]) == my_Z3_ConstStr && getNodeType(t, args[1]) == my_Z3_ConstStr && getNodeType(t, args[2]) == my_Z3_Num) { + rational arg2Int; + if (m_strutil.is_string(arg0) && m_strutil.is_string(arg1) && m_autil.is_numeral(arg2, arg2Int)) { + TRACE("t_str_rw", tout << "evaluating constant Indexof2 expression" << std::endl;); + std::string arg0str = m_strutil.get_string_constant_value(arg0); + std::string arg1str = m_strutil.get_string_constant_value(arg1); + if (arg2Int >= rational((unsigned)arg0str.length())) { + result = m_autil.mk_numeral(rational(-1), true); + } else if (arg2Int < rational(0)) { + int index = arg0str.find(arg1str); + result = m_autil.mk_numeral(rational(index), true); + } else { + std::string suffixStr = arg0str.substr(arg2Int.get_unsigned(), arg0str.length() - arg2Int.get_unsigned()); + if (suffixStr.find(arg1str) != std::string::npos) { + int index = suffixStr.find(arg1str) + arg2Int.get_unsigned(); + result = m_autil.mk_numeral(rational(index), true); + } else { + result = m_autil.mk_numeral(rational(-1), true); + } + } + return BR_DONE; + } else { + return BR_FAILED; + } +} + br_status str_rewriter::mk_app_core(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result) { SASSERT(f->get_family_id() == get_fid()); @@ -152,6 +180,9 @@ br_status str_rewriter::mk_app_core(func_decl * f, unsigned num_args, expr * con case OP_STR_INDEXOF: SASSERT(num_args == 2); return mk_str_Indexof(args[0], args[1], result); + case OP_STR_INDEXOF2: + SASSERT(num_args == 3); + return mk_str_Indexof2(args[0], args[1], args[2], result); default: return BR_FAILED; } diff --git a/src/ast/rewriter/str_rewriter.h b/src/ast/rewriter/str_rewriter.h index f22ac31a7..c0bae2881 100644 --- a/src/ast/rewriter/str_rewriter.h +++ b/src/ast/rewriter/str_rewriter.h @@ -45,6 +45,7 @@ public: br_status mk_str_EndsWith(expr * haystack, expr * needle, expr_ref & result); br_status mk_str_Contains(expr * haystack, expr * needle, expr_ref & result); br_status mk_str_Indexof(expr * haystack, expr * needle, expr_ref & result); + br_status mk_str_Indexof2(expr * arg0, expr * arg1, expr * arg2, expr_ref & result); bool reduce_eq(expr * l, expr * r, expr_ref_vector & lhs, expr_ref_vector & rhs, bool & change); bool reduce_eq(expr_ref_vector& ls, expr_ref_vector& rs, expr_ref_vector& lhs, expr_ref_vector& rhs, bool& change); diff --git a/src/ast/str_decl_plugin.cpp b/src/ast/str_decl_plugin.cpp index ea4b0c6d0..f6e458fbd 100644 --- a/src/ast/str_decl_plugin.cpp +++ b/src/ast/str_decl_plugin.cpp @@ -31,6 +31,7 @@ str_decl_plugin::str_decl_plugin(): m_endswith_decl(0), m_contains_decl(0), m_indexof_decl(0), + m_indexof2_decl(0), m_arith_plugin(0), m_arith_fid(0), m_int_sort(0){ @@ -49,6 +50,7 @@ void str_decl_plugin::finalize(void) { DEC_REF(m_endswith_decl); DEC_REF(m_contains_decl); DEC_REF(m_indexof_decl); + DEC_REF(m_indexof2_decl); DEC_REF(m_int_sort); } @@ -93,6 +95,12 @@ void str_decl_plugin::set_manager(ast_manager * m, family_id id) { m_indexof_decl = m->mk_func_decl(symbol("Indexof"), s, s, i, func_decl_info(id, OP_STR_INDEXOF)); m_manager->inc_ref(m_indexof_decl); + + { + sort * d[3] = { s, s, i }; + m_indexof2_decl = m->mk_func_decl(symbol("Indexof2"), 3, d, i, func_decl_info(id, OP_STR_INDEXOF2)); + m_manager->inc_ref(m_indexof2_decl); + } } decl_plugin * str_decl_plugin::mk_fresh() { @@ -115,6 +123,7 @@ func_decl * str_decl_plugin::mk_func_decl(decl_kind k) { case OP_STR_ENDSWITH: return m_endswith_decl; case OP_STR_CONTAINS: return m_contains_decl; case OP_STR_INDEXOF: return m_indexof_decl; + case OP_STR_INDEXOF2: return m_indexof2_decl; default: return 0; } } @@ -176,6 +185,7 @@ void str_decl_plugin::get_op_names(svector & op_names, symbol cons op_names.push_back(builtin_name("EndsWith", OP_STR_ENDSWITH)); op_names.push_back(builtin_name("Contains", OP_STR_CONTAINS)); op_names.push_back(builtin_name("Indexof", OP_STR_INDEXOF)); + op_names.push_back(builtin_name("Indexof2", OP_STR_INDEXOF2)); } void str_decl_plugin::get_sort_names(svector & sort_names, symbol const & logic) { diff --git a/src/ast/str_decl_plugin.h b/src/ast/str_decl_plugin.h index a2a355ba2..54762f6b9 100644 --- a/src/ast/str_decl_plugin.h +++ b/src/ast/str_decl_plugin.h @@ -36,6 +36,7 @@ enum str_op_kind { OP_STR_ENDSWITH, OP_STR_CONTAINS, OP_STR_INDEXOF, + OP_STR_INDEXOF2, // end LAST_STR_OP }; @@ -53,6 +54,7 @@ protected: func_decl * m_endswith_decl; func_decl * m_contains_decl; func_decl * m_indexof_decl; + func_decl * m_indexof2_decl; arith_decl_plugin * m_arith_plugin; family_id m_arith_fid; diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index df77018e9..a5244f7bb 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -531,6 +531,15 @@ app * theory_str::mk_contains(expr * haystack, expr * needle) { return contains; } +app * theory_str::mk_indexof(expr * haystack, expr * needle) { + expr * args[2] = {haystack, needle}; + app * indexof = get_manager().mk_app(get_id(), OP_STR_INDEXOF, 0, 0, 2, args); + // immediately force internalization so that axiom setup does not fail + get_context().internalize(indexof, false); + set_up_axioms(indexof); + return indexof; +} + app * theory_str::mk_strlen(expr * e) { /*if (m_strutil.is_string(e)) {*/ if (false) { const char * strval = 0; @@ -602,7 +611,7 @@ expr * theory_str::mk_concat(expr * n1, expr * n2) { bool theory_str::can_propagate() { return !m_basicstr_axiom_todo.empty() || !m_str_eq_todo.empty() || !m_concat_axiom_todo.empty() || !m_axiom_CharAt_todo.empty() || !m_axiom_StartsWith_todo.empty() || !m_axiom_EndsWith_todo.empty() - || !m_axiom_Contains_todo.empty() || !m_axiom_Indexof_todo.empty() + || !m_axiom_Contains_todo.empty() || !m_axiom_Indexof_todo.empty() || !m_axiom_Indexof2_todo.empty() ; } @@ -651,6 +660,11 @@ void theory_str::propagate() { instantiate_axiom_Indexof(m_axiom_Indexof_todo[i]); } m_axiom_Indexof_todo.reset(); + + for (unsigned i = 0; i < m_axiom_Indexof2_todo.size(); ++i) { + instantiate_axiom_Indexof2(m_axiom_Indexof2_todo[i]); + } + m_axiom_Indexof2_todo.reset(); } } @@ -994,6 +1008,74 @@ void theory_str::instantiate_axiom_Indexof(enode * e) { assert_axiom(finalAxiom); } +void theory_str::instantiate_axiom_Indexof2(enode * e) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + app * expr = e->get_owner(); + if (axiomatized_terms.contains(expr)) { + TRACE("t_str_detail", tout << "already set up Indexof2 axiom for " << mk_pp(expr, m) << std::endl;); + return; + } + axiomatized_terms.insert(expr); + + TRACE("t_str_detail", tout << "instantiate Indexof2 axiom for " << mk_pp(expr, m) << std::endl;); + + // ------------------------------------------------------------------------------- + // if (arg[2] >= length(arg[0])) // ite2 + // resAst = -1 + // else + // args[0] = prefix . suffix + // /\ indexAst = indexof(suffix, arg[1]) + // /\ args[2] = len(prefix) + // /\ if (indexAst == -1) resAst = indexAst // ite3 + // else resAst = args[2] + indexAst + // ------------------------------------------------------------------------------- + + expr_ref resAst(mk_int_var("res"), m); + expr_ref indexAst(mk_int_var("index"), m); + expr_ref prefix(mk_str_var("prefix"), m); + expr_ref suffix(mk_str_var("suffix"), m); + expr_ref prefixLen(mk_strlen(prefix), m); + expr_ref zeroAst(mk_int(0), m); + expr_ref negOneAst(mk_int(-1), m); + + expr_ref ite3(m.mk_ite( + ctx.mk_eq_atom(indexAst, negOneAst), + ctx.mk_eq_atom(resAst, negOneAst), + ctx.mk_eq_atom(resAst, m_autil.mk_add(expr->get_arg(2), indexAst)) + ),m); + + expr_ref_vector ite2ElseItems(m); + ite2ElseItems.push_back(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(prefix, suffix))); + ite2ElseItems.push_back(ctx.mk_eq_atom(indexAst, mk_indexof(suffix, expr->get_arg(1)))); + ite2ElseItems.push_back(ctx.mk_eq_atom(expr->get_arg(2), prefixLen)); + ite2ElseItems.push_back(ite3); + expr_ref ite2Else(m.mk_and(ite2ElseItems.size(), ite2ElseItems.c_ptr()), m); + SASSERT(ite2Else); + + expr_ref ite2(m.mk_ite( + //m_autil.mk_ge(expr->get_arg(2), mk_strlen(expr->get_arg(0))), + m_autil.mk_ge(m_autil.mk_add(expr->get_arg(2), m_autil.mk_mul(mk_int(-1), mk_strlen(expr->get_arg(0)))), zeroAst), + ctx.mk_eq_atom(resAst, negOneAst), + ite2Else + ), m); + SASSERT(ite2); + + expr_ref ite1(m.mk_ite( + //m_autil.mk_lt(expr->get_arg(2), zeroAst), + m.mk_not(m_autil.mk_ge(expr->get_arg(2), zeroAst)), + ctx.mk_eq_atom(resAst, mk_indexof(expr->get_arg(0), expr->get_arg(1))), + ite2 + ), m); + SASSERT(ite1); + assert_axiom(ite1); + + expr_ref reduceTerm(ctx.mk_eq_atom(expr, resAst), m); + SASSERT(reduceTerm); + assert_axiom(reduceTerm); +} + void theory_str::attach_new_th_var(enode * n) { context & ctx = get_context(); theory_var v = mk_var(n); diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index bf0fef38b..c652a3faf 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -107,11 +107,13 @@ namespace smt { ptr_vector m_string_constant_length_todo; // enode lists for term-specific axioms + // TODO maybe refactor this into a generic "library_aware_axiom_todo" list ptr_vector m_axiom_CharAt_todo; ptr_vector m_axiom_StartsWith_todo; ptr_vector m_axiom_EndsWith_todo; ptr_vector m_axiom_Contains_todo; ptr_vector m_axiom_Indexof_todo; + ptr_vector m_axiom_Indexof2_todo; // hashtable of all exprs for which we've already set up term-specific axioms -- // this prevents infinite recursive descent with respect to axioms that @@ -158,6 +160,7 @@ namespace smt { expr * mk_concat(expr * n1, expr * n2); expr * mk_concat_const_str(expr * n1, expr * n2); app * mk_contains(expr * haystack, expr * needle); + app * mk_indexof(expr * haystack, expr * needle); literal mk_literal(expr* _e); app * mk_int(int n); @@ -191,6 +194,8 @@ namespace smt { bool is_Contains(enode const * n) const { return is_Contains(n->get_owner()); } bool is_Indexof(app const * a) const { return a->is_app_of(get_id(), OP_STR_INDEXOF); } bool is_Indexof(enode const * n) const { return is_Indexof(n->get_owner()); } + bool is_Indexof2(app const * a) const { return a->is_app_of(get_id(), OP_STR_INDEXOF2); } + bool is_Indexof2(enode const * n) const { return is_Indexof2(n->get_owner()); } void instantiate_concat_axiom(enode * cat); void instantiate_basic_string_axioms(enode * str); @@ -201,6 +206,7 @@ namespace smt { void instantiate_axiom_EndsWith(enode * e); void instantiate_axiom_Contains(enode * e); void instantiate_axiom_Indexof(enode * e); + void instantiate_axiom_Indexof2(enode * e); void set_up_axioms(expr * ex); void handle_equality(expr * lhs, expr * rhs); From 7c8b882ae6603b5908d6aa6d3ce5b48422c73cb4 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 15 Jun 2016 18:04:33 -0400 Subject: [PATCH 120/410] decl and rewriter support for LastIndexof in theory_str (WIP) --- src/ast/rewriter/str_rewriter.cpp | 21 +++++++++++++++++++++ src/ast/rewriter/str_rewriter.h | 1 + src/ast/str_decl_plugin.cpp | 7 +++++++ src/ast/str_decl_plugin.h | 2 ++ 4 files changed, 31 insertions(+) diff --git a/src/ast/rewriter/str_rewriter.cpp b/src/ast/rewriter/str_rewriter.cpp index 30dcb1d95..c4f2e634e 100644 --- a/src/ast/rewriter/str_rewriter.cpp +++ b/src/ast/rewriter/str_rewriter.cpp @@ -158,6 +158,24 @@ br_status str_rewriter::mk_str_Indexof2(expr * arg0, expr * arg1, expr * arg2, e } } +br_status str_rewriter::mk_str_LastIndexof(expr * haystack, expr * needle, expr_ref & result) { + TRACE("t_str_rw", tout << "rewrite (LastIndexof " << mk_pp(haystack, m()) << " " << mk_pp(needle, m()) << ")" << std::endl;); + if (m_strutil.is_string(haystack) && m_strutil.is_string(needle)) { + TRACE("t_str_rw", tout << "evaluating constant LastIndexof expression" << std::endl;); + std::string arg0Str = m_strutil.get_string_constant_value(haystack); + std::string arg1Str = m_strutil.get_string_constant_value(needle); + if (arg0Str.rfind(arg1Str) != std::string::npos) { + int index = arg0Str.rfind(arg1Str); + result = m_autil.mk_numeral(rational(index), true); + } else { + result = m_autil.mk_numeral(rational(-1), true); + } + return BR_DONE; + } else { + return BR_FAILED; + } +} + br_status str_rewriter::mk_app_core(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result) { SASSERT(f->get_family_id() == get_fid()); @@ -183,6 +201,9 @@ br_status str_rewriter::mk_app_core(func_decl * f, unsigned num_args, expr * con case OP_STR_INDEXOF2: SASSERT(num_args == 3); return mk_str_Indexof2(args[0], args[1], args[2], result); + case OP_STR_LASTINDEXOF: + SASSERT(num_args == 2); + return mk_str_LastIndexof(args[0], args[1], result); default: return BR_FAILED; } diff --git a/src/ast/rewriter/str_rewriter.h b/src/ast/rewriter/str_rewriter.h index c0bae2881..de399acba 100644 --- a/src/ast/rewriter/str_rewriter.h +++ b/src/ast/rewriter/str_rewriter.h @@ -46,6 +46,7 @@ public: br_status mk_str_Contains(expr * haystack, expr * needle, expr_ref & result); br_status mk_str_Indexof(expr * haystack, expr * needle, expr_ref & result); br_status mk_str_Indexof2(expr * arg0, expr * arg1, expr * arg2, expr_ref & result); + br_status mk_str_LastIndexof(expr * haystack, expr * needle, expr_ref & result); bool reduce_eq(expr * l, expr * r, expr_ref_vector & lhs, expr_ref_vector & rhs, bool & change); bool reduce_eq(expr_ref_vector& ls, expr_ref_vector& rs, expr_ref_vector& lhs, expr_ref_vector& rhs, bool& change); diff --git a/src/ast/str_decl_plugin.cpp b/src/ast/str_decl_plugin.cpp index f6e458fbd..fbdb10263 100644 --- a/src/ast/str_decl_plugin.cpp +++ b/src/ast/str_decl_plugin.cpp @@ -32,6 +32,7 @@ str_decl_plugin::str_decl_plugin(): m_contains_decl(0), m_indexof_decl(0), m_indexof2_decl(0), + m_lastindexof_decl(0), m_arith_plugin(0), m_arith_fid(0), m_int_sort(0){ @@ -51,6 +52,7 @@ void str_decl_plugin::finalize(void) { DEC_REF(m_contains_decl); DEC_REF(m_indexof_decl); DEC_REF(m_indexof2_decl); + DEC_REF(m_lastindexof_decl); DEC_REF(m_int_sort); } @@ -101,6 +103,9 @@ void str_decl_plugin::set_manager(ast_manager * m, family_id id) { m_indexof2_decl = m->mk_func_decl(symbol("Indexof2"), 3, d, i, func_decl_info(id, OP_STR_INDEXOF2)); m_manager->inc_ref(m_indexof2_decl); } + + m_lastindexof_decl = m->mk_func_decl(symbol("LastIndexof"), s, s, i, func_decl_info(id, OP_STR_LASTINDEXOF)); + m_manager->inc_ref(m_lastindexof_decl); } decl_plugin * str_decl_plugin::mk_fresh() { @@ -124,6 +129,7 @@ func_decl * str_decl_plugin::mk_func_decl(decl_kind k) { case OP_STR_CONTAINS: return m_contains_decl; case OP_STR_INDEXOF: return m_indexof_decl; case OP_STR_INDEXOF2: return m_indexof2_decl; + case OP_STR_LASTINDEXOF: return m_lastindexof_decl; default: return 0; } } @@ -186,6 +192,7 @@ void str_decl_plugin::get_op_names(svector & op_names, symbol cons op_names.push_back(builtin_name("Contains", OP_STR_CONTAINS)); op_names.push_back(builtin_name("Indexof", OP_STR_INDEXOF)); op_names.push_back(builtin_name("Indexof2", OP_STR_INDEXOF2)); + op_names.push_back(builtin_name("LastIndexof", OP_STR_LASTINDEXOF)); } void str_decl_plugin::get_sort_names(svector & sort_names, symbol const & logic) { diff --git a/src/ast/str_decl_plugin.h b/src/ast/str_decl_plugin.h index 54762f6b9..3e9a1d8f5 100644 --- a/src/ast/str_decl_plugin.h +++ b/src/ast/str_decl_plugin.h @@ -37,6 +37,7 @@ enum str_op_kind { OP_STR_CONTAINS, OP_STR_INDEXOF, OP_STR_INDEXOF2, + OP_STR_LASTINDEXOF, // end LAST_STR_OP }; @@ -55,6 +56,7 @@ protected: func_decl * m_contains_decl; func_decl * m_indexof_decl; func_decl * m_indexof2_decl; + func_decl * m_lastindexof_decl; arith_decl_plugin * m_arith_plugin; family_id m_arith_fid; From be5bf7fb803a036a3b129b90d1cc88c4240493db Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 15 Jun 2016 18:45:01 -0400 Subject: [PATCH 121/410] LastIndexof support --- src/smt/theory_str.cpp | 80 +++++++++++++++++++++++++++++++++++++++++- src/smt/theory_str.h | 4 +++ 2 files changed, 83 insertions(+), 1 deletion(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index a5244f7bb..727048c11 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -611,7 +611,7 @@ expr * theory_str::mk_concat(expr * n1, expr * n2) { bool theory_str::can_propagate() { return !m_basicstr_axiom_todo.empty() || !m_str_eq_todo.empty() || !m_concat_axiom_todo.empty() || !m_axiom_CharAt_todo.empty() || !m_axiom_StartsWith_todo.empty() || !m_axiom_EndsWith_todo.empty() - || !m_axiom_Contains_todo.empty() || !m_axiom_Indexof_todo.empty() || !m_axiom_Indexof2_todo.empty() + || !m_axiom_Contains_todo.empty() || !m_axiom_Indexof_todo.empty() || !m_axiom_Indexof2_todo.empty() || !m_axiom_LastIndexof_todo.empty() ; } @@ -665,6 +665,11 @@ void theory_str::propagate() { instantiate_axiom_Indexof2(m_axiom_Indexof2_todo[i]); } m_axiom_Indexof2_todo.reset(); + + for (unsigned i = 0; i < m_axiom_LastIndexof_todo.size(); ++i) { + instantiate_axiom_LastIndexof(m_axiom_LastIndexof_todo[i]); + } + m_axiom_LastIndexof_todo.reset(); } } @@ -1076,6 +1081,75 @@ void theory_str::instantiate_axiom_Indexof2(enode * e) { assert_axiom(reduceTerm); } +void theory_str::instantiate_axiom_LastIndexof(enode * e) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + app * expr = e->get_owner(); + if (axiomatized_terms.contains(expr)) { + TRACE("t_str_detail", tout << "already set up LastIndexof axiom for " << mk_pp(expr, m) << std::endl;); + return; + } + axiomatized_terms.insert(expr); + + TRACE("t_str_detail", tout << "instantiate LastIndexof axiom for " << mk_pp(expr, m) << std::endl;); + + expr_ref x1(mk_str_var("x1"), m); + expr_ref x2(mk_str_var("x2"), m); + expr_ref indexAst(mk_int_var("index"), m); + expr_ref_vector items(m); + + // args[0] = x1 . args[1] . x2 + expr_ref eq1(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(x1, mk_concat(expr->get_arg(1), x2))), m); + expr_ref arg0HasArg1(mk_contains(expr->get_arg(0), expr->get_arg(1)), m); // arg0HasArg1 = Contains(args[0], args[1]) + items.push_back(ctx.mk_eq_atom(arg0HasArg1, eq1)); + + + expr_ref condAst(arg0HasArg1, m); + //---------------------------- + // true branch + expr_ref_vector thenItems(m); + thenItems.push_back(m_autil.mk_ge(indexAst, mk_int(0))); + // args[0] = x1 . args[1] . x2 + // x1 doesn't contain args[1] + thenItems.push_back(m.mk_not(mk_contains(x2, expr->get_arg(1)))); + thenItems.push_back(ctx.mk_eq_atom(indexAst, mk_strlen(x1))); + + bool canSkip = false; + if (m_strutil.is_string(expr->get_arg(1))) { + std::string arg1Str = m_strutil.get_string_constant_value(expr->get_arg(1)); + if (arg1Str.length() == 1) { + canSkip = true; + } + } + + if (!canSkip) { + // args[0] = x3 . x4 /\ |x3| = |x1| + 1 /\ ! contains(x4, args[1]) + expr_ref x3(mk_str_var("x3"), m); + expr_ref x4(mk_str_var("x4"), m); + expr_ref tmpLen(m_autil.mk_add(indexAst, mk_int(1)), m); + thenItems.push_back(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(x3, x4))); + thenItems.push_back(ctx.mk_eq_atom(mk_strlen(x3), tmpLen)); + thenItems.push_back(m.mk_not(mk_contains(x4, expr->get_arg(1)))); + } + //---------------------------- + // else branch + expr_ref_vector elseItems(m); + elseItems.push_back(ctx.mk_eq_atom(indexAst, mk_int(-1))); + + items.push_back(m.mk_ite(condAst, m.mk_and(thenItems.size(), thenItems.c_ptr()), m.mk_and(elseItems.size(), elseItems.c_ptr()))); + + expr_ref breakdownAssert(m.mk_and(items.size(), items.c_ptr()), m); + SASSERT(breakdownAssert); + + expr_ref reduceToIndex(ctx.mk_eq_atom(expr, indexAst), m); + SASSERT(reduceToIndex); + + expr_ref finalAxiom(m.mk_and(breakdownAssert, reduceToIndex), m); + SASSERT(finalAxiom); + assert_axiom(finalAxiom); +} + void theory_str::attach_new_th_var(enode * n) { context & ctx = get_context(); theory_var v = mk_var(n); @@ -3850,6 +3924,10 @@ void theory_str::set_up_axioms(expr * ex) { app * ap = to_app(ex); if (is_Indexof(ap)) { m_axiom_Indexof_todo.push_back(n); + } else if (is_Indexof2(ap)) { + m_axiom_Indexof2_todo.push_back(n); + } else if (is_LastIndexof(ap)) { + m_axiom_LastIndexof_todo.push_back(n); } } } else { diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index c652a3faf..70878b45f 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -114,6 +114,7 @@ namespace smt { ptr_vector m_axiom_Contains_todo; ptr_vector m_axiom_Indexof_todo; ptr_vector m_axiom_Indexof2_todo; + ptr_vector m_axiom_LastIndexof_todo; // hashtable of all exprs for which we've already set up term-specific axioms -- // this prevents infinite recursive descent with respect to axioms that @@ -196,6 +197,8 @@ namespace smt { bool is_Indexof(enode const * n) const { return is_Indexof(n->get_owner()); } bool is_Indexof2(app const * a) const { return a->is_app_of(get_id(), OP_STR_INDEXOF2); } bool is_Indexof2(enode const * n) const { return is_Indexof2(n->get_owner()); } + bool is_LastIndexof(app const * a) const { return a->is_app_of(get_id(), OP_STR_LASTINDEXOF); } + bool is_LastIndexof(enode const * n) const { return is_LastIndexof(n->get_owner()); } void instantiate_concat_axiom(enode * cat); void instantiate_basic_string_axioms(enode * str); @@ -207,6 +210,7 @@ namespace smt { void instantiate_axiom_Contains(enode * e); void instantiate_axiom_Indexof(enode * e); void instantiate_axiom_Indexof2(enode * e); + void instantiate_axiom_LastIndexof(enode * e); void set_up_axioms(expr * ex); void handle_equality(expr * lhs, expr * rhs); From fb20951064f2b67567598b987d681d1c68b92b8a Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 15 Jun 2016 20:26:07 -0400 Subject: [PATCH 122/410] theory_str Substr support WIP --- src/ast/str_decl_plugin.cpp | 10 +++++++++ src/ast/str_decl_plugin.h | 2 ++ src/smt/theory_str.cpp | 42 +++++++++++++++++++++++++++++++++++++ src/smt/theory_str.h | 4 ++++ 4 files changed, 58 insertions(+) diff --git a/src/ast/str_decl_plugin.cpp b/src/ast/str_decl_plugin.cpp index fbdb10263..7bd4ec154 100644 --- a/src/ast/str_decl_plugin.cpp +++ b/src/ast/str_decl_plugin.cpp @@ -33,6 +33,7 @@ str_decl_plugin::str_decl_plugin(): m_indexof_decl(0), m_indexof2_decl(0), m_lastindexof_decl(0), + m_substr_decl(0), m_arith_plugin(0), m_arith_fid(0), m_int_sort(0){ @@ -53,6 +54,7 @@ void str_decl_plugin::finalize(void) { DEC_REF(m_indexof_decl); DEC_REF(m_indexof2_decl); DEC_REF(m_lastindexof_decl); + DEC_REF(m_substr_decl); DEC_REF(m_int_sort); } @@ -106,6 +108,12 @@ void str_decl_plugin::set_manager(ast_manager * m, family_id id) { m_lastindexof_decl = m->mk_func_decl(symbol("LastIndexof"), s, s, i, func_decl_info(id, OP_STR_LASTINDEXOF)); m_manager->inc_ref(m_lastindexof_decl); + + { + sort * d[3] = {s, i, i }; + m_substr_decl = m->mk_func_decl(symbol("Substring"), 3, d, s, func_decl_info(id, OP_STR_SUBSTR)); + m_manager->inc_ref(m_substr_decl); + } } decl_plugin * str_decl_plugin::mk_fresh() { @@ -130,6 +138,7 @@ func_decl * str_decl_plugin::mk_func_decl(decl_kind k) { case OP_STR_INDEXOF: return m_indexof_decl; case OP_STR_INDEXOF2: return m_indexof2_decl; case OP_STR_LASTINDEXOF: return m_lastindexof_decl; + case OP_STR_SUBSTR: return m_substr_decl; default: return 0; } } @@ -193,6 +202,7 @@ void str_decl_plugin::get_op_names(svector & op_names, symbol cons op_names.push_back(builtin_name("Indexof", OP_STR_INDEXOF)); op_names.push_back(builtin_name("Indexof2", OP_STR_INDEXOF2)); op_names.push_back(builtin_name("LastIndexof", OP_STR_LASTINDEXOF)); + op_names.push_back(builtin_name("Substring", OP_STR_SUBSTR)); } void str_decl_plugin::get_sort_names(svector & sort_names, symbol const & logic) { diff --git a/src/ast/str_decl_plugin.h b/src/ast/str_decl_plugin.h index 3e9a1d8f5..bd2a70a1e 100644 --- a/src/ast/str_decl_plugin.h +++ b/src/ast/str_decl_plugin.h @@ -38,6 +38,7 @@ enum str_op_kind { OP_STR_INDEXOF, OP_STR_INDEXOF2, OP_STR_LASTINDEXOF, + OP_STR_SUBSTR, // end LAST_STR_OP }; @@ -57,6 +58,7 @@ protected: func_decl * m_indexof_decl; func_decl * m_indexof2_decl; func_decl * m_lastindexof_decl; + func_decl * m_substr_decl; arith_decl_plugin * m_arith_plugin; family_id m_arith_fid; diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 727048c11..4f04ede23 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -612,6 +612,7 @@ bool theory_str::can_propagate() { return !m_basicstr_axiom_todo.empty() || !m_str_eq_todo.empty() || !m_concat_axiom_todo.empty() || !m_axiom_CharAt_todo.empty() || !m_axiom_StartsWith_todo.empty() || !m_axiom_EndsWith_todo.empty() || !m_axiom_Contains_todo.empty() || !m_axiom_Indexof_todo.empty() || !m_axiom_Indexof2_todo.empty() || !m_axiom_LastIndexof_todo.empty() + || !m_axiom_Substr_todo.empty() ; } @@ -670,6 +671,11 @@ void theory_str::propagate() { instantiate_axiom_LastIndexof(m_axiom_LastIndexof_todo[i]); } m_axiom_LastIndexof_todo.reset(); + + for (unsigned i = 0; i < m_axiom_Substr_todo.size(); ++i) { + instantiate_axiom_Substr(m_axiom_Substr_todo[i]); + } + m_axiom_Substr_todo.reset(); } } @@ -1150,6 +1156,42 @@ void theory_str::instantiate_axiom_LastIndexof(enode * e) { assert_axiom(finalAxiom); } +void theory_str::instantiate_axiom_Substr(enode * e) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + app * expr = e->get_owner(); + if (axiomatized_terms.contains(expr)) { + TRACE("t_str_detail", tout << "already set up Substr axiom for " << mk_pp(expr, m) << std::endl;); + return; + } + axiomatized_terms.insert(expr); + + TRACE("t_str_detail", tout << "instantiate Substr axiom for " << mk_pp(expr, m) << std::endl;); + + expr_ref ts0(mk_str_var("ts0"), m); + expr_ref ts1(mk_str_var("ts1"), m); + expr_ref ts2(mk_str_var("ts2"), m); + + expr_ref ts0_contains_ts1(mk_contains(expr->get_arg(0), ts1), m); + + expr_ref_vector and_item(m); + and_item.push_back(ts0_contains_ts1); + and_item.push_back(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(ts0, mk_concat(ts1, ts2)))); + and_item.push_back(ctx.mk_eq_atom(expr->get_arg(1), mk_strlen(ts0))); + and_item.push_back(ctx.mk_eq_atom(expr->get_arg(2), mk_strlen(ts1))); + + expr_ref breakdownAssert(m.mk_and(and_item.size(), and_item.c_ptr()), m); + SASSERT(breakdownAssert); + + expr_ref reduceToVar(ctx.mk_eq_atom(expr, ts1), m); + SASSERT(reduceToVar); + + expr_ref finalAxiom(m.mk_and(breakdownAssert, reduceToVar), m); + SASSERT(finalAxiom); + assert_axiom(finalAxiom); +} + void theory_str::attach_new_th_var(enode * n) { context & ctx = get_context(); theory_var v = mk_var(n); diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 70878b45f..35a6fe91b 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -115,6 +115,7 @@ namespace smt { ptr_vector m_axiom_Indexof_todo; ptr_vector m_axiom_Indexof2_todo; ptr_vector m_axiom_LastIndexof_todo; + ptr_vector m_axiom_Substr_todo; // hashtable of all exprs for which we've already set up term-specific axioms -- // this prevents infinite recursive descent with respect to axioms that @@ -199,6 +200,8 @@ namespace smt { bool is_Indexof2(enode const * n) const { return is_Indexof2(n->get_owner()); } bool is_LastIndexof(app const * a) const { return a->is_app_of(get_id(), OP_STR_LASTINDEXOF); } bool is_LastIndexof(enode const * n) const { return is_LastIndexof(n->get_owner()); } + bool is_Substr(app const * a) const { return a->is_app_of(get_id(), OP_STR_SUBSTR); } + bool is_Substr(enode const * n) const { return is_Substr(n->get_owner()); } void instantiate_concat_axiom(enode * cat); void instantiate_basic_string_axioms(enode * str); @@ -211,6 +214,7 @@ namespace smt { void instantiate_axiom_Indexof(enode * e); void instantiate_axiom_Indexof2(enode * e); void instantiate_axiom_LastIndexof(enode * e); + void instantiate_axiom_Substr(enode * e); void set_up_axioms(expr * ex); void handle_equality(expr * lhs, expr * rhs); From 5b3c868c904065a84f00eb3042c20e0e851c2064 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 15 Jun 2016 21:14:54 -0400 Subject: [PATCH 123/410] theory_str Replace method --- src/ast/rewriter/str_rewriter.cpp | 25 +++++++++ src/ast/rewriter/str_rewriter.h | 1 + src/ast/str_decl_plugin.cpp | 10 ++++ src/ast/str_decl_plugin.h | 2 + src/smt/theory_str.cpp | 87 +++++++++++++++++++++++++++---- src/smt/theory_str.h | 4 ++ 6 files changed, 118 insertions(+), 11 deletions(-) diff --git a/src/ast/rewriter/str_rewriter.cpp b/src/ast/rewriter/str_rewriter.cpp index c4f2e634e..8dc02cc09 100644 --- a/src/ast/rewriter/str_rewriter.cpp +++ b/src/ast/rewriter/str_rewriter.cpp @@ -176,6 +176,28 @@ br_status str_rewriter::mk_str_LastIndexof(expr * haystack, expr * needle, expr_ } } +br_status str_rewriter::mk_str_Replace(expr * base, expr * source, expr * target, expr_ref & result) { + TRACE("t_str_rw", tout << "rewrite (Replace " << mk_pp(base, m()) << " " << mk_pp(source, m()) << " " << mk_pp(target, m()) << ")" << std::endl;); + if (m_strutil.is_string(base) && m_strutil.is_string(source) && m_strutil.is_string(target)) { + std::string arg0Str = m_strutil.get_string_constant_value(base); + std::string arg1Str = m_strutil.get_string_constant_value(source); + std::string arg2Str = m_strutil.get_string_constant_value(target); + if (arg0Str.find(arg1Str) != std::string::npos) { + int index1 = arg0Str.find(arg1Str); + int index2 = index1 + arg1Str.length(); + std::string substr0 = arg0Str.substr(0, index1); + std::string substr2 = arg0Str.substr(index2); + std::string replaced = substr0 + arg2Str + substr2; + result = m_strutil.mk_string(replaced); + } else { + result = base; + } + return BR_DONE; + } else { + return BR_FAILED; + } +} + br_status str_rewriter::mk_app_core(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result) { SASSERT(f->get_family_id() == get_fid()); @@ -204,6 +226,9 @@ br_status str_rewriter::mk_app_core(func_decl * f, unsigned num_args, expr * con case OP_STR_LASTINDEXOF: SASSERT(num_args == 2); return mk_str_LastIndexof(args[0], args[1], result); + case OP_STR_REPLACE: + SASSERT(num_args == 3); + return mk_str_Replace(args[0], args[1], args[2], result); default: return BR_FAILED; } diff --git a/src/ast/rewriter/str_rewriter.h b/src/ast/rewriter/str_rewriter.h index de399acba..69a7c9579 100644 --- a/src/ast/rewriter/str_rewriter.h +++ b/src/ast/rewriter/str_rewriter.h @@ -47,6 +47,7 @@ public: br_status mk_str_Indexof(expr * haystack, expr * needle, expr_ref & result); br_status mk_str_Indexof2(expr * arg0, expr * arg1, expr * arg2, expr_ref & result); br_status mk_str_LastIndexof(expr * haystack, expr * needle, expr_ref & result); + br_status mk_str_Replace(expr * base, expr * source, expr * target, expr_ref & result); bool reduce_eq(expr * l, expr * r, expr_ref_vector & lhs, expr_ref_vector & rhs, bool & change); bool reduce_eq(expr_ref_vector& ls, expr_ref_vector& rs, expr_ref_vector& lhs, expr_ref_vector& rhs, bool& change); diff --git a/src/ast/str_decl_plugin.cpp b/src/ast/str_decl_plugin.cpp index 7bd4ec154..526b02f64 100644 --- a/src/ast/str_decl_plugin.cpp +++ b/src/ast/str_decl_plugin.cpp @@ -34,6 +34,7 @@ str_decl_plugin::str_decl_plugin(): m_indexof2_decl(0), m_lastindexof_decl(0), m_substr_decl(0), + m_replace_decl(0), m_arith_plugin(0), m_arith_fid(0), m_int_sort(0){ @@ -55,6 +56,7 @@ void str_decl_plugin::finalize(void) { DEC_REF(m_indexof2_decl); DEC_REF(m_lastindexof_decl); DEC_REF(m_substr_decl); + DEC_REF(m_replace_decl); DEC_REF(m_int_sort); } @@ -114,6 +116,12 @@ void str_decl_plugin::set_manager(ast_manager * m, family_id id) { m_substr_decl = m->mk_func_decl(symbol("Substring"), 3, d, s, func_decl_info(id, OP_STR_SUBSTR)); m_manager->inc_ref(m_substr_decl); } + + { + sort * d[3] = {s, s, s}; + m_replace_decl = m->mk_func_decl(symbol("Replace"), 3, d, s, func_decl_info(id, OP_STR_REPLACE)); + m_manager->inc_ref(m_replace_decl); + } } decl_plugin * str_decl_plugin::mk_fresh() { @@ -139,6 +147,7 @@ func_decl * str_decl_plugin::mk_func_decl(decl_kind k) { case OP_STR_INDEXOF2: return m_indexof2_decl; case OP_STR_LASTINDEXOF: return m_lastindexof_decl; case OP_STR_SUBSTR: return m_substr_decl; + case OP_STR_REPLACE: return m_replace_decl; default: return 0; } } @@ -203,6 +212,7 @@ void str_decl_plugin::get_op_names(svector & op_names, symbol cons op_names.push_back(builtin_name("Indexof2", OP_STR_INDEXOF2)); op_names.push_back(builtin_name("LastIndexof", OP_STR_LASTINDEXOF)); op_names.push_back(builtin_name("Substring", OP_STR_SUBSTR)); + op_names.push_back(builtin_name("Replace", OP_STR_REPLACE)); } void str_decl_plugin::get_sort_names(svector & sort_names, symbol const & logic) { diff --git a/src/ast/str_decl_plugin.h b/src/ast/str_decl_plugin.h index bd2a70a1e..ee2432c50 100644 --- a/src/ast/str_decl_plugin.h +++ b/src/ast/str_decl_plugin.h @@ -39,6 +39,7 @@ enum str_op_kind { OP_STR_INDEXOF2, OP_STR_LASTINDEXOF, OP_STR_SUBSTR, + OP_STR_REPLACE, // end LAST_STR_OP }; @@ -59,6 +60,7 @@ protected: func_decl * m_indexof2_decl; func_decl * m_lastindexof_decl; func_decl * m_substr_decl; + func_decl * m_replace_decl; arith_decl_plugin * m_arith_plugin; family_id m_arith_fid; diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 4f04ede23..1e2107f11 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -612,7 +612,7 @@ bool theory_str::can_propagate() { return !m_basicstr_axiom_todo.empty() || !m_str_eq_todo.empty() || !m_concat_axiom_todo.empty() || !m_axiom_CharAt_todo.empty() || !m_axiom_StartsWith_todo.empty() || !m_axiom_EndsWith_todo.empty() || !m_axiom_Contains_todo.empty() || !m_axiom_Indexof_todo.empty() || !m_axiom_Indexof2_todo.empty() || !m_axiom_LastIndexof_todo.empty() - || !m_axiom_Substr_todo.empty() + || !m_axiom_Substr_todo.empty() || !m_axiom_Replace_todo.empty() ; } @@ -676,6 +676,11 @@ void theory_str::propagate() { instantiate_axiom_Substr(m_axiom_Substr_todo[i]); } m_axiom_Substr_todo.reset(); + + for (unsigned i = 0; i < m_axiom_Replace_todo.size(); ++i) { + instantiate_axiom_Replace(m_axiom_Replace_todo[i]); + } + m_axiom_Replace_todo.reset(); } } @@ -1192,6 +1197,56 @@ void theory_str::instantiate_axiom_Substr(enode * e) { assert_axiom(finalAxiom); } +void theory_str::instantiate_axiom_Replace(enode * e) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + app * expr = e->get_owner(); + if (axiomatized_terms.contains(expr)) { + TRACE("t_str_detail", tout << "already set up Replace axiom for " << mk_pp(expr, m) << std::endl;); + return; + } + axiomatized_terms.insert(expr); + + TRACE("t_str_detail", tout << "instantiate Replace axiom for " << mk_pp(expr, m) << std::endl;); + + expr_ref x1(mk_str_var("x1"), m); + expr_ref x2(mk_str_var("x2"), m); + expr_ref i1(mk_int_var("i1"), m); + expr_ref result(mk_str_var("result"), m); + + // condAst = Contains(args[0], args[1]) + expr_ref condAst(mk_contains(expr->get_arg(0), expr->get_arg(1)), m); + // ----------------------- + // true branch + expr_ref_vector thenItems(m); + // args[0] = x1 . args[1] . x2 + thenItems.push_back(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(x1, mk_concat(expr->get_arg(1), x2)))); + // i1 = |x1| + thenItems.push_back(ctx.mk_eq_atom(i1, mk_strlen(x1))); + // args[0] = x3 . x4 /\ |x3| = |x1| + |args[1]| - 1 /\ ! contains(x3, args[1]) + expr_ref x3(mk_str_var("x3"), m); + expr_ref x4(mk_str_var("x4"), m); + expr_ref tmpLen(m_autil.mk_add(i1, mk_strlen(expr->get_arg(1)), mk_int(-1)), m); + thenItems.push_back(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(x3, x4))); + thenItems.push_back(ctx.mk_eq_atom(mk_strlen(x3), tmpLen)); + thenItems.push_back(m.mk_not(mk_contains(x3, expr->get_arg(1)))); + thenItems.push_back(ctx.mk_eq_atom(result, mk_concat(x1, mk_concat(expr->get_arg(2), x2)))); + // ----------------------- + // false branch + expr_ref elseBranch(ctx.mk_eq_atom(result, expr->get_arg(0)), m); + + expr_ref breakdownAssert(m.mk_ite(condAst, m.mk_and(thenItems.size(), thenItems.c_ptr()), elseBranch), m); + SASSERT(breakdownAssert); + + expr_ref reduceToResult(ctx.mk_eq_atom(expr, result), m); + SASSERT(reduceToResult); + + expr_ref finalAxiom(m.mk_and(breakdownAssert, reduceToResult), m); + SASSERT(finalAxiom); + assert_axiom(finalAxiom); +} + void theory_str::attach_new_th_var(enode * n) { context & ctx = get_context(); theory_var v = mk_var(n); @@ -3928,6 +3983,10 @@ void theory_str::set_up_axioms(expr * ex) { } } else if (is_CharAt(ap)) { m_axiom_CharAt_todo.push_back(n); + } else if (is_Substr(ap)) { + m_axiom_Substr_todo.push_back(n); + } else if (is_Replace(ap)) { + m_axiom_Replace_todo.push_back(n); } else if (ap->get_num_args() == 0 && !is_string(ap)) { // if ex is a variable, add it to our list of variables TRACE("t_str_detail", tout << "tracking variable " << mk_ismt2_pp(ap, get_manager()) << std::endl;); @@ -3942,18 +4001,24 @@ void theory_str::set_up_axioms(expr * ex) { TRACE("t_str_detail", tout << "setting up axioms for " << mk_ismt2_pp(ex, get_manager()) << ": expr is of sort Bool" << std::endl;); // set up axioms for boolean terms - enode * n = ctx.get_enode(ex); - SASSERT(n); - if (is_app(ex)) { - app * ap = to_app(ex); - if (is_StartsWith(ap)) { - m_axiom_StartsWith_todo.push_back(n); - } else if (is_EndsWith(ap)) { - m_axiom_EndsWith_todo.push_back(n); - } else if (is_Contains(ap)) { - m_axiom_Contains_todo.push_back(n); + if (ctx.e_internalized(ex)) { + enode * n = ctx.get_enode(ex); + SASSERT(n); + + if (is_app(ex)) { + app * ap = to_app(ex); + if (is_StartsWith(ap)) { + m_axiom_StartsWith_todo.push_back(n); + } else if (is_EndsWith(ap)) { + m_axiom_EndsWith_todo.push_back(n); + } else if (is_Contains(ap)) { + m_axiom_Contains_todo.push_back(n); + } } + } else { + TRACE("t_str_detail", tout << "WARNING: Bool term " << mk_ismt2_pp(ex, get_manager()) << " not internalized. Skipping to prevent a crash." << std::endl;); + return; } } else if (ex_sort == int_sort) { TRACE("t_str_detail", tout << "setting up axioms for " << mk_ismt2_pp(ex, get_manager()) << diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 35a6fe91b..7ee1d4281 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -116,6 +116,7 @@ namespace smt { ptr_vector m_axiom_Indexof2_todo; ptr_vector m_axiom_LastIndexof_todo; ptr_vector m_axiom_Substr_todo; + ptr_vector m_axiom_Replace_todo; // hashtable of all exprs for which we've already set up term-specific axioms -- // this prevents infinite recursive descent with respect to axioms that @@ -202,6 +203,8 @@ namespace smt { bool is_LastIndexof(enode const * n) const { return is_LastIndexof(n->get_owner()); } bool is_Substr(app const * a) const { return a->is_app_of(get_id(), OP_STR_SUBSTR); } bool is_Substr(enode const * n) const { return is_Substr(n->get_owner()); } + bool is_Replace(app const * a) const { return a->is_app_of(get_id(), OP_STR_REPLACE); } + bool is_Replace(enode const * n) const { return is_Replace(n->get_owner()); } void instantiate_concat_axiom(enode * cat); void instantiate_basic_string_axioms(enode * str); @@ -215,6 +218,7 @@ namespace smt { void instantiate_axiom_Indexof2(enode * e); void instantiate_axiom_LastIndexof(enode * e); void instantiate_axiom_Substr(enode * e); + void instantiate_axiom_Replace(enode * e); void set_up_axioms(expr * ex); void handle_equality(expr * lhs, expr * rhs); From 89a337ba7eac310745fc1e475ba3e7b59e2274ac Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sun, 19 Jun 2016 18:25:31 -0400 Subject: [PATCH 124/410] quick path with string-integer integration in theory_str::simplify_concat_equality --- src/smt/theory_str.cpp | 74 ++++++++++++++++++++---------------------- 1 file changed, 36 insertions(+), 38 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 1e2107f11..56b86885b 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -1733,17 +1733,12 @@ void theory_str::simplify_concat_equality(expr * nn1, expr * nn2) { expr * a2_arg0 = a_nn2->get_arg(0); expr * a2_arg1 = a_nn2->get_arg(1); - // TODO - /* - int a1_arg0_len = getLenValue(t, a1_arg0); - int a1_arg1_len = getLenValue(t, a1_arg1); - int a2_arg0_len = getLenValue(t, a2_arg0); - int a2_arg1_len = getLenValue(t, a2_arg1); - */ - int a1_arg0_len = -1; - int a1_arg1_len = -1; - int a2_arg0_len = -1; - int a2_arg1_len = -1; + rational a1_arg0_len, a1_arg1_len, a2_arg0_len, a2_arg1_len; + + bool a1_arg0_len_exists = get_len_value(a1_arg0, a1_arg0_len); + bool a1_arg1_len_exists = get_len_value(a1_arg1, a1_arg1_len); + bool a2_arg0_len_exists = get_len_value(a2_arg0, a2_arg0_len); + bool a2_arg1_len_exists = get_len_value(a2_arg1, a2_arg1_len); TRACE("t_str", tout << "nn1 = " << mk_ismt2_pp(nn1, m) << std::endl << "nn2 = " << mk_ismt2_pp(nn2, m) << std::endl;); @@ -1797,35 +1792,38 @@ void theory_str::simplify_concat_equality(expr * nn1, expr * nn2) { } } - // TODO quick path 1-2 - /* - if(a1_arg0_len != -1 && a2_arg0_len != -1 && a1_arg0_len == a2_arg0_len){ - if (! inSameEqc(t, a1_arg0, a2_arg0)) { - __debugPrint(logFile, ">> [simplifyConcatEq] Quick Path 2-1: len(nn1.arg0) == len(nn2.arg0)\n"); - Z3_ast ax_l1 = Z3_mk_eq(ctx, nn1, nn2); - Z3_ast ax_l2 = Z3_mk_eq(ctx, mk_length(t, a1_arg0), mk_length(t, a2_arg0)); - Z3_ast ax_r1 = Z3_mk_eq(ctx, a1_arg0, a2_arg0); - Z3_ast ax_r2 = Z3_mk_eq(ctx, a1_arg1, a2_arg1); - Z3_ast toAdd = Z3_mk_implies(ctx, mk_2_and(t, ax_l1, ax_l2), mk_2_and(t, ax_r1, ax_r2)); - addAxiom(t, toAdd, __LINE__); - return; - } - } + // quick path 2-1 + if (a1_arg0_len_exists && a2_arg0_len_exists && a1_arg0_len == a2_arg0_len) { + if (!in_same_eqc(a1_arg0, a2_arg0)) { + TRACE("t_str_detail", tout << "quick path 2-1: len(nn1.arg0) == len(nn2.arg0)" << std::endl;); + expr_ref ax_l1(ctx.mk_eq_atom(nn1, nn2), m); + expr_ref ax_l2(ctx.mk_eq_atom(mk_strlen(a1_arg0), mk_strlen(a2_arg0)), m); + expr_ref ax_r1(ctx.mk_eq_atom(a1_arg0, a2_arg0), m); + expr_ref ax_r2(ctx.mk_eq_atom(a1_arg1, a2_arg1), m); - if (a1_arg1_len != -1 && a2_arg1_len != -1 && a1_arg1_len == a2_arg1_len) - { - if (!inSameEqc(t, a1_arg1, a2_arg1)) { - __debugPrint(logFile, ">> [simplifyConcatEq] Quick Path 2-2: len(nn1.arg1) == len(nn2.arg1)\n"); - Z3_ast ax_l1 = Z3_mk_eq(ctx, nn1, nn2); - Z3_ast ax_l2 = Z3_mk_eq(ctx, mk_length(t, a1_arg1), mk_length(t, a2_arg1)); - Z3_ast ax_r1 = Z3_mk_eq(ctx, a1_arg0, a2_arg0); - Z3_ast ax_r2 = Z3_mk_eq(ctx, a1_arg1, a2_arg1); - Z3_ast toAdd = Z3_mk_implies(ctx, mk_2_and(t, ax_l1, ax_l2), mk_2_and(t, ax_r1, ax_r2)); - addAxiom(t, toAdd, __LINE__); - return; + expr_ref premise(m.mk_and(ax_l1, ax_l2), m); + expr_ref conclusion(m.mk_and(ax_r1, ax_r2), m); + + assert_implication(premise, conclusion); + return; + } + } + + if (a1_arg1_len_exists && a2_arg1_len_exists && a1_arg1_len == a2_arg1_len) { + if (!in_same_eqc(a1_arg1, a2_arg1)) { + TRACE("t_str_detail", tout << "quick path 2-2: len(nn1.arg1) == len(nn2.arg1)" << std::endl;); + expr_ref ax_l1(ctx.mk_eq_atom(nn1, nn2), m); + expr_ref ax_l2(ctx.mk_eq_atom(mk_strlen(a1_arg1), mk_strlen(a2_arg1)), m); + expr_ref ax_r1(ctx.mk_eq_atom(a1_arg0, a2_arg0), m); + expr_ref ax_r2(ctx.mk_eq_atom(a1_arg1, a2_arg1), m); + + expr_ref premise(m.mk_and(ax_l1, ax_l2), m); + expr_ref conclusion(m.mk_and(ax_r1, ax_r2), m); + + assert_implication(premise, conclusion); + return; + } } - } - */ expr * new_nn1 = simplify_concat(nn1); expr * new_nn2 = simplify_concat(nn2); From ba42478f9b229154f883abb85c8e59aff13e9c2d Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Mon, 20 Jun 2016 20:02:22 -0400 Subject: [PATCH 125/410] string-integer wip --- src/smt/theory_str.cpp | 60 +++++++++++++++++++++++++++++++++++++++++- src/smt/theory_str.h | 2 ++ 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 56b86885b..15e202409 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -1716,6 +1716,64 @@ expr * theory_str::simplify_concat(expr * node) { } +void theory_str::infer_len_concat_equality(expr * nn1, expr * nn2) { + rational nnLen; + bool nnLen_exists = get_len_value(nn1, nnLen); + if (!nnLen_exists) { + nnLen_exists = get_len_value(nn2, nnLen); + } + + // case 1: + // Known: a1_arg0 and a1_arg1 + // Unknown: nn1 + + if (is_concat(to_app(nn1))) { + rational nn1ConcatLen; + bool nn1ConcatLen_exists = infer_len_concat(nn1, nn1ConcatLen); + if (nnLen_exists && nn1ConcatLen_exists) { + nnLen = nn1ConcatLen; + } + } + + // case 2: + // Known: a1_arg0 and a1_arg1 + // Unknown: nn1 + + if (is_concat(to_app(nn2))) { + rational nn2ConcatLen; + bool nn2ConcatLen_exists = infer_len_concat(nn2, nn2ConcatLen); + if (nnLen_exists && nn2ConcatLen_exists) { + nnLen = nn2ConcatLen; + } + } + + if (nnLen_exists) { + if (is_concat(to_app(nn1))) { + infer_len_concat_arg(nn1, nnLen); + } + if (is_concat(to_app(nn2))) { + infer_len_concat_arg(nn2, nnLen); + } + } + + /* + if (isConcatFunc(t, nn2)) { + int nn2ConcatLen = inferLenConcat(t, nn2); + if (nnLen == -1 && nn2ConcatLen != -1) + nnLen = nn2ConcatLen; + } + + if (nnLen != -1) { + if (isConcatFunc(t, nn1)) { + inferLenConcatArg(t, nn1, nnLen); + } + if (isConcatFunc(t, nn2)) { + inferLenConcatArg(t, nn2, nnLen); + } + } + */ +} + /* * Handle two equivalent Concats. */ @@ -1743,7 +1801,7 @@ void theory_str::simplify_concat_equality(expr * nn1, expr * nn2) { TRACE("t_str", tout << "nn1 = " << mk_ismt2_pp(nn1, m) << std::endl << "nn2 = " << mk_ismt2_pp(nn2, m) << std::endl;); - // TODO inferLenConcatEq(nn1, nn2); + infer_len_concat_equality(nn1, nn2); if (a1_arg0 == a2_arg0) { if (!in_same_eqc(a1_arg1, a2_arg1)) { diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 7ee1d4281..41091f64b 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -244,6 +244,8 @@ namespace smt { void simplify_concat_equality(expr * lhs, expr * rhs); void solve_concat_eq_str(expr * concat, expr * str); + void infer_len_concat_equality(expr * nn1, expr * nn2); + bool is_concat_eq_type1(expr * concatAst1, expr * concatAst2); bool is_concat_eq_type2(expr * concatAst1, expr * concatAst2); bool is_concat_eq_type3(expr * concatAst1, expr * concatAst2); From 1e46782392cc67428e334548f2827aa731b07fbd Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 21 Jun 2016 17:25:28 -0400 Subject: [PATCH 126/410] theory_str infer_len_concat --- src/smt/theory_str.cpp | 40 ++++++++++++++++++++++++++++++++++++++++ src/smt/theory_str.h | 1 + 2 files changed, 41 insertions(+) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 15e202409..a747ce12d 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -1716,6 +1716,46 @@ expr * theory_str::simplify_concat(expr * node) { } +// Modified signature of Z3str2's inferLenConcat(). +// Returns true iff nLen can be inferred by this method +// (i.e. the equivalent of a len_exists flag in get_len_value()). + +bool theory_str::infer_len_concat(expr * n, rational & nLen) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + expr * arg0 = to_app(n)->get_arg(0); + expr * arg1 = to_app(n)->get_arg(1); + + rational arg0_len, arg1_len; + bool arg0_len_exists = get_len_value(arg0, arg0_len); + bool arg1_len_exists = get_len_value(arg1, arg1_len); + rational tmp_len; + bool nLen_exists = get_len_value(n, tmp_len); + + if (arg0_len_exists && arg1_len_exists && !nLen_exists) { + expr_ref_vector l_items(m); + // if (mk_strlen(arg0) != mk_int(arg0_len)) { + { + l_items.push_back(ctx.mk_eq_atom(mk_strlen(arg0), mk_int(arg0_len))); + } + + // if (mk_strlen(arg1) != mk_int(arg1_len)) { + { + l_items.push_back(ctx.mk_eq_atom(mk_strlen(arg1), mk_int(arg1_len))); + } + + expr_ref axl(m.mk_and(l_items.size(), l_items.c_ptr()), m); + rational nnLen = arg0_len + arg1_len; + expr_ref axr(ctx.mk_eq_atom(mk_strlen(n), mk_int(nnLen)), m); + TRACE("t_str_detail", tout << "inferred (Length " << mk_pp(n, m) << ") = " << nnLen << std::endl;); + assert_implication(axl, axr); + nLen = nnLen; + return true; + } else { + return false; + } +} + void theory_str::infer_len_concat_equality(expr * nn1, expr * nn2) { rational nnLen; bool nnLen_exists = get_len_value(nn1, nnLen); diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 41091f64b..e3589d68d 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -245,6 +245,7 @@ namespace smt { void solve_concat_eq_str(expr * concat, expr * str); void infer_len_concat_equality(expr * nn1, expr * nn2); + bool infer_len_concat(expr * n, rational & nLen); bool is_concat_eq_type1(expr * concatAst1, expr * concatAst2); bool is_concat_eq_type2(expr * concatAst1, expr * concatAst2); From a808a8c587d20fae4b130084b6779d6bdd589cb0 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 21 Jun 2016 17:38:49 -0400 Subject: [PATCH 127/410] theory_str infer_len_concat_arg --- src/smt/theory_str.cpp | 55 ++++++++++++++++++++++++++++++++++++++++++ src/smt/theory_str.h | 1 + 2 files changed, 56 insertions(+) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index a747ce12d..e8df17c58 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -1756,6 +1756,61 @@ bool theory_str::infer_len_concat(expr * n, rational & nLen) { } } +void theory_str::infer_len_concat_arg(expr * n, rational len) { + if (len.is_neg()) { + return; + } + + context & ctx = get_context(); + ast_manager & m = get_manager(); + + expr * arg0 = to_app(n)->get_arg(0); + expr * arg1 = to_app(n)->get_arg(1); + rational arg0_len, arg1_len; + bool arg0_len_exists = get_len_value(arg0, arg0_len); + bool arg1_len_exists = get_len_value(arg1, arg1_len); + + expr_ref_vector l_items(m); + expr_ref axr(m); + axr.reset(); + + // if (mk_length(t, n) != mk_int(ctx, len)) { + { + l_items.push_back(ctx.mk_eq_atom(mk_strlen(n), mk_int(len))); + } + + if (!arg0_len_exists && arg1_len_exists) { + //if (mk_length(t, arg1) != mk_int(ctx, arg1_len)) { + { + l_items.push_back(ctx.mk_eq_atom(mk_strlen(arg1), mk_int(arg1_len))); + } + rational arg0Len = len - arg1_len; + if (arg0Len.is_nonneg()) { + axr = ctx.mk_eq_atom(mk_strlen(arg0), mk_int(arg0Len)); + } else { + // TODO negate? + } + } else if (arg0_len_exists && !arg1_len_exists) { + //if (mk_length(t, arg0) != mk_int(ctx, arg0_len)) { + { + l_items.push_back(ctx.mk_eq_atom(mk_strlen(arg0), mk_int(arg0_len))); + } + rational arg1Len = len - arg0_len; + if (arg1Len.is_nonneg()) { + axr = ctx.mk_eq_atom(mk_strlen(arg1), mk_int(arg1Len)); + } else { + // TODO negate? + } + } else { + + } + + if (axr) { + expr_ref axl(m.mk_and(l_items.size(), l_items.c_ptr()), m); + assert_implication(axl, axr); + } +} + void theory_str::infer_len_concat_equality(expr * nn1, expr * nn2) { rational nnLen; bool nnLen_exists = get_len_value(nn1, nnLen); diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index e3589d68d..151dbc53f 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -246,6 +246,7 @@ namespace smt { void infer_len_concat_equality(expr * nn1, expr * nn2); bool infer_len_concat(expr * n, rational & nLen); + void infer_len_concat_arg(expr * n, rational len); bool is_concat_eq_type1(expr * concatAst1, expr * concatAst2); bool is_concat_eq_type2(expr * concatAst1, expr * concatAst2); From 4c346298064c06fb10554477d14393e2d627ba2e Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 21 Jun 2016 21:13:16 -0400 Subject: [PATCH 128/410] starting regex support, rewriter --- src/ast/rewriter/str_rewriter.cpp | 27 +++++++++++++++++++++++++++ src/ast/rewriter/str_rewriter.h | 3 +++ src/ast/str_decl_plugin.cpp | 23 +++++++++++++++++++++++ src/ast/str_decl_plugin.h | 10 ++++++++++ 4 files changed, 63 insertions(+) diff --git a/src/ast/rewriter/str_rewriter.cpp b/src/ast/rewriter/str_rewriter.cpp index 8dc02cc09..3a0300ae4 100644 --- a/src/ast/rewriter/str_rewriter.cpp +++ b/src/ast/rewriter/str_rewriter.cpp @@ -198,6 +198,27 @@ br_status str_rewriter::mk_str_Replace(expr * base, expr * source, expr * target } } +br_status str_rewriter::mk_re_Str2Reg(expr * str, expr_ref & result) { + // the argument to Str2Reg *must* be a string constant + // TODO is an assertion error too strict here? this basically crashes the solver + VERIFY(m_strutil.is_string(str)); + return BR_FAILED; +} + +br_status str_rewriter::mk_re_RegexIn(expr * str, expr * re, expr_ref & result) { + // fast path: + // (RegexIn E (Str2Reg S)) --> (= E S) + if (m_strutil.is_re_Str2Reg(re)) { + TRACE("t_str_rw", tout << "RegexIn fast path: " << mk_pp(str, m()) << " in " << mk_pp(re, m()) << std::endl;); + expr * regexStr = to_app(re)->get_arg(0); + VERIFY(m_strutil.is_string(regexStr)); + result = m().mk_eq(str, regexStr); + return BR_REWRITE_FULL; + } + + return BR_FAILED; +} + br_status str_rewriter::mk_app_core(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result) { SASSERT(f->get_family_id() == get_fid()); @@ -229,6 +250,12 @@ br_status str_rewriter::mk_app_core(func_decl * f, unsigned num_args, expr * con case OP_STR_REPLACE: SASSERT(num_args == 3); return mk_str_Replace(args[0], args[1], args[2], result); + case OP_RE_STR2REGEX: + SASSERT(num_args == 1); + return mk_re_Str2Reg(args[0], result); + case OP_RE_REGEXIN: + SASSERT(num_args == 2); + return mk_re_RegexIn(args[0], args[1], result); default: return BR_FAILED; } diff --git a/src/ast/rewriter/str_rewriter.h b/src/ast/rewriter/str_rewriter.h index 69a7c9579..5c0e1167f 100644 --- a/src/ast/rewriter/str_rewriter.h +++ b/src/ast/rewriter/str_rewriter.h @@ -49,6 +49,9 @@ public: br_status mk_str_LastIndexof(expr * haystack, expr * needle, expr_ref & result); br_status mk_str_Replace(expr * base, expr * source, expr * target, expr_ref & result); + br_status mk_re_Str2Reg(expr * str, expr_ref & result); + br_status mk_re_RegexIn(expr * str, expr * re, expr_ref & result); + bool reduce_eq(expr * l, expr * r, expr_ref_vector & lhs, expr_ref_vector & rhs, bool & change); bool reduce_eq(expr_ref_vector& ls, expr_ref_vector& rs, expr_ref_vector& lhs, expr_ref_vector& rhs, bool& change); diff --git a/src/ast/str_decl_plugin.cpp b/src/ast/str_decl_plugin.cpp index 526b02f64..7cd03fa16 100644 --- a/src/ast/str_decl_plugin.cpp +++ b/src/ast/str_decl_plugin.cpp @@ -24,6 +24,7 @@ Revision History: str_decl_plugin::str_decl_plugin(): m_strv_sym("String"), m_str_decl(0), + m_regex_decl(0), m_concat_decl(0), m_length_decl(0), m_charat_decl(0), @@ -35,6 +36,8 @@ str_decl_plugin::str_decl_plugin(): m_lastindexof_decl(0), m_substr_decl(0), m_replace_decl(0), + m_re_str2regex_decl(0), + m_re_regexin_decl(0), m_arith_plugin(0), m_arith_fid(0), m_int_sort(0){ @@ -46,6 +49,7 @@ str_decl_plugin::~str_decl_plugin(){ void str_decl_plugin::finalize(void) { #define DEC_REF(decl) if (decl) { m_manager->dec_ref(decl); } ((void) 0) DEC_REF(m_str_decl); + DEC_REF(m_regex_decl); DEC_REF(m_concat_decl); DEC_REF(m_length_decl); DEC_REF(m_charat_decl); @@ -57,6 +61,8 @@ void str_decl_plugin::finalize(void) { DEC_REF(m_lastindexof_decl); DEC_REF(m_substr_decl); DEC_REF(m_replace_decl); + DEC_REF(m_re_str2regex_decl); + DEC_REF(m_re_regexin_decl); DEC_REF(m_int_sort); } @@ -66,6 +72,10 @@ void str_decl_plugin::set_manager(ast_manager * m, family_id id) { m->inc_ref(m_str_decl); sort * s = m_str_decl; + m_regex_decl = m->mk_sort(symbol("Regex"), sort_info(id, REGEX_SORT)); + m->inc_ref(m_regex_decl); + sort * re = m_regex_decl; + SASSERT(m_manager->has_plugin(symbol("arith"))); m_arith_fid = m_manager->mk_family_id("arith"); m_arith_plugin = static_cast(m_manager->get_plugin(m_arith_fid)); @@ -122,6 +132,13 @@ void str_decl_plugin::set_manager(ast_manager * m, family_id id) { m_replace_decl = m->mk_func_decl(symbol("Replace"), 3, d, s, func_decl_info(id, OP_STR_REPLACE)); m_manager->inc_ref(m_replace_decl); } + + m_re_str2regex_decl = m->mk_func_decl(symbol("Str2Reg"), s, re, func_decl_info(id, OP_RE_STR2REGEX)); + m_manager->inc_ref(m_re_str2regex_decl); + + m_re_regexin_decl = m->mk_func_decl(symbol("RegexIn"), s, re, boolT, func_decl_info(id, OP_RE_REGEXIN)); + m_manager->inc_ref(m_re_regexin_decl); + } decl_plugin * str_decl_plugin::mk_fresh() { @@ -131,6 +148,7 @@ decl_plugin * str_decl_plugin::mk_fresh() { sort * str_decl_plugin::mk_sort(decl_kind k, unsigned num_parameters, parameter const * parameters) { switch (k) { case STRING_SORT: return m_str_decl; + case REGEX_SORT: return m_regex_decl; default: return 0; } } @@ -148,6 +166,8 @@ func_decl * str_decl_plugin::mk_func_decl(decl_kind k) { case OP_STR_LASTINDEXOF: return m_lastindexof_decl; case OP_STR_SUBSTR: return m_substr_decl; case OP_STR_REPLACE: return m_replace_decl; + case OP_RE_STR2REGEX: return m_re_str2regex_decl; + case OP_RE_REGEXIN: return m_re_regexin_decl; default: return 0; } } @@ -213,10 +233,13 @@ void str_decl_plugin::get_op_names(svector & op_names, symbol cons op_names.push_back(builtin_name("LastIndexof", OP_STR_LASTINDEXOF)); op_names.push_back(builtin_name("Substring", OP_STR_SUBSTR)); op_names.push_back(builtin_name("Replace", OP_STR_REPLACE)); + op_names.push_back(builtin_name("Str2Reg", OP_RE_STR2REGEX)); + op_names.push_back(builtin_name("RegexIn", OP_RE_REGEXIN)); } void str_decl_plugin::get_sort_names(svector & sort_names, symbol const & logic) { sort_names.push_back(builtin_name("String", STRING_SORT)); + sort_names.push_back(builtin_name("Regex", REGEX_SORT)); } bool str_decl_plugin::is_value(app * e) const { diff --git a/src/ast/str_decl_plugin.h b/src/ast/str_decl_plugin.h index ee2432c50..496468e5a 100644 --- a/src/ast/str_decl_plugin.h +++ b/src/ast/str_decl_plugin.h @@ -23,6 +23,7 @@ Revision History: enum str_sort_kind { STRING_SORT, + REGEX_SORT, }; enum str_op_kind { @@ -40,6 +41,9 @@ enum str_op_kind { OP_STR_LASTINDEXOF, OP_STR_SUBSTR, OP_STR_REPLACE, + // regular expression operators + OP_RE_STR2REGEX, + OP_RE_REGEXIN, // end LAST_STR_OP }; @@ -48,6 +52,7 @@ class str_decl_plugin : public decl_plugin { protected: symbol m_strv_sym; sort * m_str_decl; + sort * m_regex_decl; func_decl * m_concat_decl; func_decl * m_length_decl; @@ -62,6 +67,9 @@ protected: func_decl * m_substr_decl; func_decl * m_replace_decl; + func_decl * m_re_str2regex_decl; + func_decl * m_re_regexin_decl; + arith_decl_plugin * m_arith_plugin; family_id m_arith_fid; sort * m_int_sort; @@ -103,6 +111,8 @@ public: bool is_string(expr const * n, const char ** val) const; bool is_string(expr const * n) const; + bool is_re_Str2Reg(expr const * n) const { return is_app_of(n, get_fid(), OP_RE_STR2REGEX); } + std::string get_string_constant_value(expr const *n) const; // TODO }; From 04803d7a3b5ec9e1eef2945648271880b73983e7 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Thu, 23 Jun 2016 15:24:35 -0400 Subject: [PATCH 129/410] starting regex support --- src/ast/str_decl_plugin.cpp | 21 +++++++ src/ast/str_decl_plugin.h | 6 ++ src/smt/theory_str.cpp | 117 ++++++++++++++++++++++++++++++++++-- src/smt/theory_str.h | 18 ++++++ 4 files changed, 157 insertions(+), 5 deletions(-) diff --git a/src/ast/str_decl_plugin.cpp b/src/ast/str_decl_plugin.cpp index 7cd03fa16..b140e11c3 100644 --- a/src/ast/str_decl_plugin.cpp +++ b/src/ast/str_decl_plugin.cpp @@ -38,6 +38,9 @@ str_decl_plugin::str_decl_plugin(): m_replace_decl(0), m_re_str2regex_decl(0), m_re_regexin_decl(0), + m_re_regexconcat_decl(0), + m_re_regexstar_decl(0), + m_re_regexunion_decl(0), m_arith_plugin(0), m_arith_fid(0), m_int_sort(0){ @@ -63,6 +66,9 @@ void str_decl_plugin::finalize(void) { DEC_REF(m_replace_decl); DEC_REF(m_re_str2regex_decl); DEC_REF(m_re_regexin_decl); + DEC_REF(m_re_regexconcat_decl); + DEC_REF(m_re_regexstar_decl); + DEC_REF(m_re_regexunion_decl); DEC_REF(m_int_sort); } @@ -139,6 +145,15 @@ void str_decl_plugin::set_manager(ast_manager * m, family_id id) { m_re_regexin_decl = m->mk_func_decl(symbol("RegexIn"), s, re, boolT, func_decl_info(id, OP_RE_REGEXIN)); m_manager->inc_ref(m_re_regexin_decl); + m_re_regexconcat_decl = m->mk_func_decl(symbol("RegexConcat"), re, re, re, func_decl_info(id, OP_RE_REGEXCONCAT)); + m_manager->inc_ref(m_re_regexconcat_decl); + + m_re_regexstar_decl = m->mk_func_decl(symbol("RegexStar"), re, re, func_decl_info(id, OP_RE_REGEXSTAR)); + m_manager->inc_ref(m_re_regexstar_decl); + + m_re_regexunion_decl = m->mk_func_decl(symbol("RegexUnion"), re, re, re, func_decl_info(id, OP_RE_REGEXUNION)); + m_manager->inc_ref(m_re_regexunion_decl); + } decl_plugin * str_decl_plugin::mk_fresh() { @@ -168,6 +183,9 @@ func_decl * str_decl_plugin::mk_func_decl(decl_kind k) { case OP_STR_REPLACE: return m_replace_decl; case OP_RE_STR2REGEX: return m_re_str2regex_decl; case OP_RE_REGEXIN: return m_re_regexin_decl; + case OP_RE_REGEXCONCAT: return m_re_regexconcat_decl; + case OP_RE_REGEXSTAR: return m_re_regexstar_decl; + case OP_RE_REGEXUNION: return m_re_regexunion_decl; default: return 0; } } @@ -235,6 +253,9 @@ void str_decl_plugin::get_op_names(svector & op_names, symbol cons op_names.push_back(builtin_name("Replace", OP_STR_REPLACE)); op_names.push_back(builtin_name("Str2Reg", OP_RE_STR2REGEX)); op_names.push_back(builtin_name("RegexIn", OP_RE_REGEXIN)); + op_names.push_back(builtin_name("RegexConcat", OP_RE_REGEXCONCAT)); + op_names.push_back(builtin_name("RegexStar", OP_RE_REGEXSTAR)); + op_names.push_back(builtin_name("RegexUnion", OP_RE_REGEXUNION)); } void str_decl_plugin::get_sort_names(svector & sort_names, symbol const & logic) { diff --git a/src/ast/str_decl_plugin.h b/src/ast/str_decl_plugin.h index 496468e5a..ccd2915af 100644 --- a/src/ast/str_decl_plugin.h +++ b/src/ast/str_decl_plugin.h @@ -44,6 +44,9 @@ enum str_op_kind { // regular expression operators OP_RE_STR2REGEX, OP_RE_REGEXIN, + OP_RE_REGEXCONCAT, + OP_RE_REGEXSTAR, + OP_RE_REGEXUNION, // end LAST_STR_OP }; @@ -69,6 +72,9 @@ protected: func_decl * m_re_str2regex_decl; func_decl * m_re_regexin_decl; + func_decl * m_re_regexconcat_decl; + func_decl * m_re_regexstar_decl; + func_decl * m_re_regexunion_decl; arith_decl_plugin * m_arith_plugin; family_id m_arith_fid; diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index e8df17c58..ab7bb13ef 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -479,6 +479,29 @@ app * theory_str::mk_str_var(std::string name) { return a; } +app * theory_str::mk_regex_rep_var() { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + sort * string_sort = m.mk_sort(get_family_id(), STRING_SORT); + app * a = m.mk_fresh_const("regex", string_sort); + + ctx.internalize(a, false); + SASSERT(ctx.get_enode(a) != NULL); + SASSERT(ctx.e_internalized(a)); + mk_var(ctx.get_enode(a)); + m_basicstr_axiom_todo.push_back(ctx.get_enode(a)); + + m_trail.push_back(a); + // TODO cross-check which variable sets we need + variable_set.insert(a); + //internal_variable_set.insert(a); + regex_variable_set.insert(a); + track_variable_scope(a); + + return a; +} + app * theory_str::mk_nonempty_str_var() { context & ctx = get_context(); ast_manager & m = get_manager(); @@ -613,6 +636,7 @@ bool theory_str::can_propagate() { || !m_axiom_CharAt_todo.empty() || !m_axiom_StartsWith_todo.empty() || !m_axiom_EndsWith_todo.empty() || !m_axiom_Contains_todo.empty() || !m_axiom_Indexof_todo.empty() || !m_axiom_Indexof2_todo.empty() || !m_axiom_LastIndexof_todo.empty() || !m_axiom_Substr_todo.empty() || !m_axiom_Replace_todo.empty() + || !m_axiom_RegexIn_todo.empty() ; } @@ -681,6 +705,11 @@ void theory_str::propagate() { instantiate_axiom_Replace(m_axiom_Replace_todo[i]); } m_axiom_Replace_todo.reset(); + + for (unsigned i = 0; i < m_axiom_RegexIn_todo.size(); ++i) { + instantiate_axiom_RegexIn(m_axiom_RegexIn_todo[i]); + } + m_axiom_RegexIn_todo.reset(); } } @@ -1247,6 +1276,84 @@ void theory_str::instantiate_axiom_Replace(enode * e) { assert_axiom(finalAxiom); } +expr * theory_str::mk_RegexIn(expr * str, expr * regexp) { + expr * args[2] = {str, regexp}; + app * regexIn = get_manager().mk_app(get_id(), OP_RE_REGEXIN, 0, 0, 2, args); + // immediately force internalization so that axiom setup does not fail + get_context().internalize(regexIn, false); + set_up_axioms(regexIn); + return regexIn; +} + +void theory_str::instantiate_axiom_RegexIn(enode * e) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + app * expr = e->get_owner(); + if (axiomatized_terms.contains(expr)) { + TRACE("t_str_detail", tout << "already set up RegexIn axiom for " << mk_pp(expr, m) << std::endl;); + return; + } + axiomatized_terms.insert(expr); + + TRACE("t_str_detail", tout << "instantiate RegexIn axiom for " << mk_pp(expr, m) << std::endl;); + + // I don't think we need to port regexInBoolMap and regexInVarStrMap, + // but they would go here from reduce_regexIn + + expr_ref str(expr->get_arg(0), m); + app * regex = to_app(expr->get_arg(1)); + + if (is_Str2Reg(regex)) { + expr_ref rxStr(regex->get_arg(0), m); + // want to assert 'expr IFF (str == rxStr)' + expr_ref rhs(ctx.mk_eq_atom(str, rxStr), m); + expr_ref finalAxiom(m.mk_iff(expr, rhs), m); + SASSERT(finalAxiom); + assert_axiom(finalAxiom); + } else if (is_RegexConcat(regex)) { + expr_ref var1(mk_regex_rep_var(), m); + expr_ref var2(mk_regex_rep_var(), m); + expr_ref rhs(mk_concat(var1, var2), m); + expr_ref rx1(regex->get_arg(0), m); + expr_ref rx2(regex->get_arg(1), m); + expr_ref var1InRegex1(mk_RegexIn(var1, rx1), m); + expr_ref var2InRegex2(mk_RegexIn(var2, rx2), m); + + expr_ref_vector items(m); + items.push_back(var1InRegex1); + items.push_back(var2InRegex2); + items.push_back(ctx.mk_eq_atom(expr, ctx.mk_eq_atom(str, rhs))); + + expr_ref finalAxiom(mk_and(items), m); + SASSERT(finalAxiom); + assert_axiom(finalAxiom); + /* + Z3_ast var1 = mk_regexRepVar(t); + Z3_ast var2 = mk_regexRepVar(t); + rhs = mk_concat(t, var1, var2); + + Z3_ast regex1 = Z3_get_app_arg(ctx, arg1_func_app, 0); + Z3_ast regex2 = Z3_get_app_arg(ctx, arg1_func_app, 1); + Z3_ast var1InRegex1 = mk_2_arg_app(ctx, td->RegexIn, var1, regex1); + Z3_ast var2InRegex2 = mk_2_arg_app(ctx, td->RegexIn, var2, regex2); + std::vector items; + items.push_back(var1InRegex1); + items.push_back(var2InRegex2); + items.push_back(Z3_mk_eq(ctx, resBoolVar, Z3_mk_eq(ctx, args[0], rhs))); + extraAssert = mk_and_fromVector(t, items); + return resBoolVar; + */ + } else if (is_RegexUnion(regex)) { + + } else if (is_RegexStar(regex)) { + + } else { + TRACE("t_str_detail", tout << "ERROR: unknown regex expression " << mk_pp(regex, m) << "!" << std::endl;); + NOT_IMPLEMENTED_YET(); + } +} + void theory_str::attach_new_th_var(enode * n) { context & ctx = get_context(); theory_var v = mk_var(n); @@ -4165,6 +4272,8 @@ void theory_str::set_up_axioms(expr * ex) { m_axiom_EndsWith_todo.push_back(n); } else if (is_Contains(ap)) { m_axiom_Contains_todo.push_back(n); + } else if (is_RegexIn(ap)) { + m_axiom_RegexIn_todo.push_back(n); } } } else { @@ -4319,6 +4428,7 @@ void theory_str::pop_scope_eh(unsigned num_scopes) { for (std::set::iterator var_it = vars.begin(); var_it != vars.end(); ++var_it) { variable_set.erase(*var_it); internal_variable_set.erase(*var_it); + regex_variable_set.erase(*var_it); count += 1; } TRACE("t_str_detail", tout << "cleaned up " << count << " variables" << std::endl;); @@ -5994,13 +6104,10 @@ void theory_str::process_free_var(std::map & freeVar_map) { for (std::map::iterator fvIt = freeVar_map.begin(); fvIt != freeVar_map.end(); fvIt++) { expr * freeVar = fvIt->first; - /* - std::string vName = std::string(Z3_ast_to_string(ctx, freeVar)); - if (vName.length() >= 9 && vName.substr(0, 9) == "$$_regVar") { + // skip all regular expression vars + if (regex_variable_set.find(freeVar) != regex_variable_set.end()) { continue; } - */ - // TODO skip all regular expression vars // Iterate the EQC of freeVar, its eqc variable should not be in the eqcRepSet. // If found, have to filter it out diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 151dbc53f..9aead1105 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -117,6 +117,7 @@ namespace smt { ptr_vector m_axiom_LastIndexof_todo; ptr_vector m_axiom_Substr_todo; ptr_vector m_axiom_Replace_todo; + ptr_vector m_axiom_RegexIn_todo; // hashtable of all exprs for which we've already set up term-specific axioms -- // this prevents infinite recursive descent with respect to axioms that @@ -135,6 +136,7 @@ namespace smt { std::set variable_set; std::set internal_variable_set; + std::set regex_variable_set; std::map > internal_variable_scope_levels; obj_hashtable internal_lenTest_vars; @@ -180,6 +182,7 @@ namespace smt { app * mk_nonempty_str_var(); app * mk_internal_xor_var(); expr * mk_internal_valTest_var(expr * node, int len, int vTries); + app * mk_regex_rep_var(); bool is_concat(app const * a) const { return a->is_app_of(get_id(), OP_STRCAT); } bool is_concat(enode const * n) const { return is_concat(n->get_owner()); } @@ -206,6 +209,18 @@ namespace smt { bool is_Replace(app const * a) const { return a->is_app_of(get_id(), OP_STR_REPLACE); } bool is_Replace(enode const * n) const { return is_Replace(n->get_owner()); } + bool is_RegexIn(app const * a) const { return a->is_app_of(get_id(), OP_RE_REGEXIN); } + bool is_RegexIn(enode const * n) const { return is_RegexIn(n->get_owner()); } + bool is_RegexConcat(app const * a) const { return a->is_app_of(get_id(), OP_RE_REGEXCONCAT); } + bool is_RegexConcat(enode const * n) const { return is_RegexConcat(n->get_owner()); } + bool is_RegexStar(app const * a) const { return a->is_app_of(get_id(), OP_RE_REGEXSTAR); } + bool is_RegexStar(enode const * n) const { return is_RegexStar(n->get_owner()); } + bool is_RegexUnion(app const * a) const { return a->is_app_of(get_id(), OP_RE_REGEXUNION); } + bool is_RegexUnion(enode const * n) const { return is_RegexUnion(n->get_owner()); } + bool is_Str2Reg(app const * a) const { return a->is_app_of(get_id(), OP_RE_STR2REGEX); } + bool is_Str2Reg(enode const * n) const { return is_Str2Reg(n->get_owner()); } + + void instantiate_concat_axiom(enode * cat); void instantiate_basic_string_axioms(enode * str); void instantiate_str_eq_length_axiom(enode * lhs, enode * rhs); @@ -220,6 +235,9 @@ namespace smt { void instantiate_axiom_Substr(enode * e); void instantiate_axiom_Replace(enode * e); + expr * mk_RegexIn(expr * str, expr * regexp); + void instantiate_axiom_RegexIn(enode * e); + void set_up_axioms(expr * ex); void handle_equality(expr * lhs, expr * rhs); From 020e8aef6df7cbec0a19175a99385111289bbb4d Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Thu, 23 Jun 2016 17:14:03 -0400 Subject: [PATCH 130/410] regex union --- src/smt/theory_str.cpp | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index ab7bb13ef..46248abd2 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -1328,26 +1328,21 @@ void theory_str::instantiate_axiom_RegexIn(enode * e) { expr_ref finalAxiom(mk_and(items), m); SASSERT(finalAxiom); assert_axiom(finalAxiom); - /* - Z3_ast var1 = mk_regexRepVar(t); - Z3_ast var2 = mk_regexRepVar(t); - rhs = mk_concat(t, var1, var2); - - Z3_ast regex1 = Z3_get_app_arg(ctx, arg1_func_app, 0); - Z3_ast regex2 = Z3_get_app_arg(ctx, arg1_func_app, 1); - Z3_ast var1InRegex1 = mk_2_arg_app(ctx, td->RegexIn, var1, regex1); - Z3_ast var2InRegex2 = mk_2_arg_app(ctx, td->RegexIn, var2, regex2); - std::vector items; + } else if (is_RegexUnion(regex)) { + expr_ref var1(mk_regex_rep_var(), m); + expr_ref var2(mk_regex_rep_var(), m); + expr_ref orVar(m.mk_or(ctx.mk_eq_atom(str, var1), ctx.mk_eq_atom(str, var2)), m); + expr_ref regex1(regex->get_arg(0), m); + expr_ref regex2(regex->get_arg(1), m); + expr_ref var1InRegex1(mk_RegexIn(var1, regex1), m); + expr_ref var2InRegex2(mk_RegexIn(var2, regex2), m); + expr_ref_vector items(m); items.push_back(var1InRegex1); items.push_back(var2InRegex2); - items.push_back(Z3_mk_eq(ctx, resBoolVar, Z3_mk_eq(ctx, args[0], rhs))); - extraAssert = mk_and_fromVector(t, items); - return resBoolVar; - */ - } else if (is_RegexUnion(regex)) { - + items.push_back(ctx.mk_eq_atom(expr, orVar)); + assert_axiom(mk_and(items)); } else if (is_RegexStar(regex)) { - + NOT_IMPLEMENTED_YET(); } else { TRACE("t_str_detail", tout << "ERROR: unknown regex expression " << mk_pp(regex, m) << "!" << std::endl;); NOT_IMPLEMENTED_YET(); From b31d1a92aa1627c45cb8d3708db292dfce3333a1 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Mon, 27 Jun 2016 14:41:57 -0400 Subject: [PATCH 131/410] add more support for unroll (WIP) --- src/ast/str_decl_plugin.cpp | 7 ++ src/ast/str_decl_plugin.h | 2 + src/smt/theory_str.cpp | 150 +++++++++++++++++++++++++++++++++++- src/smt/theory_str.h | 9 ++- 4 files changed, 165 insertions(+), 3 deletions(-) diff --git a/src/ast/str_decl_plugin.cpp b/src/ast/str_decl_plugin.cpp index b140e11c3..ef94272c7 100644 --- a/src/ast/str_decl_plugin.cpp +++ b/src/ast/str_decl_plugin.cpp @@ -41,6 +41,7 @@ str_decl_plugin::str_decl_plugin(): m_re_regexconcat_decl(0), m_re_regexstar_decl(0), m_re_regexunion_decl(0), + m_re_unroll_decl(0), m_arith_plugin(0), m_arith_fid(0), m_int_sort(0){ @@ -69,6 +70,7 @@ void str_decl_plugin::finalize(void) { DEC_REF(m_re_regexconcat_decl); DEC_REF(m_re_regexstar_decl); DEC_REF(m_re_regexunion_decl); + DEC_REF(m_re_unroll_decl); DEC_REF(m_int_sort); } @@ -154,6 +156,9 @@ void str_decl_plugin::set_manager(ast_manager * m, family_id id) { m_re_regexunion_decl = m->mk_func_decl(symbol("RegexUnion"), re, re, re, func_decl_info(id, OP_RE_REGEXUNION)); m_manager->inc_ref(m_re_regexunion_decl); + m_re_unroll_decl = m->mk_func_decl(symbol("Unroll"), re, i, s, func_decl_info(id, OP_RE_UNROLL)); + m_manager->inc_ref(m_re_unroll_decl); + } decl_plugin * str_decl_plugin::mk_fresh() { @@ -186,6 +191,7 @@ func_decl * str_decl_plugin::mk_func_decl(decl_kind k) { case OP_RE_REGEXCONCAT: return m_re_regexconcat_decl; case OP_RE_REGEXSTAR: return m_re_regexstar_decl; case OP_RE_REGEXUNION: return m_re_regexunion_decl; + case OP_RE_UNROLL: return m_re_unroll_decl; default: return 0; } } @@ -256,6 +262,7 @@ void str_decl_plugin::get_op_names(svector & op_names, symbol cons op_names.push_back(builtin_name("RegexConcat", OP_RE_REGEXCONCAT)); op_names.push_back(builtin_name("RegexStar", OP_RE_REGEXSTAR)); op_names.push_back(builtin_name("RegexUnion", OP_RE_REGEXUNION)); + op_names.push_back(builtin_name("Unroll", OP_RE_UNROLL)); } void str_decl_plugin::get_sort_names(svector & sort_names, symbol const & logic) { diff --git a/src/ast/str_decl_plugin.h b/src/ast/str_decl_plugin.h index ccd2915af..c2ad088a4 100644 --- a/src/ast/str_decl_plugin.h +++ b/src/ast/str_decl_plugin.h @@ -47,6 +47,7 @@ enum str_op_kind { OP_RE_REGEXCONCAT, OP_RE_REGEXSTAR, OP_RE_REGEXUNION, + OP_RE_UNROLL, // end LAST_STR_OP }; @@ -75,6 +76,7 @@ protected: func_decl * m_re_regexconcat_decl; func_decl * m_re_regexstar_decl; func_decl * m_re_regexunion_decl; + func_decl * m_re_unroll_decl; arith_decl_plugin * m_arith_plugin; family_id m_arith_fid; diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 46248abd2..947c35f98 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -454,6 +454,10 @@ app * theory_str::mk_int_var(std::string name) { return a; } +app * theory_str::mk_unroll_bound_var() { + return mk_int_var("unroll"); +} + app * theory_str::mk_str_var(std::string name) { context & ctx = get_context(); ast_manager & m = get_manager(); @@ -545,6 +549,24 @@ app * theory_str::mk_nonempty_str_var() { return a; } +app * theory_str::mk_unroll(expr * n, expr * bound) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + expr * args[2] = {n, bound}; + app * unrollFunc = get_manager().mk_app(get_id(), OP_RE_UNROLL, 0, 0, 2, args); + + expr_ref_vector items(m); + items.push_back(ctx.mk_eq_atom(ctx.mk_eq_atom(bound, mk_int(0)), ctx.mk_eq_atom(unrollFunc, m_strutil.mk_string("")))); + items.push_back(m_autil.mk_ge(bound, mk_int(0))); + items.push_back(m_autil.mk_ge(mk_strlen(unrollFunc), mk_int(0))); + + expr_ref finalAxiom(mk_and(items), m); + SASSERT(finalAxiom); + assert_axiom(finalAxiom); + return unrollFunc; +} + app * theory_str::mk_contains(expr * haystack, expr * needle) { expr * args[2] = {haystack, needle}; app * contains = get_manager().mk_app(get_id(), OP_STR_CONTAINS, 0, 0, 2, args); @@ -1342,7 +1364,16 @@ void theory_str::instantiate_axiom_RegexIn(enode * e) { items.push_back(ctx.mk_eq_atom(expr, orVar)); assert_axiom(mk_and(items)); } else if (is_RegexStar(regex)) { - NOT_IMPLEMENTED_YET(); + // slightly more complex due to the unrolling step. + expr_ref regex1(regex->get_arg(0), m); + expr_ref unrollCount(mk_unroll_bound_var(), m); + expr_ref unrollFunc(mk_unroll(regex1, unrollCount), m); + expr_ref_vector items(m); + items.push_back(ctx.mk_eq_atom(expr, ctx.mk_eq_atom(str, unrollFunc))); + items.push_back(ctx.mk_eq_atom(ctx.mk_eq_atom(unrollCount, mk_int(0)), ctx.mk_eq_atom(unrollFunc, m_strutil.mk_string("")))); + expr_ref finalAxiom(mk_and(items), m); + SASSERT(finalAxiom); + assert_axiom(finalAxiom); } else { TRACE("t_str_detail", tout << "ERROR: unknown regex expression " << mk_pp(regex, m) << "!" << std::endl;); NOT_IMPLEMENTED_YET(); @@ -3368,6 +3399,63 @@ void theory_str::process_concat_eq_type6(expr * concatAst1, expr * concatAst2) { assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); } +void theory_str::process_unroll_eq_const_str(expr * unrollFunc, expr * constStr) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + if (!is_Unroll(to_app(unrollFunc))) { + return; + } + if (!m_strutil.is_string(constStr)) { + return; + } + + expr * funcInUnroll = to_app(unrollFunc)->get_arg(0); + std::string strValue = m_strutil.get_string_constant_value(constStr); + + TRACE("t_str_detail", tout << "unrollFunc: " << mk_pp(unrollFunc, m) << std::endl + << "constStr: " << mk_pp(constStr, m) << std::endl;); + + if (strValue == "") { + return; + } + + if (is_Str2Reg(to_app(funcInUnroll))) { + unroll_str2reg_constStr(unrollFunc, constStr); + return; + } +} + +void theory_str::unroll_str2reg_constStr(expr * unrollFunc, expr * eqConstStr) { + context & ctx = get_context(); + expr * str2RegFunc = to_app(unrollFunc)->get_arg(0); + expr * strInStr2RegFunc = to_app(str2RegFunc)->get_arg(0); + expr * oriCnt = to_app(unrollFunc)->get_arg(1); + + // TODO NEXT + NOT_IMPLEMENTED_YET(); + + /* + Z3_context ctx = Z3_theory_get_context(t); + Z3_ast str2RegFunc = Z3_get_app_arg(ctx, Z3_to_app(ctx, unrollFunc), 0); + Z3_ast strInStr2RegFunc = Z3_get_app_arg(ctx, Z3_to_app(ctx, str2RegFunc), 0); + Z3_ast oriCnt = Z3_get_app_arg(ctx, Z3_to_app(ctx, unrollFunc), 1); + + std::string strValue = getConstStrValue(t, eqConstStr); + std::string regStrValue = getConstStrValue(t, strInStr2RegFunc); + int strLen = strValue.length(); + int regStrLen = regStrValue.length(); + int cnt = strLen / regStrLen; + + Z3_ast implyL = Z3_mk_eq(ctx, unrollFunc, eqConstStr); + Z3_ast implyR1 = Z3_mk_eq(ctx, oriCnt, mk_int(ctx, cnt)); + Z3_ast implyR2 = Z3_mk_eq(ctx, mk_length(t, unrollFunc), mk_int(ctx, strLen)); + Z3_ast toAssert = Z3_mk_implies(ctx, implyL, mk_2_and(t, implyR1, implyR2)); + + addAxiom(t, toAssert, __LINE__); + */ +} + /* * Look through the equivalence class of n to find a string constant. * Return that constant if it is found, and set hasEqcValue to true. @@ -3392,6 +3480,26 @@ expr * theory_str::get_eqc_value(expr * n, bool & hasEqcValue) { return n; } +void theory_str::get_eqc_all_unroll(expr * n, expr * & constStr, std::set & unrollFuncSet) { + context & ctx = get_context(); + + constStr = NULL; + unrollFuncSet.clear(); + + // iterate over the eqc of 'n' + enode * n_enode = ctx.get_enode(n); + enode * e_curr = n_enode; + do { + app * curr = e_curr->get_owner(); + if (m_strutil.is_string(curr)) { + constStr = curr; + } else if (is_Unroll(curr)) { + unrollFuncSet.insert(curr); + } + e_curr = e_curr->get_next(); + } while (e_curr != n_enode); +} + // from Z3: theory_seq.cpp static theory_mi_arith* get_th_arith(context& ctx, theory_id afid, expr* e) { @@ -4198,7 +4306,45 @@ void theory_str::handle_equality(expr * lhs, expr * rhs) { simplify_parent(lhs, rhs_value); } - // TODO regex unroll? (much later) + // regex unroll + /* + Z3_ast nn1EqConst = NULL; + std::set nn1EqUnrollFuncs; + get_eqc_allUnroll(t, nn1, nn1EqConst, nn1EqUnrollFuncs); + Z3_ast nn2EqConst = NULL; + std::set nn2EqUnrollFuncs; + get_eqc_allUnroll(t, nn2, nn2EqConst, nn2EqUnrollFuncs); + + if (nn2EqConst != NULL) { + for (std::set::iterator itor1 = nn1EqUnrollFuncs.begin(); itor1 != nn1EqUnrollFuncs.end(); itor1++) { + processUnrollEqConstStr(t, *itor1, nn2EqConst); + } + } + + if (nn1EqConst != NULL) { + for (std::set::iterator itor2 = nn2EqUnrollFuncs.begin(); itor2 != nn2EqUnrollFuncs.end(); itor2++) { + processUnrollEqConstStr(t, *itor2, nn1EqConst); + } + } + */ + expr * nn1EqConst = NULL; + std::set nn1EqUnrollFuncs; + get_eqc_all_unroll(lhs, nn1EqConst, nn1EqUnrollFuncs); + expr * nn2EqConst = NULL; + std::set nn2EqUnrollFuncs; + get_eqc_all_unroll(rhs, nn2EqConst, nn2EqUnrollFuncs); + + if (nn2EqConst != NULL) { + for (std::set::iterator itor1 = nn1EqUnrollFuncs.begin(); itor1 != nn1EqUnrollFuncs.end(); itor1++) { + process_unroll_eq_const_str(*itor1, nn2EqConst); + } + } + + if (nn1EqConst != NULL) { + for (std::set::iterator itor2 = nn2EqUnrollFuncs.begin(); itor2 != nn2EqUnrollFuncs.end(); itor2++) { + process_unroll_eq_const_str(*itor2, nn1EqConst); + } + } } void theory_str::set_up_axioms(expr * ex) { diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 9aead1105..5bf30a266 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -183,6 +183,7 @@ namespace smt { app * mk_internal_xor_var(); expr * mk_internal_valTest_var(expr * node, int len, int vTries); app * mk_regex_rep_var(); + app * mk_unroll_bound_var(); bool is_concat(app const * a) const { return a->is_app_of(get_id(), OP_STRCAT); } bool is_concat(enode const * n) const { return is_concat(n->get_owner()); } @@ -219,7 +220,8 @@ namespace smt { bool is_RegexUnion(enode const * n) const { return is_RegexUnion(n->get_owner()); } bool is_Str2Reg(app const * a) const { return a->is_app_of(get_id(), OP_RE_STR2REGEX); } bool is_Str2Reg(enode const * n) const { return is_Str2Reg(n->get_owner()); } - + bool is_Unroll(app const * a) const { return a->is_app_of(get_id(), OP_RE_UNROLL); } + bool is_Unroll(enode const * n) const { return is_Unroll(n->get_owner()); } void instantiate_concat_axiom(enode * cat); void instantiate_basic_string_axioms(enode * str); @@ -237,6 +239,11 @@ namespace smt { expr * mk_RegexIn(expr * str, expr * regexp); void instantiate_axiom_RegexIn(enode * e); + app * mk_unroll(expr * n, expr * bound); + + void get_eqc_all_unroll(expr * n, expr * & constStr, std::set & unrollFuncSet); + void process_unroll_eq_const_str(expr * unrollFunc, expr * constStr); + void unroll_str2reg_constStr(expr * unrollFunc, expr * eqConstStr); void set_up_axioms(expr * ex); void handle_equality(expr * lhs, expr * rhs); From 03827cb487bc1e083c628c0d9997f4352b93edb5 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Thu, 30 Jun 2016 01:21:21 -0400 Subject: [PATCH 132/410] add more Unroll support to final_check, ctx_dep_analysis --- src/smt/theory_str.cpp | 256 +++++++++++++++++++++++++---------------- src/smt/theory_str.h | 5 +- 2 files changed, 163 insertions(+), 98 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 947c35f98..83ce88b36 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -3426,34 +3426,104 @@ void theory_str::process_unroll_eq_const_str(expr * unrollFunc, expr * constStr) } } +void theory_str::process_concat_eq_unroll(expr * concat, expr * unroll) { + // TODO NEXT + NOT_IMPLEMENTED_YET(); + /* +#ifdef DEBUGLOG + __debugPrint(logFile, ">> processConcatEqUnroll: \n"); + __debugPrint(logFile, " * [concat] "); + printZ3Node(t, concat); + __debugPrint(logFile, "\n"); + __debugPrint(logFile, " * [unroll] "); + printZ3Node(t, unroll); + __debugPrint(logFile, "\n\n"); +#endif + + Z3_context ctx = Z3_theory_get_context(t); + std::pair key = std::make_pair(concat, unroll); + Z3_ast toAssert = NULL; + + if (concatEqUnroll_AstMap.find(key) == concatEqUnroll_AstMap.end()) { + Z3_ast arg1 = Z3_get_app_arg(ctx, Z3_to_app(ctx, concat), 0); + Z3_ast arg2 = Z3_get_app_arg(ctx, Z3_to_app(ctx, concat), 1); + Z3_ast r1 = Z3_get_app_arg(ctx, Z3_to_app(ctx, unroll), 0); + Z3_ast t1 = Z3_get_app_arg(ctx, Z3_to_app(ctx, unroll), 1); + + Z3_ast v1 = mk_regexRepVar(t); + Z3_ast v2 = mk_regexRepVar(t); + Z3_ast v3 = mk_regexRepVar(t); + Z3_ast v4 = mk_regexRepVar(t); + Z3_ast v5 = mk_regexRepVar(t); + + Z3_ast t2 = mk_unrollBoundVar(t); + Z3_ast t3 = mk_unrollBoundVar(t); + Z3_ast emptyStr = my_mk_str_value(t, ""); + + Z3_ast unroll1 = mk_unroll(t, r1, t2); + Z3_ast unroll2 = mk_unroll(t, r1, t3); + + Z3_ast op0 = Z3_mk_eq(ctx, t1, mk_int(ctx, 0)); + Z3_ast op1 = Z3_mk_ge(ctx, t1, mk_int(ctx, 1)); + + std::vector op1Items; + std::vector op2Items; + + op1Items.push_back(Z3_mk_eq(ctx, arg1, emptyStr)); + op1Items.push_back(Z3_mk_eq(ctx, arg2, emptyStr)); + op1Items.push_back(Z3_mk_eq(ctx, mk_length(t, arg1), mk_int(ctx, 0))); + op1Items.push_back(Z3_mk_eq(ctx, mk_length(t, arg2), mk_int(ctx, 0))); + Z3_ast opAnd1 = Z3_mk_eq(ctx, op0, mk_and_fromVector(t, op1Items)); + + Z3_ast v1v2 = mk_concat(t, v1, v2); + op2Items.push_back(Z3_mk_eq(ctx, arg1, v1v2)); + op2Items.push_back(Z3_mk_eq(ctx, mk_length(t, arg1), mk_2_add(t, mk_length(t, v1), mk_length(t, v2)))); + Z3_ast v3v4 = mk_concat(t, v3, v4); + op2Items.push_back(Z3_mk_eq(ctx, arg2, v3v4)); + op2Items.push_back(Z3_mk_eq(ctx, mk_length(t, arg2), mk_2_add(t, mk_length(t, v3), mk_length(t, v4)))); + + op2Items.push_back(Z3_mk_eq(ctx, v1, unroll1)); + op2Items.push_back(Z3_mk_eq(ctx, mk_length(t, v1), mk_length(t, unroll1))); + op2Items.push_back(Z3_mk_eq(ctx, v4, unroll2)); + op2Items.push_back(Z3_mk_eq(ctx, mk_length(t, v4), mk_length(t, unroll2))); + Z3_ast v2v3 = mk_concat(t, v2, v3); + op2Items.push_back(Z3_mk_eq(ctx, v5, v2v3)); + reduceVirtualRegexIn(t, v5, r1, op2Items); + op2Items.push_back(Z3_mk_eq(ctx, mk_length(t, v5), mk_2_add(t, mk_length(t, v2), mk_length(t, v3)))); + op2Items.push_back(Z3_mk_eq(ctx, mk_2_add(t, t2, t3), mk_2_sub(t, t1, mk_int(ctx, 1)))); + Z3_ast opAnd2 = Z3_mk_eq(ctx, op1, mk_and_fromVector(t, op2Items)); + + toAssert = mk_2_and(t, opAnd1, opAnd2); + concatEqUnroll_AstMap[key] = toAssert; + } else { + toAssert = concatEqUnroll_AstMap[key]; + } + + addAxiom(t, toAssert, __LINE__); + */ +} + void theory_str::unroll_str2reg_constStr(expr * unrollFunc, expr * eqConstStr) { context & ctx = get_context(); + ast_manager & m = get_manager(); + expr * str2RegFunc = to_app(unrollFunc)->get_arg(0); expr * strInStr2RegFunc = to_app(str2RegFunc)->get_arg(0); expr * oriCnt = to_app(unrollFunc)->get_arg(1); - // TODO NEXT - NOT_IMPLEMENTED_YET(); - - /* - Z3_context ctx = Z3_theory_get_context(t); - Z3_ast str2RegFunc = Z3_get_app_arg(ctx, Z3_to_app(ctx, unrollFunc), 0); - Z3_ast strInStr2RegFunc = Z3_get_app_arg(ctx, Z3_to_app(ctx, str2RegFunc), 0); - Z3_ast oriCnt = Z3_get_app_arg(ctx, Z3_to_app(ctx, unrollFunc), 1); - - std::string strValue = getConstStrValue(t, eqConstStr); - std::string regStrValue = getConstStrValue(t, strInStr2RegFunc); + std::string strValue = m_strutil.get_string_constant_value(eqConstStr); + std::string regStrValue = m_strutil.get_string_constant_value(strInStr2RegFunc); int strLen = strValue.length(); int regStrLen = regStrValue.length(); - int cnt = strLen / regStrLen; + int cnt = strLen / regStrLen; // TODO prevent DIV/0 on regStrLen - Z3_ast implyL = Z3_mk_eq(ctx, unrollFunc, eqConstStr); - Z3_ast implyR1 = Z3_mk_eq(ctx, oriCnt, mk_int(ctx, cnt)); - Z3_ast implyR2 = Z3_mk_eq(ctx, mk_length(t, unrollFunc), mk_int(ctx, strLen)); - Z3_ast toAssert = Z3_mk_implies(ctx, implyL, mk_2_and(t, implyR1, implyR2)); - - addAxiom(t, toAssert, __LINE__); - */ + expr_ref implyL(ctx.mk_eq_atom(unrollFunc, eqConstStr), m); + expr_ref implyR1(ctx.mk_eq_atom(oriCnt, mk_int(cnt)), m); + expr_ref implyR2(ctx.mk_eq_atom(mk_strlen(unrollFunc), mk_int(strLen)), m); + expr_ref axiomRHS(m.mk_and(implyR1, implyR2), m); + SASSERT(implyL); + SASSERT(axiomRHS); + assert_implication(implyL, axiomRHS); } /* @@ -4628,7 +4698,7 @@ void theory_str::classify_ast_by_type(expr * node, std::map & varMap if (canskip == 0 && concatMap.find(node) == concatMap.end()) { concatMap[node] = 1; } - } else if (false) { // TODO is_unroll() + } else if (is_Unroll(aNode)) { // Unroll if (unrollMap.find(node) == unrollMap.end()) { unrollMap[node] = 1; @@ -4696,9 +4766,12 @@ void theory_str::trace_ctx_dep(std::ofstream & tout, std::map & aliasIndexMap, std::map & var_eq_constStr_map, std::map > & var_eq_concat_map, + std::map > & var_eq_unroll_map, std::map & concat_eq_constStr_map, - std::map > & concat_eq_concat_map) { + std::map > & concat_eq_concat_map, + std::map > & unrollGroupMap) { #ifdef _TRACE + context & ctx = get_context(); ast_manager & mgr = get_manager(); { tout << "(0) alias: variables" << std::endl; @@ -4754,24 +4827,21 @@ void theory_str::trace_ctx_dep(std::ofstream & tout, } tout << std::endl; } -/*// TODO + { - __debugPrint(logFile, "(3) var = unrollFunc:\n"); - std::map >::iterator itor2 = var_eq_unroll_map.begin(); + tout << "(3) var = unrollFunc:" << std::endl; + std::map >::iterator itor2 = var_eq_unroll_map.begin(); for (; itor2 != var_eq_unroll_map.end(); itor2++) { - __debugPrint(logFile, " * "); - printZ3Node(t, itor2->first); - __debugPrint(logFile, " = { "); - std::map::iterator i_itor = itor2->second.begin(); + tout << " * " << mk_pp(itor2->first, mgr) << " = { "; + std::map::iterator i_itor = itor2->second.begin(); for (; i_itor != itor2->second.end(); i_itor++) { - printZ3Node(t, i_itor->first); - __debugPrint(logFile, ", "); + tout << mk_pp(i_itor->first, mgr) << ", "; } - __debugPrint(logFile, " }\n"); + tout << " }" << std::endl; } - __debugPrint(logFile, "\n"); + tout << std::endl; } -*/ + { tout << "(4) concat = constStr:" << std::endl; std::map::iterator itor3 = concat_eq_constStr_map.begin(); @@ -4802,44 +4872,41 @@ void theory_str::trace_ctx_dep(std::ofstream & tout, } tout << std::endl; } -/*// TODO + { - __debugPrint(logFile, "(6) eq unrolls:\n"); - std::map >::iterator itor5 = unrollGroupMap.begin(); + tout << "(6) eq unrolls:" << std::endl; + std::map >::iterator itor5 = unrollGroupMap.begin(); for (; itor5 != unrollGroupMap.end(); itor5++) { - __debugPrint(logFile, " * "); - std::set::iterator i_itor = itor5->second.begin(); + tout << " * "; + std::set::iterator i_itor = itor5->second.begin(); for (; i_itor != itor5->second.end(); i_itor++) { - printZ3Node(t, *i_itor); - __debugPrint(logFile, ", "); + tout << mk_pp(*i_itor, mgr) << ", "; } - __debugPrint(logFile, "\n"); + tout << std::endl; } - __debugPrint(logFile, "\n"); + tout << std::endl; } { - __debugPrint(logFile, "(7) unroll = concats:\n"); - std::map >::iterator itor5 = unrollGroupMap.begin(); + tout << "(7) unroll = concats:" << std::endl; + std::map >::iterator itor5 = unrollGroupMap.begin(); for (; itor5 != unrollGroupMap.end(); itor5++) { - __debugPrint(logFile, " * "); - Z3_ast unroll = itor5->first; - printZ3Node(t, unroll); - __debugPrint(logFile, "\n"); - Z3_ast curr = unroll; + tout << " * "; + expr * unroll = itor5->first; + tout << mk_pp(unroll, mgr) << std::endl; + enode * e_curr = ctx.get_enode(unroll); + enode * e_curr_end = e_curr; do { - if (isConcatFunc(t, curr)) { - __debugPrint(logFile, " >>> "); - printZ3Node(t, curr); - __debugPrint(logFile, "\n"); + app * curr = e_curr->get_owner(); + if (is_concat(curr)) { + tout << " >>> " << mk_pp(curr, mgr) << std::endl; } - curr = Z3_theory_get_eqc_next(t, curr); - }while (curr != unroll); - __debugPrint(logFile, "\n"); + e_curr = e_curr->get_next(); + } while (e_curr != e_curr_end); + tout << std::endl; } - __debugPrint(logFile, "\n"); + tout << std::endl; } - */ #else return; #endif // _TRACE @@ -4889,32 +4956,32 @@ int theory_str::ctx_dep_analysis(std::map & strVarMap, std::map aliasUnrollSet; - std::map::iterator unrollItor = unrollMap.begin(); - for (; unrollItor != unrollMap.end(); unrollItor++) { - if (aliasUnrollSet.find(unrollItor->first) != aliasUnrollSet.end()) - continue; - Z3_ast aRoot = NULL; - Z3_ast curr = unrollItor->first; - do { - if (isUnrollFunc(t, curr)) { - if (aRoot == NULL) { - aRoot = curr; - } - aliasUnrollSet[curr] = aRoot; - } - curr = Z3_theory_get_eqc_next(t, curr); - } while (curr != unrollItor->first); + std::map aliasUnrollSet; + std::map::iterator unrollItor = unrollMap.begin(); + for (; unrollItor != unrollMap.end(); ++unrollItor) { + if (aliasUnrollSet.find(unrollItor->first) != aliasUnrollSet.end()) { + continue; + } + expr * aRoot = NULL; + enode * e_currEqc = ctx.get_enode(unrollItor->first); + enode * e_curr = e_currEqc; + do { + app * curr = e_currEqc->get_owner(); + if (is_Unroll(curr)) { + if (aRoot == NULL) { + aRoot = curr; + } + aliasUnrollSet[curr] = aRoot; + } + e_currEqc = e_currEqc->get_next(); + } while (e_currEqc != e_curr); } for (unrollItor = unrollMap.begin(); unrollItor != unrollMap.end(); unrollItor++) { - Z3_ast unrFunc = unrollItor->first; - Z3_ast urKey = aliasUnrollSet[unrFunc]; + expr * unrFunc = unrollItor->first; + expr * urKey = aliasUnrollSet[unrFunc]; unrollGroupMap[urKey].insert(unrFunc); } - */ // Step 2: collect alias relation // e.g. suppose we have the equivalence class {x, y, z}; @@ -4999,13 +5066,9 @@ int theory_str::ctx_dep_analysis(std::map & strVarMap, std::map & strVarMap, std::map 0) { - computeContains(t, aliasIndexMap, concats_eq_Index_map, var_eq_constStr_map, concat_eq_constStr_map, var_eq_concat_map); + NOT_IMPLEMENTED_YET(); + compute_contains(aliasIndexMap, concats_eq_Index_map, var_eq_constStr_map, concat_eq_constStr_map, var_eq_concat_map); } */ @@ -5638,17 +5702,14 @@ final_check_status theory_str::final_check_eh() { ); } - // TODO process_concat_eq_unroll() - /* for (std::map >::iterator fvIt2 = concatFreeArgsEqUnrollsMap.begin(); fvIt2 != concatFreeArgsEqUnrollsMap.end(); fvIt2++) { expr * concat = fvIt2->first; for (std::set::iterator urItor = fvIt2->second.begin(); urItor != fvIt2->second.end(); urItor++) { - Z3_ast unroll = *urItor; - processConcatEqUnroll(concat, unroll); + expr * unroll = *urItor; + process_concat_eq_unroll(concat, unroll); } } - */ // -------- // experimental free variable assignment - begin @@ -5675,12 +5736,13 @@ final_check_status theory_str::final_check_eh() { } // experimental free variable assignment - end - // TODO more unroll stuff - /* + // more unroll stuff for (std::map >::iterator fvIt1 = fv_unrolls_map.begin(); fvIt1 != fv_unrolls_map.end(); fvIt1++) { - Z3_ast var = fvIt1->first; + expr * var = fvIt1->first; fSimpUnroll.clear(); + NOT_IMPLEMENTED_YET(); // TODO complete this unroll block + /* get_eqc_simpleUnroll(t, var, constValue, fSimpUnroll); if (fSimpUnroll.size() == 0) { genAssignUnrollReg(t, fv_unrolls_map[var]); @@ -5690,8 +5752,8 @@ final_check_status theory_str::final_check_eh() { addAxiom(t, toAssert, __LINE__); } } + */ } - */ if (opt_VerifyFinalCheckProgress && !finalCheckProgressIndicator) { TRACE("t_str", tout << "BUG: no progress in final check, giving up!!" << std::endl;); diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 5bf30a266..467727179 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -244,6 +244,7 @@ namespace smt { void get_eqc_all_unroll(expr * n, expr * & constStr, std::set & unrollFuncSet); void process_unroll_eq_const_str(expr * unrollFunc, expr * constStr); void unroll_str2reg_constStr(expr * unrollFunc, expr * eqConstStr); + void process_concat_eq_unroll(expr * concat, expr * unroll); void set_up_axioms(expr * ex); void handle_equality(expr * lhs, expr * rhs); @@ -296,8 +297,10 @@ namespace smt { std::map & aliasIndexMap, std::map & var_eq_constStr_map, std::map > & var_eq_concat_map, + std::map > & var_eq_unroll_map, std::map & concat_eq_constStr_map, - std::map > & concat_eq_concat_map); + std::map > & concat_eq_concat_map, + std::map > & unrollGroupMap); void classify_ast_by_type(expr * node, std::map & varMap, std::map & concatMap, std::map & unrollMap); From 21f0a50abaee4c6d365a910928c46bfe4bae8d58 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Thu, 30 Jun 2016 01:24:43 -0400 Subject: [PATCH 133/410] add Unroll check to get_eqc_allUnroll --- src/smt/theory_str.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 83ce88b36..b3621f61e 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -6406,7 +6406,7 @@ void theory_str::get_eqc_allUnroll(expr * n, expr * &constStr, std::set & do { if (is_string(to_app(curr))) { constStr = curr; - } else if (false) /*(td->Unroll == Z3_get_app_decl(ctx, Z3_to_app(ctx, curr)))*/ { // TODO + } else if (is_Unroll(to_app(curr))) { if (unrollFuncSet.find(curr) == unrollFuncSet.end()) { unrollFuncSet.insert(curr); } From 427632ede398c354abd0141a07d905626699643f Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Thu, 30 Jun 2016 01:42:00 -0400 Subject: [PATCH 134/410] let free variable assignment work a bit more towards unrolls --- src/smt/theory_str.cpp | 142 ++++++++++++++++++++++++++++++++--------- src/smt/theory_str.h | 5 +- 2 files changed, 117 insertions(+), 30 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index b3621f61e..64f3d1fc8 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -3550,26 +3550,6 @@ expr * theory_str::get_eqc_value(expr * n, bool & hasEqcValue) { return n; } -void theory_str::get_eqc_all_unroll(expr * n, expr * & constStr, std::set & unrollFuncSet) { - context & ctx = get_context(); - - constStr = NULL; - unrollFuncSet.clear(); - - // iterate over the eqc of 'n' - enode * n_enode = ctx.get_enode(n); - enode * e_curr = n_enode; - do { - app * curr = e_curr->get_owner(); - if (m_strutil.is_string(curr)) { - constStr = curr; - } else if (is_Unroll(curr)) { - unrollFuncSet.insert(curr); - } - e_curr = e_curr->get_next(); - } while (e_curr != n_enode); -} - // from Z3: theory_seq.cpp static theory_mi_arith* get_th_arith(context& ctx, theory_id afid, expr* e) { @@ -4399,10 +4379,10 @@ void theory_str::handle_equality(expr * lhs, expr * rhs) { */ expr * nn1EqConst = NULL; std::set nn1EqUnrollFuncs; - get_eqc_all_unroll(lhs, nn1EqConst, nn1EqUnrollFuncs); + get_eqc_allUnroll(lhs, nn1EqConst, nn1EqUnrollFuncs); expr * nn2EqConst = NULL; std::set nn2EqUnrollFuncs; - get_eqc_all_unroll(rhs, nn2EqConst, nn2EqUnrollFuncs); + get_eqc_allUnroll(rhs, nn2EqConst, nn2EqUnrollFuncs); if (nn2EqConst != NULL) { for (std::set::iterator itor1 = nn1EqUnrollFuncs.begin(); itor1 != nn1EqUnrollFuncs.end(); itor1++) { @@ -5741,18 +5721,15 @@ final_check_status theory_str::final_check_eh() { fvIt1 != fv_unrolls_map.end(); fvIt1++) { expr * var = fvIt1->first; fSimpUnroll.clear(); - NOT_IMPLEMENTED_YET(); // TODO complete this unroll block - /* - get_eqc_simpleUnroll(t, var, constValue, fSimpUnroll); + get_eqc_simpleUnroll(var, constValue, fSimpUnroll); if (fSimpUnroll.size() == 0) { - genAssignUnrollReg(t, fv_unrolls_map[var]); + gen_assign_unroll_reg(fv_unrolls_map[var]); } else { - Z3_ast toAssert = genAssignUnrollStr2Reg(t, var, fSimpUnroll); + expr * toAssert = gen_assign_unroll_Str2Reg(var, fSimpUnroll); if (toAssert != NULL) { - addAxiom(t, toAssert, __LINE__); + assert_axiom(toAssert); } } - */ } if (opt_VerifyFinalCheckProgress && !finalCheckProgressIndicator) { @@ -6037,6 +6014,89 @@ expr * theory_str::gen_free_var_options(expr * freeVar, expr * len_indicator, } } +void theory_str::gen_assign_unroll_reg(std::set & unrolls) { + // TODO + NOT_IMPLEMENTED_YET(); +} + +static int computeGCD(int x, int y) { + if (x == 0) { + return y; + } + while (y != 0) { + if (x > y) { + x = x - y; + } else { + y = y - x; + } + } + return x; +} + +static int computeLCM(int a, int b) { + int temp = computeGCD(a, b); + return temp ? (a / temp * b) : 0; +} + +static std::string get_unrolled_string(std::string core, int count) { + std::string res = ""; + for (int i = 0; i < count; i++) { + res += core; + } + return res; +} + +expr * theory_str::gen_assign_unroll_Str2Reg(expr * n, std::set & unrolls) { + context & ctx = get_context(); + ast_manager & mgr = get_manager(); + + int lcm = 1; + int coreValueCount = 0; + expr * oneUnroll = NULL; + std::string oneCoreStr = ""; + for (std::set::iterator itor = unrolls.begin(); itor != unrolls.end(); itor++) { + expr * str2RegFunc = to_app(*itor)->get_arg(0); + expr * coreVal = to_app(str2RegFunc)->get_arg(0); + std::string coreStr = m_strutil.get_string_constant_value(coreVal); + if (oneUnroll == NULL) { + oneUnroll = *itor; + oneCoreStr = coreStr; + } + coreValueCount++; + int core1Len = coreStr.length(); + lcm = computeLCM(lcm, core1Len); + } + // + bool canHaveNonEmptyAssign = true; + expr_ref_vector litems(mgr); + std::string lcmStr = get_unrolled_string(oneCoreStr, (lcm / oneCoreStr.length())); + for (std::set::iterator itor = unrolls.begin(); itor != unrolls.end(); itor++) { + expr * str2RegFunc = to_app(*itor)->get_arg(0); + expr * coreVal = to_app(str2RegFunc)->get_arg(0); + std::string coreStr = m_strutil.get_string_constant_value(coreVal); + int core1Len = coreStr.length(); + std::string uStr = get_unrolled_string(coreStr, (lcm / core1Len)); + if (uStr != lcmStr) { + canHaveNonEmptyAssign = false; + } + litems.push_back(ctx.mk_eq_atom(n, *itor)); + } + + if (canHaveNonEmptyAssign) { + return gen_unroll_conditional_options(n, unrolls, lcmStr); + } else { + expr * implyL = mk_and(litems); + expr * implyR = ctx.mk_eq_atom(n, m_strutil.mk_string("")); + // want to return (implyL -> implyR) + return mgr.mk_or(mgr.mk_not(implyL), implyR); + } +} + +expr * theory_str::gen_unroll_conditional_options(expr * var, std::set & unrolls, std::string lcmStr) { + // TODO NEXT + NOT_IMPLEMENTED_YET(); +} + expr * theory_str::gen_len_test_options(expr * freeVar, expr * indicator, int tries) { ast_manager & m = get_manager(); context & ctx = get_context(); @@ -6417,6 +6477,30 @@ void theory_str::get_eqc_allUnroll(expr * n, expr * &constStr, std::set & } while (curr != n); } +// Collect simple Unroll functions (whose core is Str2Reg) and constant strings in the EQC of n. +void theory_str::get_eqc_simpleUnroll(expr * n, expr * &constStr, std::set & unrollFuncSet) { + constStr = NULL; + unrollFuncSet.clear(); + context & ctx = get_context(); + + expr * curr = n; + do { + if (is_string(to_app(curr))) { + constStr = curr; + } else if (is_Unroll(to_app(curr))) { + expr * core = to_app(curr)->get_arg(0); + if (is_Str2Reg(to_app(core))) { + if (unrollFuncSet.find(curr) == unrollFuncSet.end()) { + unrollFuncSet.insert(curr); + } + } + } + enode * e_curr = ctx.get_enode(curr); + curr = e_curr->get_next()->get_owner(); + // curr = get_eqc_next(t, curr); + } while (curr != n); +} + void theory_str::init_model(model_generator & mg) { //TRACE("t_str", tout << "initializing model" << std::endl; display(tout);); m_factory = alloc(str_value_factory, get_manager(), get_family_id()); diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 467727179..7ef4ef7d3 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -241,7 +241,6 @@ namespace smt { void instantiate_axiom_RegexIn(enode * e); app * mk_unroll(expr * n, expr * bound); - void get_eqc_all_unroll(expr * n, expr * & constStr, std::set & unrollFuncSet); void process_unroll_eq_const_str(expr * unrollFunc, expr * constStr); void unroll_str2reg_constStr(expr * unrollFunc, expr * eqConstStr); void process_concat_eq_unroll(expr * concat, expr * unroll); @@ -332,6 +331,10 @@ namespace smt { // strRegex void get_eqc_allUnroll(expr * n, expr * &constStr, std::set & unrollFuncSet); + void get_eqc_simpleUnroll(expr * n, expr * &constStr, std::set & unrollFuncSet); + void gen_assign_unroll_reg(std::set & unrolls); + expr * gen_assign_unroll_Str2Reg(expr * n, std::set & unrolls); + expr * gen_unroll_conditional_options(expr * var, std::set & unrolls, std::string lcmStr); void dump_assignments(); void initialize_charset(); From b4110c886f13111c8273c20f00f2e54fc96bda60 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Thu, 30 Jun 2016 02:46:16 -0400 Subject: [PATCH 135/410] successful unroll of simple unbounded Str2Reg --- src/smt/theory_str.cpp | 117 ++++++++++++++++++++++++++++++++++++++++- src/smt/theory_str.h | 11 ++++ 2 files changed, 126 insertions(+), 2 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 64f3d1fc8..c8170a16a 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -32,6 +32,7 @@ theory_str::theory_str(ast_manager & m): opt_AggressiveValueTesting(true), opt_EagerStringConstantLengthAssertions(true), opt_VerifyFinalCheckProgress(true), + opt_LCMUnrollStep(2), /* Internal setup */ search_started(false), m_autil(m), @@ -458,6 +459,10 @@ app * theory_str::mk_unroll_bound_var() { return mk_int_var("unroll"); } +app * theory_str::mk_unroll_test_var() { + return mk_str_var("unrollTest"); // was uRt +} + app * theory_str::mk_str_var(std::string name) { context & ctx = get_context(); ast_manager & m = get_manager(); @@ -6093,8 +6098,116 @@ expr * theory_str::gen_assign_unroll_Str2Reg(expr * n, std::set & unrolls } expr * theory_str::gen_unroll_conditional_options(expr * var, std::set & unrolls, std::string lcmStr) { - // TODO NEXT - NOT_IMPLEMENTED_YET(); + context & ctx = get_context(); + ast_manager & mgr = get_manager(); + + int dist = opt_LCMUnrollStep; + expr_ref_vector litems(mgr); + expr_ref moreAst(m_strutil.mk_string("more"), mgr); + for (std::set::iterator itor = unrolls.begin(); itor != unrolls.end(); itor++) { + expr_ref item(ctx.mk_eq_atom(var, *itor), mgr); + TRACE("t_str_detail", tout << "considering unroll " << mk_pp(item, mgr) << std::endl;); + litems.push_back(item); + } + + if (unroll_tries_map[var][unrolls].size() == 0) { + unroll_tries_map[var][unrolls].push_back(mk_unroll_test_var()); + } + + int tries = unroll_tries_map[var][unrolls].size(); + for (int i = 0; i < tries; i++) { + expr * tester = unroll_tries_map[var][unrolls][i]; + bool testerHasValue = false; + expr * testerVal = get_eqc_value(tester, testerHasValue); + if (!testerHasValue) { + // generate make-up assertion + int l = i * dist; + int h = (i + 1) * dist; + expr_ref lImp(mk_and(litems), mgr); + expr_ref rImp(gen_unroll_assign(var, lcmStr, tester, l, h), mgr); + + SASSERT(lImp); + TRACE("t_str_detail", tout << "lImp = " << mk_pp(lImp, mgr) << std::endl;); + SASSERT(rImp); + TRACE("t_str_detail", tout << "rImp = " << mk_pp(rImp, mgr) << std::endl;); + + expr_ref toAssert(mgr.mk_or(mgr.mk_not(lImp), rImp), mgr); + SASSERT(toAssert); + TRACE("t_str_detail", tout << "Making up assignments for variable which is equal to unbounded Unroll" << std::endl;); + m_trail.push_back(toAssert); + return toAssert; + + // insert [tester = "more"] to litems so that the implyL for next tester is correct + litems.push_back(ctx.mk_eq_atom(tester, moreAst)); + } else { + std::string testerStr = m_strutil.get_string_constant_value(testerVal); + TRACE("t_str_detail", tout << "Tester [" << mk_pp(tester, mgr) << "] = " << testerStr << std::endl;); + if (testerStr == "more") { + litems.push_back(ctx.mk_eq_atom(tester, moreAst)); + } + } + } + expr * tester = mk_unroll_test_var(); + unroll_tries_map[var][unrolls].push_back(tester); + int l = tries * dist; + int h = (tries + 1) * dist; + expr_ref lImp(mk_and(litems), mgr); + expr_ref rImp(gen_unroll_assign(var, lcmStr, tester, l, h), mgr); + SASSERT(lImp); + SASSERT(rImp); + expr_ref toAssert(mgr.mk_or(mgr.mk_not(lImp), rImp), mgr); + SASSERT(toAssert); + TRACE("t_str_detail", tout << "Generating assignment for variable which is equal to unbounded Unroll" << std::endl;); + m_trail.push_back(toAssert); + return toAssert; +} + +expr * theory_str::gen_unroll_assign(expr * var, std::string lcmStr, expr * testerVar, int l, int h) { + context & ctx = get_context(); + ast_manager & mgr = get_manager(); + + TRACE("t_str_detail", tout << "entry: var = " << mk_pp(var, mgr) << ", lcmStr = " << lcmStr + << ", l = " << l << ", h = " << h << std::endl;); + + expr_ref_vector orItems(mgr); + expr_ref_vector andItems(mgr); + + for (int i = l; i < h; i++) { + std::string iStr = int_to_string(i); + expr_ref testerEqAst(ctx.mk_eq_atom(testerVar, m_strutil.mk_string(iStr)), mgr); + TRACE("t_str_detail", tout << "testerEqAst = " << mk_pp(testerEqAst, mgr) << std::endl;); + orItems.push_back(testerEqAst); + std::string unrollStrInstance = get_unrolled_string(lcmStr, i); + + expr_ref x1(ctx.mk_eq_atom(testerEqAst, ctx.mk_eq_atom(var, m_strutil.mk_string(unrollStrInstance))), mgr); + TRACE("t_str_detail", tout << "x1 = " << mk_pp(x1, mgr) << std::endl;); + andItems.push_back(x1); + + expr_ref x2(ctx.mk_eq_atom(testerEqAst, ctx.mk_eq_atom(mk_strlen(var), mk_int(i * lcmStr.length()))), mgr); + TRACE("t_str_detail", tout << "x2 = " << mk_pp(x2, mgr) << std::endl;); + andItems.push_back(x2); + } + expr_ref testerEqMore(ctx.mk_eq_atom(testerVar, m_strutil.mk_string("more")), mgr); + TRACE("t_str_detail", tout << "testerEqMore = " << mk_pp(testerEqMore, mgr) << std::endl;); + orItems.push_back(testerEqMore); + int nextLowerLenBound = h * lcmStr.length(); + expr_ref more2(ctx.mk_eq_atom(testerEqMore, + //Z3_mk_ge(mk_length(t, var), mk_int(ctx, nextLowerLenBound)) + m_autil.mk_ge(m_autil.mk_add(mk_strlen(var), mk_int(-1 * nextLowerLenBound)), mk_int(0)) + ), mgr); + TRACE("t_str_detail", tout << "more2 = " << mk_pp(more2, mgr) << std::endl;); + andItems.push_back(more2); + + expr_ref finalOR(mgr.mk_or(orItems.size(), orItems.c_ptr()), mgr); + TRACE("t_str_detail", tout << "finalOR = " << mk_pp(finalOR, mgr) << std::endl;); + andItems.push_back(mk_or(orItems)); + + expr_ref finalAND(mgr.mk_and(andItems.size(), andItems.c_ptr()), mgr); + TRACE("t_str_detail", tout << "finalAND = " << mk_pp(finalAND, mgr) << std::endl;); + + // doing the following avoids a segmentation fault + m_trail.push_back(finalAND); + return finalAND; } expr * theory_str::gen_len_test_options(expr * freeVar, expr * indicator, int tries) { diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 7ef4ef7d3..736900ba7 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -89,6 +89,11 @@ namespace smt { */ bool opt_VerifyFinalCheckProgress; + /* + * This constant controls how eagerly we expand unrolls in unbounded regex membership tests. + */ + int opt_LCMUnrollStep; + bool search_started; arith_util m_autil; str_util m_strutil; @@ -153,6 +158,10 @@ namespace smt { std::map val_range_map; + // This can't be an expr_ref_vector because the constructor is wrong, + // we would need to modify the allocator so we pass in ast_manager + std::map, ptr_vector > > unroll_tries_map; + char * char_set; std::map charSetLookupTable; int charSetSize; @@ -184,6 +193,7 @@ namespace smt { expr * mk_internal_valTest_var(expr * node, int len, int vTries); app * mk_regex_rep_var(); app * mk_unroll_bound_var(); + app * mk_unroll_test_var(); bool is_concat(app const * a) const { return a->is_app_of(get_id(), OP_STRCAT); } bool is_concat(enode const * n) const { return is_concat(n->get_owner()); } @@ -335,6 +345,7 @@ namespace smt { void gen_assign_unroll_reg(std::set & unrolls); expr * gen_assign_unroll_Str2Reg(expr * n, std::set & unrolls); expr * gen_unroll_conditional_options(expr * var, std::set & unrolls, std::string lcmStr); + expr * gen_unroll_assign(expr * var, std::string lcmStr, expr * testerVar, int l, int h); void dump_assignments(); void initialize_charset(); From a2d6149df59703b90c9835ea41077624f5192c13 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Thu, 30 Jun 2016 04:00:42 -0400 Subject: [PATCH 136/410] add general-case regex unroll model generation WIP as there is currently a SAT-as-UNSAT bug I'm trying to fix This also changes the semantics of lower_bound and upper_bound, no longer wrapping the expr that is passed in with mk_strlen(). This actually makes these methods useful for checking bounds of things other than strings. --- src/smt/theory_str.cpp | 181 +++++++++++++++++++++++++++++++++++++++-- src/smt/theory_str.h | 2 + 2 files changed, 174 insertions(+), 9 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index c8170a16a..27043315b 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -131,6 +131,7 @@ void theory_str::assert_axiom(expr * e) { if (opt_VerifyFinalCheckProgress) { finalCheckProgressIndicator = true; } + // TODO add to m_trail? if (get_manager().is_true(e)) return; TRACE("t_str_detail", tout << "asserting " << mk_ismt2_pp(e, get_manager()) << std::endl;); context & ctx = get_context(); @@ -3432,6 +3433,11 @@ void theory_str::process_unroll_eq_const_str(expr * unrollFunc, expr * constStr) } void theory_str::process_concat_eq_unroll(expr * concat, expr * unroll) { + context & ctx = get_context(); + ast_manager & mgr = get_manager(); + + TRACE("t_str_detail", tout << "concat = " << mk_pp(concat, mgr) << ", unroll = " << mk_pp(unroll, mgr) << std::endl;); + // TODO NEXT NOT_IMPLEMENTED_YET(); /* @@ -3596,20 +3602,18 @@ bool theory_str::get_value(expr* e, rational& val) const { bool theory_str::lower_bound(expr* _e, rational& lo) { context& ctx = get_context(); ast_manager & m = get_manager(); - expr_ref e(mk_strlen(_e), m); - theory_mi_arith* tha = get_th_arith(ctx, m_autil.get_family_id(), e); + theory_mi_arith* tha = get_th_arith(ctx, m_autil.get_family_id(), _e); expr_ref _lo(m); - if (!tha || !tha->get_lower(ctx.get_enode(e), _lo)) return false; + if (!tha || !tha->get_lower(ctx.get_enode(_e), _lo)) return false; return m_autil.is_numeral(_lo, lo) && lo.is_int(); } bool theory_str::upper_bound(expr* _e, rational& hi) { context& ctx = get_context(); ast_manager & m = get_manager(); - expr_ref e(mk_strlen(_e), m); - theory_mi_arith* tha = get_th_arith(ctx, m_autil.get_family_id(), e); + theory_mi_arith* tha = get_th_arith(ctx, m_autil.get_family_id(), _e); expr_ref _hi(m); - if (!tha || !tha->get_upper(ctx.get_enode(e), _hi)) return false; + if (!tha || !tha->get_upper(ctx.get_enode(_e), _hi)) return false; return m_autil.is_numeral(_hi, hi) && hi.is_int(); } @@ -5602,6 +5606,7 @@ final_check_status theory_str::final_check_eh() { for (std::map >::iterator fvIt3 = fv_unrolls_map.begin(); fvIt3 != fv_unrolls_map.end(); fvIt3++) { expr * var = fvIt3->first; + TRACE("t_str_detail", tout << "erase free variable " << mk_pp(var, m) << " from freeVar_map, it is bounded by an Unroll" << std::endl;); freeVar_map.erase(var); } @@ -5665,6 +5670,7 @@ final_check_status theory_str::final_check_eh() { } } for (std::set::iterator vItor = fvUnrollSet.begin(); vItor != fvUnrollSet.end(); vItor++) { + TRACE("t_str_detail", tout << "remove " << mk_pp(*vItor, m) << " from freeVar_map" << std::endl;); freeVar_map.erase(*vItor); } @@ -5721,7 +5727,8 @@ final_check_status theory_str::final_check_eh() { } // experimental free variable assignment - end - // more unroll stuff + // now deal with removed free variables that are bounded by an unroll + TRACE("t_str", tout << "fv_unrolls_map (#" << fv_unrolls_map.size() << ")" << std::endl;); for (std::map >::iterator fvIt1 = fv_unrolls_map.begin(); fvIt1 != fv_unrolls_map.end(); fvIt1++) { expr * var = fvIt1->first; @@ -6019,9 +6026,165 @@ expr * theory_str::gen_free_var_options(expr * freeVar, expr * len_indicator, } } +void theory_str::reduce_virtual_regex_in(expr * var, expr * regex, expr_ref_vector & items) { + context & ctx = get_context(); + ast_manager & mgr = get_manager(); + + TRACE("t_str_detail", tout << "reduce regex " << mk_pp(regex, mgr) << " with respect to variable " << mk_pp(var, mgr) << std::endl;); + + app * regexFuncDecl = to_app(regex); + if (is_Str2Reg(regexFuncDecl)) { + // --------------------------------------------------------- + // var \in Str2Reg(s1) + // ==> + // var = s1 /\ length(var) = length(s1) + // --------------------------------------------------------- + expr * strInside = to_app(regex)->get_arg(0); + items.push_back(ctx.mk_eq_atom(var, strInside)); + items.push_back(ctx.mk_eq_atom(mk_strlen(var), mk_strlen(strInside))); + return; + } + // RegexUnion + else if (is_RegexUnion(regexFuncDecl)) { + // --------------------------------------------------------- + // var \in RegexUnion(r1, r2) + // ==> + // (var = newVar1 \/ var = newVar2) + // (var = newVar1 --> length(var) = length(newVar1)) /\ (var = newVar2 --> length(var) = length(newVar2)) + // /\ (newVar1 \in r1) /\ (newVar2 \in r2) + // --------------------------------------------------------- + expr_ref newVar1(mk_regex_rep_var(), mgr); + expr_ref newVar2(mk_regex_rep_var(), mgr); + items.push_back(mgr.mk_or(ctx.mk_eq_atom(var, newVar1), ctx.mk_eq_atom(var, newVar2))); + items.push_back(mgr.mk_or( + mgr.mk_not(ctx.mk_eq_atom(var, newVar1)), + ctx.mk_eq_atom(mk_strlen(var), mk_strlen(newVar1)))); + items.push_back(mgr.mk_or( + mgr.mk_not(ctx.mk_eq_atom(var, newVar2)), + ctx.mk_eq_atom(mk_strlen(var), mk_strlen(newVar2)))); + + expr * regArg1 = to_app(regex)->get_arg(0); + reduce_virtual_regex_in(newVar1, regArg1, items); + + expr * regArg2 = to_app(regex)->get_arg(1); + reduce_virtual_regex_in(newVar2, regArg2, items); + + return; + } + // RegexConcat + else if (is_RegexConcat(regexFuncDecl)) { + // --------------------------------------------------------- + // var \in RegexConcat(r1, r2) + // ==> + // (var = newVar1 . newVar2) /\ (length(var) = length(vewVar1 . newVar2) ) + // /\ (newVar1 \in r1) /\ (newVar2 \in r2) + // --------------------------------------------------------- + expr_ref newVar1(mk_regex_rep_var(), mgr); + expr_ref newVar2(mk_regex_rep_var(), mgr); + expr_ref concatAst(mk_concat(newVar1, newVar2), mgr); + items.push_back(ctx.mk_eq_atom(var, concatAst)); + items.push_back(ctx.mk_eq_atom(mk_strlen(var), + m_autil.mk_add(mk_strlen(newVar1), mk_strlen(newVar2)))); + + expr * regArg1 = to_app(regex)->get_arg(0); + reduce_virtual_regex_in(newVar1, regArg1, items); + expr * regArg2 = to_app(regex)->get_arg(1); + reduce_virtual_regex_in(newVar2, regArg2, items); + return; + } + // Unroll + else if (is_RegexStar(regexFuncDecl)) { + // --------------------------------------------------------- + // var \in Star(r1) + // ==> + // var = unroll(r1, t1) /\ |var| = |unroll(r1, t1)| + // --------------------------------------------------------- + expr * regArg = to_app(regex)->get_arg(0); + expr_ref unrollCnt(mk_unroll_bound_var(), mgr); + expr_ref unrollFunc(mk_unroll(regArg, unrollCnt), mgr); + items.push_back(ctx.mk_eq_atom(var, unrollFunc)); + items.push_back(ctx.mk_eq_atom(mk_strlen(var), mk_strlen(unrollFunc))); + return; + } else { + UNREACHABLE(); + } +} + void theory_str::gen_assign_unroll_reg(std::set & unrolls) { - // TODO - NOT_IMPLEMENTED_YET(); + context & ctx = get_context(); + ast_manager & mgr = get_manager(); + + expr_ref_vector items(mgr); + for (std::set::iterator itor = unrolls.begin(); itor != unrolls.end(); itor++) { + expr * unrFunc = *itor; + TRACE("t_str_detail", tout << "generating assignment for unroll " << mk_pp(unrFunc, mgr) << std::endl;); + + expr * regexInUnr = to_app(unrFunc)->get_arg(0); + expr * cntInUnr = to_app(unrFunc)->get_arg(1); + items.reset(); + + rational low, high; + bool low_exists = lower_bound(cntInUnr, low); + bool high_exists = upper_bound(cntInUnr, high); + + TRACE("t_str_detail", + tout << "unroll " << mk_pp(unrFunc, mgr) << std::endl; + rational unrLenValue; + bool unrLenValue_exists = get_len_value(unrFunc, unrLenValue); + tout << "unroll length: " << (unrLenValue_exists ? unrLenValue.to_string() : "?") << std::endl; + rational cntInUnrValue; + bool cntHasValue = get_value(cntInUnr, cntInUnrValue); + tout << "unroll count: " << (cntHasValue ? cntInUnrValue.to_string() : "?") + << " low = " + << (low_exists ? low.to_string() : "?") + << " high = " + << (high_exists ? high.to_string() : "?") + << std::endl; + ); + + expr_ref toAssert(mgr); + if (low.is_neg()) { + toAssert = m_autil.mk_ge(cntInUnr, mk_int(0)); + } else { + if (unroll_var_map.find(unrFunc) == unroll_var_map.end()) { + + expr_ref newVar1(mk_regex_rep_var(), mgr); + expr_ref newVar2(mk_regex_rep_var(), mgr); + expr_ref concatAst(mk_concat(newVar1, newVar2), mgr); + expr_ref newCnt(mk_unroll_bound_var(), mgr); + expr_ref newUnrollFunc(mk_unroll(regexInUnr, newCnt), mgr); + + // unroll(r1, t1) = newVar1 . newVar2 + items.push_back(ctx.mk_eq_atom(unrFunc, concatAst)); + items.push_back(ctx.mk_eq_atom(mk_strlen(unrFunc), m_autil.mk_add(mk_strlen(newVar1), mk_strlen(newVar2)))); + items.push_back(ctx.mk_eq_atom(mk_strlen(unrFunc), mk_strlen(newVar1))); + items.push_back(ctx.mk_eq_atom(mk_strlen(unrFunc), mk_strlen(newVar2))); + // newVar1 \in r1 + reduce_virtual_regex_in(newVar1, regexInUnr, items); + items.push_back(ctx.mk_eq_atom(cntInUnr, m_autil.mk_add(newCnt, mk_int(1)))); + items.push_back(ctx.mk_eq_atom(newVar2, newUnrollFunc)); + items.push_back(ctx.mk_eq_atom(mk_strlen(newVar2), mk_strlen(newUnrollFunc))); + toAssert = ctx.mk_eq_atom( + m_autil.mk_ge(cntInUnr, mk_int(1)), + mk_and(items)); + + // option 0 + expr_ref op0(ctx.mk_eq_atom(cntInUnr, mk_int(0)), mgr); + expr_ref ast1(ctx.mk_eq_atom(unrFunc, m_strutil.mk_string("")), mgr); + expr_ref ast2(ctx.mk_eq_atom(mk_strlen(unrFunc), mk_int(0)), mgr); + expr_ref and1(mgr.mk_and(ast1, ast2), mgr); + + // put together + toAssert = mgr.mk_and(ctx.mk_eq_atom(op0, and1), toAssert); + + unroll_var_map[unrFunc] = toAssert; + } else { + toAssert = unroll_var_map[unrFunc]; + } + } + m_trail.push_back(toAssert); + assert_axiom(toAssert); + } } static int computeGCD(int x, int y) { diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 736900ba7..c61b3783a 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -161,6 +161,7 @@ namespace smt { // This can't be an expr_ref_vector because the constructor is wrong, // we would need to modify the allocator so we pass in ast_manager std::map, ptr_vector > > unroll_tries_map; + std::map unroll_var_map; char * char_set; std::map charSetLookupTable; @@ -346,6 +347,7 @@ namespace smt { expr * gen_assign_unroll_Str2Reg(expr * n, std::set & unrolls); expr * gen_unroll_conditional_options(expr * var, std::set & unrolls, std::string lcmStr); expr * gen_unroll_assign(expr * var, std::string lcmStr, expr * testerVar, int l, int h); + void reduce_virtual_regex_in(expr * var, expr * regex, expr_ref_vector & items); void dump_assignments(); void initialize_charset(); From b53da182b647b9ca1187538a76884be4534fbda5 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Thu, 30 Jun 2016 04:39:09 -0400 Subject: [PATCH 137/410] fix gen_assign_unroll_reg so that it does not assert a contradiction --- src/smt/theory_str.cpp | 30 ++++++++---------------------- src/smt/theory_str.h | 1 + 2 files changed, 9 insertions(+), 22 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 27043315b..843e78e85 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -461,7 +461,9 @@ app * theory_str::mk_unroll_bound_var() { } app * theory_str::mk_unroll_test_var() { - return mk_str_var("unrollTest"); // was uRt + app * v = mk_str_var("unrollTest"); // was uRt + internal_unrollTest_vars.insert(v); + return v; } app * theory_str::mk_str_var(std::string name) { @@ -4159,6 +4161,8 @@ bool theory_str::free_var_attempt(expr * nn1, expr * nn2) { more_value_tests(nn1, nn2_str); } return true; + } else if (internal_unrollTest_vars.contains(nn1)) { + return true; } else { return false; } @@ -4366,26 +4370,7 @@ void theory_str::handle_equality(expr * lhs, expr * rhs) { } // regex unroll - /* - Z3_ast nn1EqConst = NULL; - std::set nn1EqUnrollFuncs; - get_eqc_allUnroll(t, nn1, nn1EqConst, nn1EqUnrollFuncs); - Z3_ast nn2EqConst = NULL; - std::set nn2EqUnrollFuncs; - get_eqc_allUnroll(t, nn2, nn2EqConst, nn2EqUnrollFuncs); - if (nn2EqConst != NULL) { - for (std::set::iterator itor1 = nn1EqUnrollFuncs.begin(); itor1 != nn1EqUnrollFuncs.end(); itor1++) { - processUnrollEqConstStr(t, *itor1, nn2EqConst); - } - } - - if (nn1EqConst != NULL) { - for (std::set::iterator itor2 = nn2EqUnrollFuncs.begin(); itor2 != nn2EqUnrollFuncs.end(); itor2++) { - processUnrollEqConstStr(t, *itor2, nn1EqConst); - } - } - */ expr * nn1EqConst = NULL; std::set nn1EqUnrollFuncs; get_eqc_allUnroll(lhs, nn1EqConst, nn1EqUnrollFuncs); @@ -6157,8 +6142,9 @@ void theory_str::gen_assign_unroll_reg(std::set & unrolls) { // unroll(r1, t1) = newVar1 . newVar2 items.push_back(ctx.mk_eq_atom(unrFunc, concatAst)); items.push_back(ctx.mk_eq_atom(mk_strlen(unrFunc), m_autil.mk_add(mk_strlen(newVar1), mk_strlen(newVar2)))); - items.push_back(ctx.mk_eq_atom(mk_strlen(unrFunc), mk_strlen(newVar1))); - items.push_back(ctx.mk_eq_atom(mk_strlen(unrFunc), mk_strlen(newVar2))); + // mk_strlen(unrFunc) >= mk_strlen(newVar{1,2}) + items.push_back(m_autil.mk_ge(m_autil.mk_add(mk_strlen(unrFunc), m_autil.mk_mul(mk_int(-1), mk_strlen(newVar1))), mk_int(0))); + items.push_back(m_autil.mk_ge(m_autil.mk_add(mk_strlen(unrFunc), m_autil.mk_mul(mk_int(-1), mk_strlen(newVar2))), mk_int(0))); // newVar1 \in r1 reduce_virtual_regex_in(newVar1, regexInUnr, items); items.push_back(ctx.mk_eq_atom(cntInUnr, m_autil.mk_add(newCnt, mk_int(1)))); diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index c61b3783a..daf534686 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -146,6 +146,7 @@ namespace smt { obj_hashtable internal_lenTest_vars; obj_hashtable internal_valTest_vars; + obj_hashtable internal_unrollTest_vars; std::set input_var_in_len; From 7d903ff1fa0cf04277aee71558d2cc6c961fe7ac Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Thu, 30 Jun 2016 04:55:11 -0400 Subject: [PATCH 138/410] implement process_concat_eq_unroll, WIP --- src/smt/theory_str.cpp | 112 ++++++++++++++++++----------------------- src/smt/theory_str.h | 1 + 2 files changed, 50 insertions(+), 63 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 843e78e85..853924a94 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -3440,80 +3440,66 @@ void theory_str::process_concat_eq_unroll(expr * concat, expr * unroll) { TRACE("t_str_detail", tout << "concat = " << mk_pp(concat, mgr) << ", unroll = " << mk_pp(unroll, mgr) << std::endl;); - // TODO NEXT - NOT_IMPLEMENTED_YET(); - /* -#ifdef DEBUGLOG - __debugPrint(logFile, ">> processConcatEqUnroll: \n"); - __debugPrint(logFile, " * [concat] "); - printZ3Node(t, concat); - __debugPrint(logFile, "\n"); - __debugPrint(logFile, " * [unroll] "); - printZ3Node(t, unroll); - __debugPrint(logFile, "\n\n"); -#endif + std::pair key = std::make_pair(concat, unroll); + expr_ref toAssert(mgr); - Z3_context ctx = Z3_theory_get_context(t); - std::pair key = std::make_pair(concat, unroll); - Z3_ast toAssert = NULL; + if (concat_eq_unroll_ast_map.find(key) == concat_eq_unroll_ast_map.end()) { + expr_ref arg1(to_app(concat)->get_arg(0), mgr); + expr_ref arg2(to_app(concat)->get_arg(1), mgr); + expr_ref r1(to_app(unroll)->get_arg(0), mgr); + expr_ref t1(to_app(unroll)->get_arg(1), mgr); - if (concatEqUnroll_AstMap.find(key) == concatEqUnroll_AstMap.end()) { - Z3_ast arg1 = Z3_get_app_arg(ctx, Z3_to_app(ctx, concat), 0); - Z3_ast arg2 = Z3_get_app_arg(ctx, Z3_to_app(ctx, concat), 1); - Z3_ast r1 = Z3_get_app_arg(ctx, Z3_to_app(ctx, unroll), 0); - Z3_ast t1 = Z3_get_app_arg(ctx, Z3_to_app(ctx, unroll), 1); + expr_ref v1(mk_regex_rep_var(), mgr); + expr_ref v2(mk_regex_rep_var(), mgr); + expr_ref v3(mk_regex_rep_var(), mgr); + expr_ref v4(mk_regex_rep_var(), mgr); + expr_ref v5(mk_regex_rep_var(), mgr); - Z3_ast v1 = mk_regexRepVar(t); - Z3_ast v2 = mk_regexRepVar(t); - Z3_ast v3 = mk_regexRepVar(t); - Z3_ast v4 = mk_regexRepVar(t); - Z3_ast v5 = mk_regexRepVar(t); + expr_ref t2(mk_unroll_bound_var(), mgr); + expr_ref t3(mk_unroll_bound_var(), mgr); + expr_ref emptyStr(m_strutil.mk_string(""), mgr); - Z3_ast t2 = mk_unrollBoundVar(t); - Z3_ast t3 = mk_unrollBoundVar(t); - Z3_ast emptyStr = my_mk_str_value(t, ""); + expr_ref unroll1(mk_unroll(r1, t2), mgr); + expr_ref unroll2(mk_unroll(r1, t3), mgr); - Z3_ast unroll1 = mk_unroll(t, r1, t2); - Z3_ast unroll2 = mk_unroll(t, r1, t3); + expr_ref op0(ctx.mk_eq_atom(t1, mk_int(0)), mgr); + expr_ref op1(m_autil.mk_ge(t1, mk_int(1)), mgr); - Z3_ast op0 = Z3_mk_eq(ctx, t1, mk_int(ctx, 0)); - Z3_ast op1 = Z3_mk_ge(ctx, t1, mk_int(ctx, 1)); + expr_ref_vector op1Items(mgr); + expr_ref_vector op2Items(mgr); - std::vector op1Items; - std::vector op2Items; + op1Items.push_back(ctx.mk_eq_atom(arg1, emptyStr)); + op1Items.push_back(ctx.mk_eq_atom(arg2, emptyStr)); + op1Items.push_back(ctx.mk_eq_atom(mk_strlen(arg1), mk_int(0))); + op1Items.push_back(ctx.mk_eq_atom(mk_strlen(arg2), mk_int(0))); + expr_ref opAnd1(ctx.mk_eq_atom(op0, mk_and(op1Items)), mgr); - op1Items.push_back(Z3_mk_eq(ctx, arg1, emptyStr)); - op1Items.push_back(Z3_mk_eq(ctx, arg2, emptyStr)); - op1Items.push_back(Z3_mk_eq(ctx, mk_length(t, arg1), mk_int(ctx, 0))); - op1Items.push_back(Z3_mk_eq(ctx, mk_length(t, arg2), mk_int(ctx, 0))); - Z3_ast opAnd1 = Z3_mk_eq(ctx, op0, mk_and_fromVector(t, op1Items)); + expr_ref v1v2(mk_concat(v1, v2), mgr); + op2Items.push_back(ctx.mk_eq_atom(arg1, v1v2)); + op2Items.push_back(ctx.mk_eq_atom(mk_strlen(arg1), m_autil.mk_add(mk_strlen(v1), mk_strlen(v2)))); + expr_ref v3v4(mk_concat(v3, v4), mgr); + op2Items.push_back(ctx.mk_eq_atom(arg2, v3v4)); + op2Items.push_back(ctx.mk_eq_atom(mk_strlen(arg2), m_autil.mk_add(mk_strlen(v3), mk_strlen(v4)))); - Z3_ast v1v2 = mk_concat(t, v1, v2); - op2Items.push_back(Z3_mk_eq(ctx, arg1, v1v2)); - op2Items.push_back(Z3_mk_eq(ctx, mk_length(t, arg1), mk_2_add(t, mk_length(t, v1), mk_length(t, v2)))); - Z3_ast v3v4 = mk_concat(t, v3, v4); - op2Items.push_back(Z3_mk_eq(ctx, arg2, v3v4)); - op2Items.push_back(Z3_mk_eq(ctx, mk_length(t, arg2), mk_2_add(t, mk_length(t, v3), mk_length(t, v4)))); + op2Items.push_back(ctx.mk_eq_atom(v1, unroll1)); + op2Items.push_back(ctx.mk_eq_atom(mk_strlen(v1), mk_strlen(unroll1))); + op2Items.push_back(ctx.mk_eq_atom(v4, unroll2)); + op2Items.push_back(ctx.mk_eq_atom(mk_strlen(v4), mk_strlen(unroll2))); + expr_ref v2v3(mk_concat(v2, v3), mgr); + op2Items.push_back(ctx.mk_eq_atom(v5, v2v3)); + reduce_virtual_regex_in(v5, r1, op2Items); + op2Items.push_back(ctx.mk_eq_atom(mk_strlen(v5), m_autil.mk_add(mk_strlen(v2), mk_strlen(v3)))); + op2Items.push_back(ctx.mk_eq_atom(m_autil.mk_add(t2, t3), m_autil.mk_add(t1, mk_int(-1)))); + expr_ref opAnd2(ctx.mk_eq_atom(op1, mk_and(op2Items)), mgr); - op2Items.push_back(Z3_mk_eq(ctx, v1, unroll1)); - op2Items.push_back(Z3_mk_eq(ctx, mk_length(t, v1), mk_length(t, unroll1))); - op2Items.push_back(Z3_mk_eq(ctx, v4, unroll2)); - op2Items.push_back(Z3_mk_eq(ctx, mk_length(t, v4), mk_length(t, unroll2))); - Z3_ast v2v3 = mk_concat(t, v2, v3); - op2Items.push_back(Z3_mk_eq(ctx, v5, v2v3)); - reduceVirtualRegexIn(t, v5, r1, op2Items); - op2Items.push_back(Z3_mk_eq(ctx, mk_length(t, v5), mk_2_add(t, mk_length(t, v2), mk_length(t, v3)))); - op2Items.push_back(Z3_mk_eq(ctx, mk_2_add(t, t2, t3), mk_2_sub(t, t1, mk_int(ctx, 1)))); - Z3_ast opAnd2 = Z3_mk_eq(ctx, op1, mk_and_fromVector(t, op2Items)); + toAssert = mgr.mk_and(opAnd1, opAnd2); + m_trail.push_back(toAssert); + concat_eq_unroll_ast_map[key] = toAssert; + } else { + toAssert = concat_eq_unroll_ast_map[key]; + } - toAssert = mk_2_and(t, opAnd1, opAnd2); - concatEqUnroll_AstMap[key] = toAssert; - } else { - toAssert = concatEqUnroll_AstMap[key]; - } - - addAxiom(t, toAssert, __LINE__); - */ + assert_axiom(toAssert); } void theory_str::unroll_str2reg_constStr(expr * unrollFunc, expr * eqConstStr) { diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index daf534686..154a66c58 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -163,6 +163,7 @@ namespace smt { // we would need to modify the allocator so we pass in ast_manager std::map, ptr_vector > > unroll_tries_map; std::map unroll_var_map; + std::map, expr*> concat_eq_unroll_ast_map; char * char_set; std::map charSetLookupTable; From 9eead64d03a3e9c58313d5116a2891a031edb567 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 6 Jul 2016 17:31:37 -0400 Subject: [PATCH 139/410] prevent assertion of basic string axioms on variables that go out of scope (theory_str) this is testing a crash avoidance feature, the regression is tests/z3str/regex-026.smt2 this also adds some debugging code for a substr() crash but that is WIP --- src/smt/theory_str.cpp | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 853924a94..c28132feb 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -799,6 +799,13 @@ void theory_str::instantiate_basic_string_axioms(enode * str) { context & ctx = get_context(); ast_manager & m = get_manager(); + // TESTING: attempt to avoid a crash here when a variable goes out of scope + // TODO this seems to work so we probably need to do this for other propagate checks, etc. + if (str->get_iscope_lvl() > ctx.get_scope_level()) { + TRACE("t_str_detail", tout << "WARNING: skipping axiom setup on out-of-scope string term" << std::endl;); + return; + } + // generate a stronger axiom for constant strings app * a_str = str->get_owner(); if (m_strutil.is_string(str->get_owner())) { @@ -1400,6 +1407,7 @@ void theory_str::reset_eh() { m_basicstr_axiom_todo.reset(); m_str_eq_todo.reset(); m_concat_axiom_todo.reset(); + // TODO reset a loooooot more internal stuff pop_scope_eh(get_context().get_scope_level()); } @@ -2714,8 +2722,25 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { l_count = 2; lenDelta = str_len - y_len; } + TRACE("t_str", + tout + << "xLen? " << (x_len_exists ? "yes" : "no") << std::endl + << "mLen? " << (m_len_exists ? "yes" : "no") << std::endl + << "yLen? " << (y_len_exists ? "yes" : "no") << std::endl + << "xLen = " << x_len.to_string() << std::endl + << "yLen = " << y_len.to_string() << std::endl + << "mLen = " << m_len.to_string() << std::endl + << "strLen = " << str_len.to_string() << std::endl + << "lenDelta = " << lenDelta.to_string() << std::endl + << "strValue = \"" << strValue << "\" (len=" << strValue.length() << ")" << std::endl + ; + ); + + TRACE("t_str", tout << "*** MARKER 1 ***" << std::endl;); std::string part1Str = strValue.substr(0, lenDelta.get_unsigned()); + TRACE("t_str", tout << "*** MARKER 2 ***" << std::endl;); std::string part2Str = strValue.substr(lenDelta.get_unsigned(), strValue.length() - lenDelta.get_unsigned()); + TRACE("t_str", tout << "*** MARKER 3 ***" << std::endl;); expr_ref prefixStr(m_strutil.mk_string(part1Str), mgr); expr_ref x_concat(mk_concat(m, prefixStr), mgr); @@ -5495,7 +5520,7 @@ final_check_status theory_str::final_check_eh() { } TRACE("t_str", tout << "final check" << std::endl;); - TRACE("t_str_detail", dump_assignments();); + TRACE("t_str_dump_assign", dump_assignments();); // run dependence analysis to find free string variables std::map varAppearInAssign; From 847a5fc1f82b437b46db0d9cc8b813560141050f Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Thu, 7 Jul 2016 16:13:48 -0400 Subject: [PATCH 140/410] replace old mk_value behaviour in theory_str that creates placeholders for unused terms instead of crashing --- src/smt/theory_str.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index c28132feb..0626c6ac5 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -6844,8 +6844,7 @@ model_value_proc * theory_str::mk_value(enode * n, model_generator & mg) { TRACE("t_str", tout << "WARNING: failed to find a concrete value, falling back" << std::endl;); // TODO make absolutely sure the reason we can't find a concrete value is because of an unassigned temporary // e.g. for an expression like (Concat X $$_str0) - //return alloc(expr_wrapper_proc, m_strutil.mk_string("**UNUSED**")); - NOT_IMPLEMENTED_YET(); + return alloc(expr_wrapper_proc, m_strutil.mk_string("**UNUSED**")); } } From 8aa6fee0af66b6e2dd465dc52b4dbc23c1b719b0 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Fri, 8 Jul 2016 12:21:11 -0400 Subject: [PATCH 141/410] fixups wip --- src/smt/theory_str.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 0626c6ac5..aaecdb011 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -141,6 +141,10 @@ void theory_str::assert_axiom(expr * e) { literal lit(ctx.get_literal(e)); ctx.mark_as_relevant(lit); ctx.mk_th_axiom(get_id(), 1, &lit); + + // crash/error avoidance: add all axioms to the trail + m_trail.push_back(e); + TRACE("t_str_detail", tout << "done asserting " << mk_ismt2_pp(e, get_manager()) << std::endl;); } @@ -4622,6 +4626,7 @@ void theory_str::pop_scope_eh(unsigned num_scopes) { unsigned count = 0; std::set vars = it->second; for (std::set::iterator var_it = vars.begin(); var_it != vars.end(); ++var_it) { + TRACE("t_str_detail", tout << "clean up variable " << mk_pp(*var_it, get_manager()) << std::endl;); variable_set.erase(*var_it); internal_variable_set.erase(*var_it); regex_variable_set.erase(*var_it); From 8d47b082446cf4292643a3fc1e333db2e355e09d Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sun, 10 Jul 2016 13:05:41 -0400 Subject: [PATCH 142/410] fix out-of-scope value tester bug in theory_str::gen_free_var_options() we now pass tests/z3str/charAt-003.smt2 with detailed debugging turned off! --- src/smt/theory_str.cpp | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index aaecdb011..06b221acd 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -5968,8 +5968,28 @@ expr * theory_str::gen_free_var_options(expr * freeVar, expr * len_indicator, int len = atoi(len_valueStr.c_str()); - if (fvar_valueTester_map[freeVar].find(len) == fvar_valueTester_map[freeVar].end()) { - TRACE("t_str_detail", tout << "no previous value testers" << std::endl;); + // check whether any value tester is actually in scope + // TODO NEXT we need to do this check for other tester variables that could potentially go out of scope + TRACE("t_str_detail", tout << "checking scope of previous value testers" << std::endl;); + bool map_effectively_empty = true; + if (fvar_valueTester_map[freeVar].find(len) != fvar_valueTester_map[freeVar].end()) { + // there's *something* in the map, but check its scope + svector > entries = fvar_valueTester_map[freeVar][len]; + for (svector >::iterator it = entries.begin(); it != entries.end(); ++it) { + std::pair entry = *it; + expr * aTester = entry.second; + if (internal_variable_set.find(aTester) == internal_variable_set.end()) { + TRACE("t_str_detail", tout << mk_pp(aTester, m) << " out of scope" << std::endl;); + } else { + TRACE("t_str_detail", tout << mk_pp(aTester, m) << " in scope" << std::endl;); + map_effectively_empty = false; + break; + } + } + } + + if (map_effectively_empty) { + TRACE("t_str_detail", tout << "no previous value testers, or none of them were in scope" << std::endl;); int tries = 0; expr * val_indicator = mk_internal_valTest_var(freeVar, len, tries); valueTester_fvar_map[val_indicator] = freeVar; From 9ffcd135d5d2217c1ddd0328228d352145ec71ec Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 19 Jul 2016 15:47:41 -0400 Subject: [PATCH 143/410] add RegexPlus to theory_str --- src/ast/rewriter/str_rewriter.cpp | 24 ++++++++++++++++++++++++ src/ast/rewriter/str_rewriter.h | 1 + src/ast/str_decl_plugin.cpp | 7 +++++++ src/ast/str_decl_plugin.h | 17 ++++++++++++++++- 4 files changed, 48 insertions(+), 1 deletion(-) diff --git a/src/ast/rewriter/str_rewriter.cpp b/src/ast/rewriter/str_rewriter.cpp index 3a0300ae4..a40d52aa1 100644 --- a/src/ast/rewriter/str_rewriter.cpp +++ b/src/ast/rewriter/str_rewriter.cpp @@ -219,6 +219,27 @@ br_status str_rewriter::mk_re_RegexIn(expr * str, expr * re, expr_ref & result) return BR_FAILED; } +br_status str_rewriter::mk_re_RegexPlus(expr * re, expr_ref & result) { + /* + * Two optimizations are possible if we inspect 're'. + * If 're' is (RegexPlus X), then reduce to 're'. + * If 're' is (RegexStar X), then reduce to 're'. + * Otherwise, reduce to (RegexConcat re (RegexStar re)). + */ + + if (m_strutil.is_re_RegexPlus(re)) { + result = re; + return BR_REWRITE_FULL; + } else if (m_strutil.is_re_RegexStar(re)) { + // Z3str2 re-created the AST under 're' here, but I don't think we need to do that + result = re; + return BR_REWRITE_FULL; + } else { + result = m_strutil.mk_re_RegexConcat(re, m_strutil.mk_re_RegexStar(re)); + return BR_REWRITE_FULL; + } +} + br_status str_rewriter::mk_app_core(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result) { SASSERT(f->get_family_id() == get_fid()); @@ -256,6 +277,9 @@ br_status str_rewriter::mk_app_core(func_decl * f, unsigned num_args, expr * con case OP_RE_REGEXIN: SASSERT(num_args == 2); return mk_re_RegexIn(args[0], args[1], result); + case OP_RE_REGEXPLUS: + SASSERT(num_args == 1); + return mk_re_RegexPlus(args[0], result); default: return BR_FAILED; } diff --git a/src/ast/rewriter/str_rewriter.h b/src/ast/rewriter/str_rewriter.h index 5c0e1167f..bd79ed7a1 100644 --- a/src/ast/rewriter/str_rewriter.h +++ b/src/ast/rewriter/str_rewriter.h @@ -51,6 +51,7 @@ public: br_status mk_re_Str2Reg(expr * str, expr_ref & result); br_status mk_re_RegexIn(expr * str, expr * re, expr_ref & result); + br_status mk_re_RegexPlus(expr * re, expr_ref & result); bool reduce_eq(expr * l, expr * r, expr_ref_vector & lhs, expr_ref_vector & rhs, bool & change); bool reduce_eq(expr_ref_vector& ls, expr_ref_vector& rs, expr_ref_vector& lhs, expr_ref_vector& rhs, bool& change); diff --git a/src/ast/str_decl_plugin.cpp b/src/ast/str_decl_plugin.cpp index ef94272c7..45ff37b0f 100644 --- a/src/ast/str_decl_plugin.cpp +++ b/src/ast/str_decl_plugin.cpp @@ -42,6 +42,7 @@ str_decl_plugin::str_decl_plugin(): m_re_regexstar_decl(0), m_re_regexunion_decl(0), m_re_unroll_decl(0), + m_re_regexplus_decl(0), m_arith_plugin(0), m_arith_fid(0), m_int_sort(0){ @@ -70,6 +71,7 @@ void str_decl_plugin::finalize(void) { DEC_REF(m_re_regexconcat_decl); DEC_REF(m_re_regexstar_decl); DEC_REF(m_re_regexunion_decl); + DEC_REF(m_re_regexplus_decl); DEC_REF(m_re_unroll_decl); DEC_REF(m_int_sort); } @@ -153,6 +155,9 @@ void str_decl_plugin::set_manager(ast_manager * m, family_id id) { m_re_regexstar_decl = m->mk_func_decl(symbol("RegexStar"), re, re, func_decl_info(id, OP_RE_REGEXSTAR)); m_manager->inc_ref(m_re_regexstar_decl); + m_re_regexplus_decl = m->mk_func_decl(symbol("RegexPlus"), re, re, func_decl_info(id, OP_RE_REGEXPLUS)); + m_manager->inc_ref(m_re_regexplus_decl); + m_re_regexunion_decl = m->mk_func_decl(symbol("RegexUnion"), re, re, re, func_decl_info(id, OP_RE_REGEXUNION)); m_manager->inc_ref(m_re_regexunion_decl); @@ -190,6 +195,7 @@ func_decl * str_decl_plugin::mk_func_decl(decl_kind k) { case OP_RE_REGEXIN: return m_re_regexin_decl; case OP_RE_REGEXCONCAT: return m_re_regexconcat_decl; case OP_RE_REGEXSTAR: return m_re_regexstar_decl; + case OP_RE_REGEXPLUS: return m_re_regexplus_decl; case OP_RE_REGEXUNION: return m_re_regexunion_decl; case OP_RE_UNROLL: return m_re_unroll_decl; default: return 0; @@ -262,6 +268,7 @@ void str_decl_plugin::get_op_names(svector & op_names, symbol cons op_names.push_back(builtin_name("RegexConcat", OP_RE_REGEXCONCAT)); op_names.push_back(builtin_name("RegexStar", OP_RE_REGEXSTAR)); op_names.push_back(builtin_name("RegexUnion", OP_RE_REGEXUNION)); + op_names.push_back(builtin_name("RegexPlus", OP_RE_REGEXPLUS)); op_names.push_back(builtin_name("Unroll", OP_RE_UNROLL)); } diff --git a/src/ast/str_decl_plugin.h b/src/ast/str_decl_plugin.h index c2ad088a4..902e2208f 100644 --- a/src/ast/str_decl_plugin.h +++ b/src/ast/str_decl_plugin.h @@ -48,6 +48,8 @@ enum str_op_kind { OP_RE_REGEXSTAR, OP_RE_REGEXUNION, OP_RE_UNROLL, + // higher-level regex operators + OP_RE_REGEXPLUS, // end LAST_STR_OP }; @@ -77,6 +79,7 @@ protected: func_decl * m_re_regexstar_decl; func_decl * m_re_regexunion_decl; func_decl * m_re_unroll_decl; + func_decl * m_re_regexplus_decl; arith_decl_plugin * m_arith_plugin; family_id m_arith_fid; @@ -120,6 +123,8 @@ public: bool is_string(expr const * n) const; bool is_re_Str2Reg(expr const * n) const { return is_app_of(n, get_fid(), OP_RE_STR2REGEX); } + bool is_re_RegexStar(expr const * n) const { return is_app_of(n, get_fid(), OP_RE_REGEXSTAR); } + bool is_re_RegexPlus(expr const * n) const { return is_app_of(n, get_fid(), OP_RE_REGEXPLUS); } std::string get_string_constant_value(expr const *n) const; // TODO @@ -142,7 +147,17 @@ public: app * mk_fresh_string() { return m_plugin->mk_fresh_string(); } - // TODO + + app * mk_re_RegexConcat(expr * e1, expr * e2) { + expr * es[2] = {e1, e2}; + return m_manager.mk_app(get_fid(), OP_RE_REGEXCONCAT, 2, es); + } + + app * mk_re_RegexStar(expr * r) { + expr * es[1] = {r}; + return m_manager.mk_app(get_fid(), OP_RE_REGEXSTAR, 1, es); + } + }; #endif /* _STR_DECL_PLUGIN_H_ */ From 0f382037799c9ccab63ecb7cf5ea97c1a1aab3ac Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 19 Jul 2016 16:39:43 -0400 Subject: [PATCH 144/410] add RegexCharRange to theory_str --- src/ast/rewriter/str_rewriter.cpp | 37 ++++++++++++++++++++++++++++--- src/ast/rewriter/str_rewriter.h | 1 + src/ast/str_decl_plugin.cpp | 7 ++++++ src/ast/str_decl_plugin.h | 16 +++++++++++++ 4 files changed, 58 insertions(+), 3 deletions(-) diff --git a/src/ast/rewriter/str_rewriter.cpp b/src/ast/rewriter/str_rewriter.cpp index a40d52aa1..1449afcc3 100644 --- a/src/ast/rewriter/str_rewriter.cpp +++ b/src/ast/rewriter/str_rewriter.cpp @@ -200,8 +200,7 @@ br_status str_rewriter::mk_str_Replace(expr * base, expr * source, expr * target br_status str_rewriter::mk_re_Str2Reg(expr * str, expr_ref & result) { // the argument to Str2Reg *must* be a string constant - // TODO is an assertion error too strict here? this basically crashes the solver - VERIFY(m_strutil.is_string(str)); + ENSURE(m_strutil.is_string(str)); return BR_FAILED; } @@ -211,7 +210,7 @@ br_status str_rewriter::mk_re_RegexIn(expr * str, expr * re, expr_ref & result) if (m_strutil.is_re_Str2Reg(re)) { TRACE("t_str_rw", tout << "RegexIn fast path: " << mk_pp(str, m()) << " in " << mk_pp(re, m()) << std::endl;); expr * regexStr = to_app(re)->get_arg(0); - VERIFY(m_strutil.is_string(regexStr)); + ENSURE(m_strutil.is_string(regexStr)); result = m().mk_eq(str, regexStr); return BR_REWRITE_FULL; } @@ -240,6 +239,35 @@ br_status str_rewriter::mk_re_RegexPlus(expr * re, expr_ref & result) { } } +br_status str_rewriter::mk_re_RegexCharRange(expr * start, expr * end, expr_ref & result) { + TRACE("t_str_rw", tout << "rewrite (RegexCharRange " << mk_pp(start, m()) << " " << mk_pp(end, m()) << ")" << std::endl;); + // both 'start' and 'end' must be string constants + ENSURE(m_strutil.is_string(start) && m_strutil.is_string(end)); + std::string arg0Value = m_strutil.get_string_constant_value(start); + std::string arg1Value = m_strutil.get_string_constant_value(end); + ENSURE(arg0Value.length() == 1 && arg1Value.length() == 1); + char low = arg0Value[0]; + char high = arg1Value[0]; + if (low > high) { + char t = low; + low = high; + high = t; + } + + char c = low; + std::string cStr; + cStr.push_back(c); + expr * res = m_strutil.mk_re_Str2Reg(cStr); + c++; + for (; c <= high; c++) { + cStr.clear(); + cStr.push_back(c); + res = m_strutil.mk_re_RegexUnion(res, m_strutil.mk_re_Str2Reg(cStr)); + } + result = res; + return BR_DONE; +} + br_status str_rewriter::mk_app_core(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result) { SASSERT(f->get_family_id() == get_fid()); @@ -280,6 +308,9 @@ br_status str_rewriter::mk_app_core(func_decl * f, unsigned num_args, expr * con case OP_RE_REGEXPLUS: SASSERT(num_args == 1); return mk_re_RegexPlus(args[0], result); + case OP_RE_REGEXCHARRANGE: + SASSERT(num_args == 2); + return mk_re_RegexCharRange(args[0], args[1], result); default: return BR_FAILED; } diff --git a/src/ast/rewriter/str_rewriter.h b/src/ast/rewriter/str_rewriter.h index bd79ed7a1..dccf4a6bd 100644 --- a/src/ast/rewriter/str_rewriter.h +++ b/src/ast/rewriter/str_rewriter.h @@ -52,6 +52,7 @@ public: br_status mk_re_Str2Reg(expr * str, expr_ref & result); br_status mk_re_RegexIn(expr * str, expr * re, expr_ref & result); br_status mk_re_RegexPlus(expr * re, expr_ref & result); + br_status mk_re_RegexCharRange(expr * start, expr * end, expr_ref & result); bool reduce_eq(expr * l, expr * r, expr_ref_vector & lhs, expr_ref_vector & rhs, bool & change); bool reduce_eq(expr_ref_vector& ls, expr_ref_vector& rs, expr_ref_vector& lhs, expr_ref_vector& rhs, bool& change); diff --git a/src/ast/str_decl_plugin.cpp b/src/ast/str_decl_plugin.cpp index 45ff37b0f..08358d46b 100644 --- a/src/ast/str_decl_plugin.cpp +++ b/src/ast/str_decl_plugin.cpp @@ -43,6 +43,7 @@ str_decl_plugin::str_decl_plugin(): m_re_regexunion_decl(0), m_re_unroll_decl(0), m_re_regexplus_decl(0), + m_re_regexcharrange_decl(0), m_arith_plugin(0), m_arith_fid(0), m_int_sort(0){ @@ -72,6 +73,7 @@ void str_decl_plugin::finalize(void) { DEC_REF(m_re_regexstar_decl); DEC_REF(m_re_regexunion_decl); DEC_REF(m_re_regexplus_decl); + DEC_REF(m_re_regexcharrange_decl); DEC_REF(m_re_unroll_decl); DEC_REF(m_int_sort); } @@ -164,6 +166,9 @@ void str_decl_plugin::set_manager(ast_manager * m, family_id id) { m_re_unroll_decl = m->mk_func_decl(symbol("Unroll"), re, i, s, func_decl_info(id, OP_RE_UNROLL)); m_manager->inc_ref(m_re_unroll_decl); + m_re_regexcharrange_decl = m->mk_func_decl(symbol("RegexCharRange"), s, s, re, func_decl_info(id, OP_RE_REGEXCHARRANGE)); + m_manager->inc_ref(m_re_regexcharrange_decl); + } decl_plugin * str_decl_plugin::mk_fresh() { @@ -198,6 +203,7 @@ func_decl * str_decl_plugin::mk_func_decl(decl_kind k) { case OP_RE_REGEXPLUS: return m_re_regexplus_decl; case OP_RE_REGEXUNION: return m_re_regexunion_decl; case OP_RE_UNROLL: return m_re_unroll_decl; + case OP_RE_REGEXCHARRANGE: return m_re_regexcharrange_decl; default: return 0; } } @@ -270,6 +276,7 @@ void str_decl_plugin::get_op_names(svector & op_names, symbol cons op_names.push_back(builtin_name("RegexUnion", OP_RE_REGEXUNION)); op_names.push_back(builtin_name("RegexPlus", OP_RE_REGEXPLUS)); op_names.push_back(builtin_name("Unroll", OP_RE_UNROLL)); + op_names.push_back(builtin_name("RegexCharRange", OP_RE_REGEXCHARRANGE)); } void str_decl_plugin::get_sort_names(svector & sort_names, symbol const & logic) { diff --git a/src/ast/str_decl_plugin.h b/src/ast/str_decl_plugin.h index 902e2208f..4b7a8858e 100644 --- a/src/ast/str_decl_plugin.h +++ b/src/ast/str_decl_plugin.h @@ -50,6 +50,7 @@ enum str_op_kind { OP_RE_UNROLL, // higher-level regex operators OP_RE_REGEXPLUS, + OP_RE_REGEXCHARRANGE, // end LAST_STR_OP }; @@ -80,6 +81,7 @@ protected: func_decl * m_re_regexunion_decl; func_decl * m_re_unroll_decl; func_decl * m_re_regexplus_decl; + func_decl * m_re_regexcharrange_decl; arith_decl_plugin * m_arith_plugin; family_id m_arith_fid; @@ -148,6 +150,20 @@ public: return m_plugin->mk_fresh_string(); } + app * mk_re_Str2Reg(expr * s) { + expr * es[1] = {s}; + return m_manager.mk_app(get_fid(), OP_RE_STR2REGEX, 1, es); + } + + app * mk_re_Str2Reg(std::string s) { + return mk_re_Str2Reg(mk_string(s)); + } + + app * mk_re_RegexUnion(expr * e1, expr * e2) { + expr * es[2] = {e1, e2}; + return m_manager.mk_app(get_fid(), OP_RE_REGEXUNION, 2, es); + } + app * mk_re_RegexConcat(expr * e1, expr * e2) { expr * es[2] = {e1, e2}; return m_manager.mk_app(get_fid(), OP_RE_REGEXCONCAT, 2, es); From ac16aa7c818e5bd3ead0ab45c5d03cc0953d162b Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sat, 23 Jul 2016 16:02:11 -0400 Subject: [PATCH 145/410] fix out-of-scope variable bug in theory_str::process_concat_eq_type6 this fix will have to be made to all functions that use varForBreakConcat --- src/smt/theory_str.cpp | 47 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 06b221acd..28f972164 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -3363,18 +3363,53 @@ void theory_str::process_concat_eq_type6(expr * concatAst1, expr * concatAst2) { expr * xorFlag = NULL; std::pair key1(concatAst1, concatAst2); std::pair key2(concatAst2, concatAst1); - if (varForBreakConcat.find(key1) == varForBreakConcat.end() && varForBreakConcat.find(key2) == varForBreakConcat.end()) { + + // check the entries in this map to make sure they're still in scope + // before we use them. + // TODO something very similar might have to be done elsewhere when we use this map, if this works. + + std::map, std::map >::iterator entry1 = varForBreakConcat.find(key1); + std::map, std::map >::iterator entry2 = varForBreakConcat.find(key2); + + bool entry1InScope; + if (entry1 == varForBreakConcat.end()) { + entry1InScope = false; + } else { + if (internal_variable_set.find((entry1->second)[0]) == internal_variable_set.end() + || internal_variable_set.find((entry1->second)[1]) == internal_variable_set.end()) { + entry1InScope = false; + } else { + entry1InScope = true; + } + } + + bool entry2InScope; + if (entry2 == varForBreakConcat.end()) { + entry2InScope = false; + } else { + if (internal_variable_set.find((entry2->second)[0]) == internal_variable_set.end() + || internal_variable_set.find((entry2->second)[1]) == internal_variable_set.end()) { + entry2InScope = false; + } else { + entry2InScope = true; + } + } + + TRACE("t_str_detail", tout << "entry 1 " << (entry1InScope ? "in scope" : "not in scope") << std::endl + << "entry 2 " << (entry2InScope ? "in scope" : "not in scope") << std::endl;); + + if (!entry1InScope && !entry2InScope) { commonVar = mk_nonempty_str_var(); xorFlag = mk_internal_xor_var(); varForBreakConcat[key1][0] = commonVar; varForBreakConcat[key1][1] = xorFlag; } else { - if (varForBreakConcat.find(key1) != varForBreakConcat.end()) { - commonVar = varForBreakConcat[key1][0]; - xorFlag = varForBreakConcat[key1][1]; + if (entry1InScope) { + commonVar = (entry1->second)[0]; + xorFlag = (entry1->second)[1]; } else { - commonVar = varForBreakConcat[key2][0]; - xorFlag = varForBreakConcat[key2][1]; + commonVar = (entry2->second)[0]; + xorFlag = (entry2->second)[1]; } } From 02a66c425ee6603961802de8c1a64163f61b4fe4 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sat, 23 Jul 2016 22:43:46 -0400 Subject: [PATCH 146/410] add option to bypass quick returns in integer theory integration in theory_str this might not actually be that useful, if the problem is, as I suspect it to be, that values we get from the integer theory need not correspond with assertions in the core (that can get popped off the stack, etc.) --- src/smt/theory_str.cpp | 212 ++++++++++++++++++++++++++++++++++++----- src/smt/theory_str.h | 9 ++ 2 files changed, 196 insertions(+), 25 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 28f972164..d77290b46 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -33,6 +33,7 @@ theory_str::theory_str(ast_manager & m): opt_EagerStringConstantLengthAssertions(true), opt_VerifyFinalCheckProgress(true), opt_LCMUnrollStep(2), + opt_NoQuickReturn_Concat_IntegerTheory(true), /* Internal setup */ search_started(false), m_autil(m), @@ -145,7 +146,7 @@ void theory_str::assert_axiom(expr * e) { // crash/error avoidance: add all axioms to the trail m_trail.push_back(e); - TRACE("t_str_detail", tout << "done asserting " << mk_ismt2_pp(e, get_manager()) << std::endl;); + //TRACE("t_str_detail", tout << "done asserting " << mk_ismt2_pp(e, get_manager()) << std::endl;); } void theory_str::assert_implication(expr * premise, expr * conclusion) { @@ -2049,8 +2050,16 @@ void theory_str::simplify_concat_equality(expr * nn1, expr * nn2) { TRACE("t_str", tout << "nn1 = " << mk_ismt2_pp(nn1, m) << std::endl << "nn2 = " << mk_ismt2_pp(nn2, m) << std::endl;); + TRACE("t_str_detail", tout + << "len(" << mk_pp(a1_arg0, m) << ") = " << (a1_arg0_len_exists ? a1_arg0_len.to_string() : "?") << std::endl + << "len(" << mk_pp(a1_arg1, m) << ") = " << (a1_arg1_len_exists ? a1_arg1_len.to_string() : "?") << std::endl + << "len(" << mk_pp(a2_arg0, m) << ") = " << (a2_arg0_len_exists ? a2_arg0_len.to_string() : "?") << std::endl + << "len(" << mk_pp(a2_arg1, m) << ") = " << (a2_arg1_len_exists ? a2_arg1_len.to_string() : "?") << std::endl + << std::endl;); + infer_len_concat_equality(nn1, nn2); + // TODO we may want to add no-quick-return options for these as well if (a1_arg0 == a2_arg0) { if (!in_same_eqc(a1_arg1, a2_arg1)) { expr_ref premise(ctx.mk_eq_atom(nn1, nn2), m); @@ -2077,6 +2086,8 @@ void theory_str::simplify_concat_equality(expr * nn1, expr * nn2) { // quick path + // TODO we may want to add no-quick-return options for these as well + if (in_same_eqc(a1_arg0, a2_arg0)) { if (in_same_eqc(a1_arg1, a2_arg1)) { TRACE("t_str_detail", tout << "SKIP: a1_arg0 =~ a2_arg0 and a1_arg1 =~ a2_arg1" << std::endl;); @@ -2111,7 +2122,12 @@ void theory_str::simplify_concat_equality(expr * nn1, expr * nn2) { expr_ref conclusion(m.mk_and(ax_r1, ax_r2), m); assert_implication(premise, conclusion); - return; + + if (opt_NoQuickReturn_Concat_IntegerTheory) { + TRACE("t_str_detail", tout << "bypassing quick return from the end of this case" << std::endl;); + } else { + return; + } } } @@ -2127,7 +2143,11 @@ void theory_str::simplify_concat_equality(expr * nn1, expr * nn2) { expr_ref conclusion(m.mk_and(ax_r1, ax_r2), m); assert_implication(premise, conclusion); - return; + if (opt_NoQuickReturn_Concat_IntegerTheory) { + TRACE("t_str_detail", tout << "bypassing quick return from the end of this case" << std::endl;); + } else { + return; + } } } @@ -2328,7 +2348,42 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { std::pair key1(concatAst1, concatAst2); std::pair key2(concatAst2, concatAst1); - if (varForBreakConcat.find(key1) == varForBreakConcat.end() && varForBreakConcat.find(key2) == varForBreakConcat.end()) { + // check the entries in this map to make sure they're still in scope + // before we use them. + + std::map, std::map >::iterator entry1 = varForBreakConcat.find(key1); + std::map, std::map >::iterator entry2 = varForBreakConcat.find(key2); + + bool entry1InScope; + if (entry1 == varForBreakConcat.end()) { + entry1InScope = false; + } else { + if (internal_variable_set.find((entry1->second)[0]) == internal_variable_set.end() + || internal_variable_set.find((entry1->second)[1]) == internal_variable_set.end() + || internal_variable_set.find((entry1->second)[2]) == internal_variable_set.end()) { + entry1InScope = false; + } else { + entry1InScope = true; + } + } + + bool entry2InScope; + if (entry2 == varForBreakConcat.end()) { + entry2InScope = false; + } else { + if (internal_variable_set.find((entry2->second)[0]) == internal_variable_set.end() + || internal_variable_set.find((entry2->second)[1]) == internal_variable_set.end() + || internal_variable_set.find((entry2->second)[2]) == internal_variable_set.end()) { + entry2InScope = false; + } else { + entry2InScope = true; + } + } + + TRACE("t_str_detail", tout << "entry 1 " << (entry1InScope ? "in scope" : "not in scope") << std::endl + << "entry 2 " << (entry2InScope ? "in scope" : "not in scope") << std::endl;); + + if (!entry1InScope && !entry2InScope) { t1 = mk_nonempty_str_var(); t2 = mk_nonempty_str_var(); xorFlag = mk_internal_xor_var(); @@ -2339,7 +2394,7 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { varForBreakConcat[key1][2] = xorFlag; } else { // match found - if (varForBreakConcat.find(key1) != varForBreakConcat.end()) { + if (entry1InScope) { t1 = varForBreakConcat[key1][0]; t2 = varForBreakConcat[key1][1]; xorFlag = varForBreakConcat[key1][2]; @@ -2619,17 +2674,50 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { std::pair key1(concatAst1, concatAst2); std::pair key2(concatAst2, concatAst1); - if (varForBreakConcat.find(key1) == varForBreakConcat.end() - && varForBreakConcat.find(key2) == varForBreakConcat.end()) { + // check the entries in this map to make sure they're still in scope + // before we use them. + + std::map, std::map >::iterator entry1 = varForBreakConcat.find(key1); + std::map, std::map >::iterator entry2 = varForBreakConcat.find(key2); + + bool entry1InScope; + if (entry1 == varForBreakConcat.end()) { + entry1InScope = false; + } else { + if (internal_variable_set.find((entry1->second)[0]) == internal_variable_set.end() + || internal_variable_set.find((entry1->second)[1]) == internal_variable_set.end()) { + entry1InScope = false; + } else { + entry1InScope = true; + } + } + + bool entry2InScope; + if (entry2 == varForBreakConcat.end()) { + entry2InScope = false; + } else { + if (internal_variable_set.find((entry2->second)[0]) == internal_variable_set.end() + || internal_variable_set.find((entry2->second)[1]) == internal_variable_set.end()) { + entry2InScope = false; + } else { + entry2InScope = true; + } + } + + TRACE("t_str_detail", tout << "entry 1 " << (entry1InScope ? "in scope" : "not in scope") << std::endl + << "entry 2 " << (entry2InScope ? "in scope" : "not in scope") << std::endl;); + + + if (!entry1InScope && !entry2InScope) { temp1 = mk_nonempty_str_var(); xorFlag = mk_internal_xor_var(); varForBreakConcat[key1][0] = temp1; varForBreakConcat[key1][1] = xorFlag; } else { - if (varForBreakConcat.find(key1) != varForBreakConcat.end()) { + if (entry1InScope) { temp1 = varForBreakConcat[key1][0]; xorFlag = varForBreakConcat[key1][1]; - } else if (varForBreakConcat.find(key2) != varForBreakConcat.end()) { + } else if (entry2InScope) { temp1 = varForBreakConcat[key2][0]; xorFlag = varForBreakConcat[key2][1]; } @@ -2888,7 +2976,6 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { std::string strValue = m_strutil.get_string_constant_value(strAst); - // TODO integer theory interaction rational x_len, y_len, str_len, n_len; bool x_len_exists = get_len_value(x, x_len); bool y_len_exists = get_len_value(y, y_len); @@ -2899,14 +2986,49 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { expr_ref temp1(mgr); std::pair key1(concatAst1, concatAst2); std::pair key2(concatAst2, concatAst1); - if (varForBreakConcat.find(key1) == varForBreakConcat.end() && varForBreakConcat.find(key2) == varForBreakConcat.end()) { + + // check the entries in this map to make sure they're still in scope + // before we use them. + + std::map, std::map >::iterator entry1 = varForBreakConcat.find(key1); + std::map, std::map >::iterator entry2 = varForBreakConcat.find(key2); + + bool entry1InScope; + if (entry1 == varForBreakConcat.end()) { + entry1InScope = false; + } else { + if (internal_variable_set.find((entry1->second)[0]) == internal_variable_set.end() + || internal_variable_set.find((entry1->second)[1]) == internal_variable_set.end()) { + entry1InScope = false; + } else { + entry1InScope = true; + } + } + + bool entry2InScope; + if (entry2 == varForBreakConcat.end()) { + entry2InScope = false; + } else { + if (internal_variable_set.find((entry2->second)[0]) == internal_variable_set.end() + || internal_variable_set.find((entry2->second)[1]) == internal_variable_set.end()) { + entry2InScope = false; + } else { + entry2InScope = true; + } + } + + TRACE("t_str_detail", tout << "entry 1 " << (entry1InScope ? "in scope" : "not in scope") << std::endl + << "entry 2 " << (entry2InScope ? "in scope" : "not in scope") << std::endl;); + + + if (!entry1InScope && !entry2InScope) { temp1 = mk_nonempty_str_var(); xorFlag = mk_internal_xor_var(); varForBreakConcat[key1][0] = temp1; varForBreakConcat[key1][1] = xorFlag; } else { - if (varForBreakConcat.find(key1) != varForBreakConcat.end()) { + if (entry1InScope) { temp1 = varForBreakConcat[key1][0]; xorFlag = varForBreakConcat[key1][1]; } else if (varForBreakConcat.find(key2) != varForBreakConcat.end()) { @@ -3366,7 +3488,6 @@ void theory_str::process_concat_eq_type6(expr * concatAst1, expr * concatAst2) { // check the entries in this map to make sure they're still in scope // before we use them. - // TODO something very similar might have to be done elsewhere when we use this map, if this works. std::map, std::map >::iterator entry1 = varForBreakConcat.find(key1); std::map, std::map >::iterator entry2 = varForBreakConcat.find(key2); @@ -4084,16 +4205,44 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { expr_ref xorFlag(m); std::pair key1(arg1, arg2); std::pair key2(arg2, arg1); - std::map, std::map >::iterator varBreak_key1 = - varForBreakConcat.find(key1); - std::map, std::map >::iterator varBreak_key2 = - varForBreakConcat.find(key2); - if (varBreak_key1 == varForBreakConcat.end() && varBreak_key2 == varForBreakConcat.end()) { + + // check the entries in this map to make sure they're still in scope + // before we use them. + + std::map, std::map >::iterator entry1 = varForBreakConcat.find(key1); + std::map, std::map >::iterator entry2 = varForBreakConcat.find(key2); + + bool entry1InScope; + if (entry1 == varForBreakConcat.end()) { + entry1InScope = false; + } else { + if (internal_variable_set.find((entry1->second)[0]) == internal_variable_set.end()) { + entry1InScope = false; + } else { + entry1InScope = true; + } + } + + bool entry2InScope; + if (entry2 == varForBreakConcat.end()) { + entry2InScope = false; + } else { + if (internal_variable_set.find((entry2->second)[0]) == internal_variable_set.end()) { + entry2InScope = false; + } else { + entry2InScope = true; + } + } + + TRACE("t_str_detail", tout << "entry 1 " << (entry1InScope ? "in scope" : "not in scope") << std::endl + << "entry 2 " << (entry2InScope ? "in scope" : "not in scope") << std::endl;); + + if (!entry1InScope && !entry2InScope) { xorFlag = mk_internal_xor_var(); varForBreakConcat[key1][0] = xorFlag; - } else if (varBreak_key1 != varForBreakConcat.end()) { + } else if (entry1InScope) { xorFlag = varForBreakConcat[key1][0]; - } else { // varBreak_key2 != varForBreakConcat.end() + } else { // entry2InScope xorFlag = varForBreakConcat[key2][0]; } @@ -4632,7 +4781,7 @@ void theory_str::push_scope_eh() { context & ctx = get_context(); sLevel += 1; TRACE("t_str", tout << "push to " << sLevel << std::endl;); - TRACE("t_str_dump_assign", dump_assignments();); + TRACE("t_str_dump_assign_on_scope_change", dump_assignments();); } void theory_str::pop_scope_eh(unsigned num_scopes) { @@ -4683,7 +4832,7 @@ void theory_str::dump_assignments() { ctx.get_assignments(assignments); for (expr_ref_vector::iterator i = assignments.begin(); i != assignments.end(); ++i) { expr * ex = *i; - tout << mk_ismt2_pp(ex, m) << std::endl; + tout << mk_ismt2_pp(ex, m) << (ctx.is_relevant(ex) ? "" : " (NOT REL)") << std::endl; } ); } @@ -4697,6 +4846,9 @@ void theory_str::classify_ast_by_type(expr * node, std::map & varMap if (variable_set.find(node) != variable_set.end() && internal_lenTest_vars.find(node) == internal_lenTest_vars.end() && internal_valTest_vars.find(node) == internal_valTest_vars.end()) { + if (varMap[node] != 1) { + TRACE("t_str_detail", tout << "new variable: " << mk_pp(node, get_manager()) << std::endl;); + } varMap[node] = 1; } // check whether the node is a function that we want to inspect @@ -4755,6 +4907,10 @@ void theory_str::classify_ast_by_type_in_positive_context(std::map & // so we bypass a huge amount of work by doing the following... if (m.is_eq(argAst)) { + TRACE("t_str_detail", tout + << "eq ast " << mk_pp(argAst, m) << " is between args of sort " + << m.get_sort(to_app(argAst)->get_arg(0))->get_name() + << std::endl;); classify_ast_by_type(argAst, varMap, concatMap, unrollMap); } } @@ -4976,6 +5132,7 @@ int theory_str::ctx_dep_analysis(std::map & strVarMap, std::map::iterator it = variable_set.begin(); it != variable_set.end(); ++it) { expr* var = *it; if (internal_variable_set.find(var) == internal_variable_set.end()) { + TRACE("t_str_detail", tout << "new variable: " << mk_pp(var, m) << std::endl;); strVarMap[*it] = 1; } } @@ -5716,7 +5873,7 @@ final_check_status theory_str::final_check_eh() { constValue = NULL; { - TRACE("t_str_detail", tout << "free var map (# " << freeVar_map.size() << "):" << std::endl; + TRACE("t_str_detail", tout << "free var map (#" << freeVar_map.size() << "):" << std::endl; for (std::map::iterator freeVarItor1 = freeVar_map.begin(); freeVarItor1 != freeVar_map.end(); freeVarItor1++) { expr * freeVar = freeVarItor1->first; rational lenValue; @@ -5764,7 +5921,7 @@ final_check_status theory_str::final_check_eh() { // experimental free variable assignment - end // now deal with removed free variables that are bounded by an unroll - TRACE("t_str", tout << "fv_unrolls_map (#" << fv_unrolls_map.size() << ")" << std::endl;); + TRACE("t_str", tout << "fv_unrolls_map (#" << fv_unrolls_map.size() << "):" << std::endl;); for (std::map >::iterator fvIt1 = fv_unrolls_map.begin(); fvIt1 != fv_unrolls_map.end(); fvIt1++) { expr * var = fvIt1->first; @@ -6004,7 +6161,6 @@ expr * theory_str::gen_free_var_options(expr * freeVar, expr * len_indicator, int len = atoi(len_valueStr.c_str()); // check whether any value tester is actually in scope - // TODO NEXT we need to do this check for other tester variables that could potentially go out of scope TRACE("t_str_detail", tout << "checking scope of previous value testers" << std::endl;); bool map_effectively_empty = true; if (fvar_valueTester_map[freeVar].find(len) != fvar_valueTester_map[freeVar].end()) { @@ -6042,6 +6198,12 @@ expr * theory_str::gen_free_var_options(expr * freeVar, expr * len_indicator, for (; i < testerTotal; i++) { expr * aTester = fvar_valueTester_map[freeVar][len][i].second; + // it's probably worth checking scope here, actually + if (internal_variable_set.find(aTester) == internal_variable_set.end()) { + TRACE("t_str_detail", tout << "value tester " << mk_pp(aTester, m) << " out of scope, skipping" << std::endl;); + continue; + } + if (aTester == valTesterInCbEq) { break; } diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 154a66c58..d2b51a712 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -94,6 +94,15 @@ namespace smt { */ int opt_LCMUnrollStep; + /* + * If NoQuickReturn_Concat_IntegerTheory is set to true, + * the integer theory integration conditionals in simplify_concat_equality() + * will not return from the function after asserting their axioms. + * This means that control will fall through to the type 1-6 axioms, + * causing those to be added as well. + */ + bool opt_NoQuickReturn_Concat_IntegerTheory; + bool search_started; arith_util m_autil; str_util m_strutil; From f555074e27c6b570546cd27bd5410cf88af3faf1 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sat, 23 Jul 2016 23:29:56 -0400 Subject: [PATCH 147/410] add option to disable integer theory integration in theory_str; this is currently ENABLED --- src/smt/theory_str.cpp | 21 +++++++++++++++++++++ src/smt/theory_str.h | 16 ++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index d77290b46..35bc7ab20 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -34,6 +34,7 @@ theory_str::theory_str(ast_manager & m): opt_VerifyFinalCheckProgress(true), opt_LCMUnrollStep(2), opt_NoQuickReturn_Concat_IntegerTheory(true), + opt_DisableIntegerTheoryIntegration(true), /* Internal setup */ search_started(false), m_autil(m), @@ -3747,6 +3748,11 @@ static theory_mi_arith* get_th_arith(context& ctx, theory_id afid, expr* e) { } bool theory_str::get_value(expr* e, rational& val) const { + if (opt_DisableIntegerTheoryIntegration) { + TRACE("t_str_detail", tout << "WARNING: integer theory integration disabled" << std::endl;); + return false; + } + context& ctx = get_context(); ast_manager & m = get_manager(); theory_mi_arith* tha = get_th_arith(ctx, m_autil.get_family_id(), e); @@ -3773,6 +3779,11 @@ bool theory_str::get_value(expr* e, rational& val) const { } bool theory_str::lower_bound(expr* _e, rational& lo) { + if (opt_DisableIntegerTheoryIntegration) { + TRACE("t_str_detail", tout << "WARNING: integer theory integration disabled" << std::endl;); + return false; + } + context& ctx = get_context(); ast_manager & m = get_manager(); theory_mi_arith* tha = get_th_arith(ctx, m_autil.get_family_id(), _e); @@ -3782,6 +3793,11 @@ bool theory_str::lower_bound(expr* _e, rational& lo) { } bool theory_str::upper_bound(expr* _e, rational& hi) { + if (opt_DisableIntegerTheoryIntegration) { + TRACE("t_str_detail", tout << "WARNING: integer theory integration disabled" << std::endl;); + return false; + } + context& ctx = get_context(); ast_manager & m = get_manager(); theory_mi_arith* tha = get_th_arith(ctx, m_autil.get_family_id(), _e); @@ -3791,6 +3807,11 @@ bool theory_str::upper_bound(expr* _e, rational& hi) { } bool theory_str::get_len_value(expr* e, rational& val) { + if (opt_DisableIntegerTheoryIntegration) { + TRACE("t_str_detail", tout << "WARNING: integer theory integration disabled" << std::endl;); + return false; + } + context& ctx = get_context(); ast_manager & m = get_manager(); theory* th = ctx.get_theory(m_autil.get_family_id()); diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index d2b51a712..0ba4a1a4d 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -100,9 +100,25 @@ namespace smt { * will not return from the function after asserting their axioms. * This means that control will fall through to the type 1-6 axioms, * causing those to be added as well. + * The default behaviour of Z3str2 is to set this to 'false'. */ bool opt_NoQuickReturn_Concat_IntegerTheory; + /* + * If DisableIntegerTheoryIntegration is set to true, + * ALL calls to the integer theory integration methods + * (get_value, get_len_value, lower_bound, upper_bound) + * will ignore what the arithmetic solver believes about length terms, + * and will return no information. + * + * This reduces performance significantly, but can be useful to enable + * if it is suspected that string-integer integration, or the arithmetic solver itself, + * might have a bug. + * + * The default behaviour of Z3str2 is to set this to 'false'. + */ + bool opt_DisableIntegerTheoryIntegration; + bool search_started; arith_util m_autil; str_util m_strutil; From 1c518be61d8d133e48e3cafdd2153dc30ff7e4d1 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 27 Jul 2016 12:46:35 -0400 Subject: [PATCH 148/410] new_eq_handler improvements in theory_str, WIP --- src/smt/theory_str.cpp | 173 +++++++++++++++++++++++++++++++---------- src/smt/theory_str.h | 2 + 2 files changed, 133 insertions(+), 42 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 35bc7ab20..a0daa021a 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -33,8 +33,8 @@ theory_str::theory_str(ast_manager & m): opt_EagerStringConstantLengthAssertions(true), opt_VerifyFinalCheckProgress(true), opt_LCMUnrollStep(2), - opt_NoQuickReturn_Concat_IntegerTheory(true), - opt_DisableIntegerTheoryIntegration(true), + opt_NoQuickReturn_Concat_IntegerTheory(false), + opt_DisableIntegerTheoryIntegration(false), /* Internal setup */ search_started(false), m_autil(m), @@ -1428,7 +1428,57 @@ void theory_str::reset_eh() { * Then add an assertion: (y2 == (Concat ce m2)) AND ("str3" == (Concat abc x2)) -> (y2 != "str3") */ bool theory_str::new_eq_check(expr * lhs, expr * rhs) { - // TODO this involves messing around with enodes and equivalence classes + context & ctx = get_context(); + ast_manager & m = get_manager(); + + // Previously we did the check between LHS and RHS equivalence classes. + // However these have since been merged. + // We start by asserting that the EQCs, in fact, really are merged. + if (!in_same_eqc(lhs, rhs)) { + TRACE("t_str", tout << "BUG: lhs and rhs not in same eqc in new_eq_eh(), loss of invariant!" << std::endl;); + UNREACHABLE(); + } + + check_concat_len_in_eqc(lhs); + check_concat_len_in_eqc(rhs); + + // Now we iterate over all pairs of terms in the (shared) eqc + // and check whether we can show that any pair of distinct terms + // cannot possibly be equal. + // If that's the case, we assert an axiom to that effect and stop. + + enode * eqc_root = ctx.get_enode(lhs)->get_root(); + enode * eqc_iterator1 = eqc_root; + do { + enode * eqc_iterator2 = eqc_iterator1; + do { + if (eqc_iterator1 == eqc_iterator2) { + continue; + } + // pull terms out of the enodes + app * eqc_nn1 = eqc_iterator1->get_owner(); + app * eqc_nn2 = eqc_iterator2->get_owner(); + TRACE("t_str_detail", tout << "checking whether " << mk_pp(eqc_nn1, m) << " and " << mk_pp(eqc_nn2, m) << " can be equal" << std::endl;); + if (!can_two_nodes_eq(eqc_nn1, eqc_nn2)) { + TRACE("t_str", tout << "inconsistency detected: " << mk_pp(eqc_nn1, m) << " cannot be equal to " << mk_pp(eqc_nn2, m) << std::endl;); + expr_ref to_assert(m.mk_not(ctx.mk_eq_atom(eqc_nn1, eqc_nn2)), m); + assert_axiom(to_assert); + return false; + } + if (!check_length_consistency(eqc_nn1, eqc_nn2)) { + TRACE("t_str", tout << "inconsistency detected: " << mk_pp(eqc_nn1, m) << " and " << mk_pp(eqc_nn2, m) << " have inconsistent lengths" << std::endl;); + return false; + } + eqc_iterator2 = eqc_iterator2->get_next(); + } while (eqc_iterator2 != eqc_root); + + eqc_iterator1 = eqc_iterator1->get_next(); + } while (eqc_iterator1 != eqc_root); + + // TODO containPairBoolMap + // TODO regexInBoolMap + + // okay, all checks here passed return true; } @@ -2681,12 +2731,17 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { std::map, std::map >::iterator entry1 = varForBreakConcat.find(key1); std::map, std::map >::iterator entry2 = varForBreakConcat.find(key2); + // prevent checking scope for the XOR term, as it's always in the same scope as the split var + // TODO probably make this change everywhere else in process_concat_eq*, + // and also make sure this is correct. + bool entry1InScope; if (entry1 == varForBreakConcat.end()) { entry1InScope = false; } else { if (internal_variable_set.find((entry1->second)[0]) == internal_variable_set.end() - || internal_variable_set.find((entry1->second)[1]) == internal_variable_set.end()) { + /*|| internal_variable_set.find((entry1->second)[1]) == internal_variable_set.end()*/ + ) { entry1InScope = false; } else { entry1InScope = true; @@ -2698,7 +2753,8 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { entry2InScope = false; } else { if (internal_variable_set.find((entry2->second)[0]) == internal_variable_set.end() - || internal_variable_set.find((entry2->second)[1]) == internal_variable_set.end()) { + /*|| internal_variable_set.find((entry2->second)[1]) == internal_variable_set.end()*/ + ) { entry2InScope = false; } else { entry2InScope = true; @@ -3954,48 +4010,58 @@ bool theory_str::in_same_eqc(expr * n1, expr * n2) { return n1Node->get_root() == n2Node->get_root(); } -/* -bool canTwoNodesEq(Z3_theory t, Z3_ast n1, Z3_ast n2) { - Z3_ast n1_curr = n1; - Z3_ast n2_curr = n2; - - // case 0: n1_curr is const string, n2_curr is const string - if (isConstStr(t, n1_curr) && isConstStr(t, n2_curr)) { - if (n1_curr != n2_curr) { - return false; - } - } - // case 1: n1_curr is concat, n2_curr is const string - else if (isConcatFunc(t, n1_curr) && isConstStr(t, n2_curr)) { - std::string n2_curr_str = getConstStrValue(t, n2_curr); - if (canConcatEqStr(t, n1_curr, n2_curr_str) != 1) { - return false; - } - } - // case 2: n2_curr is concat, n1_curr is const string - else if (isConcatFunc(t, n2_curr) && isConstStr(t, n1_curr)) { - std::string n1_curr_str = getConstStrValue(t, n1_curr); - if (canConcatEqStr(t, n2_curr, n1_curr_str) != 1) { - return false; - } - } else if (isConcatFunc(t, n1_curr) && isConcatFunc(t, n2_curr)) { - if (canConcatEqConcat(t, n1_curr, n2_curr) != 1) { - return false; - } - } - - return true; -} -*/ - bool theory_str::can_concat_eq_str(expr * concat, std::string str) { - // TODO - return true; + /* + int strLen = str.length(); + if (isConcatFunc(t, concat)) { + std::vector args; + getNodesInConcat(t, concat, args); + Z3_ast ml_node = args[0]; + Z3_ast mr_node = args[args.size() - 1]; + + if (isConstStr(t, ml_node)) { + std::string ml_str = getConstStrValue(t, ml_node); + int ml_len = ml_str.length(); + if (ml_len > strLen) + return 0; + int cLen = ml_len; + if (ml_str != str.substr(0, cLen)) + return 0; + } + + if (isConstStr(t, mr_node)) { + std::string mr_str = getConstStrValue(t, mr_node); + int mr_len = mr_str.length(); + if (mr_len > strLen) + return 0; + int cLen = mr_len; + if (mr_str != str.substr(strLen - cLen, cLen)) + return 0; + } + + int sumLen = 0; + for (unsigned int i = 0; i < args.size(); i++) { + Z3_ast oneArg = args[i]; + if (isConstStr(t, oneArg)) { + std::string arg_str = getConstStrValue(t, oneArg); + if (str.find(arg_str) == std::string::npos) { + return 0; + } + sumLen += getConstStrValue(t, oneArg).length(); + } + } + if (sumLen > strLen) + return 0; + } + return 1; + */ + // TODO NEXT + NOT_IMPLEMENTED_YET(); return true; } bool theory_str::can_concat_eq_concat(expr * concat1, expr * concat2) { // TODO - return true; + NOT_IMPLEMENTED_YET(); return true; } /* @@ -4041,6 +4107,27 @@ bool theory_str::can_two_nodes_eq(expr * n1, expr * n2) { return true; } +bool theory_str::check_length_consistency(expr * n1, expr * n2) { + // TODO NEXT + NOT_IMPLEMENTED_YET(); return true; +} + +void theory_str::check_concat_len_in_eqc(expr * concat) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + enode * eqc_base = ctx.get_enode(concat); + enode * eqc_it = eqc_base; + do { + app * eqc_n = eqc_it->get_owner(); + if (is_concat(eqc_n)) { + rational unused; + infer_len_concat(eqc_n, unused); + } + eqc_it = eqc_it->get_next(); + } while (eqc_it != eqc_base); +} + /* * strArgmt::solve_concat_eq_str() * Solve concatenations of the form: @@ -4499,6 +4586,8 @@ void theory_str::handle_equality(expr * lhs, expr * rhs) { // As a result, simplify_concat_equality() is never getting called, // and if it were called, it would probably get called with the same element on both sides. + + // TODO improve these checks with an all-pairs match over LHS and RHS wrt. other concats bool hasCommon = false; if (eqc_lhs_concat.size() != 0 && eqc_rhs_concat.size() != 0) { std::set::iterator itor1 = eqc_lhs_concat.begin(); diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 0ba4a1a4d..d213f6271 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -298,6 +298,8 @@ namespace smt { bool can_two_nodes_eq(expr * n1, expr * n2); bool can_concat_eq_str(expr * concat, std::string str); bool can_concat_eq_concat(expr * concat1, expr * concat2); + void check_concat_len_in_eqc(expr * concat); + bool check_length_consistency(expr * n1, expr * n2); void get_nodes_in_concat(expr * node, ptr_vector & nodeList); expr * simplify_concat(expr * node); From ceed3f3ff0203b7ec0c5a793bc1a6530c6e0b609 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 27 Jul 2016 15:15:01 -0400 Subject: [PATCH 149/410] add theory_str::can_concat_eq_str --- src/smt/theory_str.cpp | 89 ++++++++++++++++++++++-------------------- 1 file changed, 46 insertions(+), 43 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index a0daa021a..bb7c1c9be 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -4011,52 +4011,55 @@ bool theory_str::in_same_eqc(expr * n1, expr * n2) { } bool theory_str::can_concat_eq_str(expr * concat, std::string str) { - /* - int strLen = str.length(); - if (isConcatFunc(t, concat)) { - std::vector args; - getNodesInConcat(t, concat, args); - Z3_ast ml_node = args[0]; - Z3_ast mr_node = args[args.size() - 1]; + // TODO this method could use some traces and debugging info + int strLen = str.length(); + if (is_concat(to_app(concat))) { + ptr_vector args; + get_nodes_in_concat(concat, args); + expr * ml_node = args[0]; + expr * mr_node = args[args.size() - 1]; - if (isConstStr(t, ml_node)) { - std::string ml_str = getConstStrValue(t, ml_node); - int ml_len = ml_str.length(); - if (ml_len > strLen) - return 0; - int cLen = ml_len; - if (ml_str != str.substr(0, cLen)) - return 0; - } + if (m_strutil.is_string(ml_node)) { + std::string ml_str = m_strutil.get_string_constant_value(ml_node); + int ml_len = ml_str.length(); + if (ml_len > strLen) { + return false; + } + int cLen = ml_len; + if (ml_str != str.substr(0, cLen)) { + return false; + } + } - if (isConstStr(t, mr_node)) { - std::string mr_str = getConstStrValue(t, mr_node); - int mr_len = mr_str.length(); - if (mr_len > strLen) - return 0; - int cLen = mr_len; - if (mr_str != str.substr(strLen - cLen, cLen)) - return 0; - } + if (m_strutil.is_string(mr_node)) { + std::string mr_str = m_strutil.get_string_constant_value(mr_node); + int mr_len = mr_str.length(); + if (mr_len > strLen) { + return false; + } + int cLen = mr_len; + if (mr_str != str.substr(strLen - cLen, cLen)) { + return false; + } + } - int sumLen = 0; - for (unsigned int i = 0; i < args.size(); i++) { - Z3_ast oneArg = args[i]; - if (isConstStr(t, oneArg)) { - std::string arg_str = getConstStrValue(t, oneArg); - if (str.find(arg_str) == std::string::npos) { - return 0; - } - sumLen += getConstStrValue(t, oneArg).length(); - } - } - if (sumLen > strLen) - return 0; - } - return 1; - */ - // TODO NEXT - NOT_IMPLEMENTED_YET(); return true; + int sumLen = 0; + for (unsigned int i = 0 ; i < args.size() ; i++) { + expr * oneArg = args[i]; + if (m_strutil.is_string(oneArg)) { + std::string arg_str = m_strutil.get_string_constant_value(oneArg); + if (str.find(arg_str) == std::string::npos) { + return false; + } + sumLen += arg_str.length(); + } + } + + if (sumLen > strLen) { + return false; + } + } + return true; } bool theory_str::can_concat_eq_concat(expr * concat1, expr * concat2) { From a31a948a5bf0f720fdebcd23eaa838e796eb06fe Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 27 Jul 2016 15:21:33 -0400 Subject: [PATCH 150/410] add theory_str::can_concat_eq_concat --- src/smt/theory_str.cpp | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index bb7c1c9be..3ff4ff2de 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -4063,8 +4063,39 @@ bool theory_str::can_concat_eq_str(expr * concat, std::string str) { } bool theory_str::can_concat_eq_concat(expr * concat1, expr * concat2) { - // TODO - NOT_IMPLEMENTED_YET(); return true; + // TODO this method could use some traces and debugging info + if (is_concat(to_app(concat1)) && is_concat(to_app(concat2))) { + { + // Suppose concat1 = (Concat X Y) and concat2 = (Concat M N). + expr * concat1_mostL = getMostLeftNodeInConcat(concat1); + expr * concat2_mostL = getMostLeftNodeInConcat(concat2); + // if both X and M are constant strings, check whether they have the same prefix + if (m_strutil.is_string(concat1_mostL) && m_strutil.is_string(concat2_mostL)) { + std::string concat1_mostL_str = m_strutil.get_string_constant_value(concat1_mostL); + std::string concat2_mostL_str = m_strutil.get_string_constant_value(concat2_mostL); + int cLen = std::min(concat1_mostL_str.length(), concat2_mostL_str.length()); + if (concat1_mostL_str.substr(0, cLen) != concat2_mostL_str.substr(0, cLen)) { + return false; + } + } + } + + { + // Similarly, if both Y and N are constant strings, check whether they have the same suffix + expr * concat1_mostR = getMostRightNodeInConcat(concat1); + expr * concat2_mostR = getMostRightNodeInConcat(concat2); + if (m_strutil.is_string(concat1_mostR) && m_strutil.is_string(concat2_mostR)) { + std::string concat1_mostR_str = m_strutil.get_string_constant_value(concat1_mostR); + std::string concat2_mostR_str = m_strutil.get_string_constant_value(concat2_mostR); + int cLen = std::min(concat1_mostR_str.length(), concat2_mostR_str.length()); + if (concat1_mostR_str.substr(concat1_mostR_str.length() - cLen, cLen) != + concat2_mostR_str.substr(concat2_mostR_str.length() - cLen, cLen)) { + return false; + } + } + } + } + return true; } /* From 95f1cfa5a6b7d13ff0ad927c3416450932f83b5b Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 27 Jul 2016 16:18:05 -0400 Subject: [PATCH 151/410] add theory_str::check_length_consistency, WIP --- src/smt/theory_str.cpp | 31 +++++++++++++++++++++++++++++-- src/smt/theory_str.h | 2 ++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 3ff4ff2de..24aefe3a6 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -4141,9 +4141,36 @@ bool theory_str::can_two_nodes_eq(expr * n1, expr * n2) { return true; } +// was checkLength2ConstStr() in Z3str2 +// returns true if everything is OK, or false if inconsistency detected +// - note that these are different from the semantics in Z3str2 +bool theory_str::check_length_const_string(expr * n1, expr * constStr) { + // TODO NEXT + NOT_IMPLEMENTED_YET(); return true; +} + +// returns true if everything is OK, or false if inconsistency detected +// - note that these are different from the semantics in Z3str2 +bool theory_str::check_length_eq_var_concat(expr * n1, expr * n2) { + // TODO NEXT + NOT_IMPLEMENTED_YET(); return true; +} + +// returns false if an inconsistency is detected, or true if no inconsistencies were found +// - note that these are different from the semantics of checkLengConsistency() in Z3str2 bool theory_str::check_length_consistency(expr * n1, expr * n2) { - // TODO NEXT - NOT_IMPLEMENTED_YET(); return true; + if (m_strutil.is_string(n1) && m_strutil.is_string(n2)) { + // consistency has already been checked in can_two_nodes_eq(). + return true; + } else if (m_strutil.is_string(n1) && (!m_strutil.is_string(n2))) { + return check_length_const_string(n2, n1); + } else if (m_strutil.is_string(n2) && (!m_strutil.is_string(n1))) { + return check_length_const_string(n1, n2); + } else { + // n1 and n2 are vars or concats + return check_length_eq_var_concat(n1, n2); + } + return 0; } void theory_str::check_concat_len_in_eqc(expr * concat) { diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index d213f6271..d4681856c 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -300,6 +300,8 @@ namespace smt { bool can_concat_eq_concat(expr * concat1, expr * concat2); void check_concat_len_in_eqc(expr * concat); bool check_length_consistency(expr * n1, expr * n2); + bool check_length_const_string(expr * n1, expr * constStr); + bool check_length_eq_var_concat(expr * n1, expr * n2); void get_nodes_in_concat(expr * node, ptr_vector & nodeList); expr * simplify_concat(expr * node); From 76ceac6664032e8daf8935ad51ac1d96f048f4a0 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Thu, 28 Jul 2016 16:31:40 -0400 Subject: [PATCH 152/410] add theory_str::check_length_const_string --- src/smt/theory_str.cpp | 50 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 24aefe3a6..042ff5808 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -4145,8 +4145,54 @@ bool theory_str::can_two_nodes_eq(expr * n1, expr * n2) { // returns true if everything is OK, or false if inconsistency detected // - note that these are different from the semantics in Z3str2 bool theory_str::check_length_const_string(expr * n1, expr * constStr) { - // TODO NEXT - NOT_IMPLEMENTED_YET(); return true; + ast_manager & mgr = get_manager(); + context & ctx = get_context(); + + rational strLen((unsigned) (m_strutil.get_string_constant_value(constStr).length())); + + if (is_concat(to_app(n1))) { + ptr_vector args; + expr_ref_vector items(mgr); + + get_nodes_in_concat(n1, args); + + rational sumLen(0); + for (unsigned int i = 0; i < args.size(); ++i) { + rational argLen; + bool argLen_exists = get_len_value(args[i], argLen); + if (argLen_exists) { + if (!m_strutil.is_string(args[i])) { + items.push_back(ctx.mk_eq_atom(mk_strlen(args[i]), mk_int(argLen))); + } + TRACE("t_str_detail", tout << "concat arg: " << mk_pp(args[i], mgr) << " has len = " << argLen.to_string() << std::endl;); + sumLen += argLen; + if (sumLen > strLen) { + items.push_back(ctx.mk_eq_atom(n1, constStr)); + expr_ref toAssert(mgr.mk_not(mk_and(items)), mgr); + TRACE("t_str_detail", tout << "inconsistent length: concat (len = " << sumLen << ") <==> string constant (len = " << strLen << ")" << std::endl;); + assert_axiom(toAssert); + return false; + } + } + } + } else { // !is_concat(n1) + rational oLen; + bool oLen_exists = get_len_value(n1, oLen); + if (oLen_exists && oLen != strLen) { + TRACE("t_str_detail", tout << "inconsistent length: var (len = " << oLen << ") <==> string constant (len = " << strLen << ")" << std::endl;); + expr_ref l(ctx.mk_eq_atom(n1, constStr), mgr); + expr_ref r(ctx.mk_eq_atom(mk_strlen(n1), mk_strlen(constStr)), mgr); + assert_implication(l, r); + return false; + } + } + rational unused; + if (get_len_value(n1, unused) == false) { + expr_ref l(ctx.mk_eq_atom(n1, constStr), mgr); + expr_ref r(ctx.mk_eq_atom(mk_strlen(n1), mk_strlen(constStr)), mgr); + assert_implication(l, r); + } + return true; } // returns true if everything is OK, or false if inconsistency detected From 999420485b2b5fb8e58cbe987aad2be855df91d7 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Thu, 28 Jul 2016 16:49:39 -0400 Subject: [PATCH 153/410] add theory_str::check_length_eq_var_concat and helper methods --- src/smt/theory_str.cpp | 149 ++++++++++++++++++++++++++++++++++++++++- src/smt/theory_str.h | 3 + 2 files changed, 150 insertions(+), 2 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 042ff5808..07fe3e6f6 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -4195,11 +4195,156 @@ bool theory_str::check_length_const_string(expr * n1, expr * constStr) { return true; } +bool theory_str::check_length_concat_concat(expr * n1, expr * n2) { + context & ctx = get_context(); + ast_manager & mgr = get_manager(); + + ptr_vector concat1Args; + ptr_vector concat2Args; + get_nodes_in_concat(n1, concat1Args); + get_nodes_in_concat(n2, concat2Args); + + bool concat1LenFixed = true; + bool concat2LenFixed = true; + + expr_ref_vector items(mgr); + + rational sum1(0), sum2(0); + + for (unsigned int i = 0; i < concat1Args.size(); ++i) { + expr * oneArg = concat1Args[i]; + rational argLen; + bool argLen_exists = get_len_value(oneArg, argLen); + if (argLen_exists) { + sum1 += argLen; + if (!m_strutil.is_string(oneArg)) { + items.push_back(ctx.mk_eq_atom(mk_strlen(oneArg), mk_int(argLen))); + } + } else { + concat1LenFixed = false; + } + } + + for (unsigned int i = 0; i < concat2Args.size(); ++i) { + expr * oneArg = concat2Args[i]; + rational argLen; + bool argLen_exists = get_len_value(oneArg, argLen); + if (argLen_exists) { + sum2 += argLen; + if (!m_strutil.is_string(oneArg)) { + items.push_back(ctx.mk_eq_atom(mk_strlen(oneArg), mk_int(argLen))); + } + } else { + concat2LenFixed = false; + } + } + + items.push_back(ctx.mk_eq_atom(n1, n2)); + + expr_ref toAssert(mgr.mk_not(mk_and(items)), mgr); + + bool conflict = false; + + if (concat1LenFixed && concat2LenFixed) { + if (sum1 != sum2) { + conflict = true; + } + } else if (!concat1LenFixed && concat2LenFixed) { + if (sum1 > sum2) { + conflict = true; + } + } else if (concat1LenFixed && !concat2LenFixed) { + if (sum1 < sum2) { + conflict = true; + } + } + + if (conflict) { + TRACE("t_str_detail", tout << "inconsistent length detected in concat <==> concat" << std::endl;); + return false; + } + return true; +} + +bool theory_str::check_length_concat_var(expr * concat, expr * var) { + context & ctx = get_context(); + ast_manager & mgr = get_manager(); + + rational varLen; + bool varLen_exists = get_len_value(var, varLen); + if (!varLen_exists) { + return true; + } else { + rational sumLen(0); + ptr_vector args; + expr_ref_vector items(mgr); + get_nodes_in_concat(concat, args); + for (unsigned int i = 0; i < args.size(); ++i) { + expr * oneArg = args[i]; + rational argLen; + bool argLen_exists = get_len_value(oneArg, argLen); + if (argLen_exists) { + if (!m_strutil.is_string(oneArg) && !argLen.is_zero()) { + items.push_back(ctx.mk_eq_atom(mk_strlen(oneArg), mk_int(argLen))); + } + sumLen += argLen; + if (sumLen > varLen) { + TRACE("t_str_detail", tout << "inconsistent length detected in concat <==> var" << std::endl;); + items.push_back(ctx.mk_eq_atom(mk_strlen(var), mk_int(varLen))); + items.push_back(ctx.mk_eq_atom(concat, var)); + expr_ref toAssert(mgr.mk_not(mk_and(items)), mgr); + assert_axiom(toAssert); + return false; + } + } + } + return true; + } +} + +bool theory_str::check_length_var_var(expr * var1, expr * var2) { + context & ctx = get_context(); + ast_manager & mgr = get_manager(); + + rational var1Len, var2Len; + bool var1Len_exists = get_len_value(var1, var1Len); + bool var2Len_exists = get_len_value(var2, var2Len); + + if (var1Len_exists && var2Len_exists && var1Len != var2Len) { + TRACE("t_str_detail", tout << "inconsistent length detected in var <==> var" << std::endl;); + expr_ref_vector items(mgr); + items.push_back(ctx.mk_eq_atom(mk_strlen(var1), mk_int(var1Len))); + items.push_back(ctx.mk_eq_atom(mk_strlen(var2), mk_int(var2Len))); + items.push_back(ctx.mk_eq_atom(var1, var2)); + expr_ref toAssert(mgr.mk_not(mk_and(items)), mgr); + assert_axiom(toAssert); + return false; + } + return true; +} + // returns true if everything is OK, or false if inconsistency detected // - note that these are different from the semantics in Z3str2 bool theory_str::check_length_eq_var_concat(expr * n1, expr * n2) { - // TODO NEXT - NOT_IMPLEMENTED_YET(); return true; + // n1 and n2 are not const string: either variable or concat + bool n1Concat = is_concat(to_app(n1)); + bool n2Concat = is_concat(to_app(n2)); + if (n1Concat && n2Concat) { + return check_length_concat_concat(n1, n2); + } + // n1 is concat, n2 is variable + else if (n1Concat && (!n2Concat)) { + return check_length_concat_var(n1, n2); + } + // n1 is variable, n2 is concat + else if ((!n1Concat) && n2Concat) { + return check_length_concat_var(n2, n1); + } + // n1 and n2 are both variables + else { + return check_length_var_var(n1, n2); + } + return 0; } // returns false if an inconsistency is detected, or true if no inconsistencies were found diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index d4681856c..bd66e64d4 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -302,6 +302,9 @@ namespace smt { bool check_length_consistency(expr * n1, expr * n2); bool check_length_const_string(expr * n1, expr * constStr); bool check_length_eq_var_concat(expr * n1, expr * n2); + bool check_length_concat_concat(expr * n1, expr * n2); + bool check_length_concat_var(expr * concat, expr * var); + bool check_length_var_var(expr * var1, expr * var2); void get_nodes_in_concat(expr * node, ptr_vector & nodeList); expr * simplify_concat(expr * node); From 244b611f1ca2ebabb1338fbcf0cdb8eb960e0683 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Thu, 28 Jul 2016 17:10:41 -0400 Subject: [PATCH 154/410] fix infinite loop bug in theory_str::new_eq_check --- src/smt/theory_str.cpp | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 07fe3e6f6..17246ccf8 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -1452,24 +1452,24 @@ bool theory_str::new_eq_check(expr * lhs, expr * rhs) { do { enode * eqc_iterator2 = eqc_iterator1; do { - if (eqc_iterator1 == eqc_iterator2) { - continue; - } - // pull terms out of the enodes - app * eqc_nn1 = eqc_iterator1->get_owner(); - app * eqc_nn2 = eqc_iterator2->get_owner(); - TRACE("t_str_detail", tout << "checking whether " << mk_pp(eqc_nn1, m) << " and " << mk_pp(eqc_nn2, m) << " can be equal" << std::endl;); - if (!can_two_nodes_eq(eqc_nn1, eqc_nn2)) { - TRACE("t_str", tout << "inconsistency detected: " << mk_pp(eqc_nn1, m) << " cannot be equal to " << mk_pp(eqc_nn2, m) << std::endl;); - expr_ref to_assert(m.mk_not(ctx.mk_eq_atom(eqc_nn1, eqc_nn2)), m); - assert_axiom(to_assert); - return false; - } - if (!check_length_consistency(eqc_nn1, eqc_nn2)) { - TRACE("t_str", tout << "inconsistency detected: " << mk_pp(eqc_nn1, m) << " and " << mk_pp(eqc_nn2, m) << " have inconsistent lengths" << std::endl;); - return false; + if (eqc_iterator1 != eqc_iterator2) { + // pull terms out of the enodes + app * eqc_nn1 = eqc_iterator1->get_owner(); + app * eqc_nn2 = eqc_iterator2->get_owner(); + TRACE("t_str_detail", tout << "checking whether " << mk_pp(eqc_nn1, m) << " and " << mk_pp(eqc_nn2, m) << " can be equal" << std::endl;); + if (!can_two_nodes_eq(eqc_nn1, eqc_nn2)) { + TRACE("t_str", tout << "inconsistency detected: " << mk_pp(eqc_nn1, m) << " cannot be equal to " << mk_pp(eqc_nn2, m) << std::endl;); + expr_ref to_assert(m.mk_not(ctx.mk_eq_atom(eqc_nn1, eqc_nn2)), m); + assert_axiom(to_assert); + return false; + } + if (!check_length_consistency(eqc_nn1, eqc_nn2)) { + TRACE("t_str", tout << "inconsistency detected: " << mk_pp(eqc_nn1, m) << " and " << mk_pp(eqc_nn2, m) << " have inconsistent lengths" << std::endl;); + return false; + } } eqc_iterator2 = eqc_iterator2->get_next(); + } while (eqc_iterator2 != eqc_root); eqc_iterator1 = eqc_iterator1->get_next(); From 6f67e9cdda33df556b7026cb215a555b3db4170f Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Thu, 28 Jul 2016 17:18:56 -0400 Subject: [PATCH 155/410] fix theory_str::check_length_concat_concat to actually assert the conflict axiom --- src/smt/theory_str.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 17246ccf8..6393d8154 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -4261,6 +4261,7 @@ bool theory_str::check_length_concat_concat(expr * n1, expr * n2) { if (conflict) { TRACE("t_str_detail", tout << "inconsistent length detected in concat <==> concat" << std::endl;); + assert_axiom(toAssert); return false; } return true; From 7f3a260eda927dea298761f67aa138fb02f1f5f6 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sat, 30 Jul 2016 16:58:59 -0400 Subject: [PATCH 156/410] more aggressive simplifications in theory_str::handle equality, WIP, not tested yet --- src/smt/theory_str.cpp | 141 +++++++++++++++++------------------------ 1 file changed, 59 insertions(+), 82 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 6393d8154..fe68b02ad 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -4779,117 +4779,94 @@ void theory_str::handle_equality(expr * lhs, expr * rhs) { // BEGIN new_eq_handler() in strTheory - // TODO there's some setup with getLenValue() that I don't think is necessary - // because we should already be generating the string length axioms for all string terms + { + rational nn1Len, nn2Len; + bool nn1Len_exists = get_len_value(lhs, nn1Len); + bool nn2Len_exists = get_len_value(rhs, nn2Len); + expr * emptyStr = m_strutil.mk_string(""); + + if (nn1Len_exists && nn1Len.is_zero()) { + if (!in_same_eqc(lhs, emptyStr) && rhs != emptyStr) { + expr_ref eql(ctx.mk_eq_atom(mk_strlen(lhs), mk_int(0)), m); + expr_ref eqr(ctx.mk_eq_atom(lhs, emptyStr), m); + expr_ref toAssert(ctx.mk_eq_atom(eql, eqr), m); + assert_axiom(toAssert); + } + } + + if (nn2Len_exists && nn2Len.is_zero()) { + if (!in_same_eqc(rhs, emptyStr) && lhs != emptyStr) { + expr_ref eql(ctx.mk_eq_atom(mk_strlen(rhs), mk_int(0)), m); + expr_ref eqr(ctx.mk_eq_atom(rhs, emptyStr), m); + expr_ref toAssert(ctx.mk_eq_atom(eql, eqr), m); + assert_axiom(toAssert); + } + } + } + + // TODO some setup with haveEQLength() which I skip for now, not sure if necessary instantiate_str_eq_length_axiom(ctx.get_enode(lhs), ctx.get_enode(rhs)); // group terms by equivalence class (groupNodeInEqc()) - std::set eqc_lhs_concat; - std::set eqc_lhs_var; - std::set eqc_lhs_const; - group_terms_by_eqc(lhs, eqc_lhs_concat, eqc_lhs_var, eqc_lhs_const); + // Previously we did the check between LHS and RHS equivalence classes. + // However these have since been merged. + // We start by asserting that the EQCs, in fact, really are merged. + if (!in_same_eqc(lhs, rhs)) { + TRACE("t_str", tout << "BUG: lhs and rhs not in same eqc in new_eq_eh(), loss of invariant!" << std::endl;); + UNREACHABLE(); + } + + std::set eqc_concat; + std::set eqc_var; + std::set eqc_const; + group_terms_by_eqc(lhs, eqc_concat, eqc_var, eqc_const); TRACE("t_str_detail", - tout << "eqc[lhs]:" << std::endl; + tout << "eqc:" << std::endl; tout << "Concats:" << std::endl; - for (std::set::iterator it = eqc_lhs_concat.begin(); it != eqc_lhs_concat.end(); ++it) { + for (std::set::iterator it = eqc_concat.begin(); it != eqc_concat.end(); ++it) { expr * ex = *it; tout << mk_ismt2_pp(ex, get_manager()) << std::endl; } tout << "Variables:" << std::endl; - for (std::set::iterator it = eqc_lhs_var.begin(); it != eqc_lhs_var.end(); ++it) { + for (std::set::iterator it = eqc_var.begin(); it != eqc_var.end(); ++it) { expr * ex = *it; tout << mk_ismt2_pp(ex, get_manager()) << std::endl; } tout << "Constants:" << std::endl; - for (std::set::iterator it = eqc_lhs_const.begin(); it != eqc_lhs_const.end(); ++it) { - expr * ex = *it; - tout << mk_ismt2_pp(ex, get_manager()) << std::endl; - } - ); - - std::set eqc_rhs_concat; - std::set eqc_rhs_var; - std::set eqc_rhs_const; - group_terms_by_eqc(rhs, eqc_rhs_concat, eqc_rhs_var, eqc_rhs_const); - - TRACE("t_str_detail", - tout << "eqc[rhs]:" << std::endl; - tout << "Concats:" << std::endl; - for (std::set::iterator it = eqc_rhs_concat.begin(); it != eqc_rhs_concat.end(); ++it) { - expr * ex = *it; - tout << mk_ismt2_pp(ex, get_manager()) << std::endl; - } - tout << "Variables:" << std::endl; - for (std::set::iterator it = eqc_rhs_var.begin(); it != eqc_rhs_var.end(); ++it) { - expr * ex = *it; - tout << mk_ismt2_pp(ex, get_manager()) << std::endl; - } - tout << "Constants:" << std::endl; - for (std::set::iterator it = eqc_rhs_const.begin(); it != eqc_rhs_const.end(); ++it) { + for (std::set::iterator it = eqc_const.begin(); it != eqc_const.end(); ++it) { expr * ex = *it; tout << mk_ismt2_pp(ex, get_manager()) << std::endl; } ); // step 1: Concat == Concat - // This code block may no longer be useful. - // Z3 seems to be putting LHS and RHS into the same equivalence class extremely early. - // As a result, simplify_concat_equality() is never getting called, - // and if it were called, it would probably get called with the same element on both sides. - - // TODO improve these checks with an all-pairs match over LHS and RHS wrt. other concats - bool hasCommon = false; - if (eqc_lhs_concat.size() != 0 && eqc_rhs_concat.size() != 0) { - std::set::iterator itor1 = eqc_lhs_concat.begin(); - std::set::iterator itor2 = eqc_rhs_concat.begin(); - for (; itor1 != eqc_lhs_concat.end(); ++itor1) { - if (eqc_rhs_concat.find(*itor1) != eqc_rhs_concat.end()) { - hasCommon = true; - break; - } - } - for (; !hasCommon && itor2 != eqc_rhs_concat.end(); ++itor2) { - if (eqc_lhs_concat.find(*itor2) != eqc_lhs_concat.end()) { - hasCommon = true; - break; - } - } - if (!hasCommon) { - simplify_concat_equality(*(eqc_lhs_concat.begin()), *(eqc_rhs_concat.begin())); - } - } - - if (eqc_lhs_concat.size() != 0 && eqc_rhs_concat.size() != 0) { - // let's pick the first concat in the LHS's eqc - // and find some concat in the RHS's eqc that is - // distinct from the first one we picked - expr * lhs = *eqc_lhs_concat.begin(); - std::set::iterator itor2 = eqc_rhs_concat.begin(); - for (; itor2 != eqc_rhs_concat.end(); ++itor2) { - expr * rhs = *itor2; - if (lhs != rhs) { - simplify_concat_equality(lhs, rhs); - break; + // enhancement from Z3str2: all-pairs match over LHS and RHS wrt. other concats + if (eqc_concat.size() != 0) { + std::set::iterator itor1, itor2; + for (itor1 = eqc_concat.begin(); itor1 != eqc_concat.end(); ++itor1) { + for (itor2 = itor1; itor2 != eqc_concat.end(); ++itor2) { + if (itor1 == itor2) { + continue; + } + expr * e1 = *itor1; + expr * e2 = *itor2; + TRACE("t_str_detail", tout << "simplify concat-concat pair " << mk_pp(e1, m) << " and " << mk_pp(e2, m) << std::endl;); + simplify_concat_equality(e1, e2); } } } // step 2: Concat == Constant - if (eqc_lhs_const.size() != 0) { - expr * conStr = *(eqc_lhs_const.begin()); - std::set::iterator itor2 = eqc_rhs_concat.begin(); - for (; itor2 != eqc_rhs_concat.end(); ++itor2) { + // same enhancement as above wrt. Z3str2's behaviour + if (eqc_const.size() != 0) { + expr * conStr = *(eqc_const.begin()); + std::set::iterator itor2; + for (itor2 = eqc_concat.begin(); itor2 != eqc_concat.end(); ++itor2) { solve_concat_eq_str(*itor2, conStr); } - } else if (eqc_rhs_const.size() != 0) { - expr * conStr = *(eqc_rhs_const.begin()); - std::set::iterator itor1 = eqc_lhs_concat.begin(); - for (; itor1 != eqc_lhs_concat.end(); ++itor1) { - solve_concat_eq_str(*itor1, conStr); - } } // simplify parents wrt. the equivalence class of both sides From 8958eea27cf282b86945d7fa86d03d6e60ef6273 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sun, 31 Jul 2016 11:22:04 -0400 Subject: [PATCH 157/410] crash avoidance in theory_str cut_var_map writes --- src/smt/theory_str.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index fe68b02ad..624892ee1 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -306,6 +306,9 @@ bool theory_str::has_self_cut(expr * n1, expr * n2) { } void theory_str::add_cut_info_one_node(expr * baseNode, int slevel, expr * node) { + // crash avoidance? + m_trail.push_back(baseNode); + m_trail.push_back(node); if (cut_var_map.find(baseNode) == cut_var_map.end()) { T_cut * varInfo = alloc(T_cut); varInfo->level = slevel; @@ -334,6 +337,9 @@ void theory_str::add_cut_info_one_node(expr * baseNode, int slevel, expr * node) } void theory_str::add_cut_info_merge(expr * destNode, int slevel, expr * srcNode) { + // crash avoidance? + m_trail.push_back(destNode); + m_trail.push_back(srcNode); if (cut_var_map.find(srcNode) == cut_var_map.end()) { get_manager().raise_exception("illegal state in add_cut_info_merge(): cut_var_map doesn't contain srcNode"); } From f5b82740c36da4279be9eb27f1a151bdc6a6fb6e Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sun, 31 Jul 2016 16:26:56 -0400 Subject: [PATCH 158/410] debugging length testers in theory_str::gen_len_val_options_for_free_var --- src/smt/theory_str.cpp | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 624892ee1..a2b3e731b 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -7105,11 +7105,23 @@ expr * theory_str::gen_len_val_options_for_free_var(expr * freeVar, expr * lenTe std::string effectiveLenIndiStr = ""; int lenTesterCount = (int) fvar_lenTester_map[freeVar].size(); + TRACE("t_str_detail", + tout << lenTesterCount << " length testers in fvar_lenTester_map[" << mk_pp(freeVar, m) << "]:" << std::endl; + for (int i = 0; i < lenTesterCount; ++i) { + expr * len_indicator = fvar_lenTester_map[freeVar][i]; + tout << mk_pp(len_indicator, m) << ": "; + bool effectiveInScope = (internal_variable_set.find(len_indicator) != internal_variable_set.end()); + tout << (effectiveInScope ? "in scope" : "NOT in scope"); + tout << std::endl; + } + ); + int i = 0; for (; i < lenTesterCount; ++i) { expr * len_indicator_pre = fvar_lenTester_map[freeVar][i]; // check whether this is in scope as well if (internal_variable_set.find(len_indicator_pre) == internal_variable_set.end()) { + TRACE("t_str_detail", tout << "length indicator " << mk_pp(len_indicator_pre, m) << " not in scope" << std::endl;); continue; } @@ -7133,13 +7145,26 @@ expr * theory_str::gen_len_val_options_for_free_var(expr * freeVar, expr * lenTe << " i = " << i << ", lenTesterCount = " << lenTesterCount << std::endl;); if (i > 0) { effectiveLenInd = fvar_lenTester_map[freeVar][i - 1]; + bool effectiveHasEqcValue; + expr * effective_eqc_value = get_eqc_value(effectiveLenInd, effectiveHasEqcValue); + bool effectiveInScope = (internal_variable_set.find(effectiveLenInd) != internal_variable_set.end()); + TRACE("t_str_detail", tout << "checking effective length indicator " << mk_pp(effectiveLenInd, m) << ": " + << (effectiveInScope ? "in scope" : "NOT in scope") << ", "; + if (effectiveHasEqcValue) { + tout << "~= " << mk_pp(effective_eqc_value, m); + } else { + tout << "no eqc string constant"; + } + tout << std::endl;); if (effectiveLenInd == lenTesterInCbEq) { effectiveLenIndiStr = lenTesterValue; } else { - bool effectiveHasEqcValue = false; - const char * val = 0; - m_strutil.is_string(get_eqc_value(effectiveLenInd, effectiveHasEqcValue), & val); - effectiveLenIndiStr = val; + if (effectiveHasEqcValue) { + effectiveLenIndiStr = m_strutil.get_string_constant_value(effective_eqc_value); + } else { + // TODO this should be unreachable, but can we really do anything here? + NOT_IMPLEMENTED_YET(); + } } } break; @@ -7169,6 +7194,7 @@ expr * theory_str::gen_len_val_options_for_free_var(expr * freeVar, expr * lenTe fvar_lenTester_map[freeVar].push_back(indicator); lenTester_fvar_map[indicator] = freeVar; } else { + // TODO make absolutely sure this is safe to do if 'indicator' is technically out of scope indicator = fvar_lenTester_map[freeVar][i]; testNum = i + 1; } From 41497f44c12236b74b6abda63bcf2d225c171873 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sun, 31 Jul 2016 16:30:52 -0400 Subject: [PATCH 159/410] prevent checking scope of XOR variables in theory_str::process_concat_eq --- src/smt/theory_str.cpp | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index a2b3e731b..a6db9112f 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -2417,7 +2417,7 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { } else { if (internal_variable_set.find((entry1->second)[0]) == internal_variable_set.end() || internal_variable_set.find((entry1->second)[1]) == internal_variable_set.end() - || internal_variable_set.find((entry1->second)[2]) == internal_variable_set.end()) { + /*|| internal_variable_set.find((entry1->second)[2]) == internal_variable_set.end() */) { entry1InScope = false; } else { entry1InScope = true; @@ -2430,7 +2430,7 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { } else { if (internal_variable_set.find((entry2->second)[0]) == internal_variable_set.end() || internal_variable_set.find((entry2->second)[1]) == internal_variable_set.end() - || internal_variable_set.find((entry2->second)[2]) == internal_variable_set.end()) { + /* || internal_variable_set.find((entry2->second)[2]) == internal_variable_set.end() */) { entry2InScope = false; } else { entry2InScope = true; @@ -2738,8 +2738,6 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { std::map, std::map >::iterator entry2 = varForBreakConcat.find(key2); // prevent checking scope for the XOR term, as it's always in the same scope as the split var - // TODO probably make this change everywhere else in process_concat_eq*, - // and also make sure this is correct. bool entry1InScope; if (entry1 == varForBreakConcat.end()) { @@ -3061,7 +3059,7 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { entry1InScope = false; } else { if (internal_variable_set.find((entry1->second)[0]) == internal_variable_set.end() - || internal_variable_set.find((entry1->second)[1]) == internal_variable_set.end()) { + /* || internal_variable_set.find((entry1->second)[1]) == internal_variable_set.end() */) { entry1InScope = false; } else { entry1InScope = true; @@ -3073,7 +3071,7 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { entry2InScope = false; } else { if (internal_variable_set.find((entry2->second)[0]) == internal_variable_set.end() - || internal_variable_set.find((entry2->second)[1]) == internal_variable_set.end()) { + /* || internal_variable_set.find((entry2->second)[1]) == internal_variable_set.end() */) { entry2InScope = false; } else { entry2InScope = true; @@ -3560,7 +3558,7 @@ void theory_str::process_concat_eq_type6(expr * concatAst1, expr * concatAst2) { entry1InScope = false; } else { if (internal_variable_set.find((entry1->second)[0]) == internal_variable_set.end() - || internal_variable_set.find((entry1->second)[1]) == internal_variable_set.end()) { + /* || internal_variable_set.find((entry1->second)[1]) == internal_variable_set.end() */) { entry1InScope = false; } else { entry1InScope = true; @@ -3572,7 +3570,7 @@ void theory_str::process_concat_eq_type6(expr * concatAst1, expr * concatAst2) { entry2InScope = false; } else { if (internal_variable_set.find((entry2->second)[0]) == internal_variable_set.end() - || internal_variable_set.find((entry2->second)[1]) == internal_variable_set.end()) { + /* || internal_variable_set.find((entry2->second)[1]) == internal_variable_set.end() */) { entry2InScope = false; } else { entry2InScope = true; From 9ceb2df28f868e700c3ed5be33d69d7c02d84181 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sun, 31 Jul 2016 16:51:35 -0400 Subject: [PATCH 160/410] add integer integration to theory_str::simplify_parent --- src/smt/theory_str.cpp | 88 +++++++++++++++++++++--------------------- 1 file changed, 43 insertions(+), 45 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index a6db9112f..4560de950 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -1564,6 +1564,14 @@ expr * theory_str::eval_concat(expr * n1, expr * n2) { return NULL; } +static inline std::string rational_to_string_if_exists(const rational & x, bool x_exists) { + if (x_exists) { + return x.to_string(); + } else { + return "?"; + } +} + /* * The inputs: * ~ nn: non const node @@ -1610,39 +1618,34 @@ void theory_str::simplify_parent(expr * nn, expr * eq_str) { expr * arg0 = a_parent->get_arg(0); expr * arg1 = a_parent->get_arg(1); - // TODO getLenValue() - // int parentLen = getLenValue(a_parent) - int parentLen = -1; + rational parentLen; + bool parentLen_exists = get_len_value(a_parent, parentLen); + if (arg0 == n_eq_enode->get_owner()) { - // TODO getLenValue() - // int arg0Len = getLenValue(eq_str); - // int arg1Len = getLenValue(arg1); - int arg0Len = -1; - int arg1Len = -1; + rational arg0Len, arg1Len; + bool arg0Len_exists = get_len_value(eq_str, arg0Len); + bool arg1Len_exists = get_len_value(arg1, arg1Len); TRACE("t_str_detail", tout << "simplify_parent #1:" << std::endl << "* parent = " << mk_ismt2_pp(a_parent, m) << std::endl - << "* |parent| = " << parentLen << std::endl - << "* |arg0| = " << arg0Len << std::endl - << "* |arg1| = " << arg1Len << std::endl; + << "* |parent| = " << rational_to_string_if_exists(parentLen, parentLen_exists) << std::endl + << "* |arg0| = " << rational_to_string_if_exists(arg0Len, arg0Len_exists) << std::endl + << "* |arg1| = " << rational_to_string_if_exists(arg1Len, arg1Len_exists) << std::endl; ); - if (parentLen != -1 && arg1Len == -1) { - // TODO after getLenValue() above - /* - Z3_ast implyL11 = mk_2_and(t, Z3_mk_eq(ctx, mk_length(t, parent), mk_int(ctx, parentLen)), - Z3_mk_eq(ctx, mk_length(t, arg0), mk_int(ctx, arg0Len))); - int makeUpLenArg1 = parentLen - arg0Len; - Z3_ast lenAss = NULL; - if (makeUpLenArg1 >= 0) { - Z3_ast implyR11 = Z3_mk_eq(ctx, mk_length(t, arg1), mk_int(ctx, makeUpLenArg1)); - lenAss = Z3_mk_implies(ctx, implyL11, implyR11); + if (parentLen_exists && !arg1Len_exists) { + TRACE("t_str_detail", tout << "make up len for arg1" << std::endl;); + expr_ref implyL11(m.mk_and(ctx.mk_eq_atom(mk_strlen(a_parent), mk_int(parentLen)), + ctx.mk_eq_atom(mk_strlen(arg0), mk_int(arg0Len))), m); + rational makeUpLenArg1 = parentLen - arg0Len; + if (makeUpLenArg1.is_nonneg()) { + expr_ref implyR11(ctx.mk_eq_atom(mk_strlen(arg1), mk_int(makeUpLenArg1)), m); + assert_implication(implyL11, implyR11); } else { - lenAss = Z3_mk_not(ctx, implyL11); + expr_ref neg(m.mk_not(implyL11), m); + assert_axiom(neg); } - addAxiom(t, lenAss, __LINE__); - */ } // (Concat n_eqNode arg1) /\ arg1 has eq const @@ -1691,35 +1694,30 @@ void theory_str::simplify_parent(expr * nn, expr * eq_str) { } // if (arg0 == n_eq_enode->get_owner()) if (arg1 == n_eq_enode->get_owner()) { - // TODO getLenValue() - // int arg0Len = getLenValue(arg0); - // int arg1Len = getLenValue(eq_str); - int arg0Len = -1; - int arg1Len = -1; + rational arg0Len, arg1Len; + bool arg0Len_exists = get_len_value(arg0, arg0Len); + bool arg1Len_exists = get_len_value(eq_str, arg1Len); TRACE("t_str_detail", tout << "simplify_parent #2:" << std::endl << "* parent = " << mk_ismt2_pp(a_parent, m) << std::endl - << "* |parent| = " << parentLen << std::endl - << "* |arg0| = " << arg0Len << std::endl - << "* |arg1| = " << arg1Len << std::endl; + << "* |parent| = " << rational_to_string_if_exists(parentLen, parentLen_exists) << std::endl + << "* |arg0| = " << rational_to_string_if_exists(arg0Len, arg0Len_exists) << std::endl + << "* |arg1| = " << rational_to_string_if_exists(arg1Len, arg1Len_exists) << std::endl; ); - if (parentLen != -1 && arg0Len == -1) { - // TODO after getLenValue() above - /* - Z3_ast implyL11 = mk_2_and(t, Z3_mk_eq(ctx, mk_length(t, parent), mk_int(ctx, parentLen)), - Z3_mk_eq(ctx, mk_length(t, arg1), mk_int(ctx, arg1Len))); - int makeUpLenArg0 = parentLen - arg1Len; - Z3_ast lenAss = NULL; - if (makeUpLenArg0 >= 0) { - Z3_ast implyR11 = Z3_mk_eq(ctx, mk_length(t, arg0), mk_int(ctx, makeUpLenArg0)); - lenAss = Z3_mk_implies(ctx, implyL11, implyR11); + if (parentLen_exists && !arg0Len_exists) { + TRACE("t_str_detail", tout << "make up len for arg0" << std::endl;); + expr_ref implyL11(m.mk_and(ctx.mk_eq_atom(mk_strlen(a_parent), mk_int(parentLen)), + ctx.mk_eq_atom(mk_strlen(arg1), mk_int(arg1Len))), m); + rational makeUpLenArg0 = parentLen - arg1Len; + if (makeUpLenArg0.is_nonneg()) { + expr_ref implyR11(ctx.mk_eq_atom(mk_strlen(arg0), mk_int(makeUpLenArg0)), m); + assert_implication(implyL11, implyR11); } else { - lenAss = Z3_mk_not(ctx, implyL11); + expr_ref neg(m.mk_not(implyL11), m); + assert_axiom(neg); } - addAxiom(t, lenAss, __LINE__); - */ } // (Concat arg0 n_eqNode) /\ arg0 has eq const From 778c0a5563734d08f3e80a97ed18fc61a8b67ac9 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sun, 31 Jul 2016 16:55:17 -0400 Subject: [PATCH 161/410] improve theory_str::group_terms_by_eqc now that we have simplify_concat --- src/smt/theory_str.cpp | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 4560de950..0ad47f828 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -1495,23 +1495,20 @@ void theory_str::group_terms_by_eqc(expr * n, std::set & concats, std::se do { app * ast = eqcNode->get_owner(); if (is_concat(eqcNode)) { - // TODO simplify_concat - /* - Z3_ast simConcat = simplifyConcat(t, eqcNode); - if (simConcat != eqcNode) { - if (isConcatFunc(t, simConcat)) { - concats.insert(simConcat); + expr * simConcat = simplify_concat(ast); + if (simConcat != ast) { + if (is_concat(to_app(simConcat))) { + concats.insert(simConcat); } else { - if (isConstStr(t, simConcat)) { - constStrs.insert(simConcat); - } else { - vars.insert(simConcat); - } + if (m_strutil.is_string(simConcat)) { + consts.insert(simConcat); + } else { + vars.insert(simConcat); + } } - } else { + } else { concats.insert(simConcat); - } - */ + } concats.insert(ast); } else if (is_string(eqcNode)) { consts.insert(ast); From 6e348720b1c9994bc117283ae456af7fbbf5a46a Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sun, 31 Jul 2016 18:12:57 -0400 Subject: [PATCH 162/410] add integer theory integration to theory_str::solve_concat_eq_str case 4 --- src/smt/theory_str.cpp | 96 +++++++++++++++++++++++++++++++++--------- 1 file changed, 77 insertions(+), 19 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 0ad47f828..2b94b0c40 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -4555,12 +4555,74 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { } else { // Case 4: Concat(var, var) == const TRACE("t_str", tout << "Case 4: Concat(var, var) == const" << std::endl;); - // TODO large additions required in this section - if (true) { /* if (Concat(arg1, arg2) == NULL) { */ - int arg1Len = -1; /* = getLenValue(arg1); */ - int arg2Len = -1; /* = getLenValue(arg2); */ - if (arg1Len != -1 || arg2Len != -1) { - NOT_IMPLEMENTED_YET(); // TODO + if (eval_concat(arg1, arg2) == NULL) { + rational arg1Len, arg2Len; + bool arg1Len_exists = get_len_value(arg1, arg1Len); + bool arg2Len_exists = get_len_value(arg2, arg2Len); + rational concatStrLen((unsigned)const_str.length()); + if (arg1Len_exists || arg2Len_exists) { + expr_ref ax_l1(ctx.mk_eq_atom(concat, str), m); + expr_ref ax_l2(m); + std::string prefixStr, suffixStr; + if (arg1Len_exists) { + if (arg1Len.is_neg()) { + TRACE("t_str_detail", tout << "length conflict: arg1Len = " << arg1Len << ", concatStrLen = " << concatStrLen << std::endl;); + expr_ref toAssert(m_autil.mk_ge(mk_strlen(arg1), mk_int(0)), m); + assert_axiom(toAssert); + return; + } else if (arg1Len > concatStrLen) { + TRACE("t_str_detail", tout << "length conflict: arg1Len = " << arg1Len << ", concatStrLen = " << concatStrLen << std::endl;); + expr_ref ax_r1(m_autil.mk_le(mk_strlen(arg1), mk_int(concatStrLen)), m); + assert_implication(ax_l1, ax_r1); + return; + } + + prefixStr = const_str.substr(0, arg1Len.get_unsigned()); + rational concat_minus_arg1 = concatStrLen - arg1Len; + suffixStr = const_str.substr(arg1Len.get_unsigned(), concat_minus_arg1.get_unsigned()); + ax_l2 = ctx.mk_eq_atom(mk_strlen(arg1), mk_int(arg1Len)); + } else { + // arg2's length is available + if (arg2Len.is_neg()) { + TRACE("t_str_detail", tout << "length conflict: arg2Len = " << arg2Len << ", concatStrLen = " << concatStrLen << std::endl;); + expr_ref toAssert(m_autil.mk_ge(mk_strlen(arg2), mk_int(0)), m); + assert_axiom(toAssert); + return; + } else if (arg2Len > concatStrLen) { + TRACE("t_str_detail", tout << "length conflict: arg2Len = " << arg2Len << ", concatStrLen = " << concatStrLen << std::endl;); + expr_ref ax_r1(m_autil.mk_le(mk_strlen(arg2), mk_int(concatStrLen)), m); + assert_implication(ax_l1, ax_r1); + return; + } + + rational concat_minus_arg2 = concatStrLen - arg2Len; + prefixStr = const_str.substr(0, concat_minus_arg2.get_unsigned()); + suffixStr = const_str.substr(concat_minus_arg2.get_unsigned(), arg2Len.get_unsigned()); + ax_l2 = ctx.mk_eq_atom(mk_strlen(arg2), mk_int(arg2Len)); + } + // consistency check + if (is_concat(to_app(arg1)) && !can_concat_eq_str(arg1, prefixStr)) { + expr_ref ax_r(m.mk_not(ax_l2), m); + assert_implication(ax_l1, ax_r); + return; + } + if (is_concat(to_app(arg2)) && !can_concat_eq_str(arg2, suffixStr)) { + expr_ref ax_r(m.mk_not(ax_l2), m); + assert_implication(ax_l1, ax_r); + return; + } + expr_ref_vector r_items(m); + r_items.push_back(ctx.mk_eq_atom(arg1, m_strutil.mk_string(prefixStr))); + r_items.push_back(ctx.mk_eq_atom(arg2, m_strutil.mk_string(suffixStr))); + if (!arg1Len_exists) { + r_items.push_back(ctx.mk_eq_atom(mk_strlen(arg1), mk_int(prefixStr.size()))); + } + if (!arg2Len_exists) { + r_items.push_back(ctx.mk_eq_atom(mk_strlen(arg2), mk_int(suffixStr.size()))); + } + expr_ref lhs(m.mk_and(ax_l1, ax_l2), m); + expr_ref rhs(mk_and(r_items), m); + assert_implication(lhs, rhs); } else { /* ! (arg1Len != 1 || arg2Len != 1) */ expr_ref xorFlag(m); std::pair key1(arg1, arg2); @@ -4569,6 +4631,8 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { // check the entries in this map to make sure they're still in scope // before we use them. + // TODO XOR variables will always show up as "not in scope" because of how we update internal_variable_set + std::map, std::map >::iterator entry1 = varForBreakConcat.find(key1); std::map, std::map >::iterator entry2 = varForBreakConcat.find(key2); @@ -4609,10 +4673,7 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { int concatStrLen = const_str.length(); int xor_pos = 0; int and_count = 1; - /* - expr ** xor_items = new expr*[concatStrLen + 1]; - expr ** and_items = new expr*[4 * (concatStrLen+1) + 1]; - */ + expr ** xor_items = alloc_svect(expr*, (concatStrLen+1)); expr ** and_items = alloc_svect(expr*, (4 * (concatStrLen+1) + 1)); @@ -4620,15 +4681,12 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { std::string prefixStr = const_str.substr(0, i); std::string suffixStr = const_str.substr(i, concatStrLen - i); // skip invalid options - // TODO canConcatEqStr() checks: - /* - if (isConcatFunc(t, arg1) && canConcatEqStr(t, arg1, prefixStr) == 0) { - continue; - } - if (isConcatFunc(t, arg2) && canConcatEqStr(t, arg2, suffixStr) == 0) { - continue; - } - */ + if (is_concat(to_app(arg1)) && !can_concat_eq_str(arg1, prefixStr)) { + continue; + } + if (is_concat(to_app(arg2)) && !can_concat_eq_str(arg2, suffixStr)) { + continue; + } expr_ref xorAst(ctx.mk_eq_atom(xorFlag, m_autil.mk_numeral(rational(xor_pos), true)), m); xor_items[xor_pos++] = xorAst; From ee1af96f1bf92d1546449dc30573b62f27c34fae Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Mon, 1 Aug 2016 17:05:02 -0400 Subject: [PATCH 163/410] add opt_NoQuickReturn_IntegerTheory check in theory_str::new_eq_check() This allows us to assert an "inconsistent length" axiom from the integer theory while continuing in new_eq_handler(). Currently active when opt_NoQuickReturn_IntegerTheory is 'true' but this may be necessary here and in other places, in general, to fix integer theory integration. --- src/smt/theory_str.cpp | 13 +++++++++---- src/smt/theory_str.h | 10 ++++------ 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 2b94b0c40..a80fd2165 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -33,7 +33,7 @@ theory_str::theory_str(ast_manager & m): opt_EagerStringConstantLengthAssertions(true), opt_VerifyFinalCheckProgress(true), opt_LCMUnrollStep(2), - opt_NoQuickReturn_Concat_IntegerTheory(false), + opt_NoQuickReturn_IntegerTheory(true), opt_DisableIntegerTheoryIntegration(false), /* Internal setup */ search_started(false), @@ -1467,11 +1467,16 @@ bool theory_str::new_eq_check(expr * lhs, expr * rhs) { TRACE("t_str", tout << "inconsistency detected: " << mk_pp(eqc_nn1, m) << " cannot be equal to " << mk_pp(eqc_nn2, m) << std::endl;); expr_ref to_assert(m.mk_not(ctx.mk_eq_atom(eqc_nn1, eqc_nn2)), m); assert_axiom(to_assert); + // this shouldn't use the integer theory at all, so we don't allow the option of quick-return return false; } if (!check_length_consistency(eqc_nn1, eqc_nn2)) { TRACE("t_str", tout << "inconsistency detected: " << mk_pp(eqc_nn1, m) << " and " << mk_pp(eqc_nn2, m) << " have inconsistent lengths" << std::endl;); - return false; + if (opt_NoQuickReturn_IntegerTheory){ + TRACE("t_str_detail", tout << "continuing in new_eq_check() due to opt_NoQuickReturn_IntegerTheory" << std::endl;); + } else { + return false; + } } } eqc_iterator2 = eqc_iterator2->get_next(); @@ -2175,7 +2180,7 @@ void theory_str::simplify_concat_equality(expr * nn1, expr * nn2) { assert_implication(premise, conclusion); - if (opt_NoQuickReturn_Concat_IntegerTheory) { + if (opt_NoQuickReturn_IntegerTheory) { TRACE("t_str_detail", tout << "bypassing quick return from the end of this case" << std::endl;); } else { return; @@ -2195,7 +2200,7 @@ void theory_str::simplify_concat_equality(expr * nn1, expr * nn2) { expr_ref conclusion(m.mk_and(ax_r1, ax_r2), m); assert_implication(premise, conclusion); - if (opt_NoQuickReturn_Concat_IntegerTheory) { + if (opt_NoQuickReturn_IntegerTheory) { TRACE("t_str_detail", tout << "bypassing quick return from the end of this case" << std::endl;); } else { return; diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index bd66e64d4..74c1786df 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -95,14 +95,12 @@ namespace smt { int opt_LCMUnrollStep; /* - * If NoQuickReturn_Concat_IntegerTheory is set to true, - * the integer theory integration conditionals in simplify_concat_equality() + * If NoQuickReturn_IntegerTheory is set to true, + * integer theory integration checks that assert axioms * will not return from the function after asserting their axioms. - * This means that control will fall through to the type 1-6 axioms, - * causing those to be added as well. - * The default behaviour of Z3str2 is to set this to 'false'. + * The default behaviour of Z3str2 is to set this to 'false'. This may be incorrect. */ - bool opt_NoQuickReturn_Concat_IntegerTheory; + bool opt_NoQuickReturn_IntegerTheory; /* * If DisableIntegerTheoryIntegration is set to true, From 97f07a8a7c6a8c1379da46abcf9bb7c58652c0e4 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Mon, 1 Aug 2016 18:14:56 -0400 Subject: [PATCH 164/410] fix debugging statements in theory_str::gen_len_test_options this fixes charAt-007.smt2 and prevents two unique crashes --- src/smt/theory_str.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index a80fd2165..ee17edb9c 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -6998,7 +6998,7 @@ expr * theory_str::gen_len_test_options(expr * freeVar, expr * indicator, int tr ast_manager & m = get_manager(); context & ctx = get_context(); - TRACE("t_str_detail", tout << "entry" << std::endl;); + //TRACE("t_str_detail", tout << "entry" << std::endl;); expr_ref freeVarLen(mk_strlen(freeVar), m); SASSERT(freeVarLen); @@ -7020,9 +7020,9 @@ expr * theory_str::gen_len_test_options(expr * freeVar, expr * indicator, int tr for (int i = l; i < h; ++i) { std::string i_str = int_to_string(i); expr_ref str_indicator(m_strutil.mk_string(i_str), m); - TRACE("t_str_detail", tout << "just created a string term: " << mk_ismt2_pp(str_indicator, m) << std::endl;); + //TRACE("t_str_detail", tout << "just created a string term: " << mk_ismt2_pp(str_indicator, m) << std::endl;); expr * or_expr = m.mk_eq(indicator, str_indicator); // ARGUMENT 2 IS BOGUS! WRONG SORT - TRACE("t_str_detail", tout << "or_expr = " << mk_ismt2_pp(or_expr, m) << std::endl;); + //TRACE("t_str_detail", tout << "or_expr = " << mk_ismt2_pp(or_expr, m) << std::endl;); orList.push_back(or_expr); if (opt_AggressiveLengthTesting) { @@ -7032,7 +7032,7 @@ expr * theory_str::gen_len_test_options(expr * freeVar, expr * indicator, int tr } expr * and_expr = m.mk_eq(orList[orList.size() - 1], m.mk_eq(freeVarLen, mk_int(i))); - TRACE("t_str_detail", tout << "and_expr = " << mk_ismt2_pp(and_expr, m) << std::endl;); + //TRACE("t_str_detail", tout << "and_expr = " << mk_ismt2_pp(and_expr, m) << std::endl;); andList.push_back(and_expr); } @@ -7063,7 +7063,7 @@ expr * theory_str::gen_len_test_options(expr * freeVar, expr * indicator, int tr expr * lenTestAssert = m.mk_and(andList.size() + 1, and_items); SASSERT(lenTestAssert != NULL); - TRACE("t_str_detail", tout << "lenTestAssert = " << mk_ismt2_pp(lenTestAssert, m) << std::endl;); + //TRACE("t_str_detail", tout << "lenTestAssert = " << mk_ismt2_pp(lenTestAssert, m) << std::endl;); expr * assertL = NULL; int testerCount = tries - 1; @@ -7081,13 +7081,13 @@ expr * theory_str::gen_len_test_options(expr * freeVar, expr * indicator, int tr } if (assertL != NULL) { - TRACE("t_str_detail", tout << "assertL = " << mk_ismt2_pp(assertL, m) << std::endl;); + m_trail.push_back(assertL); // return the axiom (assertL -> lenTestAssert) // would like to use mk_implies() here but... lenTestAssert = m.mk_or(m.mk_not(assertL), lenTestAssert); } - TRACE("t_str_detail", tout << "exit" << std::endl;); + //TRACE("t_str_detail", tout << "exit" << std::endl;); return lenTestAssert; From a51ad07e3f59f2db46f7534283e68729d33863b0 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Mon, 1 Aug 2016 20:52:42 -0400 Subject: [PATCH 165/410] crash avoidance in propagation of basic string axioms and gen_len_test_options --- src/smt/theory_str.cpp | 99 +++++++++++++++++++++++------------------- 1 file changed, 55 insertions(+), 44 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index ee17edb9c..b96e454eb 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -237,6 +237,7 @@ bool theory_str::internalize_term(app * term) { if (opt_EagerStringConstantLengthAssertions && m_strutil.is_string(term)) { TRACE("t_str", tout << "eagerly asserting length of string term " << mk_pp(term, m) << std::endl;); m_basicstr_axiom_todo.insert(e); + TRACE("t_str_axiom_bug", tout << "add " << mk_pp(e->get_owner(), m) << " to m_basicstr_axiom_todo" << std::endl;); } theory_var v = mk_var(e); @@ -404,6 +405,7 @@ expr * theory_str::mk_internal_lenTest_var(expr * node, int lTries) { std::string name = ss.str(); app * var = mk_str_var(name); internal_lenTest_vars.insert(var); + m_trail.push_back(var); return var; } @@ -415,6 +417,7 @@ expr * theory_str::mk_internal_valTest_var(expr * node, int len, int vTries) { std::string name = ss.str(); app * var = mk_str_var(name); internal_valTest_vars.insert(var); + m_trail.push_back(var); return var; } @@ -494,6 +497,7 @@ app * theory_str::mk_str_var(std::string name) { // this might help?? mk_var(ctx.get_enode(a)); m_basicstr_axiom_todo.push_back(ctx.get_enode(a)); + TRACE("t_str_axiom_bug", tout << "add " << mk_pp(a, m) << " to m_basicstr_axiom_todo" << std::endl;); m_trail.push_back(a); variable_set.insert(a); @@ -515,6 +519,7 @@ app * theory_str::mk_regex_rep_var() { SASSERT(ctx.e_internalized(a)); mk_var(ctx.get_enode(a)); m_basicstr_axiom_todo.push_back(ctx.get_enode(a)); + TRACE("t_str_axiom_bug", tout << "add " << mk_pp(a, m) << " to m_basicstr_axiom_todo" << std::endl;); m_trail.push_back(a); // TODO cross-check which variable sets we need @@ -689,6 +694,7 @@ void theory_str::propagate() { instantiate_basic_string_axioms(m_basicstr_axiom_todo[i]); } m_basicstr_axiom_todo.reset(); + TRACE("t_str_axiom_bug", tout << "reset m_basicstr_axiom_todo" << std::endl;); for (unsigned i = 0; i < m_str_eq_todo.size(); ++i) { std::pair pair = m_str_eq_todo[i]; @@ -811,6 +817,8 @@ void theory_str::instantiate_basic_string_axioms(enode * str) { context & ctx = get_context(); ast_manager & m = get_manager(); + TRACE("t_str_axiom_bug", tout << "set up basic string axioms on " << mk_pp(str->get_owner(), m) << std::endl;); + // TESTING: attempt to avoid a crash here when a variable goes out of scope // TODO this seems to work so we probably need to do this for other propagate checks, etc. if (str->get_iscope_lvl() > ctx.get_scope_level()) { @@ -5010,6 +5018,7 @@ void theory_str::set_up_axioms(expr * ex) { enode * n = ctx.get_enode(ex); SASSERT(n); m_basicstr_axiom_todo.push_back(n); + TRACE("t_str_axiom_bug", tout << "add " << mk_pp(ex, m) << " to m_basicstr_axiom_todo" << std::endl;); if (is_app(ex)) { @@ -5222,6 +5231,22 @@ void theory_str::pop_scope_eh(unsigned num_scopes) { vars.clear(); } } + + // TODO if this works, possibly remove axioms from other vectors as well + ptr_vector new_m_basicstr; + for (ptr_vector::iterator it = m_basicstr_axiom_todo.begin(); it != m_basicstr_axiom_todo.end(); ++it) { + enode * e = *it; + app * a = e->get_owner(); + TRACE("t_str_axiom_bug", tout << "consider deleting " << mk_pp(a, get_manager()) + << ", enode scope level is " << e->get_iscope_lvl() + << std::endl;); + if (e->get_iscope_lvl() <= (unsigned)sLevel) { + new_m_basicstr.push_back(e); + } + } + m_basicstr_axiom_todo.reset(); + m_basicstr_axiom_todo = new_m_basicstr; + theory::pop_scope_eh(num_scopes); } @@ -6998,13 +7023,11 @@ expr * theory_str::gen_len_test_options(expr * freeVar, expr * indicator, int tr ast_manager & m = get_manager(); context & ctx = get_context(); - //TRACE("t_str_detail", tout << "entry" << std::endl;); - expr_ref freeVarLen(mk_strlen(freeVar), m); SASSERT(freeVarLen); - ptr_vector orList; - ptr_vector andList; + expr_ref_vector orList(m); + expr_ref_vector andList(m); int distance = 3; int l = (tries - 1) * distance; @@ -7020,9 +7043,7 @@ expr * theory_str::gen_len_test_options(expr * freeVar, expr * indicator, int tr for (int i = l; i < h; ++i) { std::string i_str = int_to_string(i); expr_ref str_indicator(m_strutil.mk_string(i_str), m); - //TRACE("t_str_detail", tout << "just created a string term: " << mk_ismt2_pp(str_indicator, m) << std::endl;); - expr * or_expr = m.mk_eq(indicator, str_indicator); // ARGUMENT 2 IS BOGUS! WRONG SORT - //TRACE("t_str_detail", tout << "or_expr = " << mk_ismt2_pp(or_expr, m) << std::endl;); + expr_ref or_expr(ctx.mk_eq_atom(indicator, str_indicator), m); // ARGUMENT 2 IS BOGUS! WRONG SORT orList.push_back(or_expr); if (opt_AggressiveLengthTesting) { @@ -7031,8 +7052,7 @@ expr * theory_str::gen_len_test_options(expr * freeVar, expr * indicator, int tr ctx.force_phase(l); } - expr * and_expr = m.mk_eq(orList[orList.size() - 1], m.mk_eq(freeVarLen, mk_int(i))); - //TRACE("t_str_detail", tout << "and_expr = " << mk_ismt2_pp(and_expr, m) << std::endl;); + expr_ref and_expr(ctx.mk_eq_atom(orList.get(orList.size() - 1), m.mk_eq(freeVarLen, mk_int(i))), m); andList.push_back(and_expr); } @@ -7043,54 +7063,44 @@ expr * theory_str::gen_len_test_options(expr * freeVar, expr * indicator, int tr ctx.force_phase(~l); } - andList.push_back(m.mk_eq(orList[orList.size() - 1], m_autil.mk_ge(freeVarLen, mk_int(h)))); + andList.push_back(ctx.mk_eq_atom(orList.get(orList.size() - 1), m_autil.mk_ge(freeVarLen, mk_int(h)))); - // TODO refactor this to use expr_ref_vector/svector/buffer instead - expr ** or_items = alloc_svect(expr*, orList.size()); - expr ** and_items = alloc_svect(expr*, andList.size() + 1); + expr_ref_vector or_items(m); + expr_ref_vector and_items(m); for (unsigned i = 0; i < orList.size(); ++i) { - SASSERT(orList[i] != NULL); - or_items[i] = orList[i]; + or_items.push_back(orList.get(i)); } - and_items[0] = m.mk_or(orList.size(), or_items); - SASSERT(and_items[0] != NULL); + and_items.push_back(mk_or(or_items)); for(unsigned i = 0; i < andList.size(); ++i) { - SASSERT(andList[i] != NULL); - and_items[i+1] = andList[i]; + and_items.push_back(andList.get(i)); } - expr * lenTestAssert = m.mk_and(andList.size() + 1, and_items); - SASSERT(lenTestAssert != NULL); - //TRACE("t_str_detail", tout << "lenTestAssert = " << mk_ismt2_pp(lenTestAssert, m) << std::endl;); + TRACE("t_str_detail", tout << "check: " << mk_pp(mk_and(and_items), m) << std::endl;); + + expr_ref lenTestAssert = mk_and(and_items); + SASSERT(lenTestAssert); + TRACE("t_str_detail", tout << "crash avoidance lenTestAssert: " << mk_pp(lenTestAssert, m) << std::endl;); - expr * assertL = NULL; int testerCount = tries - 1; if (testerCount > 0) { - expr ** and_items_LHS = alloc_svect(expr*, testerCount); - expr * moreAst = m_strutil.mk_string("more"); + expr_ref_vector and_items_LHS(m); + expr_ref moreAst(m_strutil.mk_string("more"), m); for (int i = 0; i < testerCount; ++i) { - and_items_LHS[i] = m.mk_eq(fvar_lenTester_map[freeVar][i], moreAst); - } - if (testerCount == 1) { - assertL = and_items_LHS[0]; - } else { - assertL = m.mk_and(testerCount, and_items_LHS); + and_items_LHS.push_back(ctx.mk_eq_atom(fvar_lenTester_map[freeVar][i], moreAst)); } + expr_ref assertL(mk_and(and_items_LHS), m); + SASSERT(assertL); + expr * finalAxiom = m.mk_or(m.mk_not(assertL), lenTestAssert.get()); + SASSERT(finalAxiom != NULL); + TRACE("t_str_detail", tout << "crash avoidance finalAxiom: " << mk_pp(finalAxiom, m) << std::endl;); + return finalAxiom; + } else { + TRACE("t_str_detail", tout << "crash avoidance lenTestAssert.get(): " << mk_pp(lenTestAssert.get(), m) << std::endl;); + m_trail.push_back(lenTestAssert.get()); + return lenTestAssert.get(); } - - if (assertL != NULL) { - m_trail.push_back(assertL); - // return the axiom (assertL -> lenTestAssert) - // would like to use mk_implies() here but... - lenTestAssert = m.mk_or(m.mk_not(assertL), lenTestAssert); - } - - //TRACE("t_str_detail", tout << "exit" << std::endl;); - - return lenTestAssert; - } // ----------------------------------------------------------------------------------------------------- @@ -7237,7 +7247,7 @@ expr * theory_str::gen_len_val_options_for_free_var(expr * freeVar, expr * lenTe } // for (i : [0..lenTesterCount-1]) if (effectiveLenIndiStr == "more" || effectiveLenIndiStr == "") { TRACE("t_str", tout << "length is not fixed; generating length tester options for free var" << std::endl;); - expr * indicator = NULL; + expr_ref indicator(m); unsigned int testNum = 0; TRACE("t_str", tout << "effectiveLenIndiStr = " << effectiveLenIndiStr @@ -7261,6 +7271,7 @@ expr * theory_str::gen_len_val_options_for_free_var(expr * freeVar, expr * lenTe TRACE("t_str", tout << "length is fixed; generating models for free var" << std::endl;); // length is fixed expr * valueAssert = gen_free_var_options(freeVar, effectiveLenInd, effectiveLenIndiStr, NULL, ""); + SASSERT(valueAssert != NULL); return valueAssert; } } // fVarLenCountMap.find(...) From 45c495495975a9b88b2996b4c06e46627a87bd01 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 2 Aug 2016 14:52:44 -0400 Subject: [PATCH 166/410] add debugging to theory_str::get_len_value in preparation for fixes --- src/smt/theory_str.cpp | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index b96e454eb..395e1dab6 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -33,7 +33,7 @@ theory_str::theory_str(ast_manager & m): opt_EagerStringConstantLengthAssertions(true), opt_VerifyFinalCheckProgress(true), opt_LCMUnrollStep(2), - opt_NoQuickReturn_IntegerTheory(true), + opt_NoQuickReturn_IntegerTheory(false), opt_DisableIntegerTheoryIntegration(false), /* Internal setup */ search_started(false), @@ -3882,6 +3882,7 @@ bool theory_str::get_len_value(expr* e, rational& val) { context& ctx = get_context(); ast_manager & m = get_manager(); + theory* th = ctx.get_theory(m_autil.get_family_id()); if (!th) { TRACE("t_str_int", tout << "oops, can't get m_autil's theory" << std::endl;); @@ -3926,6 +3927,18 @@ bool theory_str::get_len_value(expr* e, rational& val) { if (ctx.e_internalized(len)) { enode * e_len = ctx.get_enode(len); tout << "has " << e_len->get_num_th_vars() << " theory vars" << std::endl; + + // eqc debugging + { + tout << "dump equivalence class of " << mk_pp(len, get_manager()) << std::endl; + enode * nNode = ctx.get_enode(len); + enode * eqcNode = nNode; + do { + app * ast = eqcNode->get_owner(); + tout << mk_pp(ast, get_manager()) << std::endl; + eqcNode = eqcNode->get_next(); + } while (eqcNode != nNode); + } } }); @@ -3939,6 +3952,7 @@ bool theory_str::get_len_value(expr* e, rational& val) { } } } + TRACE("t_str_int", tout << "length of " << mk_ismt2_pp(e, m) << " is " << val << std::endl;); return val.is_int(); } From 3c2fe497de4fee845f36e7a7bfc9b4860a6d265f Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 2 Aug 2016 16:44:54 -0400 Subject: [PATCH 167/410] modify theory_str::get_value() to check EQC for a numeral Instead of asking the arithmetic theory for the current assignment, we return the (unique) numeral in the equivalence class of the term whose length we want to know. This is because the arithmetic theory may return a default / internal value that doesn't correspond to anything actually asserted by the core solver. --- src/smt/theory_str.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 395e1dab6..2ae5dcec5 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -3832,13 +3832,13 @@ bool theory_str::get_value(expr* e, rational& val) const { enode * en_e = ctx.get_enode(e); enode * it = en_e; do { - if (tha->get_value(it, _val)) { + if (m_autil.is_numeral(it->get_owner(), val) && val.is_int()) { // found an arithmetic term - TRACE("t_str_int", tout << "get_value[" << mk_pp(it->get_owner(), m) << "] = " << mk_pp(_val, m) + TRACE("t_str_int", tout << mk_pp(it->get_owner(), m) << " is an integer ( ~= " << val << " )" << std::endl;); - return m_autil.is_numeral(_val, val) && val.is_int(); + return true; } else { - TRACE("t_str_int", tout << "get_value[" << mk_pp(it->get_owner(), m) << "] not found" << std::endl;); + TRACE("t_str_int", tout << mk_pp(it->get_owner(), m) << " not a numeral" << std::endl;); } it = it->get_next(); } while (it != en_e); From bc91d182bf97c64649d2b75d22b0163db9c29598 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 3 Aug 2016 13:39:14 -0400 Subject: [PATCH 168/410] mk_concat fixes WIP --- src/smt/theory_str.cpp | 79 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 72 insertions(+), 7 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 2ae5dcec5..6ae9fbf66 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -661,21 +661,86 @@ expr * theory_str::mk_concat_const_str(expr * n1, expr * n2) { expr * theory_str::mk_concat(expr * n1, expr * n2) { ast_manager & m = get_manager(); - if (n1 == NULL || n2 == NULL) { - m.raise_exception("strings to be concatenated cannot be NULL"); - } + ENSURE(n1 != NULL); + ENSURE(n2 != NULL); bool n1HasEqcValue = false; bool n2HasEqcValue = false; n1 = get_eqc_value(n1, n1HasEqcValue); n2 = get_eqc_value(n2, n2HasEqcValue); if (n1HasEqcValue && n2HasEqcValue) { return mk_concat_const_str(n1, n2); + } else if (n1HasEqcValue && !n2HasEqcValue) { + bool n2_isConcatFunc = is_concat(to_app(n2)); + if (m_strutil.get_string_constant_value(n1) == "") { + return n2; + } + if (n2_isConcatFunc) { + expr * n2_arg0 = to_app(n2)->get_arg(0); + expr * n2_arg1 = to_app(n2)->get_arg(1); + if (m_strutil.is_string(n2_arg0)) { + n1 = mk_concat_const_str(n1, n2_arg0); // n1 will be a constant + n2 = n2_arg1; + } + } + } else if (!n1HasEqcValue && n2HasEqcValue) { + if (m_strutil.get_string_constant_value(n2) == "") { + return n1; + } + + if (is_concat(to_app(n1))) { + expr * n1_arg0 = to_app(n1)->get_arg(0); + expr * n1_arg1 = to_app(n1)->get_arg(1); + if (m_strutil.is_string(n1_arg1)) { + n1 = n1_arg0; + n2 = mk_concat_const_str(n1_arg1, n2); // n2 will be a constant + } + } } else { - // TODO there's a *TON* of missing code here from strTheory::mk_concat() - // if all else fails, just build the application AST - expr * args[2] = {n1, n2}; - return get_manager().mk_app(get_id(), OP_STRCAT, 0, 0, 2, args); + if (is_concat(to_app(n1)) && is_concat(to_app(n2))) { + expr * n1_arg0 = to_app(n1)->get_arg(0); + expr * n1_arg1 = to_app(n1)->get_arg(1); + expr * n2_arg0 = to_app(n2)->get_arg(0); + expr * n2_arg1 = to_app(n2)->get_arg(1); + if (m_strutil.is_string(n1_arg1) && m_strutil.is_string(n2_arg0)) { + expr * tmpN1 = n1_arg0; + expr * tmpN2 = mk_concat_const_str(n1_arg1, n2_arg0); + n1 = mk_concat(tmpN1, tmpN2); + n2 = n2_arg1; + } + } } + + //------------------------------------------------------ + // * expr * ast1 = mk_2_arg_app(ctx, td->Concat, n1, n2); + // * expr * ast2 = mk_2_arg_app(ctx, td->Concat, n1, n2); + // Z3 treats (ast1) and (ast2) as two different nodes. + //------------------------------------------------------- + std::pair concatArgs(n1, n2); + expr * concatAst = NULL; + // TODO NEXT clarify semantics of this, I think we can get around this check. + NOT_IMPLEMENTED_YET(); + /* + if (concat_astNode_map.find(concatArgs) == concat_astNode_map.end()) { + concatAst = mk_2_arg_app(ctx, td->Concat, n1, n2); + concat_astNode_map[concatArgs] = concatAst; + + Z3_ast concat_length = mk_length(t, concatAst); + + std::vector childrenVector; + getNodesInConcat(t, concatAst, childrenVector); + Z3_ast * items = new Z3_ast[childrenVector.size()]; + for (unsigned int i = 0; i < childrenVector.size(); i++) { + items[i] = mk_length(t, childrenVector[i]); + } + Z3_ast lenAssert = Z3_mk_eq(ctx, concat_length, Z3_mk_add(ctx, childrenVector.size(), items)); + addAxiom(t, lenAssert, __LINE__, false); + delete[] items; + + } else { + concatAst = concat_astNode_map[concatArgs]; + } + */ + return concatAst; } bool theory_str::can_propagate() { From 0c4e7259025207a9e88f221a70147513a371ea7f Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Thu, 4 Aug 2016 16:40:05 -0400 Subject: [PATCH 169/410] finish theory_str::mk_concat, no caching of generated terms yet --- src/smt/theory_str.cpp | 41 +++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 6ae9fbf66..78ab30dd7 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -660,6 +660,7 @@ expr * theory_str::mk_concat_const_str(expr * n1, expr * n2) { } expr * theory_str::mk_concat(expr * n1, expr * n2) { + context & ctx = get_context(); ast_manager & m = get_manager(); ENSURE(n1 != NULL); ENSURE(n2 != NULL); @@ -717,29 +718,29 @@ expr * theory_str::mk_concat(expr * n1, expr * n2) { //------------------------------------------------------- std::pair concatArgs(n1, n2); expr * concatAst = NULL; - // TODO NEXT clarify semantics of this, I think we can get around this check. - NOT_IMPLEMENTED_YET(); + // TODO NEXT add cache lookups. I think we need to be more careful than just using std:: data structures here /* if (concat_astNode_map.find(concatArgs) == concat_astNode_map.end()) { - concatAst = mk_2_arg_app(ctx, td->Concat, n1, n2); - concat_astNode_map[concatArgs] = concatAst; - - Z3_ast concat_length = mk_length(t, concatAst); - - std::vector childrenVector; - getNodesInConcat(t, concatAst, childrenVector); - Z3_ast * items = new Z3_ast[childrenVector.size()]; - for (unsigned int i = 0; i < childrenVector.size(); i++) { - items[i] = mk_length(t, childrenVector[i]); - } - Z3_ast lenAssert = Z3_mk_eq(ctx, concat_length, Z3_mk_add(ctx, childrenVector.size(), items)); - addAxiom(t, lenAssert, __LINE__, false); - delete[] items; - - } else { - concatAst = concat_astNode_map[concatArgs]; - } */ + if (true) { + expr * args[2] = {n1, n2}; + concatAst = m.mk_app(get_id(), OP_STRCAT, 0, 0, 2, args); + // concat_astNode_map[concatArgs] = concatAst; + + expr_ref concat_length(mk_strlen(concatAst), m); + + ptr_vector childrenVector; + get_nodes_in_concat(concatAst, childrenVector); + expr_ref_vector items(m); + for (unsigned int i = 0; i < childrenVector.size(); i++) { + items.push_back(mk_strlen(childrenVector.get(i))); + } + expr_ref lenAssert(ctx.mk_eq_atom(concat_length, m_autil.mk_add(items.size(), items.c_ptr())), m); + assert_axiom(lenAssert); + } else { + // concatAst = concat_astNode_map[concatArgs]; + NOT_IMPLEMENTED_YET(); + } return concatAst; } From 91c336d7eeea124836c908195b91d634f23cb476 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sat, 6 Aug 2016 15:32:37 -0400 Subject: [PATCH 170/410] fix erroneous vector double-insert in theory_str::group_terms_by_eqc() --- src/smt/theory_str.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 78ab30dd7..296041a39 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -1588,7 +1588,6 @@ void theory_str::group_terms_by_eqc(expr * n, std::set & concats, std::se } else { concats.insert(simConcat); } - concats.insert(ast); } else if (is_string(eqcNode)) { consts.insert(ast); } else { From 2c91f388dfa02fa74c933af9d7249a61a276942e Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sat, 6 Aug 2016 15:35:47 -0400 Subject: [PATCH 171/410] add defensive double-non-concat check in theory_str::simplify_concat_equality() --- src/smt/theory_str.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 296041a39..ac897ee7d 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -2317,10 +2317,13 @@ void theory_str::simplify_concat_equality(expr * nn1, expr * nn2) { simplify_parent(new_nn1, new_nn2); } return; + } else if (!n1IsConcat && !n2IsConcat) { + // normally this should never happen, because group_terms_by_eqc() should have pre-simplified + // as much as possible. however, we make a defensive check here just in case + TRACE("t_str_detail", tout << "WARNING: nn1_new and nn2_new both simplify to non-concat terms" << std::endl;); + return; } - // TODO what happens if BOTH of these are simplified into non-concat terms? - expr * v1_arg0 = a_new_nn1->get_arg(0); expr * v1_arg1 = a_new_nn1->get_arg(1); expr * v2_arg0 = a_new_nn2->get_arg(0); From 43b0cd5010fe4ebb9060142d9dad127b630f4d76 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sat, 6 Aug 2016 15:38:58 -0400 Subject: [PATCH 172/410] clean up unused variables in theory_str.cpp --- src/smt/theory_str.cpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index ac897ee7d..7b96a57f7 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -422,7 +422,6 @@ expr * theory_str::mk_internal_valTest_var(expr * node, int len, int vTries) { } void theory_str::track_variable_scope(expr * var) { - context & ctx = get_context(); if (internal_variable_scope_levels.find(sLevel) == internal_variable_scope_levels.end()) { internal_variable_scope_levels[sLevel] = std::set(); } @@ -431,7 +430,6 @@ void theory_str::track_variable_scope(expr * var) { app * theory_str::mk_internal_xor_var() { ast_manager & m = get_manager(); - context & ctx = get_context(); std::stringstream ss; ss << tmpXorVarCount; tmpXorVarCount++; @@ -3730,7 +3728,6 @@ void theory_str::process_concat_eq_type6(expr * concatAst1, expr * concatAst2) { } void theory_str::process_unroll_eq_const_str(expr * unrollFunc, expr * constStr) { - context & ctx = get_context(); ast_manager & m = get_manager(); if (!is_Unroll(to_app(unrollFunc))) { @@ -4461,7 +4458,6 @@ bool theory_str::check_length_consistency(expr * n1, expr * n2) { void theory_str::check_concat_len_in_eqc(expr * concat) { context & ctx = get_context(); - ast_manager & m = get_manager(); enode * eqc_base = ctx.get_enode(concat); enode * eqc_it = eqc_base; @@ -5271,14 +5267,12 @@ void theory_str::assign_eh(bool_var v, bool is_true) { void theory_str::push_scope_eh() { theory::push_scope_eh(); - context & ctx = get_context(); sLevel += 1; TRACE("t_str", tout << "push to " << sLevel << std::endl;); TRACE("t_str_dump_assign_on_scope_change", dump_assignments();); } void theory_str::pop_scope_eh(unsigned num_scopes) { - context & ctx = get_context(); sLevel -= num_scopes; TRACE("t_str", tout << "pop " << num_scopes << " to " << sLevel << std::endl;); @@ -6664,7 +6658,6 @@ expr * theory_str::gen_val_options(expr * freeVar, expr * len_indicator, expr * expr * theory_str::gen_free_var_options(expr * freeVar, expr * len_indicator, std::string len_valueStr, expr * valTesterInCbEq, std::string valTesterValueStr) { - context & ctx = get_context(); ast_manager & m = get_manager(); int len = atoi(len_valueStr.c_str()); From 395ec4543c1332434733f78ff18f5d162e537120 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sat, 6 Aug 2016 22:19:10 -0400 Subject: [PATCH 173/410] avoid crash in theory_str, this leaks memory --- src/smt/theory_str.cpp | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 7b96a57f7..2e95020a7 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -315,12 +315,14 @@ void theory_str::add_cut_info_one_node(expr * baseNode, int slevel, expr * node) varInfo->level = slevel; varInfo->vars[node] = 1; cut_var_map[baseNode].push(varInfo); + TRACE("t_str_cut_var_map", tout << "add var info for baseNode=" << mk_pp(baseNode, get_manager()) << ", node=" << mk_pp(node, get_manager()) << std::endl;); } else { if (cut_var_map[baseNode].empty()) { T_cut * varInfo = alloc(T_cut); varInfo->level = slevel; varInfo->vars[node] = 1; cut_var_map[baseNode].push(varInfo); + TRACE("t_str_cut_var_map", tout << "add var info for baseNode=" << mk_pp(baseNode, get_manager()) << ", node=" << mk_pp(node, get_manager()) << std::endl;); } else { if (cut_var_map[baseNode].top()->level < slevel) { T_cut * varInfo = alloc(T_cut); @@ -328,8 +330,10 @@ void theory_str::add_cut_info_one_node(expr * baseNode, int slevel, expr * node) cut_vars_map_copy(varInfo->vars, cut_var_map[baseNode].top()->vars); varInfo->vars[node] = 1; cut_var_map[baseNode].push(varInfo); + TRACE("t_str_cut_var_map", tout << "add var info for baseNode=" << mk_pp(baseNode, get_manager()) << ", node=" << mk_pp(node, get_manager()) << std::endl;); } else if (cut_var_map[baseNode].top()->level == slevel) { cut_var_map[baseNode].top()->vars[node] = 1; + TRACE("t_str_cut_var_map", tout << "add var info for baseNode=" << mk_pp(baseNode, get_manager()) << ", node=" << mk_pp(node, get_manager()) << std::endl;); } else { get_manager().raise_exception("entered illegal state during add_cut_info_one_node()"); } @@ -354,6 +358,7 @@ void theory_str::add_cut_info_merge(expr * destNode, int slevel, expr * srcNode) varInfo->level = slevel; cut_vars_map_copy(varInfo->vars, cut_var_map[srcNode].top()->vars); cut_var_map[destNode].push(varInfo); + TRACE("t_str_cut_var_map", tout << "merge var info for destNode=" << mk_pp(destNode, get_manager()) << ", srcNode=" << mk_pp(srcNode, get_manager()) << std::endl;); } else { if (cut_var_map[destNode].empty() || cut_var_map[destNode].top()->level < slevel) { T_cut * varInfo = alloc(T_cut); @@ -361,8 +366,10 @@ void theory_str::add_cut_info_merge(expr * destNode, int slevel, expr * srcNode) cut_vars_map_copy(varInfo->vars, cut_var_map[destNode].top()->vars); cut_vars_map_copy(varInfo->vars, cut_var_map[srcNode].top()->vars); cut_var_map[destNode].push(varInfo); + TRACE("t_str_cut_var_map", tout << "merge var info for destNode=" << mk_pp(destNode, get_manager()) << ", srcNode=" << mk_pp(srcNode, get_manager()) << std::endl;); } else if (cut_var_map[destNode].top()->level == slevel) { cut_vars_map_copy(cut_var_map[destNode].top()->vars, cut_var_map[srcNode].top()->vars); + TRACE("t_str_cut_var_map", tout << "merge var info for destNode=" << mk_pp(destNode, get_manager()) << ", srcNode=" << mk_pp(srcNode, get_manager()) << std::endl;); } else { get_manager().raise_exception("illegal state in add_cut_info_merge(): inconsistent slevels"); } @@ -5281,12 +5288,13 @@ void theory_str::pop_scope_eh(unsigned num_scopes) { while ((varItor->second.size() > 0) && (varItor->second.top()->level != 0) && (varItor->second.top()->level >= sLevel)) { T_cut * aCut = varItor->second.top(); varItor->second.pop(); - dealloc(aCut); + // dealloc(aCut); // TODO find a safer way to do this, it is causing a crash } if (varItor->second.size() == 0) { - cut_var_map.erase(varItor); + cut_var_map.erase(varItor++); + } else { + varItor++; } - ++varItor; } // see if any internal variables went out of scope From cb566ad5ced242cad991c8381322522bcddc98eb Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sun, 7 Aug 2016 15:43:08 -0400 Subject: [PATCH 174/410] fix model validation for theory_str --- src/ast/rewriter/str_rewriter.cpp | 17 +++++++++++++++++ src/ast/rewriter/str_rewriter.h | 1 + src/model/model_evaluator.cpp | 7 +++++++ 3 files changed, 25 insertions(+) diff --git a/src/ast/rewriter/str_rewriter.cpp b/src/ast/rewriter/str_rewriter.cpp index 1449afcc3..37b6b6cbf 100644 --- a/src/ast/rewriter/str_rewriter.cpp +++ b/src/ast/rewriter/str_rewriter.cpp @@ -23,6 +23,20 @@ Notes: #include"ast_util.h" #include"well_sorted.h" +br_status str_rewriter::mk_str_Concat(expr * arg0, expr * arg1, expr_ref & result) { + TRACE("t_str_rw", tout << "rewrite (Concat " << mk_pp(arg0, m()) << " " << mk_pp(arg1, m()) << ")" << std::endl;); + if(m_strutil.is_string(arg0) && m_strutil.is_string(arg1)) { + TRACE("t_str_rw", tout << "evaluating Concat of two constant strings" << std::endl;); + std::string arg0Str = m_strutil.get_string_constant_value(arg0); + std::string arg1Str = m_strutil.get_string_constant_value(arg1); + std::string resultStr = arg0Str + arg1Str; + result = m_strutil.mk_string(resultStr); + return BR_DONE; + } else { + return BR_FAILED; + } +} + br_status str_rewriter::mk_str_CharAt(expr * arg0, expr * arg1, expr_ref & result) { TRACE("t_str_rw", tout << "rewrite (CharAt " << mk_pp(arg0, m()) << " " << mk_pp(arg1, m()) << ")" << std::endl;); // if arg0 is a string constant and arg1 is an integer constant, @@ -275,6 +289,9 @@ br_status str_rewriter::mk_app_core(func_decl * f, unsigned num_args, expr * con // TODO more rewrites for really easy cases, e.g. (Concat "abc" "def")... switch(f->get_decl_kind()) { + case OP_STRCAT: + SASSERT(num_args == 2); + return mk_str_Concat(args[0], args[1], result); case OP_STR_CHARAT: SASSERT(num_args == 2); return mk_str_CharAt(args[0], args[1], result); diff --git a/src/ast/rewriter/str_rewriter.h b/src/ast/rewriter/str_rewriter.h index dccf4a6bd..58e88591b 100644 --- a/src/ast/rewriter/str_rewriter.h +++ b/src/ast/rewriter/str_rewriter.h @@ -40,6 +40,7 @@ public: br_status mk_app_core(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result); br_status mk_eq_core(expr * lhs, expr * rhs, expr_ref & result); + br_status mk_str_Concat(expr * arg0, expr * arg1, expr_ref & result); br_status mk_str_CharAt(expr * arg0, expr * arg1, expr_ref & result); br_status mk_str_StartsWith(expr * haystack, expr * needle, expr_ref & result); br_status mk_str_EndsWith(expr * haystack, expr * needle, expr_ref & result); diff --git a/src/model/model_evaluator.cpp b/src/model/model_evaluator.cpp index eb2259263..fd420bad6 100644 --- a/src/model/model_evaluator.cpp +++ b/src/model/model_evaluator.cpp @@ -28,6 +28,7 @@ Revision History: #include"datatype_rewriter.h" #include"array_rewriter.h" #include"fpa_rewriter.h" +#include"str_rewriter.h" #include"rewriter_def.h" #include"cooperate.h" #include"ast_pp.h" @@ -44,6 +45,7 @@ struct evaluator_cfg : public default_rewriter_cfg { pb_rewriter m_pb_rw; fpa_rewriter m_f_rw; seq_rewriter m_seq_rw; + str_rewriter m_str_rw; array_util m_ar; unsigned long long m_max_memory; unsigned m_max_steps; @@ -63,6 +65,7 @@ struct evaluator_cfg : public default_rewriter_cfg { m_pb_rw(m), m_f_rw(m), m_seq_rw(m), + m_str_rw(m), m_ar(m) { bool flat = true; m_b_rw.set_flat(flat); @@ -152,6 +155,8 @@ struct evaluator_cfg : public default_rewriter_cfg { st = m_f_rw.mk_eq_core(args[0], args[1], result); else if (s_fid == m_seq_rw.get_fid()) st = m_seq_rw.mk_eq_core(args[0], args[1], result); + else if (s_fid == m_str_rw.get_fid()) + st = m_str_rw.mk_eq_core(args[0], args[1], result); else if (s_fid == m_ar_rw.get_fid()) st = mk_array_eq(args[0], args[1], result); if (st != BR_FAILED) @@ -174,6 +179,8 @@ struct evaluator_cfg : public default_rewriter_cfg { st = m_f_rw.mk_app_core(f, num, args, result); else if (fid == m_seq_rw.get_fid()) st = m_seq_rw.mk_app_core(f, num, args, result); + else if (fid == m_str_rw.get_fid()) + st = m_str_rw.mk_app_core(f, num, args, result); else if (fid == m().get_label_family_id() && num == 1) { result = args[0]; st = BR_DONE; From 3dff240bb3c552a869a9ce3d7bbdf96a7db738de Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sun, 7 Aug 2016 15:50:41 -0400 Subject: [PATCH 175/410] theory_str model validation for Length --- src/ast/rewriter/str_rewriter.cpp | 20 ++++++++++++++++++-- src/ast/rewriter/str_rewriter.h | 1 + 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/ast/rewriter/str_rewriter.cpp b/src/ast/rewriter/str_rewriter.cpp index 37b6b6cbf..fdb67f89e 100644 --- a/src/ast/rewriter/str_rewriter.cpp +++ b/src/ast/rewriter/str_rewriter.cpp @@ -26,7 +26,7 @@ Notes: br_status str_rewriter::mk_str_Concat(expr * arg0, expr * arg1, expr_ref & result) { TRACE("t_str_rw", tout << "rewrite (Concat " << mk_pp(arg0, m()) << " " << mk_pp(arg1, m()) << ")" << std::endl;); if(m_strutil.is_string(arg0) && m_strutil.is_string(arg1)) { - TRACE("t_str_rw", tout << "evaluating Concat of two constant strings" << std::endl;); + TRACE("t_str_rw", tout << "evaluating concat of two constant strings" << std::endl;); std::string arg0Str = m_strutil.get_string_constant_value(arg0); std::string arg1Str = m_strutil.get_string_constant_value(arg1); std::string resultStr = arg0Str + arg1Str; @@ -37,6 +37,20 @@ br_status str_rewriter::mk_str_Concat(expr * arg0, expr * arg1, expr_ref & resul } } +br_status str_rewriter::mk_str_Length(expr * arg0, expr_ref & result) { + TRACE("t_str_rw", tout << "rewrite (Length " << mk_pp(arg0, m()) << ")" << std::endl;); + if (m_strutil.is_string(arg0)) { + TRACE("t_str_rw", tout << "evaluating length of constant string" << std::endl;); + std::string arg0Str = m_strutil.get_string_constant_value(arg0); + rational arg0Len((unsigned)arg0Str.length()); + result = m_autil.mk_numeral(arg0Len, true); + TRACE("t_str_rw", tout << "result is " << mk_pp(result, m()) << std::endl;); + return BR_DONE; + } else { + return BR_FAILED; + } +} + br_status str_rewriter::mk_str_CharAt(expr * arg0, expr * arg1, expr_ref & result) { TRACE("t_str_rw", tout << "rewrite (CharAt " << mk_pp(arg0, m()) << " " << mk_pp(arg1, m()) << ")" << std::endl;); // if arg0 is a string constant and arg1 is an integer constant, @@ -287,11 +301,13 @@ br_status str_rewriter::mk_app_core(func_decl * f, unsigned num_args, expr * con TRACE("t_str_rw", tout << "rewrite app: " << f->get_name() << std::endl;); - // TODO more rewrites for really easy cases, e.g. (Concat "abc" "def")... switch(f->get_decl_kind()) { case OP_STRCAT: SASSERT(num_args == 2); return mk_str_Concat(args[0], args[1], result); + case OP_STRLEN: + SASSERT(num_args == 1); + return mk_str_Length(args[0], result); case OP_STR_CHARAT: SASSERT(num_args == 2); return mk_str_CharAt(args[0], args[1], result); diff --git a/src/ast/rewriter/str_rewriter.h b/src/ast/rewriter/str_rewriter.h index 58e88591b..2235425be 100644 --- a/src/ast/rewriter/str_rewriter.h +++ b/src/ast/rewriter/str_rewriter.h @@ -41,6 +41,7 @@ public: br_status mk_eq_core(expr * lhs, expr * rhs, expr_ref & result); br_status mk_str_Concat(expr * arg0, expr * arg1, expr_ref & result); + br_status mk_str_Length(expr * arg0, expr_ref & result); br_status mk_str_CharAt(expr * arg0, expr * arg1, expr_ref & result); br_status mk_str_StartsWith(expr * haystack, expr * needle, expr_ref & result); br_status mk_str_EndsWith(expr * haystack, expr * needle, expr_ref & result); From f7ba3ff0843c8ebd9df440678266c7614abac344 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 9 Aug 2016 20:11:25 -0400 Subject: [PATCH 176/410] crash avoidance in theory_str search start, fixes length-001.smt2 regression --- src/smt/theory_str.cpp | 19 ++++++++++++++++--- src/smt/theory_str.h | 3 +++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 2e95020a7..fd0b15b19 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -42,6 +42,7 @@ theory_str::theory_str(ast_manager & m): sLevel(0), finalCheckProgressIndicator(false), m_trail(m), + m_delayed_axiom_setup_terms(m), tmpStringVarCount(0), tmpXorVarCount(0), tmpLenTestVarCount(0), @@ -755,10 +756,12 @@ bool theory_str::can_propagate() { || !m_axiom_Contains_todo.empty() || !m_axiom_Indexof_todo.empty() || !m_axiom_Indexof2_todo.empty() || !m_axiom_LastIndexof_todo.empty() || !m_axiom_Substr_todo.empty() || !m_axiom_Replace_todo.empty() || !m_axiom_RegexIn_todo.empty() + || !m_delayed_axiom_setup_terms.empty(); ; } void theory_str::propagate() { + context & ctx = get_context(); while (can_propagate()) { TRACE("t_str_detail", tout << "propagating..." << std::endl;); for (unsigned i = 0; i < m_basicstr_axiom_todo.size(); ++i) { @@ -829,6 +832,13 @@ void theory_str::propagate() { instantiate_axiom_RegexIn(m_axiom_RegexIn_todo[i]); } m_axiom_RegexIn_todo.reset(); + + for (unsigned i = 0; i < m_delayed_axiom_setup_terms.size(); ++i) { + // I think this is okay + ctx.internalize(m_delayed_axiom_setup_terms[i].get(), false); + set_up_axioms(m_delayed_axiom_setup_terms[i].get()); + } + m_delayed_axiom_setup_terms.reset(); } } @@ -5140,6 +5150,7 @@ void theory_str::set_up_axioms(expr * ex) { ": expr is of sort Bool" << std::endl;); // set up axioms for boolean terms + ensure_enode(ex); if (ctx.e_internalized(ex)) { enode * n = ctx.get_enode(ex); SASSERT(n); @@ -5157,14 +5168,16 @@ void theory_str::set_up_axioms(expr * ex) { } } } else { - TRACE("t_str_detail", tout << "WARNING: Bool term " << mk_ismt2_pp(ex, get_manager()) << " not internalized. Skipping to prevent a crash." << std::endl;); + TRACE("t_str_detail", tout << "WARNING: Bool term " << mk_ismt2_pp(ex, get_manager()) << " not internalized. Delaying axiom setup to prevent a crash." << std::endl;); + ENSURE(!search_started); // infinite loop prevention + m_delayed_axiom_setup_terms.push_back(ex); return; } } else if (ex_sort == int_sort) { TRACE("t_str_detail", tout << "setting up axioms for " << mk_ismt2_pp(ex, get_manager()) << ": expr is of sort Int" << std::endl;); - // set up axioms for boolean terms - enode * n = ctx.get_enode(ex); + // set up axioms for integer terms + enode * n = ensure_enode(ex); SASSERT(n); if (is_app(ex)) { diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 74c1786df..7b4ff8ce0 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -129,6 +129,9 @@ namespace smt { str_value_factory * m_factory; + // terms we couldn't go through set_up_axioms() with because they weren't internalized + expr_ref_vector m_delayed_axiom_setup_terms; + ptr_vector m_basicstr_axiom_todo; svector > m_str_eq_todo; ptr_vector m_concat_axiom_todo; From 66129710492304874508f7d4c76f236604da6e00 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sun, 14 Aug 2016 14:15:29 -0400 Subject: [PATCH 177/410] start adding Contains checks to theory_str --- src/smt/theory_str.cpp | 116 ++++++++++++++++++++++++++++++++++++++++- src/smt/theory_str.h | 7 +++ 2 files changed, 121 insertions(+), 2 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index fd0b15b19..aed46e868 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -1124,12 +1124,13 @@ void theory_str::instantiate_axiom_Contains(enode * e) { return; } axiomatized_terms.insert(expr); + contains_map.push_back(expr); // replaces registerContain() in Z3str2 TRACE("t_str_detail", tout << "instantiate Contains axiom for " << mk_pp(expr, m) << std::endl;); expr_ref ts0(mk_str_var("ts0"), m); expr_ref ts1(mk_str_var("ts1"), m); - // TODO NEXT registerContain(expr); + expr_ref breakdownAssert(ctx.mk_eq_atom(expr, ctx.mk_eq_atom(expr->get_arg(0), mk_concat(ts0, mk_concat(expr->get_arg(1), ts1)))), m); SASSERT(breakdownAssert); assert_axiom(breakdownAssert); @@ -1575,7 +1576,11 @@ bool theory_str::new_eq_check(expr * lhs, expr * rhs) { eqc_iterator1 = eqc_iterator1->get_next(); } while (eqc_iterator1 != eqc_root); - // TODO containPairBoolMap + + if (!contains_map.empty()) { + check_contain_in_new_eq(lhs, rhs); + } + // TODO regexInBoolMap // okay, all checks here passed @@ -4118,6 +4123,113 @@ bool theory_str::in_same_eqc(expr * n1, expr * n2) { return n1Node->get_root() == n2Node->get_root(); } +expr * theory_str::collect_eq_nodes(expr * n, expr_ref_vector & eqcSet) { + context & ctx = get_context(); + expr * constStrNode = NULL; + + enode * e_base = ctx.get_enode(n); + enode * e_curr = e_base; + do { + app * ex = e_curr->get_owner(); + if (m_strutil.is_string(ex)) { + constStrNode = ex; + } + eqcSet.push_back(ex); + + e_curr = e_curr->get_next(); + } while (e_curr != e_base); + return constStrNode; +} + +void theory_str::check_contain_by_eqc_val(expr * varNode, expr * constNode) { + NOT_IMPLEMENTED_YET(); // TODO NEXT +} + +void theory_str::check_contain_by_substr(expr * varNode, expr_ref_vector & willEqClass) { + NOT_IMPLEMENTED_YET(); // TODO NEXT +} + +void theory_str::check_contain_by_eq_nodes(expr * n1, expr * n2) { + NOT_IMPLEMENTED_YET(); // TODO NEXT +} + +void theory_str::check_contain_in_new_eq(expr * n1, expr * n2) { + if (contains_map.empty()) { + return; + } + + context & ctx = get_context(); + ast_manager & m = get_manager(); + TRACE("t_str_detail", tout << "consistency check for contains wrt. " << mk_pp(n1, m) << " and " << mk_pp(n2, m) << std::endl;); + + // Modification from Z3str2: the EQC of n1 and n2 *are* now merged. + // So we don't have to do anything too special + // to prepare willEqClass any more, we just use the EQC from n1 / n2. + expr_ref_vector willEqClass(m); + expr * constStrAst = collect_eq_nodes(n1, willEqClass); + + TRACE("t_str_detail", tout << "eqc of n1 is {"; + for (ptr_vector::iterator it = willEqClass.begin(); it != willEqClass.end(); ++it) { + expr * el = *it; + tout << " " << mk_pp(el, m); + } + tout << std::endl; + if (constStrAst == NULL) { + tout << "constStrAst = NULL" << std::endl; + } else { + tout << "constStrAst = " << mk_pp(constStrAst, m) << std::endl; + } + ); + + // step 1: we may have constant values for Contains checks now + if (constStrAst != NULL) { + ptr_vector::iterator itAst = willEqClass.begin(); + for (; itAst != willEqClass.end(); itAst++) { + if (*itAst == constStrAst) { + continue; + } + check_contain_by_eqc_val(*itAst, constStrAst); + } + } else { + // no concrete value to be put in eqc, solely based on context + // Check here is used to detected the facts as follows: + // * known: contains(Z, Y) /\ Z = "abcdefg" /\ Y = M + // * new fact: M = concat(..., "jio", ...) + // Note that in this branch, either M or concat(..., "jio", ...) has a constant value + // So, only need to check + // * "EQC(M) U EQC(concat(..., "jio", ...))" as substr and + // * If strAst registered has an eqc constant in the context + // ------------------------------------------------------------- + ptr_vector::iterator itAst = willEqClass.begin(); + for (; itAst != willEqClass.end(); ++itAst) { + check_contain_by_substr(*itAst, willEqClass); + } + } + + // ------------------------------------------ + // step 2: check for b1 = contains(x, m), b2 = contains(y, n) + // (1) x = y /\ m = n ==> b1 = b2 + // (2) x = y /\ Contains(const(m), const(n)) ==> (b1 -> b2) + // (3) x = y /\ Contains(const(n), const(m)) ==> (b2 -> b1) + // (4) x = y /\ containPairBoolMap[] ==> (b1 -> b2) + // (5) x = y /\ containPairBoolMap[] ==> (b2 -> b1) + // (6) Contains(const(x), const(y)) /\ m = n ==> (b2 -> b1) + // (7) Contains(const(y), const(x)) /\ m = n ==> (b1 -> b2) + // (8) containPairBoolMap[] /\ m = n ==> (b2 -> b1) + // (9) containPairBoolMap[] /\ m = n ==> (b1 -> b2) + // ------------------------------------------ + + expr_ref_vector::iterator varItor1 = willEqClass.begin(); + for (; varItor1 != willEqClass.end(); ++varItor1) { + expr * varAst1 = *varItor1; + expr_ref_vector::iterator varItor2 = varItor1; + for (; varItor2 != willEqClass.end(); ++varItor2) { + expr * varAst2 = *varItor2; + check_contain_by_eq_nodes(varAst1, varAst2); + } + } +} + bool theory_str::can_concat_eq_str(expr * concat, std::string str) { // TODO this method could use some traces and debugging info int strLen = str.length(); diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 7b4ff8ce0..61eefece8 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -191,6 +191,8 @@ namespace smt { std::map unroll_var_map; std::map, expr*> concat_eq_unroll_ast_map; + expr_ref_vector contains_map; // was containPairBoolMap in Z3str2 + char * char_set; std::map charSetLookupTable; int charSetSize; @@ -290,6 +292,7 @@ namespace smt { app * mk_value_helper(app * n); expr * get_eqc_value(expr * n, bool & hasEqcValue); bool in_same_eqc(expr * n1, expr * n2); + expr * collect_eq_nodes(expr * n, expr_ref_vector & eqcSet); bool get_value(expr* e, rational& val) const; bool get_len_value(expr* e, rational& val); @@ -306,6 +309,10 @@ namespace smt { bool check_length_concat_concat(expr * n1, expr * n2); bool check_length_concat_var(expr * concat, expr * var); bool check_length_var_var(expr * var1, expr * var2); + void check_contain_in_new_eq(expr * n1, expr * n2); + void check_contain_by_eqc_val(expr * varNode, expr * constNode); + void check_contain_by_substr(expr * varNode, expr_ref_vector & willEqClass); + void check_contain_by_eq_nodes(expr * n1, expr * n2); void get_nodes_in_concat(expr * node, ptr_vector & nodeList); expr * simplify_concat(expr * node); From 1f594b190a8a00a167dcb234c43caf3d684d9d1c Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sun, 14 Aug 2016 14:55:29 -0400 Subject: [PATCH 178/410] add theory_str::check_contain_by_eqc_val --- src/smt/theory_str.cpp | 169 ++++++++++++++++++++++++++++++++++++++++- src/smt/theory_str.h | 2 + 2 files changed, 170 insertions(+), 1 deletion(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index aed46e868..18157e4be 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -4141,8 +4141,161 @@ expr * theory_str::collect_eq_nodes(expr * n, expr_ref_vector & eqcSet) { return constStrNode; } +/* + * Collect constant strings (from left to right) in an AST node. + */ +void theory_str::get_const_str_asts_in_node(expr * node, expr_ref_vector & astList) { + ast_manager & m = get_manager(); + if (m_strutil.is_string(node)) { + astList.push_back(node); + //} else if (getNodeType(t, node) == my_Z3_Func) { + } else if (is_app(node)) { + app * func_app = to_app(node); + unsigned int argCount = func_app->get_num_args(); + for (unsigned int i = 0; i < argCount; i++) { + expr * argAst = func_app->get_arg(i); + get_const_str_asts_in_node(argAst, astList); + } + } +} + void theory_str::check_contain_by_eqc_val(expr * varNode, expr * constNode) { - NOT_IMPLEMENTED_YET(); // TODO NEXT + context & ctx = get_context(); + ast_manager & m = get_manager(); + + TRACE("t_str_detail", tout << "varNode = " << mk_pp(varNode, m) << ", constNode = " << mk_pp(constNode, m) << std::endl;); + + expr_ref_vector litems(m); + + // Modification from Z3str: + // since we don't track containPairIdxMap any more, + // we check each element of contains_map to see whether + // either of its arguments are equal to varNode. + // This could possibly be made faster if we had a map class that + // let us use an expr_ref as a key. + + expr_ref_vector::iterator itor1 = contains_map.begin(); + for (; itor1 != contains_map.end(); ++itor1) { + expr * boolVar = *itor1; + // boolVar is actually a Contains term + app * containsApp = to_app(boolVar); + expr * strAst = containsApp->get_arg(0); + expr * substrAst = containsApp->get_arg(1); + + // we only want to inspect the Contains terms where either of strAst or substrAst + // are equal to varNode. + + TRACE("t_str_detail", tout << "considering Contains with strAst = " << mk_pp(strAst, m) << ", substrAst = " << mk_pp(substrAst, m) << "..." << std::endl;); + + if (varNode != strAst && varNode != substrAst) { + TRACE("t_str_detail", tout << "varNode not equal to strAst or substrAst, skip" << std::endl;); + continue; + } + TRACE("t_str_detail", tout << "varNode matched one of strAst or substrAst. Continuing" << std::endl;); + + // varEqcNode is str + if (strAst == varNode) { + expr_ref implyR(m); + litems.reset(); + + if (strAst != constNode) { + litems.push_back(ctx.mk_eq_atom(strAst, constNode)); + } + std::string strConst = m_strutil.get_string_constant_value(constNode); + bool subStrHasEqcValue = false; + expr * substrValue = get_eqc_value(substrAst, subStrHasEqcValue); + if (substrValue != substrAst) { + litems.push_back(ctx.mk_eq_atom(substrAst, substrValue)); + } + + if (subStrHasEqcValue) { + // subStr has an eqc constant value + std::string subStrConst = m_strutil.get_string_constant_value(substrValue); + + TRACE("t_str_detail", tout << "strConst = " << strConst << ", subStrConst = " << subStrConst << std::endl;); + + if (strConst.find(subStrConst) != std::string::npos) { + //implyR = ctx.mk_eq(ctx, boolVar, Z3_mk_true(ctx)); + implyR = boolVar; + } else { + //implyR = Z3_mk_eq(ctx, boolVar, Z3_mk_false(ctx)); + implyR = m.mk_not(boolVar); + } + } else { + // ------------------------------------------------------------------------------------------------ + // subStr doesn't have an eqc contant value + // however, subStr equals to some concat(arg_1, arg_2, ..., arg_n) + // if arg_j is a constant and is not a part of the strConst, it's sure that the contains is false + // ** This check is needed here because the "strConst" and "strAst" may not be in a same eqc yet + // ------------------------------------------------------------------------------------------------ + // collect eqc concat + std::set eqcConcats; + get_concats_in_eqc(substrAst, eqcConcats); + for (std::set::iterator concatItor = eqcConcats.begin(); + concatItor != eqcConcats.end(); concatItor++) { + expr_ref_vector constList(m); + bool counterEgFound = false; + // get constant strings in concat + expr * aConcat = *concatItor; + get_const_str_asts_in_node(aConcat, constList); + for (expr_ref_vector::iterator cstItor = constList.begin(); + cstItor != constList.end(); cstItor++) { + std::string pieceStr = m_strutil.get_string_constant_value(*cstItor); + if (strConst.find(pieceStr) == std::string::npos) { + counterEgFound = true; + if (aConcat != substrAst) { + litems.push_back(ctx.mk_eq_atom(substrAst, aConcat)); + } + //implyR = Z3_mk_eq(ctx, boolVar, Z3_mk_false(ctx)); + implyR = m.mk_not(boolVar); + break; + } + } + if (counterEgFound) { + TRACE("t_str_detail", tout << "Inconsistency found!" << std::endl;); + break; + } + } + } + // add assertion + if (implyR != NULL) { + expr_ref implyLHS(mk_and(litems), m); + assert_implication(implyLHS, implyR); + } + } + // varEqcNode is subStr + else if (substrAst == varNode) { + expr_ref implyR(m); + litems.reset(); + + if (substrAst != constNode) { + litems.push_back(ctx.mk_eq_atom(substrAst, constNode)); + } + bool strHasEqcValue = false; + expr * strValue = get_eqc_value(strAst, strHasEqcValue); + if (strValue != strAst) { + litems.push_back(ctx.mk_eq_atom(strAst, strValue)); + } + + if (strHasEqcValue) { + std::string strConst = m_strutil.get_string_constant_value(strValue); + std::string subStrConst = m_strutil.get_string_constant_value(constNode); + if (strConst.find(subStrConst) != std::string::npos) { + //implyR = Z3_mk_eq(ctx, boolVar, Z3_mk_true(ctx)); + implyR = boolVar; + } else { + // implyR = Z3_mk_eq(ctx, boolVar, Z3_mk_false(ctx)); + implyR = m.mk_not(boolVar); + } + } + + // add assertion + if (implyR != NULL) { + expr_ref implyLHS(mk_and(litems), m); + assert_implication(implyLHS, implyR); + } + } + } // for (itor1 : contains_map) } void theory_str::check_contain_by_substr(expr * varNode, expr_ref_vector & willEqClass) { @@ -7485,6 +7638,20 @@ expr * theory_str::gen_len_val_options_for_free_var(expr * freeVar, expr * lenTe } // fVarLenCountMap.find(...) } +void theory_str::get_concats_in_eqc(expr * n, std::set & concats) { + context & ctx = get_context(); + + expr * eqcNode = n; + do { + if (is_concat(to_app(eqcNode))) { + concats.insert(eqcNode); + } + enode * e_eqc = ctx.get_enode(eqcNode); + eqcNode = e_eqc->get_next()->get_owner(); + // eqcNode = Z3_theory_get_eqc_next(t, eqcNode); + } while (eqcNode != n); +} + void theory_str::get_var_in_eqc(expr * n, std::set & varSet) { context & ctx = get_context(); diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 61eefece8..476519e5c 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -379,6 +379,8 @@ namespace smt { expr * getMostLeftNodeInConcat(expr * node); expr * getMostRightNodeInConcat(expr * node); void get_var_in_eqc(expr * n, std::set & varSet); + void get_concats_in_eqc(expr * n, std::set & concats); + void get_const_str_asts_in_node(expr * node, expr_ref_vector & constList); expr * eval_concat(expr * n1, expr * n2); // strRegex From ee6f1eef6919b72640f4f12a33795cc54b4346de Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sun, 14 Aug 2016 15:14:48 -0400 Subject: [PATCH 179/410] add theory_str::check_contain_by_substr --- src/smt/theory_str.cpp | 67 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 18157e4be..2258646bb 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -4299,7 +4299,72 @@ void theory_str::check_contain_by_eqc_val(expr * varNode, expr * constNode) { } void theory_str::check_contain_by_substr(expr * varNode, expr_ref_vector & willEqClass) { - NOT_IMPLEMENTED_YET(); // TODO NEXT + context & ctx = get_context(); + ast_manager & m = get_manager(); + expr_ref_vector litems(m); + + // same deal as before, we do not track containPairIdxMap + // and so we check elements of contains_map instead + + expr_ref_vector::iterator itor1 = contains_map.begin(); + for (; itor1 != contains_map.end(); ++itor1) { + expr * boolVar = *itor1; + // boolVar is actually a Contains term + app * containsApp = to_app(boolVar); + expr * strAst = containsApp->get_arg(0); + expr * substrAst = containsApp->get_arg(1); + + // we only want to inspect the Contains terms where either of strAst or substrAst + // are equal to varNode. + + TRACE("t_str_detail", tout << "considering Contains with strAst = " << mk_pp(strAst, m) << ", substrAst = " << mk_pp(substrAst, m) << "..." << std::endl;); + + if (varNode != strAst && varNode != substrAst) { + TRACE("t_str_detail", tout << "varNode not equal to strAst or substrAst, skip" << std::endl;); + continue; + } + TRACE("t_str_detail", tout << "varNode matched one of strAst or substrAst. Continuing" << std::endl;); + + if (substrAst == varNode) { + bool strAstHasVal = false; + expr * strValue = get_eqc_value(strAst, strAstHasVal); + if (strAstHasVal) { + TRACE("t_str_detail", tout << mk_pp(strAst, m) << " has constant eqc value " << mk_pp(strValue) << std::endl;); + if (strValue != strAst) { + litems.push_back(ctx.mk_eq_atom(strAst, strValue)); + } + std::string strConst = m_strutil.get_string_constant_value(strValue); + // iterate eqc (also eqc-to-be) of substr + for (expr_ref_vector::iterator itAst = willEqClass.begin(); itAst != willEqClass.end(); itAst++) { + bool counterEgFound = false; + if (is_concat(to_app(*itAst))) { + expr_ref_vector constList(m); + // get constant strings in concat + app * aConcat = to_app(*itAst); + get_const_str_asts_in_node(aConcat, constList); + for (expr_ref_vector::iterator cstItor = constList.begin(); + cstItor != constList.end(); cstItor++) { + std::string pieceStr = m_strutil.get_string_constant_value(*cstItor); + if (strConst.find(pieceStr) == std::string::npos) { + TRACE("t_str_detail", tout << "Inconsistency found!" << std::endl;); + counterEgFound = true; + if (aConcat != substrAst) { + litems.push_back(ctx.mk_eq_atom(substrAst, aConcat)); + } + expr_ref implyLHS(mk_and(litems), m); + expr_ref implyR(m.mk_not(boolVar), m); + assert_implication(implyLHS, implyR); + break; + } + } + } + if (counterEgFound) { + break; + } + } + } + } + } } void theory_str::check_contain_by_eq_nodes(expr * n1, expr * n2) { From f48377e78004f9c503b9bbba8e4dd588450ce107 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sun, 14 Aug 2016 16:14:48 -0400 Subject: [PATCH 180/410] temporarily disable a third Contains check for testing purposes --- src/smt/theory_str.cpp | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 2258646bb..28b310196 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -48,7 +48,8 @@ theory_str::theory_str(ast_manager & m): tmpLenTestVarCount(0), tmpValTestVarCount(0), avoidLoopCut(true), - loopDetected(false) + loopDetected(false), + contains_map(m) { initialize_charset(); } @@ -4258,7 +4259,7 @@ void theory_str::check_contain_by_eqc_val(expr * varNode, expr * constNode) { } } // add assertion - if (implyR != NULL) { + if (implyR) { expr_ref implyLHS(mk_and(litems), m); assert_implication(implyLHS, implyR); } @@ -4290,7 +4291,7 @@ void theory_str::check_contain_by_eqc_val(expr * varNode, expr * constNode) { } // add assertion - if (implyR != NULL) { + if (implyR) { expr_ref implyLHS(mk_and(litems), m); assert_implication(implyLHS, implyR); } @@ -4329,7 +4330,7 @@ void theory_str::check_contain_by_substr(expr * varNode, expr_ref_vector & willE bool strAstHasVal = false; expr * strValue = get_eqc_value(strAst, strAstHasVal); if (strAstHasVal) { - TRACE("t_str_detail", tout << mk_pp(strAst, m) << " has constant eqc value " << mk_pp(strValue) << std::endl;); + TRACE("t_str_detail", tout << mk_pp(strAst, m) << " has constant eqc value " << mk_pp(strValue, m) << std::endl;); if (strValue != strAst) { litems.push_back(ctx.mk_eq_atom(strAst, strValue)); } @@ -4387,7 +4388,7 @@ void theory_str::check_contain_in_new_eq(expr * n1, expr * n2) { expr * constStrAst = collect_eq_nodes(n1, willEqClass); TRACE("t_str_detail", tout << "eqc of n1 is {"; - for (ptr_vector::iterator it = willEqClass.begin(); it != willEqClass.end(); ++it) { + for (expr_ref_vector::iterator it = willEqClass.begin(); it != willEqClass.end(); ++it) { expr * el = *it; tout << " " << mk_pp(el, m); } @@ -4401,7 +4402,7 @@ void theory_str::check_contain_in_new_eq(expr * n1, expr * n2) { // step 1: we may have constant values for Contains checks now if (constStrAst != NULL) { - ptr_vector::iterator itAst = willEqClass.begin(); + expr_ref_vector::iterator itAst = willEqClass.begin(); for (; itAst != willEqClass.end(); itAst++) { if (*itAst == constStrAst) { continue; @@ -4418,7 +4419,7 @@ void theory_str::check_contain_in_new_eq(expr * n1, expr * n2) { // * "EQC(M) U EQC(concat(..., "jio", ...))" as substr and // * If strAst registered has an eqc constant in the context // ------------------------------------------------------------- - ptr_vector::iterator itAst = willEqClass.begin(); + expr_ref_vector::iterator itAst = willEqClass.begin(); for (; itAst != willEqClass.end(); ++itAst) { check_contain_by_substr(*itAst, willEqClass); } @@ -4443,7 +4444,9 @@ void theory_str::check_contain_in_new_eq(expr * n1, expr * n2) { expr_ref_vector::iterator varItor2 = varItor1; for (; varItor2 != willEqClass.end(); ++varItor2) { expr * varAst2 = *varItor2; - check_contain_by_eq_nodes(varAst1, varAst2); + // for testing purposes + TRACE("t_str", tout << "WARNING: some Contains checks disabled!" << std::endl;); + // check_contain_by_eq_nodes(varAst1, varAst2); } } } From d28ef1d47185d6d67a95e1cb3b6251ed9e72c6da Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Mon, 15 Aug 2016 17:38:24 -0400 Subject: [PATCH 181/410] add theory_str::check_contain_by_eq_nodes --- src/smt/theory_str.cpp | 382 ++++++++++++++++++++++++++++++++++++++-- src/smt/theory_str.h | 27 ++- src/util/obj_pair_set.h | 5 + 3 files changed, 394 insertions(+), 20 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 28b310196..93173402c 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -152,6 +152,11 @@ void theory_str::assert_axiom(expr * e) { //TRACE("t_str_detail", tout << "done asserting " << mk_ismt2_pp(e, get_manager()) << std::endl;); } +expr * theory_str::rewrite_implication(expr * premise, expr * conclusion) { + ast_manager & m = get_manager(); + return m.mk_or(m.mk_not(premise), conclusion); +} + void theory_str::assert_implication(expr * premise, expr * conclusion) { ast_manager & m = get_manager(); TRACE("t_str_detail", tout << "asserting implication " << mk_ismt2_pp(premise, m) << " -> " << mk_ismt2_pp(conclusion, m) << std::endl;); @@ -1119,20 +1124,28 @@ void theory_str::instantiate_axiom_Contains(enode * e) { context & ctx = get_context(); ast_manager & m = get_manager(); - app * expr = e->get_owner(); - if (axiomatized_terms.contains(expr)) { - TRACE("t_str_detail", tout << "already set up Contains axiom for " << mk_pp(expr, m) << std::endl;); + app * ex = e->get_owner(); + if (axiomatized_terms.contains(ex)) { + TRACE("t_str_detail", tout << "already set up Contains axiom for " << mk_pp(ex, m) << std::endl;); return; } - axiomatized_terms.insert(expr); - contains_map.push_back(expr); // replaces registerContain() in Z3str2 + axiomatized_terms.insert(ex); + { // register Contains() + expr * str = ex->get_arg(0); + expr * substr = ex->get_arg(1); + contains_map.push_back(ex); + std::pair key = std::pair(str, substr); + contain_pair_bool_map.insert(str, substr, ex); + contain_pair_idx_map[str].insert(key); + contain_pair_idx_map[substr].insert(key); + } - TRACE("t_str_detail", tout << "instantiate Contains axiom for " << mk_pp(expr, m) << std::endl;); + TRACE("t_str_detail", tout << "instantiate Contains axiom for " << mk_pp(ex, m) << std::endl;); expr_ref ts0(mk_str_var("ts0"), m); expr_ref ts1(mk_str_var("ts1"), m); - expr_ref breakdownAssert(ctx.mk_eq_atom(expr, ctx.mk_eq_atom(expr->get_arg(0), mk_concat(ts0, mk_concat(expr->get_arg(1), ts1)))), m); + expr_ref breakdownAssert(ctx.mk_eq_atom(ex, ctx.mk_eq_atom(ex->get_arg(0), mk_concat(ts0, mk_concat(ex->get_arg(1), ts1)))), m); SASSERT(breakdownAssert); assert_axiom(breakdownAssert); } @@ -4168,12 +4181,7 @@ void theory_str::check_contain_by_eqc_val(expr * varNode, expr * constNode) { expr_ref_vector litems(m); - // Modification from Z3str: - // since we don't track containPairIdxMap any more, - // we check each element of contains_map to see whether - // either of its arguments are equal to varNode. - // This could possibly be made faster if we had a map class that - // let us use an expr_ref as a key. + // TODO refactor to use the new contain_pair_idx_map expr_ref_vector::iterator itor1 = contains_map.begin(); for (; itor1 != contains_map.end(); ++itor1) { @@ -4304,8 +4312,7 @@ void theory_str::check_contain_by_substr(expr * varNode, expr_ref_vector & willE ast_manager & m = get_manager(); expr_ref_vector litems(m); - // same deal as before, we do not track containPairIdxMap - // and so we check elements of contains_map instead + // TODO refactor to use the new contain_pair_idx_map expr_ref_vector::iterator itor1 = contains_map.begin(); for (; itor1 != contains_map.end(); ++itor1) { @@ -4368,8 +4375,347 @@ void theory_str::check_contain_by_substr(expr * varNode, expr_ref_vector & willE } } +bool theory_str::in_contain_idx_map(expr * n) { + return contain_pair_idx_map.contains(n); +} + void theory_str::check_contain_by_eq_nodes(expr * n1, expr * n2) { - NOT_IMPLEMENTED_YET(); // TODO NEXT + context & ctx = get_context(); + ast_manager & m = get_manager(); + + if (in_contain_idx_map(n1) && in_contain_idx_map(n2)) { + obj_pair_set::iterator keysItor1 = contain_pair_idx_map[n1].begin(); + obj_pair_set::iterator keysItor2; + + for (; keysItor1 != contain_pair_idx_map[n1].end(); keysItor1++) { + // keysItor1 is on set {<.., n1>, ..., , ...} + std::pair key1 = *keysItor1; + if (key1.first == n1 && key1.second == n2) { + expr_ref implyL(m); + expr_ref implyR(contain_pair_bool_map[key1], m); + if (n1 != n2) { + implyL = ctx.mk_eq_atom(n1, n2); + assert_implication(implyL, implyR); + } else { + assert_axiom(implyR); + } + } + + for (keysItor2 = contain_pair_idx_map[n2].begin(); + keysItor2 != contain_pair_idx_map[n2].end(); keysItor2++) { + // keysItor2 is on set {<.., n2>, ..., , ...} + std::pair key2 = *keysItor2; + // skip if the pair is eq + if (key1 == key2) { + continue; + } + + // *************************** + // Case 1: Contains(m, ...) /\ Contains(n, ) /\ m = n + // *************************** + if (key1.first == n1 && key2.first == n2) { + expr * subAst1 = key1.second; + expr * subAst2 = key2.second; + bool subAst1HasValue = false; + bool subAst2HasValue = false; + expr * subValue1 = get_eqc_value(subAst1, subAst1HasValue); + expr * subValue2 = get_eqc_value(subAst2, subAst2HasValue); + + TRACE("t_str_detail", + tout << "(Contains " << mk_pp(n1, m) << " " << mk_pp(subAst1, m) << ")" << std::endl; + tout << "(Contains " << mk_pp(n2, m) << " " << mk_pp(subAst2, m) << ")" << std::endl; + if (subAst1 != subValue1) { + tout << mk_pp(subAst1, m) << " = " << mk_pp(subValue1, m) << std::endl; + } + if (subAst2 != subValue2) { + tout << mk_pp(subAst2, m) << " = " << mk_pp(subValue2, m) << std::endl; + } + ); + + if (subAst1HasValue && subAst2HasValue) { + expr_ref_vector litems1(m); + if (n1 != n2) { + litems1.push_back(ctx.mk_eq_atom(n1, n2)); + } + if (subValue1 != subAst1) { + litems1.push_back(ctx.mk_eq_atom(subAst1, subValue1)); + } + if (subValue2 != subAst2) { + litems1.push_back(ctx.mk_eq_atom(subAst2, subValue2)); + } + + std::string subConst1 = m_strutil.get_string_constant_value(subValue1); + std::string subConst2 = m_strutil.get_string_constant_value(subValue2); + expr_ref implyR(m); + if (subConst1 == subConst2) { + // key1.first = key2.first /\ key1.second = key2.second + // ==> (containPairBoolMap[key1] = containPairBoolMap[key2]) + implyR = ctx.mk_eq_atom(contain_pair_bool_map[key1], contain_pair_bool_map[key2]); + } else if (subConst1.find(subConst2) != std::string::npos) { + // key1.first = key2.first /\ Contains(key1.second, key2.second) + // ==> (containPairBoolMap[key1] --> containPairBoolMap[key2]) + implyR = rewrite_implication(contain_pair_bool_map[key1], contain_pair_bool_map[key2]); + } else if (subConst2.find(subConst1) != std::string::npos) { + // key1.first = key2.first /\ Contains(key2.second, key1.second) + // ==> (containPairBoolMap[key2] --> containPairBoolMap[key1]) + implyR = rewrite_implication(contain_pair_bool_map[key2], contain_pair_bool_map[key1]); + } + + if (implyR) { + if (litems1.empty()) { + assert_axiom(implyR); + } else { + assert_implication(mk_and(litems1), implyR); + } + } + } else { + expr_ref_vector subAst1Eqc(m); + expr_ref_vector subAst2Eqc(m); + collect_eq_nodes(subAst1, subAst1Eqc); + collect_eq_nodes(subAst2, subAst2Eqc); + + if (subAst1Eqc.contains(subAst2)) { + // ----------------------------------------------------------- + // * key1.first = key2.first /\ key1.second = key2.second + // --> containPairBoolMap[key1] = containPairBoolMap[key2] + // ----------------------------------------------------------- + expr_ref_vector litems2(m); + if (n1 != n2) { + litems2.push_back(ctx.mk_eq_atom(n1, n2)); + } + if (subAst1 != subAst2) { + litems2.push_back(ctx.mk_eq_atom(subAst1, subAst2)); + } + expr_ref implyR(ctx.mk_eq_atom(contain_pair_bool_map[key1], contain_pair_bool_map[key2]), m); + if (litems2.empty()) { + assert_axiom(implyR); + } else { + assert_implication(mk_and(litems2), implyR); + } + } else { + // ----------------------------------------------------------- + // * key1.first = key2.first + // check eqc(key1.second) and eqc(key2.second) + // ----------------------------------------------------------- + expr_ref_vector::iterator eqItorSub1 = subAst1Eqc.begin(); + for (; eqItorSub1 != subAst1Eqc.end(); eqItorSub1++) { + expr_ref_vector::iterator eqItorSub2 = subAst2Eqc.begin(); + for (; eqItorSub2 != subAst2Eqc.end(); eqItorSub2++) { + // ------------ + // key1.first = key2.first /\ containPairBoolMap[] + // ==> (containPairBoolMap[key1] --> containPairBoolMap[key2]) + // ------------ + { + expr_ref_vector litems3(m); + if (n1 != n2) { + litems3.push_back(ctx.mk_eq_atom(n1, n2)); + } + expr * eqSubVar1 = *eqItorSub1; + if (eqSubVar1 != subAst1) { + litems3.push_back(ctx.mk_eq_atom(subAst1, eqSubVar1)); + } + expr * eqSubVar2 = *eqItorSub2; + if (eqSubVar2 != subAst2) { + litems3.push_back(ctx.mk_eq_atom(subAst2, eqSubVar2)); + } + std::pair tryKey1 = std::make_pair(eqSubVar1, eqSubVar2); + if (contain_pair_bool_map.contains(tryKey1)) { + TRACE("t_str_detail", tout << "(Contains " << mk_pp(eqSubVar1, m) << " " << mk_pp(eqSubVar2, m) << ")" << std::endl;); + litems3.push_back(contain_pair_bool_map[tryKey1]); + expr_ref implR(rewrite_implication(contain_pair_bool_map[key1], contain_pair_bool_map[key2]), m); + assert_implication(mk_and(litems3), implR); + } + } + // ------------ + // key1.first = key2.first /\ containPairBoolMap[] + // ==> (containPairBoolMap[key2] --> containPairBoolMap[key1]) + // ------------ + { + expr_ref_vector litems4(m); + if (n1 != n2) { + litems4.push_back(ctx.mk_eq_atom(n1, n2)); + } + expr * eqSubVar1 = *eqItorSub1; + if (eqSubVar1 != subAst1) { + litems4.push_back(ctx.mk_eq_atom(subAst1, eqSubVar1)); + } + expr * eqSubVar2 = *eqItorSub2; + if (eqSubVar2 != subAst2) { + litems4.push_back(ctx.mk_eq_atom(subAst2, eqSubVar2)); + } + std::pair tryKey2 = std::make_pair(eqSubVar2, eqSubVar1); + if (contain_pair_bool_map.contains(tryKey2)) { + TRACE("t_str_detail", tout << "(Contains " << mk_pp(eqSubVar2, m) << " " << mk_pp(eqSubVar1, m) << ")" << std::endl;); + litems4.push_back(contain_pair_bool_map[tryKey2]); + expr_ref implR(rewrite_implication(contain_pair_bool_map[key2], contain_pair_bool_map[key1]), m); + assert_implication(mk_and(litems4), implR); + } + } + } + } + } + } + } + // *************************** + // Case 2: Contains(..., m) /\ Contains(... , n) /\ m = n + // *************************** + else if (key1.second == n1 && key2.second == n2) { + expr * str1 = key1.first; + expr * str2 = key2.first; + bool str1HasValue = false; + bool str2HasValue = false; + expr * strVal1 = get_eqc_value(str1, str1HasValue); + expr * strVal2 = get_eqc_value(str2, str2HasValue); + + TRACE("t_str_detail", + tout << "(Contains " << mk_pp(str1, m) << " " << mk_pp(n1, m) << ")" << std::endl; + tout << "(Contains " << mk_pp(str2, m) << " " << mk_pp(n2, m) << ")" << std::endl; + if (str1 != strVal1) { + tout << mk_pp(str1, m) << " = " << mk_pp(strVal1, m) << std::endl; + } + if (str2 != strVal2) { + tout << mk_pp(str2, m) << " = " << mk_pp(strVal2, m) << std::endl; + } + ); + + if (str1HasValue && str2HasValue) { + expr_ref_vector litems1(m); + if (n1 != n2) { + litems1.push_back(ctx.mk_eq_atom(n1, n2)); + } + if (strVal1 != str1) { + litems1.push_back(ctx.mk_eq_atom(str1, strVal1)); + } + if (strVal2 != str2) { + litems1.push_back(ctx.mk_eq_atom(str2, strVal2)); + } + + std::string const1 = m_strutil.get_string_constant_value(strVal1); + std::string const2 = m_strutil.get_string_constant_value(strVal2); + expr_ref implyR(m); + + if (const1 == const2) { + // key1.second = key2.second /\ key1.first = key2.first + // ==> (containPairBoolMap[key1] = containPairBoolMap[key2]) + implyR = ctx.mk_eq_atom(contain_pair_bool_map[key1], contain_pair_bool_map[key2]); + } else if (const1.find(const2) != std::string::npos) { + // key1.second = key2.second /\ Contains(key1.first, key2.first) + // ==> (containPairBoolMap[key2] --> containPairBoolMap[key1]) + implyR = rewrite_implication(contain_pair_bool_map[key2], contain_pair_bool_map[key1]); + } else if (const2.find(const1) != std::string::npos) { + // key1.first = key2.first /\ Contains(key2.first, key1.first) + // ==> (containPairBoolMap[key1] --> containPairBoolMap[key2]) + implyR = rewrite_implication(contain_pair_bool_map[key1], contain_pair_bool_map[key2]); + } + + if (implyR) { + if (litems1.size() == 0) { + assert_axiom(implyR); + } else { + assert_implication(mk_and(litems1), implyR); + } + } + } + + else { + expr_ref_vector str1Eqc(m); + expr_ref_vector str2Eqc(m); + collect_eq_nodes(str1, str1Eqc); + collect_eq_nodes(str2, str2Eqc); + + if (str1Eqc.contains(str2)) { + // ----------------------------------------------------------- + // * key1.first = key2.first /\ key1.second = key2.second + // --> containPairBoolMap[key1] = containPairBoolMap[key2] + // ----------------------------------------------------------- + expr_ref_vector litems2(m); + if (n1 != n2) { + litems2.push_back(ctx.mk_eq_atom(n1, n2)); + } + if (str1 != str2) { + litems2.push_back(ctx.mk_eq_atom(str1, str2)); + } + expr_ref implyR(ctx.mk_eq_atom(contain_pair_bool_map[key1], contain_pair_bool_map[key2]), m); + if (litems2.empty()) { + assert_axiom(implyR); + } else { + assert_implication(mk_and(litems2), implyR); + } + } else { + // ----------------------------------------------------------- + // * key1.second = key2.second + // check eqc(key1.first) and eqc(key2.first) + // ----------------------------------------------------------- + expr_ref_vector::iterator eqItorStr1 = str1Eqc.begin(); + for (; eqItorStr1 != str1Eqc.end(); eqItorStr1++) { + expr_ref_vector::iterator eqItorStr2 = str2Eqc.begin(); + for (; eqItorStr2 != str2Eqc.end(); eqItorStr2++) { + { + expr_ref_vector litems3(m); + if (n1 != n2) { + litems3.push_back(ctx.mk_eq_atom(n1, n2)); + } + expr * eqStrVar1 = *eqItorStr1; + if (eqStrVar1 != str1) { + litems3.push_back(ctx.mk_eq_atom(str1, eqStrVar1)); + } + expr * eqStrVar2 = *eqItorStr2; + if (eqStrVar2 != str2) { + litems3.push_back(ctx.mk_eq_atom(str2, eqStrVar2)); + } + std::pair tryKey1 = std::make_pair(eqStrVar1, eqStrVar2); + if (contain_pair_bool_map.contains(tryKey1)) { + TRACE("t_str_detail", tout << "(Contains " << mk_pp(eqStrVar1, m) << " " << mk_pp(eqStrVar2, m) << ")" << std::endl;); + litems3.push_back(contain_pair_bool_map[tryKey1]); + + // ------------ + // key1.second = key2.second /\ containPairBoolMap[] + // ==> (containPairBoolMap[key2] --> containPairBoolMap[key1]) + // ------------ + expr_ref implR(rewrite_implication(contain_pair_bool_map[key2], contain_pair_bool_map[key1]), m); + assert_implication(mk_and(litems3), implR); + } + } + + { + expr_ref_vector litems4(m); + if (n1 != n2) { + litems4.push_back(ctx.mk_eq_atom(n1, n2)); + } + expr * eqStrVar1 = *eqItorStr1; + if (eqStrVar1 != str1) { + litems4.push_back(ctx.mk_eq_atom(str1, eqStrVar1)); + } + expr *eqStrVar2 = *eqItorStr2; + if (eqStrVar2 != str2) { + litems4.push_back(ctx.mk_eq_atom(str2, eqStrVar2)); + } + std::pair tryKey2 = std::make_pair(eqStrVar2, eqStrVar1); + + if (contain_pair_bool_map.contains(tryKey2)) { + TRACE("t_str_detail", tout << "(Contains " << mk_pp(eqStrVar2, m) << " " << mk_pp(eqStrVar1, m) << ")" << std::endl;); + litems4.push_back(contain_pair_bool_map[tryKey2]); + // ------------ + // key1.first = key2.first /\ containPairBoolMap[] + // ==> (containPairBoolMap[key1] --> containPairBoolMap[key2]) + // ------------ + expr_ref implR(ctx.mk_eq_atom(contain_pair_bool_map[key1], contain_pair_bool_map[key2]), m); + assert_implication(mk_and(litems4), implR); + } + } + } + } + } + } + + } + } + + if (n1 == n2) { + break; + } + } + } // (in_contain_idx_map(n1) && in_contain_idx_map(n2)) } void theory_str::check_contain_in_new_eq(expr * n1, expr * n2) { @@ -4444,9 +4790,7 @@ void theory_str::check_contain_in_new_eq(expr * n1, expr * n2) { expr_ref_vector::iterator varItor2 = varItor1; for (; varItor2 != willEqClass.end(); ++varItor2) { expr * varAst2 = *varItor2; - // for testing purposes - TRACE("t_str", tout << "WARNING: some Contains checks disabled!" << std::endl;); - // check_contain_by_eq_nodes(varAst1, varAst2); + check_contain_by_eq_nodes(varAst1, varAst2); } } } diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 476519e5c..e5fd25894 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -49,6 +49,26 @@ namespace smt { virtual void register_value(expr * n) { /* Ignore */ } }; + // rather than modify obj_pair_map I inherit from it and add my own helper methods + class theory_str_contain_pair_bool_map_t : public obj_pair_map { + public: + expr * operator[](std::pair key) const { + expr * value; + bool found = this->find(key.first, key.second, value); + if (found) { + return value; + } else { + TRACE("t_str", tout << "WARNING: lookup miss in contain_pair_bool_map!" << std::endl;); + return NULL; + } + } + + bool contains(std::pair key) const { + expr * unused; + return this->find(key.first, key.second, unused); + } + }; + class theory_str : public theory { struct T_cut { @@ -191,7 +211,10 @@ namespace smt { std::map unroll_var_map; std::map, expr*> concat_eq_unroll_ast_map; - expr_ref_vector contains_map; // was containPairBoolMap in Z3str2 + expr_ref_vector contains_map; + + theory_str_contain_pair_bool_map_t contain_pair_bool_map; + obj_map > contain_pair_idx_map; char * char_set; std::map charSetLookupTable; @@ -200,6 +223,7 @@ namespace smt { protected: void assert_axiom(expr * e); void assert_implication(expr * premise, expr * conclusion); + expr * rewrite_implication(expr * premise, expr * conclusion); app * mk_strlen(expr * e); expr * mk_concat(expr * n1, expr * n2); @@ -313,6 +337,7 @@ namespace smt { void check_contain_by_eqc_val(expr * varNode, expr * constNode); void check_contain_by_substr(expr * varNode, expr_ref_vector & willEqClass); void check_contain_by_eq_nodes(expr * n1, expr * n2); + bool in_contain_idx_map(expr * n); void get_nodes_in_concat(expr * node, ptr_vector & nodeList); expr * simplify_concat(expr * node); diff --git a/src/util/obj_pair_set.h b/src/util/obj_pair_set.h index 29139a51d..c4212977c 100644 --- a/src/util/obj_pair_set.h +++ b/src/util/obj_pair_set.h @@ -46,6 +46,11 @@ public: bool contains(obj_pair const & p) const { return m_set.contains(p); } void reset() { m_set.reset(); } bool empty() const { return m_set.empty(); } + + typedef typename chashtable::iterator iterator; + + iterator begin() { return m_set.begin(); } + iterator end() { return m_set.end(); } }; #endif From 685edbb268cb1034c580fda269c1e226118da17e Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Mon, 15 Aug 2016 18:58:36 -0400 Subject: [PATCH 182/410] pull out incorrectly-used data structures in theory_str for contains check, this will need to be revisited --- src/smt/theory_str.cpp | 6 +++--- src/smt/theory_str.h | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 93173402c..40745b069 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -4376,7 +4376,7 @@ void theory_str::check_contain_by_substr(expr * varNode, expr_ref_vector & willE } bool theory_str::in_contain_idx_map(expr * n) { - return contain_pair_idx_map.contains(n); + return contain_pair_idx_map.find(n) != contain_pair_idx_map.end(); } void theory_str::check_contain_by_eq_nodes(expr * n1, expr * n2) { @@ -4384,8 +4384,8 @@ void theory_str::check_contain_by_eq_nodes(expr * n1, expr * n2) { ast_manager & m = get_manager(); if (in_contain_idx_map(n1) && in_contain_idx_map(n2)) { - obj_pair_set::iterator keysItor1 = contain_pair_idx_map[n1].begin(); - obj_pair_set::iterator keysItor2; + std::set >::iterator keysItor1 = contain_pair_idx_map[n1].begin(); + std::set >::iterator keysItor2; for (; keysItor1 != contain_pair_idx_map[n1].end(); keysItor1++) { // keysItor1 is on set {<.., n1>, ..., , ...} diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index e5fd25894..fd93edfd4 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -214,7 +214,9 @@ namespace smt { expr_ref_vector contains_map; theory_str_contain_pair_bool_map_t contain_pair_bool_map; - obj_map > contain_pair_idx_map; + //obj_map > contain_pair_idx_map; + // TODO Find a better data structure, this is 100% a hack right now + std::map > > contain_pair_idx_map; char * char_set; std::map charSetLookupTable; From 48081864b01904f096f219cd30d119b05c0b8961 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 16 Aug 2016 18:07:31 -0400 Subject: [PATCH 183/410] add regex validation in str_rewriter --- src/ast/rewriter/str_rewriter.cpp | 200 ++++++++++++++++++++++++++++++ src/ast/str_decl_plugin.h | 4 +- 2 files changed, 202 insertions(+), 2 deletions(-) diff --git a/src/ast/rewriter/str_rewriter.cpp b/src/ast/rewriter/str_rewriter.cpp index fdb67f89e..54e0dd443 100644 --- a/src/ast/rewriter/str_rewriter.cpp +++ b/src/ast/rewriter/str_rewriter.cpp @@ -22,6 +22,192 @@ Notes: #include"ast_pp.h" #include"ast_util.h" #include"well_sorted.h" +#include +#include +#include + +class nfa { +protected: + str_util & m_strutil; + + bool m_valid; + unsigned m_next_id; + + unsigned next_id() { + unsigned retval = m_next_id; + ++m_next_id; + return retval; + } + + unsigned m_start_state; + unsigned m_end_state; + + std::map > transition_map; + std::map > epsilon_map; + + void make_transition(unsigned start, char symbol, unsigned end) { + transition_map[start][symbol] = end; + } + + void make_epsilon_move(unsigned start, unsigned end) { + epsilon_map[start].insert(end); + } + + // Convert a regular expression to an e-NFA using Thompson's construction + void convert_re(expr * e, unsigned & start, unsigned & end) { + start = next_id(); + end = next_id(); + if (m_strutil.is_re_Str2Reg(e)) { + app * a = to_app(e); + expr * arg_str = a->get_arg(0); + if (m_strutil.is_string(arg_str)) { + std::string str = m_strutil.get_string_constant_value(arg_str); + TRACE("t_str_rw", tout << "build NFA for '" << str << "'" << std::endl;); + + // TODO this assumes the string is not empty + /* + * For an n-character string, we make (n-1) intermediate states, + * labelled i_(0) through i_(n-2). + * Then we construct the following transitions: + * start --str[0]--> i_(0) --str[1]--> i_(1) --...--> i_(n-2) --str[n-1]--> final + */ + unsigned last = start; + for (unsigned i = 0; i <= str.length() - 2; ++i) { + unsigned i_state = next_id(); + make_transition(last, str.at(i), i_state); + TRACE("t_str_rw", tout << "string transition " << last << "--" << str.at(i) << "--> " << i_state << std::endl;); + last = i_state; + } + make_transition(last, str.at(str.length() - 1), end); + TRACE("t_str_rw", tout << "string transition " << last << "--" << str.at(str.length() - 1) << "--> " << end << std::endl;); + TRACE("t_str_rw", tout << "string NFA: start = " << start << ", end = " << end << std::endl;); + } else { + TRACE("t_str_rw", tout << "invalid string constant in Str2Reg" << std::endl;); + m_valid = false; + return; + } + } else if (m_strutil.is_re_RegexConcat(e)){ + app * a = to_app(e); + expr * re1 = a->get_arg(0); + expr * re2 = a->get_arg(1); + unsigned start1, end1; + convert_re(re1, start1, end1); + unsigned start2, end2; + convert_re(re2, start2, end2); + // start --e--> start1 --...--> end1 --e--> start2 --...--> end2 --e--> end + make_epsilon_move(start, start1); + make_epsilon_move(end1, start2); + make_epsilon_move(end2, end); + TRACE("t_str_rw", tout << "concat NFA: start = " << start << ", end = " << end << std::endl;); + } else if (m_strutil.is_re_RegexUnion(e)) { + app * a = to_app(e); + expr * re1 = a->get_arg(0); + expr * re2 = a->get_arg(1); + unsigned start1, end1; + convert_re(re1, start1, end1); + unsigned start2, end2; + convert_re(re2, start2, end2); + + // start --e--> start1 ; start --e--> start2 + // end1 --e--> end ; end2 --e--> end + make_epsilon_move(start, start1); + make_epsilon_move(start, start2); + make_epsilon_move(end1, end); + make_epsilon_move(end2, end); + TRACE("t_str_rw", tout << "union NFA: start = " << start << ", end = " << end << std::endl;); + } else if (m_strutil.is_re_RegexStar(e)) { + app * a = to_app(e); + expr * subex = a->get_arg(0); + unsigned start_subex, end_subex; + convert_re(subex, start_subex, end_subex); + // start --e--> start_subex, start --e--> end + // end_subex --e--> start_subex, end_subex --e--> end + make_epsilon_move(start, start_subex); + make_epsilon_move(start, end); + make_epsilon_move(end_subex, start_subex); + make_epsilon_move(end_subex, end); + TRACE("t_str_rw", tout << "star NFA: start = " << start << ", end = " << end << std::endl;); + } else { + TRACE("t_str_rw", tout << "invalid regular expression" << std::endl;); + m_valid = false; + return; + } + } + +public: + nfa(str_util & m_strutil, expr * e) +: m_strutil(m_strutil), + m_valid(true), m_next_id(0), m_start_state(0), m_end_state(0) { + convert_re(e, m_start_state, m_end_state); + } + + bool is_valid() const { + return m_valid; + } + + void epsilon_closure(unsigned start, std::set & closure) { + std::deque worklist; + closure.insert(start); + worklist.push_back(start); + + while(!worklist.empty()) { + unsigned state = worklist.front(); + worklist.pop_front(); + if (epsilon_map.find(state) != epsilon_map.end()) { + for (std::set::iterator it = epsilon_map[state].begin(); + it != epsilon_map[state].end(); ++it) { + unsigned new_state = *it; + if (closure.find(new_state) == closure.end()) { + closure.insert(new_state); + worklist.push_back(new_state); + } + } + } + } + } + + bool matches(std::string input) { + /* + * Keep a set of all states the NFA can currently be in. + * Initially this is the e-closure of m_start_state + * For each character A in the input string, + * the set of next states contains + * all states in transition_map[S][A] for each S in current_states, + * and all states in epsilon_map[S] for each S in current_states. + * After consuming the entire input string, + * the match is successful iff current_states contains m_end_state. + */ + std::set current_states; + epsilon_closure(m_start_state, current_states); + for (unsigned i = 0; i < input.length(); ++i) { + char A = input.at(i); + std::set next_states; + for (std::set::iterator it = current_states.begin(); + it != current_states.end(); ++it) { + unsigned S = *it; + // check transition_map + if (transition_map[S].find(A) != transition_map[S].end()) { + next_states.insert(transition_map[S][A]); + } + } + + // take e-closure over next_states to compute the actual next_states + std::set epsilon_next_states; + for (std::set::iterator it = next_states.begin(); it != next_states.end(); ++it) { + unsigned S = *it; + std::set closure; + epsilon_closure(S, closure); + epsilon_next_states.insert(closure.begin(), closure.end()); + } + current_states = epsilon_next_states; + } + if (current_states.find(m_end_state) != current_states.end()) { + return true; + } else { + return false; + } + } +}; br_status str_rewriter::mk_str_Concat(expr * arg0, expr * arg1, expr_ref & result) { TRACE("t_str_rw", tout << "rewrite (Concat " << mk_pp(arg0, m()) << " " << mk_pp(arg1, m()) << ")" << std::endl;); @@ -243,6 +429,20 @@ br_status str_rewriter::mk_re_RegexIn(expr * str, expr * re, expr_ref & result) return BR_REWRITE_FULL; } + // necessary for model validation + if (m_strutil.is_string(str)) { + TRACE("t_str_rw", tout << "RegexIn with constant string argument" << std::endl;); + nfa regex_nfa(m_strutil, re); + ENSURE(regex_nfa.is_valid()); + std::string input = m_strutil.get_string_constant_value(str); + if (regex_nfa.matches(input)) { + result = m().mk_true(); + } else { + result = m().mk_false(); + } + return BR_DONE; + } + return BR_FAILED; } diff --git a/src/ast/str_decl_plugin.h b/src/ast/str_decl_plugin.h index 4b7a8858e..5b0ca2a3a 100644 --- a/src/ast/str_decl_plugin.h +++ b/src/ast/str_decl_plugin.h @@ -111,7 +111,6 @@ public: virtual bool is_value(app * e) const; virtual bool is_unique_value(app * e) const { return is_value(e); } - // TODO }; class str_recognizers { @@ -125,11 +124,12 @@ public: bool is_string(expr const * n) const; bool is_re_Str2Reg(expr const * n) const { return is_app_of(n, get_fid(), OP_RE_STR2REGEX); } + bool is_re_RegexConcat(expr const * n) const { return is_app_of(n, get_fid(), OP_RE_REGEXCONCAT); } + bool is_re_RegexUnion(expr const * n) const { return is_app_of(n, get_fid(), OP_RE_REGEXUNION); } bool is_re_RegexStar(expr const * n) const { return is_app_of(n, get_fid(), OP_RE_REGEXSTAR); } bool is_re_RegexPlus(expr const * n) const { return is_app_of(n, get_fid(), OP_RE_REGEXPLUS); } std::string get_string_constant_value(expr const *n) const; - // TODO }; class str_util : public str_recognizers { From 0834229b394754ef6cec69f1d4de7206a669bcaf Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 17 Aug 2016 15:33:02 -0400 Subject: [PATCH 184/410] theory_str model validation for substr --- src/ast/rewriter/str_rewriter.cpp | 20 ++++++++++++++++++++ src/ast/rewriter/str_rewriter.h | 1 + 2 files changed, 21 insertions(+) diff --git a/src/ast/rewriter/str_rewriter.cpp b/src/ast/rewriter/str_rewriter.cpp index 54e0dd443..fe434575e 100644 --- a/src/ast/rewriter/str_rewriter.cpp +++ b/src/ast/rewriter/str_rewriter.cpp @@ -412,6 +412,23 @@ br_status str_rewriter::mk_str_Replace(expr * base, expr * source, expr * target } } +br_status str_rewriter::mk_str_Substr(expr * base, expr * start, expr * len, expr_ref & result) { + TRACE("t_str_rw", tout << "rewrite (Substr " << mk_pp(base, m()) << " " << mk_pp(start, m()) << " " << mk_pp(len, m()) << ")" << std::endl;); + rational startVal, lenVal; + if (m_strutil.is_string(base) && m_autil.is_numeral(start, startVal) && m_autil.is_numeral(len, lenVal)) { + std::string baseStr = m_strutil.get_string_constant_value(base); + // TODO handling for invalid start/len + if (startVal.is_nonneg() && lenVal.is_nonneg() && startVal.get_unsigned() <= baseStr.length()) { + TRACE("t_str_rw", tout << "rewriting constant Substr expression" << std::endl;); + std::string substr = baseStr.substr(startVal.get_unsigned(), lenVal.get_unsigned()); + result = m_strutil.mk_string(substr); + return BR_DONE; + } + } + + return BR_FAILED; +} + br_status str_rewriter::mk_re_Str2Reg(expr * str, expr_ref & result) { // the argument to Str2Reg *must* be a string constant ENSURE(m_strutil.is_string(str)); @@ -532,6 +549,9 @@ br_status str_rewriter::mk_app_core(func_decl * f, unsigned num_args, expr * con case OP_STR_REPLACE: SASSERT(num_args == 3); return mk_str_Replace(args[0], args[1], args[2], result); + case OP_STR_SUBSTR: + SASSERT(num_args == 3); + return mk_str_Substr(args[0], args[1], args[2], result); case OP_RE_STR2REGEX: SASSERT(num_args == 1); return mk_re_Str2Reg(args[0], result); diff --git a/src/ast/rewriter/str_rewriter.h b/src/ast/rewriter/str_rewriter.h index 2235425be..862fc3e7e 100644 --- a/src/ast/rewriter/str_rewriter.h +++ b/src/ast/rewriter/str_rewriter.h @@ -50,6 +50,7 @@ public: br_status mk_str_Indexof2(expr * arg0, expr * arg1, expr * arg2, expr_ref & result); br_status mk_str_LastIndexof(expr * haystack, expr * needle, expr_ref & result); br_status mk_str_Replace(expr * base, expr * source, expr * target, expr_ref & result); + br_status mk_str_Substr(expr * base, expr * start, expr * len, expr_ref & result); br_status mk_re_Str2Reg(expr * str, expr_ref & result); br_status mk_re_RegexIn(expr * str, expr * re, expr_ref & result); From 71ad4d3a4abc08f4e2638fa7e0b23b14ab8575d4 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 17 Aug 2016 16:21:19 -0400 Subject: [PATCH 185/410] add regex_in_bool_map to theory_str --- src/smt/theory_str.cpp | 74 ++++++++++++++++++++++++++++++++++-------- src/smt/theory_str.h | 4 +++ 2 files changed, 65 insertions(+), 13 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 40745b069..087bf6ad0 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -1438,30 +1438,78 @@ expr * theory_str::mk_RegexIn(expr * str, expr * regexp) { return regexIn; } +static std::string str2RegexStr(std::string str) { + std::string res = ""; + int len = str.size(); + for (int i = 0; i < len; i++) { + char nc = str[i]; + // 12 special chars + if (nc == '\\' || nc == '^' || nc == '$' || nc == '.' || nc == '|' || nc == '?' + || nc == '*' || nc == '+' || nc == '(' || nc == ')' || nc == '[' || nc == '{') { + res.append(1, '\\'); + } + res.append(1, str[i]); + } + return res; +} + +std::string theory_str::get_std_regex_str(expr * regex) { + app * a_regex = to_app(regex); + if (is_Str2Reg(a_regex)) { + expr * regAst = a_regex->get_arg(0); + std::string regStr = str2RegexStr(m_strutil.get_string_constant_value(regAst)); + return regStr; + } else if (is_RegexConcat(a_regex)) { + expr * reg1Ast = a_regex->get_arg(0); + expr * reg2Ast = a_regex->get_arg(1); + std::string reg1Str = get_std_regex_str(reg1Ast); + std::string reg2Str = get_std_regex_str(reg2Ast); + return "(" + reg1Str + ")(" + reg2Str + ")"; + } else if (is_RegexUnion(a_regex)) { + expr * reg1Ast = a_regex->get_arg(0); + expr * reg2Ast = a_regex->get_arg(1); + std::string reg1Str = get_std_regex_str(reg1Ast); + std::string reg2Str = get_std_regex_str(reg2Ast); + return "(" + reg1Str + ")|(" + reg2Str + ")"; + } else if (is_RegexStar(a_regex)) { + expr * reg1Ast = a_regex->get_arg(0); + std::string reg1Str = get_std_regex_str(reg1Ast); + return "(" + reg1Str + ")*"; + } else { + TRACE("t_str", tout << "BUG: unrecognized regex term " << mk_pp(regex, get_manager()) << std::endl;); + UNREACHABLE(); return ""; + } +} + void theory_str::instantiate_axiom_RegexIn(enode * e) { context & ctx = get_context(); ast_manager & m = get_manager(); - app * expr = e->get_owner(); - if (axiomatized_terms.contains(expr)) { - TRACE("t_str_detail", tout << "already set up RegexIn axiom for " << mk_pp(expr, m) << std::endl;); + app * ex = e->get_owner(); + if (axiomatized_terms.contains(ex)) { + TRACE("t_str_detail", tout << "already set up RegexIn axiom for " << mk_pp(ex, m) << std::endl;); return; } - axiomatized_terms.insert(expr); + axiomatized_terms.insert(ex); - TRACE("t_str_detail", tout << "instantiate RegexIn axiom for " << mk_pp(expr, m) << std::endl;); + TRACE("t_str_detail", tout << "instantiate RegexIn axiom for " << mk_pp(ex, m) << std::endl;); - // I don't think we need to port regexInBoolMap and regexInVarStrMap, - // but they would go here from reduce_regexIn + { + std::string regexStr = get_std_regex_str(ex->get_arg(1)); + std::pair key1(ex->get_arg(0), regexStr); + // skip Z3str's map check, because we already check if we set up axioms on this term + regex_in_bool_map[key1] = ex; + regex_in_var_reg_str_map[ex->get_arg(0)].insert(regexStr); + } - expr_ref str(expr->get_arg(0), m); - app * regex = to_app(expr->get_arg(1)); + expr_ref str(ex->get_arg(0), m); + app * regex = to_app(ex->get_arg(1)); if (is_Str2Reg(regex)) { expr_ref rxStr(regex->get_arg(0), m); // want to assert 'expr IFF (str == rxStr)' expr_ref rhs(ctx.mk_eq_atom(str, rxStr), m); - expr_ref finalAxiom(m.mk_iff(expr, rhs), m); + expr_ref finalAxiom(m.mk_iff(ex, rhs), m); SASSERT(finalAxiom); assert_axiom(finalAxiom); } else if (is_RegexConcat(regex)) { @@ -1476,7 +1524,7 @@ void theory_str::instantiate_axiom_RegexIn(enode * e) { expr_ref_vector items(m); items.push_back(var1InRegex1); items.push_back(var2InRegex2); - items.push_back(ctx.mk_eq_atom(expr, ctx.mk_eq_atom(str, rhs))); + items.push_back(ctx.mk_eq_atom(ex, ctx.mk_eq_atom(str, rhs))); expr_ref finalAxiom(mk_and(items), m); SASSERT(finalAxiom); @@ -1492,7 +1540,7 @@ void theory_str::instantiate_axiom_RegexIn(enode * e) { expr_ref_vector items(m); items.push_back(var1InRegex1); items.push_back(var2InRegex2); - items.push_back(ctx.mk_eq_atom(expr, orVar)); + items.push_back(ctx.mk_eq_atom(ex, orVar)); assert_axiom(mk_and(items)); } else if (is_RegexStar(regex)) { // slightly more complex due to the unrolling step. @@ -1500,7 +1548,7 @@ void theory_str::instantiate_axiom_RegexIn(enode * e) { expr_ref unrollCount(mk_unroll_bound_var(), m); expr_ref unrollFunc(mk_unroll(regex1, unrollCount), m); expr_ref_vector items(m); - items.push_back(ctx.mk_eq_atom(expr, ctx.mk_eq_atom(str, unrollFunc))); + items.push_back(ctx.mk_eq_atom(ex, ctx.mk_eq_atom(str, unrollFunc))); items.push_back(ctx.mk_eq_atom(ctx.mk_eq_atom(unrollCount, mk_int(0)), ctx.mk_eq_atom(unrollFunc, m_strutil.mk_string("")))); expr_ref finalAxiom(mk_and(items), m); SASSERT(finalAxiom); diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index fd93edfd4..06a72c3e2 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -218,6 +218,9 @@ namespace smt { // TODO Find a better data structure, this is 100% a hack right now std::map > > contain_pair_idx_map; + std::map, expr*> regex_in_bool_map; + std::map > regex_in_var_reg_str_map; + char * char_set; std::map charSetLookupTable; int charSetSize; @@ -419,6 +422,7 @@ namespace smt { expr * gen_unroll_conditional_options(expr * var, std::set & unrolls, std::string lcmStr); expr * gen_unroll_assign(expr * var, std::string lcmStr, expr * testerVar, int l, int h); void reduce_virtual_regex_in(expr * var, expr * regex, expr_ref_vector & items); + std::string get_std_regex_str(expr * regex); void dump_assignments(); void initialize_charset(); From 6263391c11278ca6653d61f9cc059b9b9232b4e5 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 17 Aug 2016 20:58:57 -0400 Subject: [PATCH 186/410] fix out-of-range integer comparison bug in string NFA --- src/ast/rewriter/str_rewriter.cpp | 290 +++++++++++++----------------- src/ast/rewriter/str_rewriter.h | 47 +++++ src/smt/theory_str.cpp | 59 +++++- src/smt/theory_str.h | 12 ++ 4 files changed, 243 insertions(+), 165 deletions(-) diff --git a/src/ast/rewriter/str_rewriter.cpp b/src/ast/rewriter/str_rewriter.cpp index fe434575e..c644ecd46 100644 --- a/src/ast/rewriter/str_rewriter.cpp +++ b/src/ast/rewriter/str_rewriter.cpp @@ -26,188 +26,150 @@ Notes: #include #include -class nfa { -protected: - str_util & m_strutil; +// Convert a regular expression to an e-NFA using Thompson's construction +void nfa::convert_re(expr * e, unsigned & start, unsigned & end, str_util & m_strutil) { + start = next_id(); + end = next_id(); + if (m_strutil.is_re_Str2Reg(e)) { + app * a = to_app(e); + expr * arg_str = a->get_arg(0); + if (m_strutil.is_string(arg_str)) { + std::string str = m_strutil.get_string_constant_value(arg_str); + TRACE("t_str_rw", tout << "build NFA for '" << str << "'" << std::endl;); - bool m_valid; - unsigned m_next_id; - - unsigned next_id() { - unsigned retval = m_next_id; - ++m_next_id; - return retval; - } - - unsigned m_start_state; - unsigned m_end_state; - - std::map > transition_map; - std::map > epsilon_map; - - void make_transition(unsigned start, char symbol, unsigned end) { - transition_map[start][symbol] = end; - } - - void make_epsilon_move(unsigned start, unsigned end) { - epsilon_map[start].insert(end); - } - - // Convert a regular expression to an e-NFA using Thompson's construction - void convert_re(expr * e, unsigned & start, unsigned & end) { - start = next_id(); - end = next_id(); - if (m_strutil.is_re_Str2Reg(e)) { - app * a = to_app(e); - expr * arg_str = a->get_arg(0); - if (m_strutil.is_string(arg_str)) { - std::string str = m_strutil.get_string_constant_value(arg_str); - TRACE("t_str_rw", tout << "build NFA for '" << str << "'" << std::endl;); - - // TODO this assumes the string is not empty - /* - * For an n-character string, we make (n-1) intermediate states, - * labelled i_(0) through i_(n-2). - * Then we construct the following transitions: - * start --str[0]--> i_(0) --str[1]--> i_(1) --...--> i_(n-2) --str[n-1]--> final - */ - unsigned last = start; - for (unsigned i = 0; i <= str.length() - 2; ++i) { - unsigned i_state = next_id(); - make_transition(last, str.at(i), i_state); - TRACE("t_str_rw", tout << "string transition " << last << "--" << str.at(i) << "--> " << i_state << std::endl;); - last = i_state; - } - make_transition(last, str.at(str.length() - 1), end); - TRACE("t_str_rw", tout << "string transition " << last << "--" << str.at(str.length() - 1) << "--> " << end << std::endl;); - TRACE("t_str_rw", tout << "string NFA: start = " << start << ", end = " << end << std::endl;); - } else { - TRACE("t_str_rw", tout << "invalid string constant in Str2Reg" << std::endl;); - m_valid = false; - return; + // TODO this assumes the string is not empty + /* + * For an n-character string, we make (n-1) intermediate states, + * labelled i_(0) through i_(n-2). + * Then we construct the following transitions: + * start --str[0]--> i_(0) --str[1]--> i_(1) --...--> i_(n-2) --str[n-1]--> final + */ + unsigned last = start; + for (int i = 0; i <= ((int)str.length()) - 2; ++i) { + unsigned i_state = next_id(); + make_transition(last, str.at(i), i_state); + TRACE("t_str_rw", tout << "string transition " << last << "--" << str.at(i) << "--> " << i_state << std::endl;); + last = i_state; } - } else if (m_strutil.is_re_RegexConcat(e)){ - app * a = to_app(e); - expr * re1 = a->get_arg(0); - expr * re2 = a->get_arg(1); - unsigned start1, end1; - convert_re(re1, start1, end1); - unsigned start2, end2; - convert_re(re2, start2, end2); - // start --e--> start1 --...--> end1 --e--> start2 --...--> end2 --e--> end - make_epsilon_move(start, start1); - make_epsilon_move(end1, start2); - make_epsilon_move(end2, end); - TRACE("t_str_rw", tout << "concat NFA: start = " << start << ", end = " << end << std::endl;); - } else if (m_strutil.is_re_RegexUnion(e)) { - app * a = to_app(e); - expr * re1 = a->get_arg(0); - expr * re2 = a->get_arg(1); - unsigned start1, end1; - convert_re(re1, start1, end1); - unsigned start2, end2; - convert_re(re2, start2, end2); - - // start --e--> start1 ; start --e--> start2 - // end1 --e--> end ; end2 --e--> end - make_epsilon_move(start, start1); - make_epsilon_move(start, start2); - make_epsilon_move(end1, end); - make_epsilon_move(end2, end); - TRACE("t_str_rw", tout << "union NFA: start = " << start << ", end = " << end << std::endl;); - } else if (m_strutil.is_re_RegexStar(e)) { - app * a = to_app(e); - expr * subex = a->get_arg(0); - unsigned start_subex, end_subex; - convert_re(subex, start_subex, end_subex); - // start --e--> start_subex, start --e--> end - // end_subex --e--> start_subex, end_subex --e--> end - make_epsilon_move(start, start_subex); - make_epsilon_move(start, end); - make_epsilon_move(end_subex, start_subex); - make_epsilon_move(end_subex, end); - TRACE("t_str_rw", tout << "star NFA: start = " << start << ", end = " << end << std::endl;); + make_transition(last, str.at(str.length() - 1), end); + TRACE("t_str_rw", tout << "string transition " << last << "--" << str.at(str.length() - 1) << "--> " << end << std::endl;); + TRACE("t_str_rw", tout << "string NFA: start = " << start << ", end = " << end << std::endl;); } else { - TRACE("t_str_rw", tout << "invalid regular expression" << std::endl;); + TRACE("t_str_rw", tout << "invalid string constant in Str2Reg" << std::endl;); m_valid = false; return; } + } else if (m_strutil.is_re_RegexConcat(e)){ + app * a = to_app(e); + expr * re1 = a->get_arg(0); + expr * re2 = a->get_arg(1); + unsigned start1, end1; + convert_re(re1, start1, end1, m_strutil); + unsigned start2, end2; + convert_re(re2, start2, end2, m_strutil); + // start --e--> start1 --...--> end1 --e--> start2 --...--> end2 --e--> end + make_epsilon_move(start, start1); + make_epsilon_move(end1, start2); + make_epsilon_move(end2, end); + TRACE("t_str_rw", tout << "concat NFA: start = " << start << ", end = " << end << std::endl;); + } else if (m_strutil.is_re_RegexUnion(e)) { + app * a = to_app(e); + expr * re1 = a->get_arg(0); + expr * re2 = a->get_arg(1); + unsigned start1, end1; + convert_re(re1, start1, end1, m_strutil); + unsigned start2, end2; + convert_re(re2, start2, end2, m_strutil); + + // start --e--> start1 ; start --e--> start2 + // end1 --e--> end ; end2 --e--> end + make_epsilon_move(start, start1); + make_epsilon_move(start, start2); + make_epsilon_move(end1, end); + make_epsilon_move(end2, end); + TRACE("t_str_rw", tout << "union NFA: start = " << start << ", end = " << end << std::endl;); + } else if (m_strutil.is_re_RegexStar(e)) { + app * a = to_app(e); + expr * subex = a->get_arg(0); + unsigned start_subex, end_subex; + convert_re(subex, start_subex, end_subex, m_strutil); + // start --e--> start_subex, start --e--> end + // end_subex --e--> start_subex, end_subex --e--> end + make_epsilon_move(start, start_subex); + make_epsilon_move(start, end); + make_epsilon_move(end_subex, start_subex); + make_epsilon_move(end_subex, end); + TRACE("t_str_rw", tout << "star NFA: start = " << start << ", end = " << end << std::endl;); + } else { + TRACE("t_str_rw", tout << "invalid regular expression" << std::endl;); + m_valid = false; + return; } +} -public: - nfa(str_util & m_strutil, expr * e) -: m_strutil(m_strutil), - m_valid(true), m_next_id(0), m_start_state(0), m_end_state(0) { - convert_re(e, m_start_state, m_end_state); - } +void nfa::epsilon_closure(unsigned start, std::set & closure) { + std::deque worklist; + closure.insert(start); + worklist.push_back(start); - bool is_valid() const { - return m_valid; - } - - void epsilon_closure(unsigned start, std::set & closure) { - std::deque worklist; - closure.insert(start); - worklist.push_back(start); - - while(!worklist.empty()) { - unsigned state = worklist.front(); - worklist.pop_front(); - if (epsilon_map.find(state) != epsilon_map.end()) { - for (std::set::iterator it = epsilon_map[state].begin(); - it != epsilon_map[state].end(); ++it) { - unsigned new_state = *it; - if (closure.find(new_state) == closure.end()) { - closure.insert(new_state); - worklist.push_back(new_state); - } + while(!worklist.empty()) { + unsigned state = worklist.front(); + worklist.pop_front(); + if (epsilon_map.find(state) != epsilon_map.end()) { + for (std::set::iterator it = epsilon_map[state].begin(); + it != epsilon_map[state].end(); ++it) { + unsigned new_state = *it; + if (closure.find(new_state) == closure.end()) { + closure.insert(new_state); + worklist.push_back(new_state); } } } } +} - bool matches(std::string input) { - /* - * Keep a set of all states the NFA can currently be in. - * Initially this is the e-closure of m_start_state - * For each character A in the input string, - * the set of next states contains - * all states in transition_map[S][A] for each S in current_states, - * and all states in epsilon_map[S] for each S in current_states. - * After consuming the entire input string, - * the match is successful iff current_states contains m_end_state. - */ - std::set current_states; - epsilon_closure(m_start_state, current_states); - for (unsigned i = 0; i < input.length(); ++i) { - char A = input.at(i); - std::set next_states; - for (std::set::iterator it = current_states.begin(); - it != current_states.end(); ++it) { - unsigned S = *it; - // check transition_map - if (transition_map[S].find(A) != transition_map[S].end()) { - next_states.insert(transition_map[S][A]); - } +bool nfa::matches(std::string input) { + /* + * Keep a set of all states the NFA can currently be in. + * Initially this is the e-closure of m_start_state + * For each character A in the input string, + * the set of next states contains + * all states in transition_map[S][A] for each S in current_states, + * and all states in epsilon_map[S] for each S in current_states. + * After consuming the entire input string, + * the match is successful iff current_states contains m_end_state. + */ + std::set current_states; + epsilon_closure(m_start_state, current_states); + for (unsigned i = 0; i < input.length(); ++i) { + char A = input.at(i); + std::set next_states; + for (std::set::iterator it = current_states.begin(); + it != current_states.end(); ++it) { + unsigned S = *it; + // check transition_map + if (transition_map[S].find(A) != transition_map[S].end()) { + next_states.insert(transition_map[S][A]); } + } - // take e-closure over next_states to compute the actual next_states - std::set epsilon_next_states; - for (std::set::iterator it = next_states.begin(); it != next_states.end(); ++it) { - unsigned S = *it; - std::set closure; - epsilon_closure(S, closure); - epsilon_next_states.insert(closure.begin(), closure.end()); - } - current_states = epsilon_next_states; - } - if (current_states.find(m_end_state) != current_states.end()) { - return true; - } else { - return false; + // take e-closure over next_states to compute the actual next_states + std::set epsilon_next_states; + for (std::set::iterator it = next_states.begin(); it != next_states.end(); ++it) { + unsigned S = *it; + std::set closure; + epsilon_closure(S, closure); + epsilon_next_states.insert(closure.begin(), closure.end()); } + current_states = epsilon_next_states; } -}; + if (current_states.find(m_end_state) != current_states.end()) { + return true; + } else { + return false; + } +} + br_status str_rewriter::mk_str_Concat(expr * arg0, expr * arg1, expr_ref & result) { TRACE("t_str_rw", tout << "rewrite (Concat " << mk_pp(arg0, m()) << " " << mk_pp(arg1, m()) << ")" << std::endl;); diff --git a/src/ast/rewriter/str_rewriter.h b/src/ast/rewriter/str_rewriter.h index 862fc3e7e..c64d086f9 100644 --- a/src/ast/rewriter/str_rewriter.h +++ b/src/ast/rewriter/str_rewriter.h @@ -21,6 +21,8 @@ Notes: #include"arith_decl_plugin.h" #include"rewriter_types.h" #include"params.h" +#include +#include class str_rewriter { str_util m_strutil; @@ -61,3 +63,48 @@ public: bool reduce_eq(expr_ref_vector& ls, expr_ref_vector& rs, expr_ref_vector& lhs, expr_ref_vector& rhs, bool& change); }; + +class nfa { +protected: + bool m_valid; + unsigned m_next_id; + + unsigned next_id() { + unsigned retval = m_next_id; + ++m_next_id; + return retval; + } + + unsigned m_start_state; + unsigned m_end_state; + + std::map > transition_map; + std::map > epsilon_map; + + void make_transition(unsigned start, char symbol, unsigned end) { + transition_map[start][symbol] = end; + } + + void make_epsilon_move(unsigned start, unsigned end) { + epsilon_map[start].insert(end); + } + + // Convert a regular expression to an e-NFA using Thompson's construction + void convert_re(expr * e, unsigned & start, unsigned & end, str_util & m_strutil); + +public: + nfa(str_util & m_strutil, expr * e) +: m_valid(true), m_next_id(0), m_start_state(0), m_end_state(0) { + convert_re(e, m_start_state, m_end_state, m_strutil); + } + + nfa() : m_valid(false), m_next_id(0), m_start_state(0), m_end_state(0) {} + + bool is_valid() const { + return m_valid; + } + + void epsilon_closure(unsigned start, std::set & closure); + + bool matches(std::string input); +}; diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 087bf6ad0..d249649c7 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -35,6 +35,7 @@ theory_str::theory_str(ast_manager & m): opt_LCMUnrollStep(2), opt_NoQuickReturn_IntegerTheory(false), opt_DisableIntegerTheoryIntegration(false), + opt_NoCheckRegexIn(false), /* Internal setup */ search_started(false), m_autil(m), @@ -1643,7 +1644,14 @@ bool theory_str::new_eq_check(expr * lhs, expr * rhs) { check_contain_in_new_eq(lhs, rhs); } - // TODO regexInBoolMap + if (!regex_in_bool_map.empty()) { + if (opt_NoCheckRegexIn) { + TRACE("t_str", tout << "WARNING: skipping check_regex_in()" << std::endl;); + } else { + TRACE("t_str", tout << "checking regex consistency" << std::endl;); + check_regex_in(lhs, rhs); + } + } // okay, all checks here passed return true; @@ -5213,6 +5221,55 @@ void theory_str::check_concat_len_in_eqc(expr * concat) { } while (eqc_it != eqc_base); } +void theory_str::check_regex_in(expr * nn1, expr * nn2) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + expr_ref_vector eqNodeSet(m); + expr * constStr = collect_eq_nodes(nn1, eqNodeSet); + + if (constStr == NULL) { + return; + } else { + expr_ref_vector::iterator itor = eqNodeSet.begin(); + for (; itor != eqNodeSet.end(); itor++) { + if (regex_in_var_reg_str_map.find(*itor) != regex_in_var_reg_str_map.end()) { + std::set::iterator strItor = regex_in_var_reg_str_map[*itor].begin(); + for (; strItor != regex_in_var_reg_str_map[*itor].end(); strItor++) { + std::string regStr = *strItor; + std::string constStrValue = m_strutil.get_string_constant_value(constStr); + std::pair key1 = std::make_pair(*itor, regStr); + if (regex_in_bool_map.find(key1) != regex_in_bool_map.end()) { + expr * boolVar = regex_in_bool_map[key1]; // actually the RegexIn term + app * a_regexIn = to_app(boolVar); + expr * regexTerm = a_regexIn->get_arg(1); + + if (regex_nfa_cache.find(regexTerm) == regex_nfa_cache.end()) { + TRACE("t_str_detail", tout << "regex_nfa_cache: cache miss" << std::endl;); + regex_nfa_cache[regexTerm] = nfa(m_strutil, regexTerm); + } else { + TRACE("t_str_detail", tout << "regex_nfa_cache: cache hit" << std::endl;); + } + + nfa regexNFA = regex_nfa_cache[regexTerm]; + ENSURE(regexNFA.is_valid()); + bool matchRes = regexNFA.matches(constStrValue); + + TRACE("t_str_detail", tout << mk_pp(*itor, m) << " in " << regStr << " : " << (matchRes ? "yes" : "no") << std::endl;); + + expr_ref implyL(ctx.mk_eq_atom(*itor, constStr), m); + if (matchRes) { + assert_implication(implyL, boolVar); + } else { + assert_implication(implyL, m.mk_not(boolVar)); + } + } + } + } + } + } +} + /* * strArgmt::solve_concat_eq_str() * Solve concatenations of the form: diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 06a72c3e2..8acdb4f02 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -25,6 +25,7 @@ Revision History: #include"arith_decl_plugin.h" #include #include +#include"str_rewriter.h" namespace smt { @@ -137,6 +138,14 @@ namespace smt { */ bool opt_DisableIntegerTheoryIntegration; + /* + * If NoCheckRegexIn is set to true, + * an expensive regular expression membership test is skipped. + * This option is for experiment purposes only and should be set to 'false' + * as skipping this check impacts the correctness of the solver. + */ + bool opt_NoCheckRegexIn; + bool search_started; arith_util m_autil; str_util m_strutil; @@ -221,6 +230,8 @@ namespace smt { std::map, expr*> regex_in_bool_map; std::map > regex_in_var_reg_str_map; + std::map regex_nfa_cache; // Regex term --> NFA + char * char_set; std::map charSetLookupTable; int charSetSize; @@ -423,6 +434,7 @@ namespace smt { expr * gen_unroll_assign(expr * var, std::string lcmStr, expr * testerVar, int l, int h); void reduce_virtual_regex_in(expr * var, expr * regex, expr_ref_vector & items); std::string get_std_regex_str(expr * regex); + void check_regex_in(expr * nn1, expr * nn2); void dump_assignments(); void initialize_charset(); From 54d7e4bbb59f7b255bc70c11dce9acdef6daf30b Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 17 Aug 2016 21:12:19 -0400 Subject: [PATCH 187/410] remove the option to bypass check_regex_in in theory_str --- src/smt/theory_str.cpp | 9 ++------- src/smt/theory_str.h | 8 -------- 2 files changed, 2 insertions(+), 15 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index d249649c7..ae002f979 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -35,7 +35,6 @@ theory_str::theory_str(ast_manager & m): opt_LCMUnrollStep(2), opt_NoQuickReturn_IntegerTheory(false), opt_DisableIntegerTheoryIntegration(false), - opt_NoCheckRegexIn(false), /* Internal setup */ search_started(false), m_autil(m), @@ -1645,12 +1644,8 @@ bool theory_str::new_eq_check(expr * lhs, expr * rhs) { } if (!regex_in_bool_map.empty()) { - if (opt_NoCheckRegexIn) { - TRACE("t_str", tout << "WARNING: skipping check_regex_in()" << std::endl;); - } else { - TRACE("t_str", tout << "checking regex consistency" << std::endl;); - check_regex_in(lhs, rhs); - } + TRACE("t_str", tout << "checking regex consistency" << std::endl;); + check_regex_in(lhs, rhs); } // okay, all checks here passed diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 8acdb4f02..527753b73 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -138,14 +138,6 @@ namespace smt { */ bool opt_DisableIntegerTheoryIntegration; - /* - * If NoCheckRegexIn is set to true, - * an expensive regular expression membership test is skipped. - * This option is for experiment purposes only and should be set to 'false' - * as skipping this check impacts the correctness of the solver. - */ - bool opt_NoCheckRegexIn; - bool search_started; arith_util m_autil; str_util m_strutil; From 3c8b833eebd7ac8c946958720d3f95c14840355a Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Thu, 18 Aug 2016 17:03:32 -0400 Subject: [PATCH 188/410] fix expression dereference error in theory_str::gen_assign_unroll_Str2Reg --- src/smt/theory_str.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index ae002f979..05cbe8803 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -7772,10 +7772,11 @@ expr * theory_str::gen_assign_unroll_Str2Reg(expr * n, std::set & unrolls if (canHaveNonEmptyAssign) { return gen_unroll_conditional_options(n, unrolls, lcmStr); } else { - expr * implyL = mk_and(litems); - expr * implyR = ctx.mk_eq_atom(n, m_strutil.mk_string("")); + expr_ref implyL(mk_and(litems), mgr); + expr_ref implyR(ctx.mk_eq_atom(n, m_strutil.mk_string("")), mgr); // want to return (implyL -> implyR) - return mgr.mk_or(mgr.mk_not(implyL), implyR); + expr * final_axiom = rewrite_implication(implyL, implyR); + return final_axiom; } } From 8598a48e3b459c0b4e2ce491b40f8438211a0e07 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Thu, 18 Aug 2016 19:14:50 -0400 Subject: [PATCH 189/410] fix weird Contains rewriter behaviour in theory_str --- src/smt/theory_str.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 05cbe8803..360bfa26a 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -1130,6 +1130,22 @@ void theory_str::instantiate_axiom_Contains(enode * e) { return; } axiomatized_terms.insert(ex); + + // quick path, because this is necessary due to rewriter behaviour + // (at minimum it should fix z3str/concat-006.smt2 + // TODO: see if it's necessary for other such terms + if (m_strutil.is_string(ex->get_arg(0)) && m_strutil.is_string(ex->get_arg(1))) { + TRACE("t_str_detail", tout << "eval constant Contains term " << mk_pp(ex, m) << std::endl;); + std::string haystackStr = m_strutil.get_string_constant_value(ex->get_arg(0)); + std::string needleStr = m_strutil.get_string_constant_value(ex->get_arg(1)); + if (haystackStr.find(needleStr) != std::string::npos) { + assert_axiom(ex); + } else { + assert_axiom(m.mk_not(ex)); + } + return; + } + { // register Contains() expr * str = ex->get_arg(0); expr * substr = ex->get_arg(1); From 481e97a274c994d66f9c34bd615c8b4af214439d Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Fri, 19 Aug 2016 22:53:36 -0400 Subject: [PATCH 190/410] propagate early in theory_str to set up contains/regex maps this fixes an unsat-as-sat error in a regex test and flips around some timeouts so more work will be required to track this down --- src/smt/theory_str.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 360bfa26a..c781cae04 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -6052,6 +6052,10 @@ void theory_str::init_search_eh() { } */ + // this might be cheating but we need to make sure that certain maps are populated + // before the first call to new_eq_eh() + propagate(); + TRACE("t_str", tout << "search started" << std::endl;); search_started = true; } From 1a75781a3ceb28f7dad6be75140c5b9685c935a7 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sat, 20 Aug 2016 23:09:08 -0400 Subject: [PATCH 191/410] add experimental option to defer new_eq_check to final_check in theory_str --- src/smt/theory_str.cpp | 87 +++++++++++++++++++++++++++++++++++++----- src/smt/theory_str.h | 9 ++++- 2 files changed, 86 insertions(+), 10 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index c781cae04..f53eefb6d 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -35,6 +35,7 @@ theory_str::theory_str(ast_manager & m): opt_LCMUnrollStep(2), opt_NoQuickReturn_IntegerTheory(false), opt_DisableIntegerTheoryIntegration(false), + opt_DeferEQCConsistencyCheck(true), /* Internal setup */ search_started(false), m_autil(m), @@ -1613,8 +1614,11 @@ bool theory_str::new_eq_check(expr * lhs, expr * rhs) { UNREACHABLE(); } - check_concat_len_in_eqc(lhs); - check_concat_len_in_eqc(rhs); + // skip this check if we defer consistency checking, as we can do it for every EQC in final check + if (!opt_DeferEQCConsistencyCheck) { + check_concat_len_in_eqc(lhs); + check_concat_len_in_eqc(rhs); + } // Now we iterate over all pairs of terms in the (shared) eqc // and check whether we can show that any pair of distinct terms @@ -5197,7 +5201,7 @@ bool theory_str::check_length_eq_var_concat(expr * n1, expr * n2) { else { return check_length_var_var(n1, n2); } - return 0; + return true; } // returns false if an inconsistency is detected, or true if no inconsistencies were found @@ -5214,22 +5218,31 @@ bool theory_str::check_length_consistency(expr * n1, expr * n2) { // n1 and n2 are vars or concats return check_length_eq_var_concat(n1, n2); } - return 0; + return true; } -void theory_str::check_concat_len_in_eqc(expr * concat) { +// Modified signature: returns true if nothing was learned, or false if at least one axiom was asserted. +// (This is used for deferred consistency checking) +bool theory_str::check_concat_len_in_eqc(expr * concat) { context & ctx = get_context(); + bool no_assertions = true; + enode * eqc_base = ctx.get_enode(concat); enode * eqc_it = eqc_base; do { app * eqc_n = eqc_it->get_owner(); if (is_concat(eqc_n)) { rational unused; - infer_len_concat(eqc_n, unused); + bool status = infer_len_concat(eqc_n, unused); + if (status) { + no_assertions = false; + } } eqc_it = eqc_it->get_next(); } while (eqc_it != eqc_base); + + return no_assertions; } void theory_str::check_regex_in(expr * nn1, expr * nn2) { @@ -5730,9 +5743,13 @@ void theory_str::handle_equality(expr * lhs, expr * rhs) { } } - // newEqCheck() -- check consistency wrt. existing equivalence classes - if (!new_eq_check(lhs, rhs)) { - return; + if (opt_DeferEQCConsistencyCheck) { + TRACE("t_str_detail", tout << "opt_DeferEQCConsistencyCheck is set; deferring new_eq_check call" << std::endl;); + } else { + // newEqCheck() -- check consistency wrt. existing equivalence classes + if (!new_eq_check(lhs, rhs)) { + return; + } } // BEGIN new_eq_handler() in strTheory @@ -7040,6 +7057,58 @@ final_check_status theory_str::final_check_eh() { TRACE("t_str", tout << "final check" << std::endl;); TRACE("t_str_dump_assign", dump_assignments();); + if (opt_DeferEQCConsistencyCheck) { + TRACE("t_str_detail", tout << "performing deferred EQC consistency check" << std::endl;); + std::set eqc_roots; + for (ptr_vector::const_iterator it = ctx.begin_enodes(); it != ctx.end_enodes(); ++it) { + enode * e = *it; + enode * root = e->get_root(); + eqc_roots.insert(root); + } + + bool found_inconsistency = false; + + for (std::set::iterator it = eqc_roots.begin(); it != eqc_roots.end(); ++it) { + enode * e = *it; + app * a = e->get_owner(); + if (!(is_sort_of(m.get_sort(a), m_strutil.get_fid(), STRING_SORT))) { + TRACE("t_str_detail", tout << "EQC root " << mk_pp(a, m) << " not a string term; skipping" << std::endl;); + } else { + TRACE("t_str_detail", tout << "EQC root " << mk_pp(a, m) << " is a string term. Checking this EQC" << std::endl;); + // first call check_concat_len_in_eqc() on each member of the eqc + enode * e_it = e; + enode * e_root = e_it; + do { + bool status = check_concat_len_in_eqc(e_it->get_owner()); + if (!status) { + TRACE("t_str_detail", tout << "concat-len check asserted an axiom on " << mk_pp(e_it->get_owner(), m) << std::endl;); + found_inconsistency = true; + } + e_it = e_it->get_next(); + } while (e_it != e_root); + + // now grab any two distinct elements from the EQC and call new_eq_check() on them + enode * e1 = e; + enode * e2 = e1->get_next(); + if (e1 != e2) { + TRACE("t_str_detail", tout << "deferred new_eq_check() over EQC of " << mk_pp(e1->get_owner(), m) << " and " << mk_pp(e2->get_owner(), m) << std::endl;); + bool result = new_eq_check(e1->get_owner(), e2->get_owner()); + if (!result) { + TRACE("t_str_detail", tout << "new_eq_check found inconsistencies" << std::endl;); + found_inconsistency = true; + } + } + } + } + + if (found_inconsistency) { + TRACE("t_str", tout << "Found inconsistency in final check! Returning to search." << std::endl;); + return FC_CONTINUE; + } else { + TRACE("t_str", tout << "Deferred consistency check passed. Continuing in final check." << std::endl;); + } + } + // run dependence analysis to find free string variables std::map varAppearInAssign; std::map freeVar_map; diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 527753b73..60a1d70e2 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -138,6 +138,13 @@ namespace smt { */ bool opt_DisableIntegerTheoryIntegration; + /* + * If DeferEQCConsistencyCheck is set to true, + * expensive calls to new_eq_check() will be deferred until final check, + * at which time the consistency of *all* string equivalence classes will be validated. + */ + bool opt_DeferEQCConsistencyCheck; + bool search_started; arith_util m_autil; str_util m_strutil; @@ -334,7 +341,7 @@ namespace smt { bool can_two_nodes_eq(expr * n1, expr * n2); bool can_concat_eq_str(expr * concat, std::string str); bool can_concat_eq_concat(expr * concat1, expr * concat2); - void check_concat_len_in_eqc(expr * concat); + bool check_concat_len_in_eqc(expr * concat); bool check_length_consistency(expr * n1, expr * n2); bool check_length_const_string(expr * n1, expr * constStr); bool check_length_eq_var_concat(expr * n1, expr * n2); From 7b3203b48e2f53775e08921acb8f6792f4579623 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sun, 21 Aug 2016 00:30:29 -0400 Subject: [PATCH 192/410] disable aggressive length/value testing in theory_str, it seems to be detrimental --- src/smt/theory_str.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index f53eefb6d..a0b06bcbf 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -28,8 +28,8 @@ namespace smt { theory_str::theory_str(ast_manager & m): theory(m.mk_family_id("str")), /* Options */ - opt_AggressiveLengthTesting(true), - opt_AggressiveValueTesting(true), + opt_AggressiveLengthTesting(false), + opt_AggressiveValueTesting(false), opt_EagerStringConstantLengthAssertions(true), opt_VerifyFinalCheckProgress(true), opt_LCMUnrollStep(2), From 2a199294a1bfd8511caa0ee0b421c2d2f65de1da Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sun, 21 Aug 2016 00:43:00 -0400 Subject: [PATCH 193/410] remove incorrect null pointer check from theory_str::gen_len_val_options_for_free_var everything that calls this method knows that it can legally return null --- src/smt/theory_str.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index a0b06bcbf..28420de26 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -8234,7 +8234,6 @@ expr * theory_str::gen_len_val_options_for_free_var(expr * freeVar, expr * lenTe TRACE("t_str", tout << "length is fixed; generating models for free var" << std::endl;); // length is fixed expr * valueAssert = gen_free_var_options(freeVar, effectiveLenInd, effectiveLenIndiStr, NULL, ""); - SASSERT(valueAssert != NULL); return valueAssert; } } // fVarLenCountMap.find(...) From 89d5f4ffb4a8d6e4b159c6c919964cffcb12754b Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sun, 21 Aug 2016 21:37:46 -0400 Subject: [PATCH 194/410] add compute_contains check to theory_str this may cause a crash in indexof-002.smt2 but I cannot reproduce it --- src/smt/theory_str.cpp | 341 +++++++++++++++++++++++++++++++++++++++-- src/smt/theory_str.h | 14 ++ 2 files changed, 346 insertions(+), 9 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 28420de26..90b0992d6 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -21,6 +21,7 @@ Revision History: #include"ast_pp.h" #include"ast_ll_pp.h" #include +#include #include"theory_arith.h" namespace smt { @@ -3102,15 +3103,14 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { int option = 0; int pos = 1; - expr_ref temp1_strAst(mk_concat(temp1, strAst), mgr); // TODO assert concat axioms? + expr_ref temp1_strAst(mk_concat(temp1, strAst), mgr); // m cuts y if (can_two_nodes_eq(y, temp1_strAst)) { if (!avoidLoopCut || !has_self_cut(m, y)) { // break down option 2-1 - // TODO or_item[option] = ctx.mk_eq_atom(xorFlag, mk_int(option)); - expr_ref x_temp1(mk_concat(x, temp1), mgr); // TODO assert concat axioms? + expr_ref x_temp1(mk_concat(x, temp1), mgr); and_item[pos++] = ctx.mk_eq_atom(or_item[option], ctx.mk_eq_atom(m, x_temp1)); and_item[pos++] = ctx.mk_eq_atom(or_item[option], ctx.mk_eq_atom(y, temp1_strAst)); @@ -3131,7 +3131,7 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { std::string part1Str = strValue.substr(0, i); std::string part2Str = strValue.substr(i, strValue.size() - i); expr_ref prefixStr(m_strutil.mk_string(part1Str), mgr); - expr_ref x_concat(mk_concat(m, prefixStr), mgr); // TODO concat axioms? + expr_ref x_concat(mk_concat(m, prefixStr), mgr); expr_ref cropStr(m_strutil.mk_string(part2Str), mgr); if (can_two_nodes_eq(x, x_concat) && can_two_nodes_eq(y, cropStr)) { // break down option 2-2 @@ -4866,6 +4866,332 @@ void theory_str::check_contain_in_new_eq(expr * n1, expr * n2) { } } +expr * theory_str::dealias_node(expr * node, std::map & varAliasMap, std::map & concatAliasMap) { + if (variable_set.find(node) != variable_set.end()) { + return get_alias_index_ast(varAliasMap, node); + } else if (is_concat(to_app(node))) { + return get_alias_index_ast(concatAliasMap, node); + } + return node; +} + +void theory_str::get_grounded_concats(expr* node, std::map & varAliasMap, + std::map & concatAliasMap, std::map & varConstMap, + std::map & concatConstMap, std::map > & varEqConcatMap, + std::map, std::set > > & groundedMap) { + if (is_Unroll(to_app(node))) { + return; + } + // ************************************************** + // first deAlias the node if it is a var or concat + // ************************************************** + node = dealias_node(node, varAliasMap, concatAliasMap); + + if (groundedMap.find(node) != groundedMap.end()) { + return; + } + + // haven't computed grounded concats for "node" (de-aliased) + // --------------------------------------------------------- + + context & ctx = get_context(); + ast_manager & m = get_manager(); + + // const strings: node is de-aliased + if (m_strutil.is_string(node)) { + std::vector concatNodes; + concatNodes.push_back(node); + groundedMap[node][concatNodes].clear(); // no condition + } + // Concat functions + else if (is_concat(to_app(node))) { + // if "node" equals to a constant string, thenjust push the constant into the concat vector + // Again "node" has been de-aliased at the very beginning + if (concatConstMap.find(node) != concatConstMap.end()) { + std::vector concatNodes; + concatNodes.push_back(concatConstMap[node]); + groundedMap[node][concatNodes].clear(); + groundedMap[node][concatNodes].insert(ctx.mk_eq_atom(node, concatConstMap[node])); + } + // node doesn't have eq constant value. Process its children. + else { + // merge arg0 and arg1 + expr * arg0 = to_app(node)->get_arg(0); + expr * arg1 = to_app(node)->get_arg(1); + expr * arg0DeAlias = dealias_node(arg0, varAliasMap, concatAliasMap); + expr * arg1DeAlias = dealias_node(arg1, varAliasMap, concatAliasMap); + get_grounded_concats(arg0DeAlias, varAliasMap, concatAliasMap, varConstMap, concatConstMap, varEqConcatMap, groundedMap); + get_grounded_concats(arg1DeAlias, varAliasMap, concatAliasMap, varConstMap, concatConstMap, varEqConcatMap, groundedMap); + + std::map, std::set >::iterator arg0_grdItor = groundedMap[arg0DeAlias].begin(); + std::map, std::set >::iterator arg1_grdItor; + for (; arg0_grdItor != groundedMap[arg0DeAlias].end(); arg0_grdItor++) { + arg1_grdItor = groundedMap[arg1DeAlias].begin(); + for (; arg1_grdItor != groundedMap[arg1DeAlias].end(); arg1_grdItor++) { + std::vector ndVec; + ndVec.insert(ndVec.end(), arg0_grdItor->first.begin(), arg0_grdItor->first.end()); + int arg0VecSize = arg0_grdItor->first.size(); + int arg1VecSize = arg1_grdItor->first.size(); + if (arg0VecSize > 0 && arg1VecSize > 0 && m_strutil.is_string(arg0_grdItor->first[arg0VecSize - 1]) && m_strutil.is_string(arg1_grdItor->first[0])) { + ndVec.pop_back(); + ndVec.push_back(mk_concat(arg0_grdItor->first[arg0VecSize - 1], arg1_grdItor->first[0])); + for (int i = 1; i < arg1VecSize; i++) { + ndVec.push_back(arg1_grdItor->first[i]); + } + } else { + ndVec.insert(ndVec.end(), arg1_grdItor->first.begin(), arg1_grdItor->first.end()); + } + // only insert if we don't know "node = concat(ndVec)" since one set of condition leads to this is enough + if (groundedMap[node].find(ndVec) == groundedMap[node].end()) { + groundedMap[node][ndVec]; + if (arg0 != arg0DeAlias) { + groundedMap[node][ndVec].insert(ctx.mk_eq_atom(arg0, arg0DeAlias)); + } + groundedMap[node][ndVec].insert(arg0_grdItor->second.begin(), arg0_grdItor->second.end()); + + if (arg1 != arg1DeAlias) { + groundedMap[node][ndVec].insert(ctx.mk_eq_atom(arg1, arg1DeAlias)); + } + groundedMap[node][ndVec].insert(arg1_grdItor->second.begin(), arg1_grdItor->second.end()); + } + } + } + } + } + // string variables + else if (variable_set.find(node) != variable_set.end()) { + // deAliasedVar = Constant + if (varConstMap.find(node) != varConstMap.end()) { + std::vector concatNodes; + concatNodes.push_back(varConstMap[node]); + groundedMap[node][concatNodes].clear(); + groundedMap[node][concatNodes].insert(ctx.mk_eq_atom(node, varConstMap[node])); + } + // deAliasedVar = someConcat + else if (varEqConcatMap.find(node) != varEqConcatMap.end()) { + expr * eqConcat = varEqConcatMap[node].begin()->first; + expr * deAliasedEqConcat = dealias_node(eqConcat, varAliasMap, concatAliasMap); + get_grounded_concats(deAliasedEqConcat, varAliasMap, concatAliasMap, varConstMap, concatConstMap, varEqConcatMap, groundedMap); + + std::map, std::set >::iterator grdItor = groundedMap[deAliasedEqConcat].begin(); + for (; grdItor != groundedMap[deAliasedEqConcat].end(); grdItor++) { + std::vector ndVec; + ndVec.insert(ndVec.end(), grdItor->first.begin(), grdItor->first.end()); + // only insert if we don't know "node = concat(ndVec)" since one set of condition leads to this is enough + if (groundedMap[node].find(ndVec) == groundedMap[node].end()) { + // condition: node = deAliasedEqConcat + groundedMap[node][ndVec].insert(ctx.mk_eq_atom(node, deAliasedEqConcat)); + // appending conditions for "deAliasedEqConcat = CONCAT(ndVec)" + groundedMap[node][ndVec].insert(grdItor->second.begin(), grdItor->second.end()); + } + } + } + // node (has been de-aliased) != constant && node (has been de-aliased) != any concat + // just push in the deAliasedVar + else { + std::vector concatNodes; + concatNodes.push_back(node); + groundedMap[node][concatNodes]; // TODO ??? + } + } +} + +void theory_str::print_grounded_concat(expr * node, std::map, std::set > > & groundedMap) { + ast_manager & m = get_manager(); + TRACE("t_str_detail", tout << mk_pp(node, m) << std::endl;); + if (groundedMap.find(node) != groundedMap.end()) { + std::map, std::set >::iterator itor = groundedMap[node].begin(); + for (; itor != groundedMap[node].end(); ++itor) { + TRACE("t_str_detail", + tout << "\t[grounded] "; + std::vector::const_iterator vIt = itor->first.begin(); + for (; vIt != itor->first.end(); ++vIt) { + tout << mk_pp(*vIt, m) << ", "; + } + tout << std::endl; + tout << "\t[condition] "; + std::set::iterator sIt = itor->second.begin(); + for (; sIt != itor->second.end(); sIt++) { + tout << mk_pp(*sIt, m) << ", "; + } + tout << std::endl; + ); + } + } else { + TRACE("t_str_detail", tout << "not found" << std::endl;); + } +} + +bool theory_str::is_partial_in_grounded_concat(const std::vector & strVec, const std::vector & subStrVec) { + int strCnt = strVec.size(); + int subStrCnt = subStrVec.size(); + + if (strCnt == 0 || subStrCnt == 0) { + return false; + } + + // The assumption is that all consecutive constant strings are merged into one node + if (strCnt < subStrCnt) { + return false; + } + + if (subStrCnt == 1) { + if (m_strutil.is_string(subStrVec[0])) { + std::string subStrVal = m_strutil.get_string_constant_value(subStrVec[0]); + for (int i = 0; i < strCnt; i++) { + if (m_strutil.is_string(strVec[i])) { + std::string strVal = m_strutil.get_string_constant_value(strVec[i]); + if (strVal.find(subStrVal) != std::string::npos) { + return true; + } + } + } + } else { + for (int i = 0; i < strCnt; i++) { + if (strVec[i] == subStrVec[0]) { + return true; + } + } + } + return false; + } else { + for (int i = 0; i <= (strCnt - subStrCnt); i++) { + // The first node in subStrVect should be + // * constant: a suffix of a note in strVec[i] + // * variable: + bool firstNodesOK = true; + if (m_strutil.is_string(subStrVec[0])) { + std::string subStrHeadVal = m_strutil.get_string_constant_value(subStrVec[0]); + if (m_strutil.is_string(strVec[i])) { + std::string strHeadVal = m_strutil.get_string_constant_value(strVec[i]); + if (strHeadVal.size() >= subStrHeadVal.size()) { + std::string suffix = strHeadVal.substr(strHeadVal.size() - subStrHeadVal.size(), subStrHeadVal.size()); + if (suffix != subStrHeadVal) { + firstNodesOK = false; + } + } else { + firstNodesOK = false; + } + } else { + if (subStrVec[0] != strVec[i]) { + firstNodesOK = false; + } + } + } + if (!firstNodesOK) { + continue; + } + + // middle nodes + bool midNodesOK = true; + for (int j = 1; j < subStrCnt - 1; j++) { + if (subStrVec[j] != strVec[i + j]) { + midNodesOK = false; + break; + } + } + if (!midNodesOK) { + continue; + } + + // tail nodes + int tailIdx = i + subStrCnt - 1; + if (m_strutil.is_string(subStrVec[subStrCnt - 1])) { + std::string subStrTailVal = m_strutil.get_string_constant_value(subStrVec[subStrCnt - 1]); + if (m_strutil.is_string(strVec[tailIdx])) { + std::string strTailVal = m_strutil.get_string_constant_value(strVec[tailIdx]); + if (strTailVal.size() >= subStrTailVal.size()) { + std::string prefix = strTailVal.substr(0, subStrTailVal.size()); + if (prefix == subStrTailVal) { + return true; + } else { + continue; + } + } else { + continue; + } + } + } else { + if (subStrVec[subStrCnt - 1] == strVec[tailIdx]) { + return true; + } else { + continue; + } + } + } + return false; + } +} + +void theory_str::check_subsequence(expr* str, expr* strDeAlias, expr* subStr, expr* subStrDeAlias, expr* boolVar, + std::map, std::set > > & groundedMap) { + + context & ctx = get_context(); + ast_manager & m = get_manager(); + std::map, std::set >::iterator itorStr = groundedMap[strDeAlias].begin(); + std::map, std::set >::iterator itorSubStr; + for (; itorStr != groundedMap[strDeAlias].end(); itorStr++) { + itorSubStr = groundedMap[subStrDeAlias].begin(); + for (; itorSubStr != groundedMap[subStrDeAlias].end(); itorSubStr++) { + bool contain = is_partial_in_grounded_concat(itorStr->first, itorSubStr->first); + if (contain) { + expr_ref_vector litems(m); + if (str != strDeAlias) { + litems.push_back(ctx.mk_eq_atom(str, strDeAlias)); + } + if (subStr != subStrDeAlias) { + litems.push_back(ctx.mk_eq_atom(subStr, subStrDeAlias)); + } + + //litems.insert(itorStr->second.begin(), itorStr->second.end()); + //litems.insert(itorSubStr->second.begin(), itorSubStr->second.end()); + for (std::set::const_iterator i1 = itorStr->second.begin(); + i1 != itorStr->second.end(); ++i1) { + litems.push_back(*i1); + } + for (std::set::const_iterator i1 = itorSubStr->second.begin(); + i1 != itorSubStr->second.end(); ++i1) { + litems.push_back(*i1); + } + + expr_ref implyR(boolVar, m); + + if (litems.empty()) { + assert_axiom(implyR); + } else { + expr_ref implyL(mk_and(litems), m); + assert_implication(implyL, implyR); + } + + } + } + } +} + +void theory_str::compute_contains(std::map & varAliasMap, + std::map & concatAliasMap, std::map & varConstMap, + std::map & concatConstMap, std::map > & varEqConcatMap) { + std::map, std::set > > groundedMap; + theory_str_contain_pair_bool_map_t::iterator containItor = contain_pair_bool_map.begin(); + for (; containItor != contain_pair_bool_map.end(); containItor++) { + expr* containBoolVar = containItor->get_value(); + expr* str = containItor->get_key1(); + expr* subStr = containItor->get_key2(); + + expr* strDeAlias = dealias_node(str, varAliasMap, concatAliasMap); + expr* subStrDeAlias = dealias_node(subStr, varAliasMap, concatAliasMap); + + get_grounded_concats(strDeAlias, varAliasMap, concatAliasMap, varConstMap, concatConstMap, varEqConcatMap, groundedMap); + get_grounded_concats(subStrDeAlias, varAliasMap, concatAliasMap, varConstMap, concatConstMap, varEqConcatMap, groundedMap); + + // debugging + print_grounded_concat(strDeAlias, groundedMap); + print_grounded_concat(subStrDeAlias, groundedMap); + + check_subsequence(str, strDeAlias, subStr, subStrDeAlias, containBoolVar, groundedMap); + } +} + bool theory_str::can_concat_eq_str(expr * concat, std::string str) { // TODO this method could use some traces and debugging info int strLen = str.length(); @@ -6668,12 +6994,9 @@ int theory_str::ctx_dep_analysis(std::map & strVarMap, std::map 0) { - NOT_IMPLEMENTED_YET(); - compute_contains(aliasIndexMap, concats_eq_Index_map, var_eq_constStr_map, concat_eq_constStr_map, var_eq_concat_map); + if (!contain_pair_bool_map.empty()) { + compute_contains(aliasIndexMap, concats_eq_index_map, var_eq_constStr_map, concat_eq_constStr_map, var_eq_concat_map); } - */ // step 4: dependence analysis diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 60a1d70e2..ba132a579 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -25,6 +25,7 @@ Revision History: #include"arith_decl_plugin.h" #include #include +#include #include"str_rewriter.h" namespace smt { @@ -353,6 +354,19 @@ namespace smt { void check_contain_by_substr(expr * varNode, expr_ref_vector & willEqClass); void check_contain_by_eq_nodes(expr * n1, expr * n2); bool in_contain_idx_map(expr * n); + // TODO refactor these methods to use expr_ref_vector instead of std::vector + void compute_contains(std::map & varAliasMap, + std::map & concatAliasMap, std::map & varConstMap, + std::map & concatConstMap, std::map > & varEqConcatMap); + expr * dealias_node(expr * node, std::map & varAliasMap, std::map & concatAliasMap); + void get_grounded_concats(expr* node, std::map & varAliasMap, + std::map & concatAliasMap, std::map & varConstMap, + std::map & concatConstMap, std::map > & varEqConcatMap, + std::map, std::set > > & groundedMap); + void print_grounded_concat(expr * node, std::map, std::set > > & groundedMap); + void check_subsequence(expr* str, expr* strDeAlias, expr* subStr, expr* subStrDeAlias, expr* boolVar, + std::map, std::set > > & groundedMap); + bool is_partial_in_grounded_concat(const std::vector & strVec, const std::vector & subStrVec); void get_nodes_in_concat(expr * node, ptr_vector & nodeList); expr * simplify_concat(expr * node); From 5e22bc57c8a484d0891340ab2b095bbc9648cc4a Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 31 Aug 2016 19:19:23 -0400 Subject: [PATCH 195/410] theory_str cleanup --- src/smt/theory_str.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 90b0992d6..9c69f9716 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -3401,7 +3401,7 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { std::string part2Str = strValue.substr(i, strValue.size() - i); expr_ref cropStr(m_strutil.mk_string(part1Str), mgr); expr_ref suffixStr(m_strutil.mk_string(part2Str), mgr); - expr_ref y_concat(mk_concat(suffixStr, n), mgr); // TODO concat axioms? + expr_ref y_concat(mk_concat(suffixStr, n), mgr); if (can_two_nodes_eq(x, cropStr) && can_two_nodes_eq(y, y_concat)) { // break down option 3-1 @@ -6435,6 +6435,7 @@ void theory_str::push_scope_eh() { void theory_str::pop_scope_eh(unsigned num_scopes) { sLevel -= num_scopes; TRACE("t_str", tout << "pop " << num_scopes << " to " << sLevel << std::endl;); + TRACE("t_str_dump_assign_on_scope_change", dump_assignments();); std::map >::iterator varItor = cut_var_map.begin(); while (varItor != cut_var_map.end()) { From f9b4f21683c19cf629dbc7f6d49793788c87f696 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 31 Aug 2016 19:22:04 -0400 Subject: [PATCH 196/410] add rewrite for theory_str rewriter RegexPlus fixes regex-013.smt2 --- src/ast/rewriter/str_rewriter.cpp | 12 ++++++++++++ src/ast/rewriter/str_rewriter.h | 1 + 2 files changed, 13 insertions(+) diff --git a/src/ast/rewriter/str_rewriter.cpp b/src/ast/rewriter/str_rewriter.cpp index c644ecd46..015898a64 100644 --- a/src/ast/rewriter/str_rewriter.cpp +++ b/src/ast/rewriter/str_rewriter.cpp @@ -425,6 +425,15 @@ br_status str_rewriter::mk_re_RegexIn(expr * str, expr * re, expr_ref & result) return BR_FAILED; } +br_status str_rewriter::mk_re_RegexStar(expr * re, expr_ref & result) { + if (m_strutil.is_re_RegexStar(re)) { + result = re; + return BR_REWRITE_FULL; + } else { + return BR_FAILED; + } +} + br_status str_rewriter::mk_re_RegexPlus(expr * re, expr_ref & result) { /* * Two optimizations are possible if we inspect 're'. @@ -523,6 +532,9 @@ br_status str_rewriter::mk_app_core(func_decl * f, unsigned num_args, expr * con case OP_RE_REGEXPLUS: SASSERT(num_args == 1); return mk_re_RegexPlus(args[0], result); + case OP_RE_REGEXSTAR: + SASSERT(num_args == 1); + return mk_re_RegexStar(args[0], result); case OP_RE_REGEXCHARRANGE: SASSERT(num_args == 2); return mk_re_RegexCharRange(args[0], args[1], result); diff --git a/src/ast/rewriter/str_rewriter.h b/src/ast/rewriter/str_rewriter.h index c64d086f9..d147e82e8 100644 --- a/src/ast/rewriter/str_rewriter.h +++ b/src/ast/rewriter/str_rewriter.h @@ -57,6 +57,7 @@ public: br_status mk_re_Str2Reg(expr * str, expr_ref & result); br_status mk_re_RegexIn(expr * str, expr * re, expr_ref & result); br_status mk_re_RegexPlus(expr * re, expr_ref & result); + br_status mk_re_RegexStar(expr * re, expr_ref & result); br_status mk_re_RegexCharRange(expr * start, expr * end, expr_ref & result); bool reduce_eq(expr * l, expr * r, expr_ref_vector & lhs, expr_ref_vector & rhs, bool & change); From d3062a8eff28019af492691c6030235ffe18cff1 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Fri, 2 Sep 2016 18:23:41 -0400 Subject: [PATCH 197/410] omit out-of-scope length testers from axiom premise in theory_str::gen_len_test_options this fixes a regression in charAt-007.smt2 --- src/smt/theory_str.cpp | 52 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 43 insertions(+), 9 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 9c69f9716..7faca9922 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -22,6 +22,7 @@ Revision History: #include"ast_ll_pp.h" #include #include +#include #include"theory_arith.h" namespace smt { @@ -8375,7 +8376,14 @@ expr * theory_str::gen_len_test_options(expr * freeVar, expr * indicator, int tr expr_ref_vector and_items_LHS(m); expr_ref moreAst(m_strutil.mk_string("more"), m); for (int i = 0; i < testerCount; ++i) { - and_items_LHS.push_back(ctx.mk_eq_atom(fvar_lenTester_map[freeVar][i], moreAst)); + expr * indicator = fvar_lenTester_map[freeVar][i]; + if (internal_variable_set.find(indicator) == internal_variable_set.end()) { + TRACE("t_str_detail", tout << "indicator " << mk_pp(indicator, m) << " out of scope; continuing" << std::endl;); + continue; + } else { + TRACE("t_str_detail", tout << "indicator " << mk_pp(indicator, m) << " in scope" << std::endl;); + and_items_LHS.push_back(ctx.mk_eq_atom(indicator, moreAst)); + } } expr_ref assertL(mk_and(and_items_LHS), m); SASSERT(assertL); @@ -8591,6 +8599,12 @@ void theory_str::get_var_in_eqc(expr * n, std::set & varSet) { } while (eqcNode != n); } +bool cmpvarnames(expr * lhs, expr * rhs) { + symbol lhs_name = to_app(lhs)->get_decl()->get_name(); + symbol rhs_name = to_app(rhs)->get_decl()->get_name(); + return lhs_name.str() < rhs_name.str(); +} + void theory_str::process_free_var(std::map & freeVar_map) { context & ctx = get_context(); ast_manager & m = get_manager(); @@ -8664,14 +8678,34 @@ void theory_str::process_free_var(std::map & freeVar_map) { // TODO here's a great place for debugging info - for(std::set::iterator itor1 = leafVarSet.begin(); - itor1 != leafVarSet.end(); ++itor1) { - expr * toAssert = gen_len_val_options_for_free_var(*itor1, NULL, ""); - // gen_len_val_options_for_free_var() can legally return NULL, - // as methods that it calls may assert their own axioms instead. - if (toAssert != NULL) { - assert_axiom(toAssert); - } + // testing: iterate over leafVarSet deterministically + if (false) { + // *** TESTING CODE + std::vector sortedLeafVarSet; + for (std::set::iterator itor1 = leafVarSet.begin(); itor1 != leafVarSet.end(); ++itor1) { + sortedLeafVarSet.push_back(*itor1); + } + std::sort(sortedLeafVarSet.begin(), sortedLeafVarSet.end(), cmpvarnames); + for(std::vector::iterator itor1 = sortedLeafVarSet.begin(); + itor1 != sortedLeafVarSet.end(); ++itor1) { + expr * toAssert = gen_len_val_options_for_free_var(*itor1, NULL, ""); + // gen_len_val_options_for_free_var() can legally return NULL, + // as methods that it calls may assert their own axioms instead. + if (toAssert != NULL) { + assert_axiom(toAssert); + } + } + } else { + // *** CODE FROM BEFORE + for(std::set::iterator itor1 = leafVarSet.begin(); + itor1 != leafVarSet.end(); ++itor1) { + expr * toAssert = gen_len_val_options_for_free_var(*itor1, NULL, ""); + // gen_len_val_options_for_free_var() can legally return NULL, + // as methods that it calls may assert their own axioms instead. + if (toAssert != NULL) { + assert_axiom(toAssert); + } + } } for (std::map >::iterator mItor = aloneVars.begin(); From 2b8f165cc47e197dfc7a6aef0849c2af0c067018 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Fri, 2 Sep 2016 19:04:20 -0400 Subject: [PATCH 198/410] patch UNSAT to UNKNOWN in cmd_context for theory_str --- src/smt/smt_context.cpp | 20 ++++++++++++++++++++ src/smt/theory_str.h | 2 ++ 2 files changed, 22 insertions(+) diff --git a/src/smt/smt_context.cpp b/src/smt/smt_context.cpp index 046e2028e..251cf3b9b 100644 --- a/src/smt/smt_context.cpp +++ b/src/smt/smt_context.cpp @@ -37,6 +37,7 @@ Revision History: #include"model_pp.h" #include"ast_smt2_pp.h" #include"ast_translation.h" +#include"theory_str.h" namespace smt { @@ -3086,6 +3087,25 @@ namespace smt { if (r == l_true && get_cancel_flag()) { r = l_undef; } + + // PATCH for theory_str: + // UNSAT + overlapping variables => UNKNOWN + if (r == l_false) { + ptr_vector::iterator it = m_theory_set.begin(); + ptr_vector::iterator end = m_theory_set.end(); + for (; it != end; ++it) { + theory * th = *it; + if (strcmp(th->get_name(), "strings") == 0) { + theory_str * str = (theory_str*)th; + if (str->overlapping_variables_detected()) { + TRACE("t_str", tout << "WARNING: overlapping variables detected, UNSAT changed to UNKNOWN!" << std::endl;); + r = l_undef; + } + break; + } + } + } + return r; } diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index ba132a579..1fad18293 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -457,6 +457,8 @@ namespace smt { virtual char const * get_name() const { return "strings"; } virtual void display(std::ostream & out) const; + + bool overlapping_variables_detected() const { return loopDetected; } protected: virtual bool internalize_atom(app * atom, bool gate_ctx); virtual bool internalize_term(app * term); From 347f441517c2f6ca2d44eec295361d376b52baa8 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Fri, 2 Sep 2016 20:44:14 -0400 Subject: [PATCH 199/410] add a check for variable scope to theory_str --- src/smt/theory_str.cpp | 52 ++++++++++++++++++++++++++++++++++++++++++ src/smt/theory_str.h | 12 ++++++++++ 2 files changed, 64 insertions(+) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 7faca9922..f19553864 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -38,6 +38,7 @@ theory_str::theory_str(ast_manager & m): opt_NoQuickReturn_IntegerTheory(false), opt_DisableIntegerTheoryIntegration(false), opt_DeferEQCConsistencyCheck(true), + opt_CheckVariableScope(true), /* Internal setup */ search_started(false), m_autil(m), @@ -6433,6 +6434,54 @@ void theory_str::push_scope_eh() { TRACE("t_str_dump_assign_on_scope_change", dump_assignments();); } +void theory_str::recursive_check_variable_scope(expr * ex) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + if (is_app(ex)) { + app * a = to_app(ex); + if (a->get_num_args() == 0) { + // we only care about string variables + sort * s = m.get_sort(ex); + sort * string_sort = m.mk_sort(get_family_id(), STRING_SORT); + if (s != string_sort) { + return; + } + // base case: string constant / var + if (m_strutil.is_string(a)) { + return; + } else { + // assume var + if (variable_set.find(ex) == variable_set.end() + && internal_variable_set.find(ex) == internal_variable_set.end()) { + TRACE("t_str_detail", tout << "WARNING: possible reference to out-of-scope variable " << mk_pp(ex, m) << std::endl;); + } + } + } else { + for (unsigned i = 0; i < a->get_num_args(); ++i) { + recursive_check_variable_scope(a->get_arg(i)); + } + } + } +} + +void theory_str::check_variable_scope() { + if (!opt_CheckVariableScope) { + return; + } + TRACE("t_str_detail", tout << "checking scopes of variables in the current assignment" << std::endl;); + + context & ctx = get_context(); + ast_manager & m = get_manager(); + + expr_ref_vector assignments(m); + ctx.get_assignments(assignments); + for (expr_ref_vector::iterator i = assignments.begin(); i != assignments.end(); ++i) { + expr * ex = *i; + recursive_check_variable_scope(ex); + } +} + void theory_str::pop_scope_eh(unsigned num_scopes) { sLevel -= num_scopes; TRACE("t_str", tout << "pop " << num_scopes << " to " << sLevel << std::endl;); @@ -6487,6 +6536,8 @@ void theory_str::pop_scope_eh(unsigned num_scopes) { m_basicstr_axiom_todo = new_m_basicstr; theory::pop_scope_eh(num_scopes); + + check_variable_scope(); } void theory_str::dump_assignments() { @@ -7381,6 +7432,7 @@ final_check_status theory_str::final_check_eh() { TRACE("t_str", tout << "final check" << std::endl;); TRACE("t_str_dump_assign", dump_assignments();); + check_variable_scope(); if (opt_DeferEQCConsistencyCheck) { TRACE("t_str_detail", tout << "performing deferred EQC consistency check" << std::endl;); diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 1fad18293..8a0a2ea81 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -146,6 +146,14 @@ namespace smt { */ bool opt_DeferEQCConsistencyCheck; + /* + * If CheckVariableScope is set to true, + * pop_scope_eh() and final_check_eh() will run extra checks + * to determine whether the current assignment + * contains references to any internal variables that are no longer in scope. + */ + bool opt_CheckVariableScope; + bool search_started; arith_util m_autil; str_util m_strutil; @@ -451,6 +459,10 @@ namespace smt { void dump_assignments(); void initialize_charset(); + + void check_variable_scope(); + void recursive_check_variable_scope(expr * ex); + public: theory_str(ast_manager & m); virtual ~theory_str(); From 7b34efada78d48d2d1d55896662092605d57e5d1 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sun, 4 Sep 2016 18:48:15 -0400 Subject: [PATCH 200/410] add aggressive unroll test option to theory_str --- src/smt/theory_str.cpp | 18 ++++++++++++++++++ src/smt/theory_str.h | 6 ++++++ 2 files changed, 24 insertions(+) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index f19553864..749b9c036 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -32,6 +32,7 @@ theory_str::theory_str(ast_manager & m): /* Options */ opt_AggressiveLengthTesting(false), opt_AggressiveValueTesting(false), + opt_AggressiveUnrollTesting(true), opt_EagerStringConstantLengthAssertions(true), opt_VerifyFinalCheckProgress(true), opt_LCMUnrollStep(2), @@ -8265,6 +8266,7 @@ expr * theory_str::gen_unroll_conditional_options(expr * var, std::set & int tries = unroll_tries_map[var][unrolls].size(); for (int i = 0; i < tries; i++) { + // TODO possibly missing a scope check here expr * tester = unroll_tries_map[var][unrolls][i]; bool testerHasValue = false; expr * testerVal = get_eqc_value(tester, testerHasValue); @@ -8318,6 +8320,10 @@ expr * theory_str::gen_unroll_assign(expr * var, std::string lcmStr, expr * test TRACE("t_str_detail", tout << "entry: var = " << mk_pp(var, mgr) << ", lcmStr = " << lcmStr << ", l = " << l << ", h = " << h << std::endl;); + if (opt_AggressiveUnrollTesting) { + TRACE("t_str_detail", tout << "note: aggressive unroll testing is active" << std::endl;); + } + expr_ref_vector orItems(mgr); expr_ref_vector andItems(mgr); @@ -8325,6 +8331,12 @@ expr * theory_str::gen_unroll_assign(expr * var, std::string lcmStr, expr * test std::string iStr = int_to_string(i); expr_ref testerEqAst(ctx.mk_eq_atom(testerVar, m_strutil.mk_string(iStr)), mgr); TRACE("t_str_detail", tout << "testerEqAst = " << mk_pp(testerEqAst, mgr) << std::endl;); + if (opt_AggressiveUnrollTesting) { + literal l = mk_eq(testerVar, m_strutil.mk_string(iStr), false); + ctx.mark_as_relevant(l); + ctx.force_phase(l); + } + orItems.push_back(testerEqAst); std::string unrollStrInstance = get_unrolled_string(lcmStr, i); @@ -8338,6 +8350,12 @@ expr * theory_str::gen_unroll_assign(expr * var, std::string lcmStr, expr * test } expr_ref testerEqMore(ctx.mk_eq_atom(testerVar, m_strutil.mk_string("more")), mgr); TRACE("t_str_detail", tout << "testerEqMore = " << mk_pp(testerEqMore, mgr) << std::endl;); + if (opt_AggressiveUnrollTesting) { + literal l = mk_eq(testerVar, m_strutil.mk_string("more"), false); + ctx.mark_as_relevant(l); + ctx.force_phase(~l); + } + orItems.push_back(testerEqMore); int nextLowerLenBound = h * lcmStr.length(); expr_ref more2(ctx.mk_eq_atom(testerEqMore, diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 8a0a2ea81..6ce46abb4 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -96,6 +96,12 @@ namespace smt { */ bool opt_AggressiveValueTesting; + /* + * If AggressiveUnrollTesting is true, we manipulate the phase of regex unroll tester equalities + * to prioritize trying concrete unroll counts over choosing the "more" option. + */ + bool opt_AggressiveUnrollTesting; + /* * Setting EagerStringConstantLengthAssertions to true allows some methods, * in particular internalize_term(), to add From c83e39d3b8ab55ab0309bfc160ee951f244979e3 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Mon, 5 Sep 2016 17:45:10 -0400 Subject: [PATCH 201/410] fix incorrect axiom in theory_str for Contains check this partially fixes a regression in contains-034.smt2, which now is at least not a SAT-as-UNSAT --- src/smt/theory_str.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 749b9c036..421a45e57 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -4773,7 +4773,7 @@ void theory_str::check_contain_by_eq_nodes(expr * n1, expr * n2) { // key1.first = key2.first /\ containPairBoolMap[] // ==> (containPairBoolMap[key1] --> containPairBoolMap[key2]) // ------------ - expr_ref implR(ctx.mk_eq_atom(contain_pair_bool_map[key1], contain_pair_bool_map[key2]), m); + expr_ref implR(rewrite_implication(contain_pair_bool_map[key1], contain_pair_bool_map[key2]), m); assert_implication(mk_and(litems4), implR); } } From 82e07aae8c921eb6a0d552bf3e0604b38241b840 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Thu, 8 Sep 2016 19:55:08 -0400 Subject: [PATCH 202/410] disable deferred eqc check in theory_str --- src/smt/theory_str.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 421a45e57..d393f1cdb 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -38,7 +38,7 @@ theory_str::theory_str(ast_manager & m): opt_LCMUnrollStep(2), opt_NoQuickReturn_IntegerTheory(false), opt_DisableIntegerTheoryIntegration(false), - opt_DeferEQCConsistencyCheck(true), + opt_DeferEQCConsistencyCheck(false), opt_CheckVariableScope(true), /* Internal setup */ search_started(false), From 2c5569aa1f0f4881d6354af3ccc4b29284e6e949 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Mon, 12 Sep 2016 15:43:58 -0400 Subject: [PATCH 203/410] change cut_var_map to obj_map --- src/smt/theory_str.cpp | 41 +++++++++++++++++++++++++++-------------- src/smt/theory_str.h | 2 +- 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index d393f1cdb..511585fa8 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -299,10 +299,10 @@ static void cut_vars_map_copy(std::map & dest, std::map } bool theory_str::has_self_cut(expr * n1, expr * n2) { - if (cut_var_map.find(n1) == cut_var_map.end()) { + if (!cut_var_map.contains(n1)) { return false; } - if (cut_var_map.find(n2) == cut_var_map.end()) { + if (!cut_var_map.contains(n2)) { return false; } if (cut_var_map[n1].empty() || cut_var_map[n2].empty()) { @@ -322,10 +322,11 @@ void theory_str::add_cut_info_one_node(expr * baseNode, int slevel, expr * node) // crash avoidance? m_trail.push_back(baseNode); m_trail.push_back(node); - if (cut_var_map.find(baseNode) == cut_var_map.end()) { + if (!cut_var_map.contains(baseNode)) { T_cut * varInfo = alloc(T_cut); varInfo->level = slevel; varInfo->vars[node] = 1; + cut_var_map.insert(baseNode, std::stack()); cut_var_map[baseNode].push(varInfo); TRACE("t_str_cut_var_map", tout << "add var info for baseNode=" << mk_pp(baseNode, get_manager()) << ", node=" << mk_pp(node, get_manager()) << std::endl;); } else { @@ -357,7 +358,7 @@ void theory_str::add_cut_info_merge(expr * destNode, int slevel, expr * srcNode) // crash avoidance? m_trail.push_back(destNode); m_trail.push_back(srcNode); - if (cut_var_map.find(srcNode) == cut_var_map.end()) { + if (!cut_var_map.contains(srcNode)) { get_manager().raise_exception("illegal state in add_cut_info_merge(): cut_var_map doesn't contain srcNode"); } @@ -365,10 +366,11 @@ void theory_str::add_cut_info_merge(expr * destNode, int slevel, expr * srcNode) get_manager().raise_exception("illegal state in add_cut_info_merge(): cut_var_map[srcNode] is empty"); } - if (cut_var_map.find(destNode) == cut_var_map.end()) { + if (!cut_var_map.contains(destNode)) { T_cut * varInfo = alloc(T_cut); varInfo->level = slevel; cut_vars_map_copy(varInfo->vars, cut_var_map[srcNode].top()->vars); + cut_var_map.insert(destNode, std::stack()); cut_var_map[destNode].push(varInfo); TRACE("t_str_cut_var_map", tout << "merge var info for destNode=" << mk_pp(destNode, get_manager()) << ", srcNode=" << mk_pp(srcNode, get_manager()) << std::endl;); } else { @@ -389,7 +391,7 @@ void theory_str::add_cut_info_merge(expr * destNode, int slevel, expr * srcNode) } void theory_str::check_and_init_cut_var(expr * node) { - if (cut_var_map.find(node) != cut_var_map.end()) { + if (cut_var_map.contains(node)) { return; } else if (!m_strutil.is_string(node)) { add_cut_info_one_node(node, -1, node); @@ -6488,18 +6490,29 @@ void theory_str::pop_scope_eh(unsigned num_scopes) { TRACE("t_str", tout << "pop " << num_scopes << " to " << sLevel << std::endl;); TRACE("t_str_dump_assign_on_scope_change", dump_assignments();); - std::map >::iterator varItor = cut_var_map.begin(); + // list of expr* to remove from cut_var_map + ptr_vector cutvarmap_removes; + + obj_map >::iterator varItor = cut_var_map.begin(); while (varItor != cut_var_map.end()) { - while ((varItor->second.size() > 0) && (varItor->second.top()->level != 0) && (varItor->second.top()->level >= sLevel)) { - T_cut * aCut = varItor->second.top(); - varItor->second.pop(); + std::stack & val = cut_var_map[varItor->m_key]; + while ((val.size() > 0) && (val.top()->level != 0) && (val.top()->level >= sLevel)) { + T_cut * aCut = val.top(); + val.pop(); // dealloc(aCut); // TODO find a safer way to do this, it is causing a crash } - if (varItor->second.size() == 0) { - cut_var_map.erase(varItor++); - } else { - varItor++; + if (val.size() == 0) { + cutvarmap_removes.insert(varItor->m_key); } + varItor++; + } + + if (!cutvarmap_removes.empty()) { + ptr_vector::iterator it = cutvarmap_removes.begin(); + for (; it != cutvarmap_removes.end(); ++it) { + expr * ex = *it; + cut_var_map.remove(ex); + } } // see if any internal variables went out of scope diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 6ce46abb4..bb2fc01d6 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -206,7 +206,7 @@ namespace smt { bool avoidLoopCut; bool loopDetected; - std::map > cut_var_map; + obj_map > cut_var_map; std::set variable_set; std::set internal_variable_set; From b3fddf47076faa65964735c5c64963dfe202af11 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Mon, 12 Sep 2016 16:41:35 -0400 Subject: [PATCH 204/410] performance optimization in theory_str::classify_ast_by_type --- src/smt/theory_str.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 511585fa8..ecc3b7247 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -6597,10 +6597,10 @@ void theory_str::classify_ast_by_type(expr * node, std::map & varMap expr * arg1Val = get_eqc_value(arg1, arg1HasEq); int canskip = 0; - if (arg0HasEq && arg0Val == m_strutil.mk_string("")) { + if (arg0HasEq && m_strutil.get_string_constant_value(arg0Val).empty()) { canskip = 1; } - if (canskip == 0 && arg1HasEq && arg1Val == m_strutil.mk_string("")) { + if (canskip == 0 && arg1HasEq && m_strutil.get_string_constant_value(arg1Val).empty()) { canskip = 1; } if (canskip == 0 && concatMap.find(node) == concatMap.end()) { From 015016c92b43e4b01eadd00e00e151d59952362a Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Mon, 12 Sep 2016 16:57:05 -0400 Subject: [PATCH 205/410] disable variable scope check if not tracing in theory_str --- src/smt/theory_str.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index ecc3b7247..29bedbe86 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -6472,6 +6472,11 @@ void theory_str::check_variable_scope() { if (!opt_CheckVariableScope) { return; } + + if (!is_trace_enabled("t_str_detail")) { + return; + } + TRACE("t_str_detail", tout << "checking scopes of variables in the current assignment" << std::endl;); context & ctx = get_context(); From ca71a20ab75e8d868bfafb28115a7597717ea7f2 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Mon, 12 Sep 2016 17:17:17 -0400 Subject: [PATCH 206/410] add caching to theory_str::mk_concat, WIP --- src/smt/theory_str.cpp | 15 +++++---------- src/smt/theory_str.h | 2 ++ 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 29bedbe86..f44cb8322 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -735,16 +735,14 @@ expr * theory_str::mk_concat(expr * n1, expr * n2) { // * expr * ast2 = mk_2_arg_app(ctx, td->Concat, n1, n2); // Z3 treats (ast1) and (ast2) as two different nodes. //------------------------------------------------------- - std::pair concatArgs(n1, n2); + expr * concatAst = NULL; - // TODO NEXT add cache lookups. I think we need to be more careful than just using std:: data structures here - /* - if (concat_astNode_map.find(concatArgs) == concat_astNode_map.end()) { - */ - if (true) { + + if (!concat_astNode_map.find(n1, n2, concatAst)) { expr * args[2] = {n1, n2}; concatAst = m.mk_app(get_id(), OP_STRCAT, 0, 0, 2, args); - // concat_astNode_map[concatArgs] = concatAst; + m_trail.push_back(concatAst); + concat_astNode_map.insert(n1, n2, concatAst); expr_ref concat_length(mk_strlen(concatAst), m); @@ -756,9 +754,6 @@ expr * theory_str::mk_concat(expr * n1, expr * n2) { } expr_ref lenAssert(ctx.mk_eq_atom(concat_length, m_autil.mk_add(items.size(), items.c_ptr())), m); assert_axiom(lenAssert); - } else { - // concatAst = concat_astNode_map[concatArgs]; - NOT_IMPLEMENTED_YET(); } return concatAst; } diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index bb2fc01d6..9f7d51a8f 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -250,6 +250,8 @@ namespace smt { std::map charSetLookupTable; int charSetSize; + obj_pair_map concat_astNode_map; + protected: void assert_axiom(expr * e); void assert_implication(expr * premise, expr * conclusion); From aea0032aa7d5c2ed5022517217383becf861db24 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 13 Sep 2016 18:01:45 -0400 Subject: [PATCH 207/410] manage our own union-find structure in theory_str concat-086.smt2 passes with this, for the first time ever --- src/smt/theory_str.cpp | 213 ++++++++++++++++++++++++++--------------- src/smt/theory_str.h | 17 ++++ 2 files changed, 154 insertions(+), 76 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index f44cb8322..939a63160 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -54,12 +54,15 @@ theory_str::theory_str(ast_manager & m): tmpValTestVarCount(0), avoidLoopCut(true), loopDetected(false), - contains_map(m) + contains_map(m), + m_find(*this), + m_trail_stack(*this) { initialize_charset(); } theory_str::~theory_str() { + m_trail_stack.reset(); } void theory_str::initialize_charset() { @@ -284,7 +287,7 @@ theory_var theory_str::mk_var(enode* n) { } else { theory_var v = theory::mk_var(n); - // m_find.mk_var(); + m_find.mk_var(); get_context().attach_th_var(n, this, v); get_context().mark_as_relevant(n); return v; @@ -1586,6 +1589,8 @@ void theory_str::attach_new_th_var(enode * n) { void theory_str::reset_eh() { TRACE("t_str", tout << "resetting" << std::endl;); + m_trail_stack.reset(); + m_basicstr_axiom_todo.reset(); m_str_eq_todo.reset(); m_concat_axiom_todo.reset(); @@ -1673,13 +1678,40 @@ bool theory_str::new_eq_check(expr * lhs, expr * rhs) { return true; } +// support for user_smt_theory-style EQC handling + +app * theory_str::get_ast(theory_var i) { + return get_enode(i)->get_owner(); +} + +theory_var theory_str::get_var(expr * n) const { + if (!is_app(n)) { + return null_theory_var; + } + context & ctx = get_context(); + if (ctx.e_internalized(to_app(n))) { + enode * e = ctx.get_enode(to_app(n)); + return e->get_th_var(get_id()); + } + return null_theory_var; +} + +// simulate Z3_theory_get_eqc_next() +expr * theory_str::get_eqc_next(expr * n) { + theory_var v = get_var(n); + if (v != null_theory_var) { + theory_var r = m_find.next(v); + return get_ast(r); + } + return n; +} + void theory_str::group_terms_by_eqc(expr * n, std::set & concats, std::set & vars, std::set & consts) { context & ctx = get_context(); - enode * nNode = ctx.get_enode(n); - enode * eqcNode = nNode; + expr * eqcNode = n; do { - app * ast = eqcNode->get_owner(); - if (is_concat(eqcNode)) { + app * ast = to_app(eqcNode); + if (is_concat(ast)) { expr * simConcat = simplify_concat(ast); if (simConcat != ast) { if (is_concat(to_app(simConcat))) { @@ -1694,13 +1726,13 @@ void theory_str::group_terms_by_eqc(expr * n, std::set & concats, std::se } else { concats.insert(simConcat); } - } else if (is_string(eqcNode)) { + } else if (is_string(ast)) { consts.insert(ast); } else { vars.insert(ast); } - eqcNode = eqcNode->get_next(); - } while (eqcNode != nNode); + eqcNode = get_eqc_next(eqcNode); + } while (eqcNode != n); } void theory_str::get_nodes_in_concat(expr * node, ptr_vector & nodeList) { @@ -3975,6 +4007,22 @@ expr * theory_str::get_eqc_value(expr * n, bool & hasEqcValue) { return n; } +// Simulate the behaviour of get_eqc_value() from Z3str2. +// We only check m_find for a string constant. + +expr * theory_str::z3str2_get_eqc_value(expr * n , bool & hasEqcValue) { + expr * curr = n; + do { + if (m_strutil.is_string(curr)) { + hasEqcValue = true; + return curr; + } + curr = get_eqc_next(curr); + } while (curr != n); + hasEqcValue = false; + return n; +} + // from Z3: theory_seq.cpp static theory_mi_arith* get_th_arith(context& ctx, theory_id afid, expr* e) { @@ -6110,106 +6158,107 @@ void theory_str::handle_equality(expr * lhs, expr * rhs) { instantiate_str_eq_length_axiom(ctx.get_enode(lhs), ctx.get_enode(rhs)); // group terms by equivalence class (groupNodeInEqc()) - // Previously we did the check between LHS and RHS equivalence classes. - // However these have since been merged. - // We start by asserting that the EQCs, in fact, really are merged. - if (!in_same_eqc(lhs, rhs)) { - TRACE("t_str", tout << "BUG: lhs and rhs not in same eqc in new_eq_eh(), loss of invariant!" << std::endl;); - UNREACHABLE(); - } - std::set eqc_concat; - std::set eqc_var; - std::set eqc_const; - group_terms_by_eqc(lhs, eqc_concat, eqc_var, eqc_const); + std::set eqc_concat_lhs; + std::set eqc_var_lhs; + std::set eqc_const_lhs; + group_terms_by_eqc(lhs, eqc_concat_lhs, eqc_var_lhs, eqc_const_lhs); + + std::set eqc_concat_rhs; + std::set eqc_var_rhs; + std::set eqc_const_rhs; + group_terms_by_eqc(rhs, eqc_concat_rhs, eqc_var_rhs, eqc_const_rhs); TRACE("t_str_detail", - tout << "eqc:" << std::endl; + tout << "lhs eqc:" << std::endl; tout << "Concats:" << std::endl; - for (std::set::iterator it = eqc_concat.begin(); it != eqc_concat.end(); ++it) { + for (std::set::iterator it = eqc_concat_lhs.begin(); it != eqc_concat_lhs.end(); ++it) { expr * ex = *it; tout << mk_ismt2_pp(ex, get_manager()) << std::endl; } tout << "Variables:" << std::endl; - for (std::set::iterator it = eqc_var.begin(); it != eqc_var.end(); ++it) { + for (std::set::iterator it = eqc_var_lhs.begin(); it != eqc_var_lhs.end(); ++it) { expr * ex = *it; tout << mk_ismt2_pp(ex, get_manager()) << std::endl; } tout << "Constants:" << std::endl; - for (std::set::iterator it = eqc_const.begin(); it != eqc_const.end(); ++it) { + for (std::set::iterator it = eqc_const_lhs.begin(); it != eqc_const_lhs.end(); ++it) { + expr * ex = *it; + tout << mk_ismt2_pp(ex, get_manager()) << std::endl; + } + + tout << "rhs eqc:" << std::endl; + tout << "Concats:" << std::endl; + for (std::set::iterator it = eqc_concat_rhs.begin(); it != eqc_concat_rhs.end(); ++it) { + expr * ex = *it; + tout << mk_ismt2_pp(ex, get_manager()) << std::endl; + } + tout << "Variables:" << std::endl; + for (std::set::iterator it = eqc_var_rhs.begin(); it != eqc_var_rhs.end(); ++it) { + expr * ex = *it; + tout << mk_ismt2_pp(ex, get_manager()) << std::endl; + } + tout << "Constants:" << std::endl; + for (std::set::iterator it = eqc_const_rhs.begin(); it != eqc_const_rhs.end(); ++it) { expr * ex = *it; tout << mk_ismt2_pp(ex, get_manager()) << std::endl; } ); // step 1: Concat == Concat - - // enhancement from Z3str2: all-pairs match over LHS and RHS wrt. other concats - if (eqc_concat.size() != 0) { - std::set::iterator itor1, itor2; - for (itor1 = eqc_concat.begin(); itor1 != eqc_concat.end(); ++itor1) { - for (itor2 = itor1; itor2 != eqc_concat.end(); ++itor2) { - if (itor1 == itor2) { - continue; - } - expr * e1 = *itor1; - expr * e2 = *itor2; - TRACE("t_str_detail", tout << "simplify concat-concat pair " << mk_pp(e1, m) << " and " << mk_pp(e2, m) << std::endl;); - simplify_concat_equality(e1, e2); + int hasCommon = 0; + if (eqc_concat_lhs.size() != 0 && eqc_concat_rhs.size() != 0) { + std::set::iterator itor1 = eqc_concat_lhs.begin(); + std::set::iterator itor2 = eqc_concat_rhs.begin(); + for (; itor1 != eqc_concat_lhs.end(); itor1++) { + if (eqc_concat_rhs.find(*itor1) != eqc_concat_rhs.end()) { + hasCommon = 1; + break; } } + for (; itor2 != eqc_concat_rhs.end(); itor2++) { + if (eqc_concat_lhs.find(*itor2) != eqc_concat_lhs.end()) { + hasCommon = 1; + break; + } + } + if (hasCommon == 0) { + simplify_concat_equality(*(eqc_concat_lhs.begin()), *(eqc_concat_rhs.begin())); + } } // step 2: Concat == Constant - // same enhancement as above wrt. Z3str2's behaviour - if (eqc_const.size() != 0) { - expr * conStr = *(eqc_const.begin()); - std::set::iterator itor2; - for (itor2 = eqc_concat.begin(); itor2 != eqc_concat.end(); ++itor2) { + + if (eqc_const_lhs.size() != 0) { + expr * conStr = *(eqc_const_lhs.begin()); + std::set::iterator itor2 = eqc_const_rhs.begin(); + for (; itor2 != eqc_const_rhs.end(); itor2++) { solve_concat_eq_str(*itor2, conStr); } + } else if (eqc_const_rhs.size() != 0) { + expr* conStr = *(eqc_const_rhs.begin()); + std::set::iterator itor1 = eqc_const_lhs.begin(); + for (; itor1 != eqc_const_lhs.end(); itor1++) { + solve_concat_eq_str(*itor1, conStr); + } } // simplify parents wrt. the equivalence class of both sides - // TODO this is slightly broken, re-enable it once some semantics have been fixed - // Briefly, Z3str2 expects that as this function is entered, - // lhs and rhs are NOT in the same equivalence class yet. - // However, newer versions of Z3 appear to behave differently, - // putting lhs and rhs into the same equivalence class - // *before* this function is called. - // Instead we do something possibly more aggressive here. - /* - bool lhs_has_eqc_value = false; - bool rhs_has_eqc_value = false; - expr * lhs_value = get_eqc_value(lhs, lhs_has_eqc_value); - expr * rhs_value = get_eqc_value(rhs, rhs_has_eqc_value); - if (lhs_has_eqc_value && !rhs_has_eqc_value) { - simplify_parent(rhs, lhs_value); + bool nn1HasEqcValue = false; + bool nn2HasEqcValue = false; + // we want the Z3str2 eqc check here... + expr * nn1_value = z3str2_get_eqc_value(lhs, nn1HasEqcValue); + expr * nn2_value = z3str2_get_eqc_value(rhs, nn2HasEqcValue); + if (nn1HasEqcValue && !nn2HasEqcValue) { + simplify_parent(rhs, nn1_value); } - if (!lhs_has_eqc_value && rhs_has_eqc_value) { - simplify_parent(lhs, rhs_value); - } - */ - bool lhs_has_eqc_value = false; - bool rhs_has_eqc_value = false; - expr * lhs_value = get_eqc_value(lhs, lhs_has_eqc_value); - expr * rhs_value = get_eqc_value(rhs, rhs_has_eqc_value); - - // TODO this depends on the old, possibly broken, semantics of is_string(). - // we explicitly want to test whether lhs/rhs is actually a string constant. - bool lhs_is_string_constant = m_strutil.is_string(lhs); - bool rhs_is_string_constant = m_strutil.is_string(rhs); - - - if (lhs_has_eqc_value && !rhs_is_string_constant) { - simplify_parent(rhs, lhs_value); - } - if (rhs_has_eqc_value && !lhs_is_string_constant) { - simplify_parent(lhs, rhs_value); + if (!nn1HasEqcValue && nn2HasEqcValue) { + simplify_parent(lhs, nn2_value); } // regex unroll + // TODO NEXT check EQC semantics here too expr * nn1EqConst = NULL; std::set nn1EqUnrollFuncs; @@ -6229,6 +6278,7 @@ void theory_str::handle_equality(expr * lhs, expr * rhs) { process_unroll_eq_const_str(*itor2, nn1EqConst); } } + } void theory_str::set_up_axioms(expr * ex) { @@ -6407,7 +6457,15 @@ void theory_str::new_eq_eh(theory_var x, theory_var y) { //TRACE("t_str_detail", tout << "new eq: v#" << x << " = v#" << y << std::endl;); TRACE("t_str", tout << "new eq: " << mk_ismt2_pp(get_enode(x)->get_owner(), get_manager()) << " = " << mk_ismt2_pp(get_enode(y)->get_owner(), get_manager()) << std::endl;); + /* + if (m_find.find(x) == m_find.find(y)) { + return; + } + */ handle_equality(get_enode(x)->get_owner(), get_enode(y)->get_owner()); + + // replicate Z3str2 behaviour: merge eqc **AFTER** handle_equality + m_find.merge(x, y); } void theory_str::new_diseq_eh(theory_var x, theory_var y) { @@ -6427,6 +6485,8 @@ void theory_str::assign_eh(bool_var v, bool is_true) { void theory_str::push_scope_eh() { theory::push_scope_eh(); + m_trail_stack.push_scope(); + sLevel += 1; TRACE("t_str", tout << "push to " << sLevel << std::endl;); TRACE("t_str_dump_assign_on_scope_change", dump_assignments();); @@ -6549,6 +6609,7 @@ void theory_str::pop_scope_eh(unsigned num_scopes) { m_basicstr_axiom_todo.reset(); m_basicstr_axiom_todo = new_m_basicstr; + m_trail_stack.pop_scope(num_scopes); theory::pop_scope_eh(num_scopes); check_variable_scope(); diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 9f7d51a8f..58b104209 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -27,6 +27,7 @@ Revision History: #include #include #include"str_rewriter.h" +#include"union_find.h" namespace smt { @@ -81,6 +82,10 @@ namespace smt { level = -100; } }; + + typedef trail_stack th_trail_stack; + typedef union_find th_union_find; + protected: // Some options that control how the solver operates. @@ -252,6 +257,12 @@ namespace smt { obj_pair_map concat_astNode_map; + th_union_find m_find; + th_trail_stack m_trail_stack; + theory_var get_var(expr * n) const; + expr * get_eqc_next(expr * n); + app * get_ast(theory_var i); + protected: void assert_axiom(expr * e); void assert_implication(expr * premise, expr * conclusion); @@ -347,6 +358,7 @@ namespace smt { app * mk_value_helper(app * n); expr * get_eqc_value(expr * n, bool & hasEqcValue); + expr * z3str2_get_eqc_value(expr * n , bool & hasEqcValue); bool in_same_eqc(expr * n1, expr * n2); expr * collect_eq_nodes(expr * n, expr_ref_vector & eqcSet); @@ -479,6 +491,11 @@ namespace smt { virtual void display(std::ostream & out) const; bool overlapping_variables_detected() const { return loopDetected; } + + th_trail_stack& get_trail_stack() { return m_trail_stack; } + void merge_eh(theory_var, theory_var, theory_var v1, theory_var v2) {} + void after_merge_eh(theory_var r1, theory_var r2, theory_var v1, theory_var v2) { } + void unmerge_eh(theory_var v1, theory_var v2) {} protected: virtual bool internalize_atom(app * atom, bool gate_ctx); virtual bool internalize_term(app * term); From 8f636e1f57be63f2cdb071b44fc7baae0d05e924 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 13 Sep 2016 18:16:21 -0400 Subject: [PATCH 208/410] fix typo'ed set reference in handle_equality --- src/smt/theory_str.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 939a63160..c3ae176b7 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -6231,14 +6231,14 @@ void theory_str::handle_equality(expr * lhs, expr * rhs) { if (eqc_const_lhs.size() != 0) { expr * conStr = *(eqc_const_lhs.begin()); - std::set::iterator itor2 = eqc_const_rhs.begin(); - for (; itor2 != eqc_const_rhs.end(); itor2++) { + std::set::iterator itor2 = eqc_concat_rhs.begin(); + for (; itor2 != eqc_concat_rhs.end(); itor2++) { solve_concat_eq_str(*itor2, conStr); } } else if (eqc_const_rhs.size() != 0) { expr* conStr = *(eqc_const_rhs.begin()); - std::set::iterator itor1 = eqc_const_lhs.begin(); - for (; itor1 != eqc_const_lhs.end(); itor1++) { + std::set::iterator itor1 = eqc_concat_lhs.begin(); + for (; itor1 != eqc_concat_lhs.end(); itor1++) { solve_concat_eq_str(*itor1, conStr); } } From 34dc65515041928c4ef5116871c959a55f42fc08 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 13 Sep 2016 18:24:59 -0400 Subject: [PATCH 209/410] z3str2 eqc semantics for theory_str unroll checks --- src/smt/theory_str.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index c3ae176b7..6d4fb1aab 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -8883,9 +8883,7 @@ void theory_str::get_eqc_allUnroll(expr * n, expr * &constStr, std::set & unrollFuncSet.insert(curr); } } - enode * e_curr = ctx.get_enode(curr); - curr = e_curr->get_next()->get_owner(); - // curr = get_eqc_next(t, curr); + curr = get_eqc_next(curr); } while (curr != n); } @@ -8907,9 +8905,7 @@ void theory_str::get_eqc_simpleUnroll(expr * n, expr * &constStr, std::setget_next()->get_owner(); - // curr = get_eqc_next(t, curr); + curr = get_eqc_next(curr); } while (curr != n); } From 9481601b4b315ada3abf46dd90b85b99136d2c94 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 14 Sep 2016 15:15:47 -0400 Subject: [PATCH 210/410] restore z3str2 eqc semantics in theory_str::new_eq_check --- src/smt/theory_str.cpp | 73 +++++++++++++++--------------------------- 1 file changed, 25 insertions(+), 48 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 6d4fb1aab..1238eb069 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -32,7 +32,7 @@ theory_str::theory_str(ast_manager & m): /* Options */ opt_AggressiveLengthTesting(false), opt_AggressiveValueTesting(false), - opt_AggressiveUnrollTesting(true), + opt_AggressiveUnrollTesting(false), opt_EagerStringConstantLengthAssertions(true), opt_VerifyFinalCheckProgress(true), opt_LCMUnrollStep(2), @@ -143,7 +143,7 @@ void theory_str::assert_axiom(expr * e) { if (opt_VerifyFinalCheckProgress) { finalCheckProgressIndicator = true; } - // TODO add to m_trail? + if (get_manager().is_true(e)) return; TRACE("t_str_detail", tout << "asserting " << mk_ismt2_pp(e, get_manager()) << std::endl;); context & ctx = get_context(); @@ -1612,58 +1612,42 @@ bool theory_str::new_eq_check(expr * lhs, expr * rhs) { context & ctx = get_context(); ast_manager & m = get_manager(); - // Previously we did the check between LHS and RHS equivalence classes. - // However these have since been merged. - // We start by asserting that the EQCs, in fact, really are merged. - if (!in_same_eqc(lhs, rhs)) { - TRACE("t_str", tout << "BUG: lhs and rhs not in same eqc in new_eq_eh(), loss of invariant!" << std::endl;); - UNREACHABLE(); - } - // skip this check if we defer consistency checking, as we can do it for every EQC in final check if (!opt_DeferEQCConsistencyCheck) { check_concat_len_in_eqc(lhs); check_concat_len_in_eqc(rhs); } - // Now we iterate over all pairs of terms in the (shared) eqc + // Now we iterate over all pairs of terms across both EQCs // and check whether we can show that any pair of distinct terms // cannot possibly be equal. // If that's the case, we assert an axiom to that effect and stop. - enode * eqc_root = ctx.get_enode(lhs)->get_root(); - enode * eqc_iterator1 = eqc_root; + expr * eqc_nn1 = lhs; do { - enode * eqc_iterator2 = eqc_iterator1; + expr * eqc_nn2 = rhs; do { - if (eqc_iterator1 != eqc_iterator2) { - // pull terms out of the enodes - app * eqc_nn1 = eqc_iterator1->get_owner(); - app * eqc_nn2 = eqc_iterator2->get_owner(); - TRACE("t_str_detail", tout << "checking whether " << mk_pp(eqc_nn1, m) << " and " << mk_pp(eqc_nn2, m) << " can be equal" << std::endl;); - if (!can_two_nodes_eq(eqc_nn1, eqc_nn2)) { - TRACE("t_str", tout << "inconsistency detected: " << mk_pp(eqc_nn1, m) << " cannot be equal to " << mk_pp(eqc_nn2, m) << std::endl;); - expr_ref to_assert(m.mk_not(ctx.mk_eq_atom(eqc_nn1, eqc_nn2)), m); - assert_axiom(to_assert); - // this shouldn't use the integer theory at all, so we don't allow the option of quick-return + TRACE("t_str_detail", tout << "checking whether " << mk_pp(eqc_nn1, m) << " and " << mk_pp(eqc_nn2, m) << " can be equal" << std::endl;); + // inconsistency check: value + if (!can_two_nodes_eq(eqc_nn1, eqc_nn2)) { + TRACE("t_str", tout << "inconsistency detected: " << mk_pp(eqc_nn1, m) << " cannot be equal to " << mk_pp(eqc_nn2, m) << std::endl;); + expr_ref to_assert(m.mk_not(ctx.mk_eq_atom(eqc_nn1, eqc_nn2)), m); + assert_axiom(to_assert); + // this shouldn't use the integer theory at all, so we don't allow the option of quick-return + return false; + } + if (!check_length_consistency(eqc_nn1, eqc_nn2)) { + TRACE("t_str", tout << "inconsistency detected: " << mk_pp(eqc_nn1, m) << " and " << mk_pp(eqc_nn2, m) << " have inconsistent lengths" << std::endl;); + if (opt_NoQuickReturn_IntegerTheory){ + TRACE("t_str_detail", tout << "continuing in new_eq_check() due to opt_NoQuickReturn_IntegerTheory" << std::endl;); + } else { return false; } - if (!check_length_consistency(eqc_nn1, eqc_nn2)) { - TRACE("t_str", tout << "inconsistency detected: " << mk_pp(eqc_nn1, m) << " and " << mk_pp(eqc_nn2, m) << " have inconsistent lengths" << std::endl;); - if (opt_NoQuickReturn_IntegerTheory){ - TRACE("t_str_detail", tout << "continuing in new_eq_check() due to opt_NoQuickReturn_IntegerTheory" << std::endl;); - } else { - return false; - } - } } - eqc_iterator2 = eqc_iterator2->get_next(); - - } while (eqc_iterator2 != eqc_root); - - eqc_iterator1 = eqc_iterator1->get_next(); - } while (eqc_iterator1 != eqc_root); - + eqc_nn2 = get_eqc_next(eqc_nn2); + } while (eqc_nn2 != rhs); + eqc_nn1 = get_eqc_next(eqc_nn1); + } while (eqc_nn1 != lhs); if (!contains_map.empty()) { check_contain_in_new_eq(lhs, rhs); @@ -2327,7 +2311,6 @@ void theory_str::simplify_concat_equality(expr * nn1, expr * nn2) { infer_len_concat_equality(nn1, nn2); - // TODO we may want to add no-quick-return options for these as well if (a1_arg0 == a2_arg0) { if (!in_same_eqc(a1_arg1, a2_arg1)) { expr_ref premise(ctx.mk_eq_atom(nn1, nn2), m); @@ -2354,8 +2337,6 @@ void theory_str::simplify_concat_equality(expr * nn1, expr * nn2) { // quick path - // TODO we may want to add no-quick-return options for these as well - if (in_same_eqc(a1_arg0, a2_arg0)) { if (in_same_eqc(a1_arg1, a2_arg1)) { TRACE("t_str_detail", tout << "SKIP: a1_arg0 =~ a2_arg0 and a1_arg1 =~ a2_arg1" << std::endl;); @@ -4846,9 +4827,8 @@ void theory_str::check_contain_in_new_eq(expr * n1, expr * n2) { ast_manager & m = get_manager(); TRACE("t_str_detail", tout << "consistency check for contains wrt. " << mk_pp(n1, m) << " and " << mk_pp(n2, m) << std::endl;); - // Modification from Z3str2: the EQC of n1 and n2 *are* now merged. - // So we don't have to do anything too special - // to prepare willEqClass any more, we just use the EQC from n1 / n2. + // Modification from Z3str2: if we use the merged EQC directly from the context, + // we don't have to do anything special to merge n1/n2's EQCs. expr_ref_vector willEqClass(m); expr * constStrAst = collect_eq_nodes(n1, willEqClass); @@ -6257,9 +6237,6 @@ void theory_str::handle_equality(expr * lhs, expr * rhs) { simplify_parent(lhs, nn2_value); } - // regex unroll - // TODO NEXT check EQC semantics here too - expr * nn1EqConst = NULL; std::set nn1EqUnrollFuncs; get_eqc_allUnroll(lhs, nn1EqConst, nn1EqUnrollFuncs); From ec9e1686f75d5171983f821c1a3e44312c7a9f19 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 14 Sep 2016 15:32:49 -0400 Subject: [PATCH 211/410] fix semantics of collect_eq_nodes and simplify_parent --- src/smt/theory_str.cpp | 78 ++++++++++-------------------------------- 1 file changed, 18 insertions(+), 60 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 1238eb069..3727e15e1 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -1785,18 +1785,16 @@ void theory_str::simplify_parent(expr * nn, expr * eq_str) { << " with respect to " << mk_ismt2_pp(eq_str, m) << std::endl;); ctx.internalize(nn, false); - enode * n_eq_enode = ctx.get_enode(nn); - enode * nn_enode = n_eq_enode; std::string eq_strValue = m_strutil.get_string_constant_value(eq_str); - + app * n_eqNode = nn; do { - app * n_eqNode = n_eq_enode->get_owner(); + enode * n_eq_enode = ctx.get_enode(n_eqNode); TRACE("t_str_detail", tout << "considering all parents of " << mk_ismt2_pp(n_eqNode, m) << std::endl << "associated n_eq_enode has " << n_eq_enode->get_num_parents() << " parents" << std::endl;); // the goal of this next bit is to avoid dereferencing a bogus e_parent in the following loop. - // what I image is causing this bug is that, for example, we examine some parent, we add an axiom that involves it, + // what I imagine is causing this bug is that, for example, we examine some parent, we add an axiom that involves it, // and the parent_it iterator becomes invalidated, because we indirectly modified the container that we're iterating over. enode_vector current_parents; @@ -2068,8 +2066,8 @@ void theory_str::simplify_parent(expr * nn, expr * eq_str) { // check next EQC member - n_eq_enode = n_eq_enode->get_next(); - } while (n_eq_enode != nn_enode); + n_eqNode = get_eqc_next(n_eqNode); + } while (n_eqNode != nn); } expr * theory_str::simplify_concat(expr * node) { @@ -4158,45 +4156,6 @@ bool theory_str::get_len_value(expr* e, rational& val) { return val.is_int(); } -/* - * Look through the equivalence class of n to find an integer constant. - * Return that constant if it is found. Otherwise, return -1. - * Note that a return value of -1 should not normally be possible, as - * string length cannot be negative. - */ - -/* -rational theory_str::get_len_value(expr * x) { - ast_manager & m = get_manager(); - context & ctx = get_context(); - ctx.internalize(x, false); - expr * n = mk_strlen(x); - ctx.internalize(n, false); - - TRACE("t_str_detail", tout << "checking eqc of " << mk_ismt2_pp(n, m) << " for an integer constant" << std::endl;); - - enode * nNode = ctx.get_enode(n); - enode * eqcNode = nNode; - do { - app * ast = eqcNode->get_owner(); - rational val; - bool is_int; - TRACE("t_str_detail", tout << "eqc member: " << mk_ismt2_pp(ast, m) << std::endl;); - if (m_autil.is_numeral(ast, val, is_int)) { - if (is_int) { - TRACE("t_str_detail", tout << "eqc contains integer constant " << val << std::endl;); - SASSERT(!val.is_neg()); - return val; - } - } - eqcNode = eqcNode->get_next(); - } while (eqcNode != nNode); - // not found - TRACE("t_str_detail", tout << "eqc contains no integer constants" << std::endl;); - return rational(-1); -} -*/ - /* * Decide whether n1 and n2 are already in the same equivalence class. * This only checks whether the core considers them to be equal; @@ -4241,17 +4200,15 @@ expr * theory_str::collect_eq_nodes(expr * n, expr_ref_vector & eqcSet) { context & ctx = get_context(); expr * constStrNode = NULL; - enode * e_base = ctx.get_enode(n); - enode * e_curr = e_base; + app * ex = n; do { - app * ex = e_curr->get_owner(); if (m_strutil.is_string(ex)) { constStrNode = ex; } eqcSet.push_back(ex); - e_curr = e_curr->get_next(); - } while (e_curr != e_base); + ex = get_eqc_next(ex); + } while (ex != n); return constStrNode; } @@ -4827,10 +4784,10 @@ void theory_str::check_contain_in_new_eq(expr * n1, expr * n2) { ast_manager & m = get_manager(); TRACE("t_str_detail", tout << "consistency check for contains wrt. " << mk_pp(n1, m) << " and " << mk_pp(n2, m) << std::endl;); - // Modification from Z3str2: if we use the merged EQC directly from the context, - // we don't have to do anything special to merge n1/n2's EQCs. expr_ref_vector willEqClass(m); - expr * constStrAst = collect_eq_nodes(n1, willEqClass); + expr * constStrAst_1 = collect_eq_nodes(n1, willEqClass); + expr * constStrAst_2 = collect_eq_nodes(n2, willEqClass); + expr * constStrAst = (constStrAst_1 != NULL) ? constStrAst_1 : constStrAst_2; TRACE("t_str_detail", tout << "eqc of n1 is {"; for (expr_ref_vector::iterator it = willEqClass.begin(); it != willEqClass.end(); ++it) { @@ -5582,10 +5539,8 @@ bool theory_str::check_concat_len_in_eqc(expr * concat) { bool no_assertions = true; - enode * eqc_base = ctx.get_enode(concat); - enode * eqc_it = eqc_base; + app * eqc_n = concat; do { - app * eqc_n = eqc_it->get_owner(); if (is_concat(eqc_n)) { rational unused; bool status = infer_len_concat(eqc_n, unused); @@ -5593,8 +5548,8 @@ bool theory_str::check_concat_len_in_eqc(expr * concat) { no_assertions = false; } } - eqc_it = eqc_it->get_next(); - } while (eqc_it != eqc_base); + eqc_n = get_eqc_next(eqc_n); + } while (eqc_n != concat); return no_assertions; } @@ -5604,7 +5559,10 @@ void theory_str::check_regex_in(expr * nn1, expr * nn2) { ast_manager & m = get_manager(); expr_ref_vector eqNodeSet(m); - expr * constStr = collect_eq_nodes(nn1, eqNodeSet); + + expr * constStr_1 = collect_eq_nodes(nn1, eqNodeSet); + expr * constStr_2 = collect_eq_nodes(nn2, eqNodeSet); + expr * constStr = (constStr_1 != NULL) ? constStr_1 : constStr_2; if (constStr == NULL) { return; From 87d61d6d6ed4a685df7d0364e5f37f1297d54175 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 14 Sep 2016 15:35:37 -0400 Subject: [PATCH 212/410] fix semantics of in_same_eqc --- src/smt/theory_str.cpp | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 3727e15e1..ce5aabb6c 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -4178,22 +4178,13 @@ bool theory_str::in_same_eqc(expr * n1, expr * n2) { ctx.internalize(n2, false); } - enode * n1Node = ctx.get_enode(n1); - enode * n2Node = ctx.get_enode(n2); - - // here's what the old Z3str2 would have done; we can do something much better - /* - n1Node->get_root(); - enode * curr = n1Node->get_next(); - while (curr != n1Node) { - if (curr == n2Node) { + expr * curr = get_eqc_next(n1); + while (curr != n1) { + if (curr == n2) return true; - } - curr = curr->get_next(); + curr = get_eqc_next(curr); } return false; - */ - return n1Node->get_root() == n2Node->get_root(); } expr * theory_str::collect_eq_nodes(expr * n, expr_ref_vector & eqcSet) { From 50353168ef008a87bc38d37de305bfdfe43627e1 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 14 Sep 2016 15:36:36 -0400 Subject: [PATCH 213/410] fix semantics of get_concats_in_eqc and get_var_in_eqc --- src/smt/theory_str.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index ce5aabb6c..a773e0d6d 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -8649,9 +8649,7 @@ void theory_str::get_concats_in_eqc(expr * n, std::set & concats) { if (is_concat(to_app(eqcNode))) { concats.insert(eqcNode); } - enode * e_eqc = ctx.get_enode(eqcNode); - eqcNode = e_eqc->get_next()->get_owner(); - // eqcNode = Z3_theory_get_eqc_next(t, eqcNode); + eqcNode = get_eqc_next(eqcNode); } while (eqcNode != n); } @@ -8663,9 +8661,7 @@ void theory_str::get_var_in_eqc(expr * n, std::set & varSet) { if (variable_set.find(eqcNode) != variable_set.end()) { varSet.insert(eqcNode); } - enode * e_eqc = ctx.get_enode(eqcNode); - eqcNode = e_eqc->get_next()->get_owner(); - // eqcNode = Z3_theory_get_eqc_next(t, eqcNode); + eqcNode = get_eqc_next(eqcNode); } while (eqcNode != n); } From 804009a75754864c26e19adffdefc0d31c4016cd Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 14 Sep 2016 15:37:48 -0400 Subject: [PATCH 214/410] use z3str2 eqc semantics for get_eqc_value --- src/smt/theory_str.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index a773e0d6d..c3c8a50cf 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -3967,6 +3967,7 @@ void theory_str::unroll_str2reg_constStr(expr * unrollFunc, expr * eqConstStr) { * Return that constant if it is found, and set hasEqcValue to true. * Otherwise, return n, and set hasEqcValue to false. */ +/* expr * theory_str::get_eqc_value(expr * n, bool & hasEqcValue) { context & ctx = get_context(); // I hope this works @@ -3985,6 +3986,12 @@ expr * theory_str::get_eqc_value(expr * n, bool & hasEqcValue) { hasEqcValue = false; return n; } +*/ + +expr * theory_str::get_eqc_value(expr * n, bool & hasEqcValue) { + return z3str2_get_eqc_value(n, hasEqcValue); +} + // Simulate the behaviour of get_eqc_value() from Z3str2. // We only check m_find for a string constant. From e46fc7b0b68b30f952ab3681a841e7a53e55cc92 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 14 Sep 2016 15:51:33 -0400 Subject: [PATCH 215/410] fix expr-app conversion --- src/smt/theory_str.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index c3c8a50cf..14d30d4a6 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -1787,7 +1787,7 @@ void theory_str::simplify_parent(expr * nn, expr * eq_str) { ctx.internalize(nn, false); std::string eq_strValue = m_strutil.get_string_constant_value(eq_str); - app * n_eqNode = nn; + expr * n_eqNode = nn; do { enode * n_eq_enode = ctx.get_enode(n_eqNode); TRACE("t_str_detail", tout << "considering all parents of " << mk_ismt2_pp(n_eqNode, m) << std::endl @@ -1872,7 +1872,7 @@ void theory_str::simplify_parent(expr * nn, expr * eq_str) { assert_implication(implyL, implyR); } - } else if (is_concat(n_eqNode)) { + } else if (is_concat(to_app(n_eqNode))) { expr_ref simpleConcat(m); simpleConcat = mk_concat(eq_str, arg1); if (!in_same_eqc(a_parent, simpleConcat)) { @@ -1943,7 +1943,7 @@ void theory_str::simplify_parent(expr * nn, expr * eq_str) { assert_implication(implyL, implyR); } - } else if (is_concat(n_eqNode)) { + } else if (is_concat(to_app(n_eqNode))) { expr_ref simpleConcat(m); simpleConcat = mk_concat(arg0, eq_str); if (!in_same_eqc(a_parent, simpleConcat)) { @@ -4198,9 +4198,9 @@ expr * theory_str::collect_eq_nodes(expr * n, expr_ref_vector & eqcSet) { context & ctx = get_context(); expr * constStrNode = NULL; - app * ex = n; + expr * ex = n; do { - if (m_strutil.is_string(ex)) { + if (m_strutil.is_string(to_app(ex))) { constStrNode = ex; } eqcSet.push_back(ex); @@ -5537,9 +5537,9 @@ bool theory_str::check_concat_len_in_eqc(expr * concat) { bool no_assertions = true; - app * eqc_n = concat; + expr * eqc_n = concat; do { - if (is_concat(eqc_n)) { + if (is_concat(to_app(eqc_n))) { rational unused; bool status = infer_len_concat(eqc_n, unused); if (status) { From a294c145dc0a4c55e5c89a6b5573ca8c1f84795a Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 14 Sep 2016 16:18:03 -0400 Subject: [PATCH 216/410] add theory_str::try_eval_concat to work around rewriter behaviour this fixes a regression in concat-013.smt2 --- src/smt/theory_str.cpp | 62 +++++++++++++++++++++++++++++++++++++++++- src/smt/theory_str.h | 2 ++ 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 14d30d4a6..3687dc9b7 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -762,7 +762,7 @@ expr * theory_str::mk_concat(expr * n1, expr * n2) { } bool theory_str::can_propagate() { - return !m_basicstr_axiom_todo.empty() || !m_str_eq_todo.empty() || !m_concat_axiom_todo.empty() + return !m_basicstr_axiom_todo.empty() || !m_str_eq_todo.empty() || !m_concat_axiom_todo.empty() || !m_concat_eval_todo.empty() || !m_axiom_CharAt_todo.empty() || !m_axiom_StartsWith_todo.empty() || !m_axiom_EndsWith_todo.empty() || !m_axiom_Contains_todo.empty() || !m_axiom_Indexof_todo.empty() || !m_axiom_Indexof2_todo.empty() || !m_axiom_LastIndexof_todo.empty() || !m_axiom_Substr_todo.empty() || !m_axiom_Replace_todo.empty() @@ -794,6 +794,11 @@ void theory_str::propagate() { } m_concat_axiom_todo.reset(); + for (unsigned i = 0; i < m_concat_eval_todo.size(); ++i) { + try_eval_concat(m_concat_eval_todo[i]); + } + m_concat_eval_todo.reset(); + for (unsigned i = 0; i < m_axiom_CharAt_todo.size(); ++i) { instantiate_axiom_CharAt(m_axiom_CharAt_todo[i]); } @@ -853,6 +858,58 @@ void theory_str::propagate() { } } +/* + * Attempt to evaluate a concat over constant strings, + * and if this is possible, assert equality between the + * flattened string and the original term. + */ + +void theory_str::try_eval_concat(enode * cat) { + SASSERT(is_concat(cat)); + app * a_cat = cat->get_owner(); + + context & ctx = get_context(); + ast_manager & m = get_manager(); + + TRACE("t_str_detail", tout << "attempting to flatten " << mk_pp(a_cat, m) << std::endl;); + + std::stack worklist; + std::string flattenedString(""); + bool constOK = true; + + { + app * arg0 = to_app(a_cat->get_arg(0)); + app * arg1 = to_app(a_cat->get_arg(1)); + + worklist.push(arg1); + worklist.push(arg0); + } + + while (constOK && !worklist.empty()) { + app * evalArg = worklist.top(); worklist.pop(); + if (m_strutil.is_string(evalArg)) { + std::string nextStr = m_strutil.get_string_constant_value(evalArg); + flattenedString.append(nextStr); + } else if (is_concat(evalArg)) { + app * arg0 = to_app(evalArg->get_arg(0)); + app * arg1 = to_app(evalArg->get_arg(1)); + + worklist.push(arg1); + worklist.push(arg0); + } else { + TRACE("t_str_detail", tout << "non-constant term in concat -- giving up." << std::endl;); + constOK = false; + break; + } + } + if (constOK) { + TRACE("t_str_detail", tout << "flattened to \"" << flattenedString << "\"" << std::endl;); + expr_ref constStr(m_strutil.mk_string(flattenedString), m); + expr_ref axiom(ctx.mk_eq_atom(a_cat, constStr), m); + assert_axiom(axiom); + } +} + /* * Instantiate an axiom of the following form: * Length(Concat(x, y)) = Length(x) + Length(y) @@ -6240,6 +6297,9 @@ void theory_str::set_up_axioms(expr * ex) { if (is_concat(ap)) { // if ex is a concat, set up concat axioms later m_concat_axiom_todo.push_back(n); + // we also want to check whether we can eval this concat, + // in case the rewriter did not totally finish with this term + m_concat_eval_todo.push_back(n); } else if (is_strlen(ap)) { // if the argument is a variable, // keep track of this for later, we'll need it during model gen diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 58b104209..745d22ac2 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -184,6 +184,7 @@ namespace smt { svector > m_str_eq_todo; ptr_vector m_concat_axiom_todo; ptr_vector m_string_constant_length_todo; + ptr_vector m_concat_eval_todo; // enode lists for term-specific axioms // TODO maybe refactor this into a generic "library_aware_axiom_todo" list @@ -332,6 +333,7 @@ namespace smt { bool is_Unroll(enode const * n) const { return is_Unroll(n->get_owner()); } void instantiate_concat_axiom(enode * cat); + void try_eval_concat(enode * cat); void instantiate_basic_string_axioms(enode * str); void instantiate_str_eq_length_axiom(enode * lhs, enode * rhs); From 9ee7326a19fd46cb0aa61719558c941c5e560051 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 14 Sep 2016 17:26:52 -0400 Subject: [PATCH 217/410] tweaks to process_concat_eq_type_3 --- src/smt/theory_str.cpp | 63 +++++++++++++++++++++--------------------- 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 3687dc9b7..27c12b267 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -3393,10 +3393,10 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { expr_ref suf_n_concat(mk_concat(suffixAst, n), mgr); if (can_two_nodes_eq(x, prefixAst) && can_two_nodes_eq(y, suf_n_concat)) { - expr ** r_items = alloc_svect(expr*, 2); - r_items[0] = ctx.mk_eq_atom(x, prefixAst); - r_items[1] = ctx.mk_eq_atom(y, suf_n_concat); - assert_implication(ax_l, mgr.mk_and(2, r_items)); + expr_ref_vector r_items(mgr); + r_items.push_back(ctx.mk_eq_atom(x, prefixAst)); + r_items.push_back(ctx.mk_eq_atom(y, suf_n_concat)); + assert_implication(ax_l, mk_and(r_items)); } else { // negate! It's impossible to split str with these lengths TRACE("t_str", tout << "CONFLICT: Impossible to split str with these lengths." << std::endl;); @@ -3433,11 +3433,11 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { if (can_two_nodes_eq(x, str_temp1)) { if (!avoidLoopCut || !(has_self_cut(x, n))) { - expr ** r_items = alloc_svect(expr*, 3); - r_items[0] = ctx.mk_eq_atom(x, str_temp1); - r_items[1] = ctx.mk_eq_atom(n, temp1_y); - r_items[2] = ctx.mk_eq_atom(mk_strlen(temp1), mk_int(tmpLen)); - expr_ref ax_r(mgr.mk_and(3, r_items), mgr); + expr_ref_vector r_items(mgr); + r_items.push_back(ctx.mk_eq_atom(x, str_temp1)); + r_items.push_back(ctx.mk_eq_atom(n, temp1_y)); + r_items.push_back(ctx.mk_eq_atom(mk_strlen(temp1), mk_int(tmpLen))); + expr_ref ax_r(mk_and(r_items), mgr); //Cut Info add_cut_info_merge(temp1, sLevel, x); @@ -3460,9 +3460,9 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { // Split type -1. We know nothing about the length... int optionTotal = 2 + strValue.length(); - expr ** or_item = alloc_svect(expr*, optionTotal); - int option = 0; - expr ** and_item = alloc_svect(expr*, (2 + 4 * optionTotal)); + expr_ref_vector or_item(mgr); + unsigned option = 0; + expr_ref_vector and_item(mgr); int pos = 1; for (int i = 0; i <= (int) strValue.size(); i++) { std::string part1Str = strValue.substr(0, i); @@ -3474,11 +3474,11 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { if (can_two_nodes_eq(x, cropStr) && can_two_nodes_eq(y, y_concat)) { // break down option 3-1 expr_ref x_eq_str(ctx.mk_eq_atom(x, cropStr), mgr); - or_item[option] = ctx.mk_eq_atom(xorFlag, mk_int(option)); - and_item[pos++] = ctx.mk_eq_atom(or_item[option], x_eq_str); - and_item[pos++] = ctx.mk_eq_atom(or_item[option], ctx.mk_eq_atom(y, y_concat)); + or_item.push_back(ctx.mk_eq_atom(xorFlag, mk_int(option))); + and_item.push_back(ctx.mk_eq_atom(or_item.get(option), x_eq_str)); ++pos; + and_item.push_back(ctx.mk_eq_atom(or_item.get(option), ctx.mk_eq_atom(y, y_concat))); - and_item[pos++] = ctx.mk_eq_atom(or_item[option], ctx.mk_eq_atom(mk_strlen(x), mk_strlen(cropStr))); + and_item.push_back(ctx.mk_eq_atom(or_item.get(option), ctx.mk_eq_atom(mk_strlen(x), mk_strlen(cropStr)))); ++pos; // and_item[pos++] = Z3_mk_eq(ctx, or_item[option], Z3_mk_eq(ctx, mk_length(t, y), mk_length(t, y_concat))); // adding length constraint for _ = constStr seems slowing things down. @@ -3495,18 +3495,18 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { if (can_two_nodes_eq(x, strAst_temp1)) { if (!avoidLoopCut || !(has_self_cut(x, n))) { // break down option 3-2 - or_item[option] = ctx.mk_eq_atom(xorFlag, mk_int(option)); + or_item.push_back(ctx.mk_eq_atom(xorFlag, mk_int(option))); expr_ref temp1_y(mk_concat(temp1, y), mgr); - and_item[pos++] = ctx.mk_eq_atom(or_item[option], ctx.mk_eq_atom(x, strAst_temp1)); - and_item[pos++] = ctx.mk_eq_atom(or_item[option], ctx.mk_eq_atom(n, temp1_y)); + and_item.push_back(ctx.mk_eq_atom(or_item.get(option), ctx.mk_eq_atom(x, strAst_temp1))); ++pos; + and_item.push_back(ctx.mk_eq_atom(or_item.get(option), ctx.mk_eq_atom(n, temp1_y))); ++pos; - and_item[pos++] = ctx.mk_eq_atom(or_item[option], ctx.mk_eq_atom(mk_strlen(x), - m_autil.mk_add(mk_strlen(strAst), mk_strlen(temp1)) )); + and_item.push_back(ctx.mk_eq_atom(or_item.get(option), ctx.mk_eq_atom(mk_strlen(x), + m_autil.mk_add(mk_strlen(strAst), mk_strlen(temp1)) )) ); ++pos; option++; - add_cut_info_merge(temp1, ctx.get_scope_level(), x); - add_cut_info_merge(temp1, ctx.get_scope_level(), n); + add_cut_info_merge(temp1, sLevel, x); + add_cut_info_merge(temp1, sLevel, n); } else { loopDetected = true; TRACE("t_str", tout << "AVOID LOOP: SKIPPED." << std::endl;); @@ -3517,11 +3517,11 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { if (option > 0) { if (option == 1) { - and_item[0] = or_item[0]; + and_item.push_back(or_item.get(0)); } else { - and_item[0] = mgr.mk_or(option, or_item); + and_item.push_back(mk_or(or_item)); } - expr_ref implyR(mgr.mk_and(pos, and_item), mgr); + expr_ref implyR(mk_and(and_item), mgr); assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); } else { TRACE("t_str", tout << "STOP: should not split two eq. concats" << std::endl;); @@ -6531,11 +6531,11 @@ void theory_str::check_variable_scope() { ast_manager & m = get_manager(); expr_ref_vector assignments(m); - ctx.get_assignments(assignments); - for (expr_ref_vector::iterator i = assignments.begin(); i != assignments.end(); ++i) { - expr * ex = *i; - recursive_check_variable_scope(ex); - } + ctx.get_assignments(assignments); + for (expr_ref_vector::iterator i = assignments.begin(); i != assignments.end(); ++i) { + expr * ex = *i; + recursive_check_variable_scope(ex); + } } void theory_str::pop_scope_eh(unsigned num_scopes) { @@ -6587,6 +6587,7 @@ void theory_str::pop_scope_eh(unsigned num_scopes) { } } + // TODO use the trail stack to do this for us! requires lots of refactoring // TODO if this works, possibly remove axioms from other vectors as well ptr_vector new_m_basicstr; for (ptr_vector::iterator it = m_basicstr_axiom_todo.begin(); it != m_basicstr_axiom_todo.end(); ++it) { From f22f4da023fae6347d96167211ad5244c00f8714 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 14 Sep 2016 17:33:47 -0400 Subject: [PATCH 218/410] remove unused variable --- src/smt/theory_str.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 27c12b267..5ef3518d7 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -151,6 +151,7 @@ void theory_str::assert_axiom(expr * e) { ctx.internalize(e, true); } literal lit(ctx.get_literal(e)); + // TESTING! ctx.mark_as_relevant(lit); ctx.mk_th_axiom(get_id(), 1, &lit); @@ -3459,7 +3460,6 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { else { // Split type -1. We know nothing about the length... - int optionTotal = 2 + strValue.length(); expr_ref_vector or_item(mgr); unsigned option = 0; expr_ref_vector and_item(mgr); From d334403720f1da03219307edf2d976e5fdd90121 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 14 Sep 2016 17:42:40 -0400 Subject: [PATCH 219/410] remove relevancy testing experiment --- src/smt/theory_str.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 5ef3518d7..6034395fc 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -151,7 +151,6 @@ void theory_str::assert_axiom(expr * e) { ctx.internalize(e, true); } literal lit(ctx.get_literal(e)); - // TESTING! ctx.mark_as_relevant(lit); ctx.mk_th_axiom(get_id(), 1, &lit); From 15055c8041d3244a303f017692d4e86ba383baeb Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 14 Sep 2016 19:01:14 -0400 Subject: [PATCH 220/410] use mk_int_var to make xor terms --- src/smt/theory_str.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 6034395fc..34ac58d18 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -32,7 +32,7 @@ theory_str::theory_str(ast_manager & m): /* Options */ opt_AggressiveLengthTesting(false), opt_AggressiveValueTesting(false), - opt_AggressiveUnrollTesting(false), + opt_AggressiveUnrollTesting(true), opt_EagerStringConstantLengthAssertions(true), opt_VerifyFinalCheckProgress(true), opt_LCMUnrollStep(2), @@ -453,6 +453,7 @@ void theory_str::track_variable_scope(expr * var) { } app * theory_str::mk_internal_xor_var() { + /* ast_manager & m = get_manager(); std::stringstream ss; ss << tmpXorVarCount; @@ -460,6 +461,7 @@ app * theory_str::mk_internal_xor_var() { std::string name = "$$_xor_" + ss.str(); // Z3_sort r = of_sort(mk_c(c)->m().mk_sort(mk_c(c)->get_arith_fid(), INT_SORT)); sort * int_sort = m.mk_sort(m_autil.get_family_id(), INT_SORT); + char * new_buffer = alloc_svect(char, name.length() + 1); strcpy(new_buffer, name.c_str()); symbol sym(new_buffer); @@ -467,6 +469,8 @@ app * theory_str::mk_internal_xor_var() { app * a = m.mk_const(m.mk_const_decl(sym, int_sort)); m_trail.push_back(a); return a; + */ + return mk_int_var("$$_xor"); } app * theory_str::mk_int_var(std::string name) { From ad7247df51042eba8d509e8d9cd801ae21720e15 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 14 Sep 2016 19:32:14 -0400 Subject: [PATCH 221/410] make calls to theory_str::dump_assignments depend on the correct trace flags --- src/smt/theory_str.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 34ac58d18..dbf5d2f38 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -6485,7 +6485,7 @@ void theory_str::push_scope_eh() { sLevel += 1; TRACE("t_str", tout << "push to " << sLevel << std::endl;); - TRACE("t_str_dump_assign_on_scope_change", dump_assignments();); + TRACE_CODE(dump_assignments();); } void theory_str::recursive_check_variable_scope(expr * ex) { @@ -6544,7 +6544,7 @@ void theory_str::check_variable_scope() { void theory_str::pop_scope_eh(unsigned num_scopes) { sLevel -= num_scopes; TRACE("t_str", tout << "pop " << num_scopes << " to " << sLevel << std::endl;); - TRACE("t_str_dump_assign_on_scope_change", dump_assignments();); + TRACE_CODE(dump_assignments();); // list of expr* to remove from cut_var_map ptr_vector cutvarmap_removes; @@ -6615,7 +6615,7 @@ void theory_str::pop_scope_eh(unsigned num_scopes) { void theory_str::dump_assignments() { ast_manager & m = get_manager(); context & ctx = get_context(); - TRACE("t_str_detail", + TRACE("t_str_dump_assign_on_scope_change", tout << "dumping all assignments:" << std::endl; expr_ref_vector assignments(m); ctx.get_assignments(assignments); @@ -7503,7 +7503,7 @@ final_check_status theory_str::final_check_eh() { } TRACE("t_str", tout << "final check" << std::endl;); - TRACE("t_str_dump_assign", dump_assignments();); + TRACE_CODE(dump_assignments();); check_variable_scope(); if (opt_DeferEQCConsistencyCheck) { From bed40c45b80d83ed7ad82ef14ec92f3b8352854b Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 14 Sep 2016 21:48:27 -0400 Subject: [PATCH 222/410] cleanup --- src/smt/theory_str.cpp | 10 ++++++---- src/smt/theory_str.h | 2 ++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index dbf5d2f38..1dcfb0b29 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -2161,6 +2161,7 @@ expr * theory_str::simplify_concat(expr * node) { if (in_same_eqc(node, resultAst)) { TRACE("t_str_detail", tout << "SKIP: both concats are already in the same equivalence class" << std::endl;); } else { + // TODO refactor expr ** items = alloc_svect(expr*, resolvedMap.size()); int pos = 0; std::map::iterator itor = resolvedMap.begin(); @@ -2459,8 +2460,8 @@ void theory_str::simplify_concat_equality(expr * nn1, expr * nn2) { } } - expr * new_nn1 = simplify_concat(nn1); - expr * new_nn2 = simplify_concat(nn2); + expr_ref new_nn1(simplify_concat(nn1), m); + expr_ref new_nn2(simplify_concat(nn2), m); app * a_new_nn1 = to_app(new_nn1); app * a_new_nn2 = to_app(new_nn2); @@ -5466,8 +5467,6 @@ bool theory_str::check_length_concat_concat(expr * n1, expr * n2) { items.push_back(ctx.mk_eq_atom(n1, n2)); - expr_ref toAssert(mgr.mk_not(mk_and(items)), mgr); - bool conflict = false; if (concat1LenFixed && concat2LenFixed) { @@ -5486,6 +5485,7 @@ bool theory_str::check_length_concat_concat(expr * n1, expr * n2) { if (conflict) { TRACE("t_str_detail", tout << "inconsistent length detected in concat <==> concat" << std::endl;); + expr_ref toAssert(mgr.mk_not(mk_and(items)), mgr); assert_axiom(toAssert); return false; } @@ -6619,10 +6619,12 @@ void theory_str::dump_assignments() { tout << "dumping all assignments:" << std::endl; expr_ref_vector assignments(m); ctx.get_assignments(assignments); + /* for (expr_ref_vector::iterator i = assignments.begin(); i != assignments.end(); ++i) { expr * ex = *i; tout << mk_ismt2_pp(ex, m) << (ctx.is_relevant(ex) ? "" : " (NOT REL)") << std::endl; } + */ ); } diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 745d22ac2..7af6ab1ca 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -29,6 +29,8 @@ Revision History: #include"str_rewriter.h" #include"union_find.h" +// TODO refactor: anything that returns an expr* instead returns an expr_ref + namespace smt { class str_value_factory : public value_factory { From 8776b97841c80e476bf18dc32304c6efb5146050 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 14 Sep 2016 22:08:40 -0400 Subject: [PATCH 223/410] variable scope correctness hack in theory_str --- src/smt/theory_str.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 1dcfb0b29..d73d55dc3 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -6483,6 +6483,12 @@ void theory_str::push_scope_eh() { theory::push_scope_eh(); m_trail_stack.push_scope(); + // TODO out-of-scope term debugging, see comment in pop_scope_eh() + context & ctx = get_context(); + ast_manager & m = get_manager(); + expr_ref_vector assignments(m); + ctx.get_assignments(assignments); + sLevel += 1; TRACE("t_str", tout << "push to " << sLevel << std::endl;); TRACE_CODE(dump_assignments();); @@ -6544,6 +6550,12 @@ void theory_str::check_variable_scope() { void theory_str::pop_scope_eh(unsigned num_scopes) { sLevel -= num_scopes; TRACE("t_str", tout << "pop " << num_scopes << " to " << sLevel << std::endl;); + // TODO: figure out what's going out of scope and why + context & ctx = get_context(); + ast_manager & m = get_manager(); + expr_ref_vector assignments(m); + ctx.get_assignments(assignments); + TRACE_CODE(dump_assignments();); // list of expr* to remove from cut_var_map @@ -7500,6 +7512,10 @@ final_check_status theory_str::final_check_eh() { context & ctx = get_context(); ast_manager & m = get_manager(); + // TODO out-of-scope term debugging, see comment in pop_scope_eh() + expr_ref_vector assignments(m); + ctx.get_assignments(assignments); + if (opt_VerifyFinalCheckProgress) { finalCheckProgressIndicator = false; } From e7c0c29ae5675afbac72b38ceab0f6e28b7ef525 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Thu, 15 Sep 2016 15:59:56 -0400 Subject: [PATCH 224/410] potentially fix out-of-scope infinite loop bug in theory_str gen_unroll_conditional_options --- src/smt/theory_str.cpp | 57 +++++++++++++++++++++++++++++++----------- 1 file changed, 42 insertions(+), 15 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index d73d55dc3..43d14ccf1 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -504,6 +504,7 @@ app * theory_str::mk_unroll_bound_var() { app * theory_str::mk_unroll_test_var() { app * v = mk_str_var("unrollTest"); // was uRt internal_unrollTest_vars.insert(v); + track_variable_scope(v); return v; } @@ -6595,6 +6596,7 @@ void theory_str::pop_scope_eh(unsigned num_scopes) { variable_set.erase(*var_it); internal_variable_set.erase(*var_it); regex_variable_set.erase(*var_it); + internal_unrollTest_vars.erase(*var_it); count += 1; } TRACE("t_str_detail", tout << "cleaned up " << count << " variables" << std::endl;); @@ -8349,13 +8351,35 @@ expr * theory_str::gen_unroll_conditional_options(expr * var, std::set & litems.push_back(item); } + // handle out-of-scope entries in unroll_tries_map + + ptr_vector outOfScopeTesters; + // TODO refactor unroll_tries_map and internal_unrollTest_vars to use m_trail_stack + + for (ptr_vector::iterator it = unroll_tries_map[var][unrolls].begin(); + it != unroll_tries_map[var][unrolls].end(); ++it) { + expr * tester = *it; + bool inScope = (internal_unrollTest_vars.find(tester) != internal_unrollTest_vars.end()); + TRACE("t_str_detail", tout << "unroll test var " << mk_pp(tester, mgr) + << (inScope ? " in scope" : " out of scope") + << std::endl;); + if (!inScope) { + outOfScopeTesters.push_back(tester); + } + } + + for (ptr_vector::iterator it = outOfScopeTesters.begin(); + it != outOfScopeTesters.end(); ++it) { + unroll_tries_map[var][unrolls].erase(*it); + } + + if (unroll_tries_map[var][unrolls].size() == 0) { unroll_tries_map[var][unrolls].push_back(mk_unroll_test_var()); } int tries = unroll_tries_map[var][unrolls].size(); for (int i = 0; i < tries; i++) { - // TODO possibly missing a scope check here expr * tester = unroll_tries_map[var][unrolls][i]; bool testerHasValue = false; expr * testerVal = get_eqc_value(tester, testerHasValue); @@ -8377,6 +8401,9 @@ expr * theory_str::gen_unroll_conditional_options(expr * var, std::set & m_trail.push_back(toAssert); return toAssert; + // note: this is how the code looks in Z3str2's strRegex.cpp:genUnrollConditionalOptions. + // the return is in the same place + // insert [tester = "more"] to litems so that the implyL for next tester is correct litems.push_back(ctx.mk_eq_atom(tester, moreAst)); } else { @@ -8881,21 +8908,21 @@ void theory_str::process_free_var(std::map & freeVar_map) { * and constant string in eqc of node n */ void theory_str::get_eqc_allUnroll(expr * n, expr * &constStr, std::set & unrollFuncSet) { - constStr = NULL; - unrollFuncSet.clear(); - context & ctx = get_context(); + constStr = NULL; + unrollFuncSet.clear(); + context & ctx = get_context(); - expr * curr = n; - do { - if (is_string(to_app(curr))) { - constStr = curr; - } else if (is_Unroll(to_app(curr))) { - if (unrollFuncSet.find(curr) == unrollFuncSet.end()) { - unrollFuncSet.insert(curr); - } - } - curr = get_eqc_next(curr); - } while (curr != n); + expr * curr = n; + do { + if (is_string(to_app(curr))) { + constStr = curr; + } else if (is_Unroll(to_app(curr))) { + if (unrollFuncSet.find(curr) == unrollFuncSet.end()) { + unrollFuncSet.insert(curr); + } + } + curr = get_eqc_next(curr); + } while (curr != n); } // Collect simple Unroll functions (whose core is Str2Reg) and constant strings in the EQC of n. From 91b625768c4ef1c202817e9c1cff66f6c34b2f15 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Thu, 15 Sep 2016 17:01:59 -0400 Subject: [PATCH 225/410] fix tracing in theory_str --- src/smt/theory_str.cpp | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 43d14ccf1..22df46980 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -148,7 +148,7 @@ void theory_str::assert_axiom(expr * e) { TRACE("t_str_detail", tout << "asserting " << mk_ismt2_pp(e, get_manager()) << std::endl;); context & ctx = get_context(); if (!ctx.b_internalized(e)) { - ctx.internalize(e, true); + ctx.internalize(e, false); } literal lit(ctx.get_literal(e)); ctx.mark_as_relevant(lit); @@ -6492,7 +6492,7 @@ void theory_str::push_scope_eh() { sLevel += 1; TRACE("t_str", tout << "push to " << sLevel << std::endl;); - TRACE_CODE(dump_assignments();); + TRACE_CODE(if (is_trace_enabled("t_str_dump_assign_on_scope_change")) { dump_assignments(); }); } void theory_str::recursive_check_variable_scope(expr * ex) { @@ -6557,7 +6557,7 @@ void theory_str::pop_scope_eh(unsigned num_scopes) { expr_ref_vector assignments(m); ctx.get_assignments(assignments); - TRACE_CODE(dump_assignments();); + TRACE_CODE(if (is_trace_enabled("t_str_dump_assign_on_scope_change")) { dump_assignments(); }); // list of expr* to remove from cut_var_map ptr_vector cutvarmap_removes; @@ -6627,18 +6627,16 @@ void theory_str::pop_scope_eh(unsigned num_scopes) { } void theory_str::dump_assignments() { - ast_manager & m = get_manager(); - context & ctx = get_context(); - TRACE("t_str_dump_assign_on_scope_change", - tout << "dumping all assignments:" << std::endl; - expr_ref_vector assignments(m); - ctx.get_assignments(assignments); - /* - for (expr_ref_vector::iterator i = assignments.begin(); i != assignments.end(); ++i) { - expr * ex = *i; - tout << mk_ismt2_pp(ex, m) << (ctx.is_relevant(ex) ? "" : " (NOT REL)") << std::endl; - } - */ + TRACE_CODE( + ast_manager & m = get_manager(); + context & ctx = get_context(); + tout << "dumping all assignments:" << std::endl; + expr_ref_vector assignments(m); + ctx.get_assignments(assignments); + for (expr_ref_vector::iterator i = assignments.begin(); i != assignments.end(); ++i) { + expr * ex = *i; + tout << mk_ismt2_pp(ex, m) << (ctx.is_relevant(ex) ? "" : " (NOT REL)") << std::endl; + } ); } @@ -7523,7 +7521,7 @@ final_check_status theory_str::final_check_eh() { } TRACE("t_str", tout << "final check" << std::endl;); - TRACE_CODE(dump_assignments();); + TRACE_CODE(if (is_trace_enabled("t_str_dump_assign")) { dump_assignments(); }); check_variable_scope(); if (opt_DeferEQCConsistencyCheck) { From c38f63dd2a6ddd2b19e2cd5bb75837ba08128728 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Mon, 19 Sep 2016 19:42:16 -0400 Subject: [PATCH 226/410] fix eqc management and unroll test var gen in theory_str::final_check --- src/smt/theory_str.cpp | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 22df46980..4ba9aa0ff 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -228,7 +228,7 @@ bool theory_str::internalize_term(app * term) { mk_var(e); return true; } - TRACE("t_str", tout << "internalizing term: " << mk_ismt2_pp(term, get_manager()) << std::endl;); + TRACE("t_str_detail", tout << "internalizing term: " << mk_ismt2_pp(term, get_manager()) << std::endl;); unsigned num_args = term->get_num_args(); expr* arg; for (unsigned i = 0; i < num_args; i++) { @@ -517,6 +517,9 @@ app * theory_str::mk_str_var(std::string name) { sort * string_sort = m.mk_sort(get_family_id(), STRING_SORT); app * a = m.mk_fresh_const(name.c_str(), string_sort); + TRACE("t_str_detail", tout << "a->get_family_id() = " << a->get_family_id() << std::endl + << "this->get_family_id() = " << this->get_family_id() << std::endl;); + // I have a hunch that this may not get internalized for free... ctx.internalize(a, false); SASSERT(ctx.get_enode(a) != NULL); @@ -6584,6 +6587,7 @@ void theory_str::pop_scope_eh(unsigned num_scopes) { } } + /* // see if any internal variables went out of scope for (int check_level = sLevel + num_scopes ; check_level > sLevel; --check_level) { TRACE("t_str_detail", tout << "cleaning up internal variables at scope level " << check_level << std::endl;); @@ -6603,6 +6607,7 @@ void theory_str::pop_scope_eh(unsigned num_scopes) { vars.clear(); } } + */ // TODO use the trail stack to do this for us! requires lots of refactoring // TODO if this works, possibly remove axioms from other vectors as well @@ -6623,7 +6628,7 @@ void theory_str::pop_scope_eh(unsigned num_scopes) { m_trail_stack.pop_scope(num_scopes); theory::pop_scope_eh(num_scopes); - check_variable_scope(); + //check_variable_scope(); } void theory_str::dump_assignments() { @@ -6648,7 +6653,8 @@ void theory_str::classify_ast_by_type(expr * node, std::map & varMap // note that internal variables don't count if they're only length tester / value tester vars. if (variable_set.find(node) != variable_set.end() && internal_lenTest_vars.find(node) == internal_lenTest_vars.end() - && internal_valTest_vars.find(node) == internal_valTest_vars.end()) { + && internal_valTest_vars.find(node) == internal_valTest_vars.end() + && internal_unrollTest_vars.find(node) == internal_unrollTest_vars.end()) { if (varMap[node] != 1) { TRACE("t_str_detail", tout << "new variable: " << mk_pp(node, get_manager()) << std::endl;); } @@ -6988,10 +6994,7 @@ int theory_str::ctx_dep_analysis(std::map & strVarMap, std::mapget_next(); - curr = eqcNode->get_owner(); + curr = get_eqc_next(curr); } while (curr != varItor->first); } @@ -7017,8 +7020,7 @@ int theory_str::ctx_dep_analysis(std::map & strVarMap, std::mapget_next()->get_owner(); + expr * curr = get_eqc_next(deAliasNode); while (curr != deAliasNode) { app * aCurr = to_app(curr); // collect concat @@ -7055,9 +7057,7 @@ int theory_str::ctx_dep_analysis(std::map & strVarMap, std::mapget_next()->get_owner(); + curr = get_eqc_next(curr); } } @@ -7086,9 +7086,7 @@ int theory_str::ctx_dep_analysis(std::map & strVarMap, std::mapget_next()->get_owner(); + curr = get_eqc_next(curr); } while (curr != concatItor->first); } @@ -7121,9 +7119,7 @@ int theory_str::ctx_dep_analysis(std::map & strVarMap, std::mapget_next()->get_owner(); + curr = get_eqc_next(curr); } while (curr != deAliasConcat); } } From 9615b191dedc90f2ca939ff21e23a5e07333620b Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Mon, 19 Sep 2016 23:40:17 -0400 Subject: [PATCH 227/410] theory_str hacking for theory var stuff WIP --- src/smt/smt_context.cpp | 6 +++++ src/smt/theory_str.cpp | 53 +++++++++++++++++++++++++++++++++++++++-- src/smt/theory_str.h | 3 +++ 3 files changed, 60 insertions(+), 2 deletions(-) diff --git a/src/smt/smt_context.cpp b/src/smt/smt_context.cpp index 251cf3b9b..c712135d3 100644 --- a/src/smt/smt_context.cpp +++ b/src/smt/smt_context.cpp @@ -1691,6 +1691,12 @@ namespace smt { for (unsigned i = 0; i < m_th_eq_propagation_queue.size() && !inconsistent(); i++) { new_th_eq curr = m_th_eq_propagation_queue[i]; theory * th = get_theory(curr.m_th_id); + TRACE("t_str_eq_bug", tout + << "th->name = " << th->get_name() << std::endl + << "m_th_id = " << curr.m_th_id << std::endl + << "m_lhs = " << curr.m_lhs << std::endl + << "m_rhs = " << curr.m_rhs << std::endl + << std::endl;); SASSERT(th); th->new_eq_eh(curr.m_lhs, curr.m_rhs); #ifdef Z3DEBUG diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 4ba9aa0ff..8cd7c227c 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -200,6 +200,43 @@ bool theory_str::internalize_term(app * term) { ast_manager & m = get_manager(); SASSERT(term->get_family_id() == get_family_id()); + TRACE("t_str_detail", tout << "internalizing term: " << mk_ismt2_pp(term, get_manager()) << std::endl;); + + // emulation of user_smt_theory::internalize_term() + + unsigned num_args = term->get_num_args(); + for (unsigned i = 0; i < num_args; ++i) { + ctx.internalize(term->get_arg(i), false); + } + if (ctx.e_internalized(term)) { + enode * e = ctx.get_enode(term); + mk_var(e); + return true; + } + // m_parents.push_back(term); + enode * e = 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.set_enode_flag(bv, true); + } + // make sure every argument is attached to a theory variable + for (unsigned i = 0; i < num_args; ++i) { + enode * arg = e->get_arg(i); + theory_var v_arg = mk_var(arg); + TRACE("t_str_detail", tout << "arg has theory var #" << v_arg << std::endl;); + } + + theory_var v = mk_var(e); + TRACE("t_str_detail", tout << "term has theory var #" << v << std::endl;); + + if (opt_EagerStringConstantLengthAssertions && m_strutil.is_string(term)) { + TRACE("t_str", tout << "eagerly asserting length of string term " << mk_pp(term, m) << std::endl;); + m_basicstr_axiom_todo.insert(e); + TRACE("t_str_axiom_bug", tout << "add " << mk_pp(e->get_owner(), m) << " to m_basicstr_axiom_todo" << std::endl;); + } + return true; + /* // what I had before SASSERT(!ctx.e_internalized(term)); @@ -223,6 +260,7 @@ bool theory_str::internalize_term(app * term) { // TODO do we still need to do instantiate_concat_axiom()? // partially from theory_seq::internalize_term() + /* if (ctx.e_internalized(term)) { enode* e = ctx.get_enode(term); mk_var(e); @@ -259,6 +297,7 @@ bool theory_str::internalize_term(app * term) { TRACE("t_str_detail", tout << "term " << mk_ismt2_pp(term, get_manager()) << " = v#" << v << std::endl;); return true; + */ } enode* theory_str::ensure_enode(expr* e) { @@ -271,7 +310,14 @@ enode* theory_str::ensure_enode(expr* e) { return n; } +void theory_str::refresh_theory_var(expr * e) { + enode * en = ensure_enode(e); + theory_var v = mk_var(en); + TRACE("t_str_detail", tout << "refresh " << mk_pp(e, get_manager()) << ": v#" << v << std::endl;); +} + theory_var theory_str::mk_var(enode* n) { + TRACE("t_str_detail", tout << "mk_var for " << mk_pp(n->get_owner(), get_manager()) << std::endl;); /* if (!m_strutil.is_string(n->get_owner())) { return null_theory_var; @@ -283,11 +329,12 @@ theory_var theory_str::mk_var(enode* n) { return null_theory_var; } if (is_attached_to_var(n)) { + TRACE("t_str_detail", tout << "already attached to theory var" << std::endl;); return n->get_th_var(get_id()); - } - else { + } else { theory_var v = theory::mk_var(n); m_find.mk_var(); + TRACE("t_str_detail", tout << "new theory var v#" << v << std::endl;); get_context().attach_th_var(n, this, v); get_context().mark_as_relevant(n); return v; @@ -8375,6 +8422,8 @@ expr * theory_str::gen_unroll_conditional_options(expr * var, std::set & int tries = unroll_tries_map[var][unrolls].size(); for (int i = 0; i < tries; i++) { expr * tester = unroll_tries_map[var][unrolls][i]; + // TESTING + refresh_theory_var(tester); bool testerHasValue = false; expr * testerVal = get_eqc_value(tester, testerHasValue); if (!testerHasValue) { diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 7af6ab1ca..85209c631 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -487,6 +487,9 @@ namespace smt { void check_variable_scope(); void recursive_check_variable_scope(expr * ex); + // TESTING + void refresh_theory_var(expr * e); + public: theory_str(ast_manager & m); virtual ~theory_str(); From f1d7ffcdced6635717911f18c57d46b2af0c69bf Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 20 Sep 2016 00:14:38 -0400 Subject: [PATCH 228/410] fix regression regex-020 --- src/smt/theory_str.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 8cd7c227c..44a4b0d7c 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -7641,9 +7641,9 @@ final_check_status theory_str::final_check_eh() { get_eqc_value(*it, has_eqc_value); if (!has_eqc_value) { // if this is an internal variable, it can be ignored...I think - if (internal_variable_set.find(*it) != internal_variable_set.end()) { + if (internal_variable_set.find(*it) != internal_variable_set.end() || regex_variable_set.find(*it) != regex_variable_set.end()) { TRACE("t_str_detail", tout << "WARNING: free internal variable " << mk_ismt2_pp(*it, m) << std::endl;); - unused_internal_variables.insert(*it); + //unused_internal_variables.insert(*it); } else { needToAssignFreeVars = true; free_variables.insert(*it); From 447c6e4ce362a71f2c2b31f1a1eff7a2a4b91213 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 20 Sep 2016 00:28:29 -0400 Subject: [PATCH 229/410] refresh length tester in theory_str::gen_len_val_options_for_free_var fixes charAt-007.smt2 --- src/smt/theory_str.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 44a4b0d7c..9f0975609 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -8786,6 +8786,7 @@ expr * theory_str::gen_len_val_options_for_free_var(expr * freeVar, expr * lenTe } else { // TODO make absolutely sure this is safe to do if 'indicator' is technically out of scope indicator = fvar_lenTester_map[freeVar][i]; + refresh_theory_var(indicator); testNum = i + 1; } expr * lenTestAssert = gen_len_test_options(freeVar, indicator, testNum); From 48eaa6159cc06f0935d78fe16916c9b147ee79d9 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 20 Sep 2016 01:10:27 -0400 Subject: [PATCH 230/410] disable aggressive unroll testing in theory_str, it may be doing more harm than good --- src/smt/theory_str.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 9f0975609..b2e0e70ba 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -32,7 +32,7 @@ theory_str::theory_str(ast_manager & m): /* Options */ opt_AggressiveLengthTesting(false), opt_AggressiveValueTesting(false), - opt_AggressiveUnrollTesting(true), + opt_AggressiveUnrollTesting(false), opt_EagerStringConstantLengthAssertions(true), opt_VerifyFinalCheckProgress(true), opt_LCMUnrollStep(2), From feef85c129aca77b1f160851681a61042c9f5a66 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 20 Sep 2016 15:37:29 -0400 Subject: [PATCH 231/410] override scope check in theory_str::solve_concat_eq_str fixes indexof2-009.smt2 --- src/smt/theory_str.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index b2e0e70ba..798e16e7c 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -5973,24 +5973,40 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { bool entry1InScope; if (entry1 == varForBreakConcat.end()) { + TRACE("t_str_detail", tout << "key1 no entry" << std::endl;); entry1InScope = false; } else { + // OVERRIDE. + entry1InScope = true; + TRACE("t_str_detail", tout << "key1 entry" << std::endl;); + /* if (internal_variable_set.find((entry1->second)[0]) == internal_variable_set.end()) { + TRACE("t_str_detail", tout << "key1 entry not in scope" << std::endl;); entry1InScope = false; } else { + TRACE("t_str_detail", tout << "key1 entry in scope" << std::endl;); entry1InScope = true; } + */ } bool entry2InScope; if (entry2 == varForBreakConcat.end()) { + TRACE("t_str_detail", tout << "key2 no entry" << std::endl;); entry2InScope = false; } else { + // OVERRIDE. + entry2InScope = true; + TRACE("t_str_detail", tout << "key2 entry" << std::endl;); + /* if (internal_variable_set.find((entry2->second)[0]) == internal_variable_set.end()) { + TRACE("t_str_detail", tout << "key2 entry not in scope" << std::endl;); entry2InScope = false; } else { + TRACE("t_str_detail", tout << "key2 entry in scope" << std::endl;); entry2InScope = true; } + */ } TRACE("t_str_detail", tout << "entry 1 " << (entry1InScope ? "in scope" : "not in scope") << std::endl From 4433417b6ebdf34384e46462fbe5c2f647437200 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 20 Sep 2016 16:25:28 -0400 Subject: [PATCH 232/410] faster push_scope in theory_str --- src/smt/theory_str.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 798e16e7c..59db86212 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -32,7 +32,7 @@ theory_str::theory_str(ast_manager & m): /* Options */ opt_AggressiveLengthTesting(false), opt_AggressiveValueTesting(false), - opt_AggressiveUnrollTesting(false), + opt_AggressiveUnrollTesting(true), opt_EagerStringConstantLengthAssertions(true), opt_VerifyFinalCheckProgress(true), opt_LCMUnrollStep(2), @@ -6551,10 +6551,12 @@ void theory_str::push_scope_eh() { m_trail_stack.push_scope(); // TODO out-of-scope term debugging, see comment in pop_scope_eh() + /* context & ctx = get_context(); ast_manager & m = get_manager(); expr_ref_vector assignments(m); ctx.get_assignments(assignments); + */ sLevel += 1; TRACE("t_str", tout << "push to " << sLevel << std::endl;); From 1061cdf58ab4f3a95675fc13530df3b1aa259136 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Thu, 22 Sep 2016 15:40:43 -0400 Subject: [PATCH 233/410] fix value tester theory var reuse in theory_str fixes release regression in charAt-007 --- src/smt/theory_str.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 59db86212..ab6a9f229 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -8131,6 +8131,7 @@ expr * theory_str::gen_free_var_options(expr * freeVar, expr * len_indicator, if (!anEqcHasValue) { TRACE("t_str_detail", tout << "value tester " << mk_ismt2_pp(aTester, m) << " doesn't have an equivalence class value." << std::endl;); + refresh_theory_var(aTester); expr * makeupAssert = gen_val_options(freeVar, len_indicator, aTester, len_valueStr, i); @@ -8147,6 +8148,7 @@ expr * theory_str::gen_free_var_options(expr * freeVar, expr * len_indicator, expr * valTester = NULL; if (i + 1 < testerTotal) { valTester = fvar_valueTester_map[freeVar][len][i + 1].second; + refresh_theory_var(valTester); } else { valTester = mk_internal_valTest_var(freeVar, len, i + 1); valueTester_fvar_map[valTester] = freeVar; From dc8062ba6727e59dc789ba1a4e27c2fe48eecbb5 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Thu, 22 Sep 2016 20:14:42 -0400 Subject: [PATCH 234/410] patch out contains check for substr reduction fixes all regressions in release build, we may want to revisit this later --- src/smt/theory_str.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index ab6a9f229..23b2af0fb 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -1496,7 +1496,8 @@ void theory_str::instantiate_axiom_Substr(enode * e) { expr_ref ts0_contains_ts1(mk_contains(expr->get_arg(0), ts1), m); expr_ref_vector and_item(m); - and_item.push_back(ts0_contains_ts1); + // TODO simulate this contains check; it causes problems with a few regressions but we might need it for performance + //and_item.push_back(ts0_contains_ts1); and_item.push_back(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(ts0, mk_concat(ts1, ts2)))); and_item.push_back(ctx.mk_eq_atom(expr->get_arg(1), mk_strlen(ts0))); and_item.push_back(ctx.mk_eq_atom(expr->get_arg(2), mk_strlen(ts1))); From ce53b368647c27176d87aa20b97414b7cc7eddf4 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Fri, 14 Oct 2016 12:34:11 -0400 Subject: [PATCH 235/410] theory_str API started --- src/api/api_ast.cpp | 16 ++++++++ src/api/api_context.cpp | 2 + src/api/api_context.h | 6 +++ src/api/api_str.cpp | 80 +++++++++++++++++++++++++++++++++++++ src/api/z3_api.h | 57 ++++++++++++++++++++++++++ src/ast/str_decl_plugin.cpp | 1 + src/ast/str_decl_plugin.h | 5 +++ 7 files changed, 167 insertions(+) create mode 100644 src/api/api_str.cpp diff --git a/src/api/api_ast.cpp b/src/api/api_ast.cpp index 1f16b2d35..7774efcd9 100644 --- a/src/api/api_ast.cpp +++ b/src/api/api_ast.cpp @@ -647,6 +647,12 @@ extern "C" { else if (fid == mk_c(c)->get_seq_fid() && k == RE_SORT) { return Z3_RE_SORT; } + else if (fid == mk_c(c)->get_str_fid() && k == STRING_SORT) { + return Z3_STRING_SORT; + } + else if (fid == mk_c(c)->get_str_fid() && k == REGEX_SORT) { + return Z3_REGEX_SORT; + } else { return Z3_UNKNOWN_SORT; } @@ -1139,6 +1145,16 @@ extern "C" { } } + if (mk_c(c)->get_str_fid() == _d->get_family_id()) { + switch (_d->get_decl_kind()) { + // TODO(z3str2) add others + case OP_STRCAT: return Z3_OP_STR_CONCAT; + case OP_STRLEN: return Z3_OP_STR_LENGTH; + default: + return Z3_OP_UNINTERPRETED; + } + } + if (mk_c(c)->get_fpa_fid() == _d->get_family_id()) { switch (_d->get_decl_kind()) { case OP_FPA_RM_NEAREST_TIES_TO_EVEN: return Z3_OP_FPA_RM_NEAREST_TIES_TO_EVEN; diff --git a/src/api/api_context.cpp b/src/api/api_context.cpp index bc48874a7..8fbb02598 100644 --- a/src/api/api_context.cpp +++ b/src/api/api_context.cpp @@ -74,6 +74,7 @@ namespace api { m_fpa_util(m()), m_dtutil(m()), m_sutil(m()), + m_strutil(m()), m_last_result(m()), m_ast_trail(m()), m_pmanager(m_limit) { @@ -98,6 +99,7 @@ namespace api { m_datalog_fid = m().mk_family_id("datalog_relation"); m_fpa_fid = m().mk_family_id("fpa"); m_seq_fid = m().mk_family_id("seq"); + m_str_fid = m().mk_family_id("str"); m_dt_plugin = static_cast(m().get_plugin(m_dt_fid)); install_tactics(*this); diff --git a/src/api/api_context.h b/src/api/api_context.h index fa6754120..0f2104a2b 100644 --- a/src/api/api_context.h +++ b/src/api/api_context.h @@ -26,6 +26,7 @@ Revision History: #include"arith_decl_plugin.h" #include"bv_decl_plugin.h" #include"seq_decl_plugin.h" +#include"str_decl_plugin.h" #include"datatype_decl_plugin.h" #include"dl_decl_plugin.h" #include"fpa_decl_plugin.h" @@ -61,6 +62,8 @@ namespace api { datatype_util m_dtutil; seq_util m_sutil; + str_util m_strutil; + // Support for old solver API smt_params m_fparams; // ------------------------------- @@ -79,6 +82,7 @@ namespace api { family_id m_pb_fid; family_id m_fpa_fid; family_id m_seq_fid; + family_id m_str_fid; datatype_decl_plugin * m_dt_plugin; std::string m_string_buffer; // temporary buffer used to cache strings sent to the "external" world. @@ -123,6 +127,7 @@ namespace api { fpa_util & fpautil() { return m_fpa_util; } datatype_util& dtutil() { return m_dtutil; } seq_util& sutil() { return m_sutil; } + str_util& strutil() { return m_strutil; } family_id get_basic_fid() const { return m_basic_fid; } family_id get_array_fid() const { return m_array_fid; } family_id get_arith_fid() const { return m_arith_fid; } @@ -132,6 +137,7 @@ namespace api { family_id get_pb_fid() const { return m_pb_fid; } family_id get_fpa_fid() const { return m_fpa_fid; } family_id get_seq_fid() const { return m_seq_fid; } + family_id get_str_fid() const { return m_str_fid; } datatype_decl_plugin * get_dt_plugin() const { return m_dt_plugin; } Z3_error_code get_error_code() const { return m_error_code; } diff --git a/src/api/api_str.cpp b/src/api/api_str.cpp new file mode 100644 index 000000000..a42600f2f --- /dev/null +++ b/src/api/api_str.cpp @@ -0,0 +1,80 @@ +/*++ +Copyright (c) 2016 Microsoft Corporation + +Module Name: + + api_str.cpp + +Abstract: + + API for strings and regular expressions (Z3str2 implementation). + +Author: + + Murphy Berzish (mtrberzi) 2016-10-03. + +Revision History: + +--*/ +#include +#include"z3.h" +#include"api_log_macros.h" +#include"api_context.h" +#include"api_util.h" +#include"ast_pp.h" + +extern "C" { + + Z3_sort Z3_API Z3_mk_str_sort(Z3_context c) { + Z3_TRY; + LOG_Z3_mk_str_sort(c); + RESET_ERROR_CODE(); + sort * ty = mk_c(c)->strutil().mk_string_sort(); + mk_c(c)->save_ast_trail(ty); + RETURN_Z3(of_sort(ty)); + Z3_CATCH_RETURN(0); + } + + Z3_bool Z3_API Z3_is_str_sort(Z3_context c, Z3_sort s) { + Z3_TRY; + LOG_Z3_is_str_sort(c, s); + RESET_ERROR_CODE(); + bool result = mk_c(c)->strutil().is_str_sort(to_sort(s)); + return result?Z3_TRUE:Z3_FALSE; + Z3_CATCH_RETURN(Z3_FALSE); + } + + Z3_bool Z3_API Z3_is_str(Z3_context c, Z3_ast s) { + Z3_TRY; + LOG_Z3_is_str(c, s); + RESET_ERROR_CODE(); + bool result = mk_c(c)->strutil().is_string(to_expr(s)); + return result ? Z3_TRUE : Z3_FALSE; + Z3_CATCH_RETURN(Z3_FALSE); + } + + Z3_string Z3_API Z3_get_str(Z3_context c, Z3_ast s) { + Z3_TRY; + LOG_Z3_get_str(c, s); + RESET_ERROR_CODE(); + if (!mk_c(c)->strutil().is_string(to_expr(s))) { + SET_ERROR_CODE(Z3_INVALID_ARG); + return ""; + } + std::string result = mk_c(c)->strutil().get_string_constant_value(to_expr(s)); + return mk_c(c)->mk_external_string(result); + Z3_CATCH_RETURN(""); + } + + Z3_ast Z3_API Z3_mk_str(Z3_context c, Z3_string str) { + Z3_TRY; + LOG_Z3_mk_str(c, str); + RESET_ERROR_CODE(); + std::string s(str); + app * a = mk_c(c)->strutil().mk_string(str); + mk_c(c)->save_ast_trail(a); + RETURN_Z3(of_ast(a)); + Z3_CATCH_RETURN(0); + } + +}; diff --git a/src/api/z3_api.h b/src/api/z3_api.h index 114490015..7afba979e 100644 --- a/src/api/z3_api.h +++ b/src/api/z3_api.h @@ -165,6 +165,8 @@ typedef enum Z3_ROUNDING_MODE_SORT, Z3_SEQ_SORT, Z3_RE_SORT, + Z3_STRING_SORT, + Z3_REGEX_SORT, Z3_UNKNOWN_SORT = 1000 } Z3_sort_kind; @@ -1150,6 +1152,10 @@ typedef enum { Z3_OP_RE_CONCAT, Z3_OP_RE_UNION, + // theory_str + Z3_OP_STR_CONCAT, + Z3_OP_STR_LENGTH, + // Auxiliary Z3_OP_LABEL = 0x700, Z3_OP_LABEL_LIT, @@ -3145,6 +3151,57 @@ extern "C" { /*@}*/ + /** @name Strings and regular expressions (Z3str2 implementation) */ + /*@{*/ + + /** + \brief Create a string sort for 8-bit ASCII strings. + + This function creates a sort for ASCII strings. + Each character is 8 bits. + + def_API('Z3_mk_str_sort', SORT, (_in(CONTEXT), )) + */ + Z3_sort Z3_API Z3_mk_str_sort(Z3_context c); + + /** + \brief Check if \c s is a string sort. + + def_API('Z3_is_str_sort', BOOL, (_in(CONTEXT), _in(SORT))) + */ + + Z3_bool Z3_API Z3_is_str_sort(Z3_context c, Z3_sort s); + + /** + \brief Determine if \c s is a string constant. + + def_API('Z3_is_str', BOOL, (_in(CONTEXT), _in(AST))) + */ + + Z3_bool Z3_API Z3_is_str(Z3_context c, Z3_ast s); + + /** + \brief Retrieve the string constant stored in \c s. + + \pre Z3_is_str(c, s) + + def_API('Z3_get_str', STRING, (_in(CONTEXT), _in(AST))) + */ + + Z3_string Z3_API Z3_get_str(Z3_context c, Z3_ast s); + + /** + \brief Create a string constant. + + \param c logical context. + \param str The ASCII representation of the string constant. + + def_API('Z3_mk_str', AST, (_in(CONTEXT), _in(STRING))) + */ + Z3_ast Z3_API Z3_mk_str(Z3_context c, Z3_string str); + + /*@}*/ + /** @name Sequences and regular expressions */ /*@{*/ diff --git a/src/ast/str_decl_plugin.cpp b/src/ast/str_decl_plugin.cpp index 08358d46b..aa12e5946 100644 --- a/src/ast/str_decl_plugin.cpp +++ b/src/ast/str_decl_plugin.cpp @@ -322,4 +322,5 @@ str_util::str_util(ast_manager &m) : m_manager(m) { SASSERT(m.has_plugin(symbol("str"))); m_plugin = static_cast(m.get_plugin(m.mk_family_id(symbol("str")))); + m_fid = m_plugin->get_family_id(); } diff --git a/src/ast/str_decl_plugin.h b/src/ast/str_decl_plugin.h index 5b0ca2a3a..aa8204459 100644 --- a/src/ast/str_decl_plugin.h +++ b/src/ast/str_decl_plugin.h @@ -120,6 +120,8 @@ public: family_id get_fid() const { return m_afid; } family_id get_family_id() const { return get_fid(); } + bool is_str_sort(sort* s) const { return is_sort_of(s, m_afid, STRING_SORT); } + bool is_string(expr const * n, const char ** val) const; bool is_string(expr const * n) const; @@ -135,11 +137,14 @@ public: class str_util : public str_recognizers { ast_manager & m_manager; str_decl_plugin * m_plugin; + family_id m_fid; public: str_util(ast_manager & m); ast_manager & get_manager() const { return m_manager; } str_decl_plugin & plugin() { return *m_plugin; } + sort* mk_string_sort() const { return get_manager().mk_sort(m_fid, STRING_SORT, 0, 0); } + app * mk_string(const char * val) { return m_plugin->mk_string(val); } From d57c92f69e14db91d460e32b1430acf8c428adc2 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Thu, 20 Oct 2016 12:25:52 -0400 Subject: [PATCH 236/410] theory_str api: concat, length --- src/api/api_str.cpp | 3 +++ src/api/z3_api.h | 13 +++++++++++++ 2 files changed, 16 insertions(+) diff --git a/src/api/api_str.cpp b/src/api/api_str.cpp index a42600f2f..e28c6a501 100644 --- a/src/api/api_str.cpp +++ b/src/api/api_str.cpp @@ -77,4 +77,7 @@ extern "C" { Z3_CATCH_RETURN(0); } + MK_BINARY(Z3_mk_str_concat, mk_c(c)->get_str_fid(), OP_STRCAT, SKIP); + MK_UNARY(Z3_mk_str_length, mk_c(c)->get_str_fid(), OP_STRLEN, SKIP); + }; diff --git a/src/api/z3_api.h b/src/api/z3_api.h index 7afba979e..c938678d6 100644 --- a/src/api/z3_api.h +++ b/src/api/z3_api.h @@ -3200,6 +3200,19 @@ extern "C" { */ Z3_ast Z3_API Z3_mk_str(Z3_context c, Z3_string str); + /** + \brief Create a string concatenation term. + def_API('Z3_mk_str_concat', AST, (_in(CONTEXT), _in(AST), _in(AST))) + */ + Z3_ast Z3_API Z3_mk_str_concat(Z3_context c, Z3_ast s1, Z3_ast s2); + + /** + \brief Create a string length term. (Integer representation) + def_API('Z3_mk_str_length', AST, (_in(CONTEXT), _in(AST))) + */ + Z3_ast Z3_API Z3_mk_str_length(Z3_context c, Z3_ast s); + + /*@}*/ /** @name Sequences and regular expressions */ From 05dfa5509a0263f48a29e33bf2ebc24a9590b472 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Thu, 20 Oct 2016 15:36:54 -0400 Subject: [PATCH 237/410] theory_str high-level and regex API --- src/api/api_str.cpp | 78 +++++++++++++++++++++++++++++++++++++++++ src/api/z3_api.h | 84 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 162 insertions(+) diff --git a/src/api/api_str.cpp b/src/api/api_str.cpp index e28c6a501..1a1debb5b 100644 --- a/src/api/api_str.cpp +++ b/src/api/api_str.cpp @@ -79,5 +79,83 @@ extern "C" { MK_BINARY(Z3_mk_str_concat, mk_c(c)->get_str_fid(), OP_STRCAT, SKIP); MK_UNARY(Z3_mk_str_length, mk_c(c)->get_str_fid(), OP_STRLEN, SKIP); + MK_BINARY(Z3_mk_str_at, mk_c(c)->get_str_fid(), OP_STR_CHARAT, SKIP); + // translate prefixof/suffixof to StartsWith/EndsWith + // TODO string standardization might just remove StartsWith/EndsWith in future + Z3_ast Z3_API Z3_mk_str_prefixof(Z3_context c, Z3_ast pre, Z3_ast full) { + LOG_Z3_mk_str_prefixof(c, pre, full); + Z3_TRY; + RESET_ERROR_CODE(); + expr * args[2] = { to_expr(full), to_expr(pre) }; // reverse args + ast * a = mk_c(c)->m().mk_app(mk_c(c)->get_str_fid(), OP_STR_STARTSWITH, 0, 0, 2, args); + mk_c(c)->save_ast_trail(a); + check_sorts(c, a); + RETURN_Z3(of_ast(a)); + Z3_CATCH_RETURN(0); + } + Z3_ast Z3_API Z3_mk_str_suffixof(Z3_context c, Z3_ast suf, Z3_ast full) { + LOG_Z3_mk_str_suffixof(c, suf, full); + Z3_TRY; + RESET_ERROR_CODE(); + expr * args[2] = { to_expr(full), to_expr(suf) }; // reverse args + ast * a = mk_c(c)->m().mk_app(mk_c(c)->get_str_fid(), OP_STR_ENDSWITH, 0, 0, 2, args); + mk_c(c)->save_ast_trail(a); + check_sorts(c, a); + RETURN_Z3(of_ast(a)); + Z3_CATCH_RETURN(0); + } + + MK_BINARY(Z3_mk_str_contains, mk_c(c)->get_str_fid(), OP_STR_CONTAINS, SKIP); + MK_TERNARY(Z3_mk_str_indexof, mk_c(c)->get_str_fid(), OP_STR_INDEXOF, SKIP); + MK_TERNARY(Z3_mk_str_substr, mk_c(c)->get_str_fid(), OP_STR_SUBSTR, SKIP); + MK_TERNARY(Z3_mk_str_replace, mk_c(c)->get_str_fid(), OP_STR_REPLACE, SKIP); + + Z3_ast Z3_API Z3_mk_str_to_regex(Z3_context c, Z3_string str) { + LOG_Z3_mk_str_to_regex(c, str); + Z3_TRY; + RESET_ERROR_CODE(); + std::string s(str); + app * a = mk_c(c)->strutil().mk_string(str); + mk_c(c)->save_ast_trail(a); + + expr * args[1] = { to_expr(a) }; + ast * re = mk_c(c)->m().mk_app(mk_c(c)->get_str_fid(), OP_RE_STR2REGEX, 0, 0, 1, args); + mk_c(c)->save_ast_trail(re); + check_sorts(c, re); + RETURN_Z3(of_ast(re)); + Z3_CATCH_RETURN(0); + } + + MK_BINARY(Z3_mk_str_in_regex, mk_c(c)->get_str_fid(), OP_RE_REGEXIN, SKIP); + MK_BINARY(Z3_mk_regex_concat, mk_c(c)->get_str_fid(), OP_RE_REGEXCONCAT, SKIP); + MK_BINARY(Z3_mk_regex_union, mk_c(c)->get_str_fid(), OP_RE_REGEXUNION, SKIP); + MK_UNARY(Z3_mk_regex_star, mk_c(c)->get_str_fid(), OP_RE_REGEXSTAR, SKIP); + MK_UNARY(Z3_mk_regex_plus, mk_c(c)->get_str_fid(), OP_RE_REGEXPLUS, SKIP); + + Z3_ast Z3_API Z3_mk_regex_range(Z3_context c, Z3_string start, Z3_string end) { + LOG_Z3_mk_regex_range(c, start, end); + Z3_TRY; + RESET_ERROR_CODE(); + + std::string cStart(start); + std::string cEnd(end); + if(cStart.length() != 1 || cEnd.length() != 1) { + SET_ERROR_CODE(Z3_INVALID_ARG); + return 0; + } + + app * a1 = mk_c(c)->strutil().mk_string(cStart); + mk_c(c)->save_ast_trail(a1); + app * a2 = mk_c(c)->strutil().mk_string(cEnd); + mk_c(c)->save_ast_trail(a2); + + expr * args[2] = { to_expr(a1), to_expr(a2) }; + ast * range = mk_c(c)->m().mk_app(mk_c(c)->get_str_fid(), OP_RE_REGEXCHARRANGE, 0, 0, 2, args); + mk_c(c)->save_ast_trail(range); + check_sorts(c, range); + RETURN_Z3(of_ast(range)); + + Z3_CATCH_RETURN(0); + } }; diff --git a/src/api/z3_api.h b/src/api/z3_api.h index c938678d6..7494bcb17 100644 --- a/src/api/z3_api.h +++ b/src/api/z3_api.h @@ -3212,6 +3212,90 @@ extern "C" { */ Z3_ast Z3_API Z3_mk_str_length(Z3_context c, Z3_ast s); + /** + \brief Create 'character at index' term. + def_API('Z3_mk_str_at', AST, (_in(CONTEXT), _in(AST), _in(AST))) + */ + Z3_ast Z3_API Z3_mk_str_at(Z3_context c, Z3_ast s, Z3_ast idx); + + /** + \brief Create 'str.prefixof' term. + def_API('Z3_mk_str_prefixof', AST, (_in(CONTEXT), _in(AST), _in(AST))) + */ + Z3_ast Z3_API Z3_mk_str_prefixof(Z3_context c, Z3_ast pre, Z3_ast full); + + /** + \brief Create 'str.suffixof' term. + def_API('Z3_mk_str_suffixof', AST, (_in(CONTEXT), _in(AST), _in(AST))) + */ + Z3_ast Z3_API Z3_mk_str_suffixof(Z3_context c, Z3_ast suf, Z3_ast full); + + /** + \brief Create 'str.contains' term. + def_API('Z3_mk_str_contains', AST, (_in(CONTEXT), _in(AST), _in(AST))) + */ + Z3_ast Z3_API Z3_mk_str_contains(Z3_context c, Z3_ast needle, Z3_ast haystack); + + /** + \brief Create 'str.indexof' term. + def_API('Z3_mk_str_indexof', AST, (_in(CONTEXT), _in(AST), _in(AST), _in(AST))) + */ + Z3_ast Z3_API Z3_mk_str_indexof(Z3_context c, Z3_ast haystack, Z3_ast needle, Z3_ast start); + + /** + \brief Create 'str.substr' term. + def_API('Z3_mk_str_substr', AST, (_in(CONTEXT), _in(AST), _in(AST), _in(AST))) + */ + Z3_ast Z3_API Z3_mk_str_substr(Z3_context c, Z3_ast s, Z3_ast start, Z3_ast count); + + /** + \brief Create 'str.replace' term. + def_API('Z3_mk_str_replace', AST, (_in(CONTEXT), _in(AST), _in(AST), _in(AST))) + */ + Z3_ast Z3_API Z3_mk_str_replace(Z3_context c, Z3_ast base, Z3_ast target, Z3_ast replacement); + + + /** + \brief Create a regular expression that matches the given string constant. + def_API('Z3_mk_str_to_regex', AST, (_in(CONTEXT), _in(STRING))) + */ + Z3_ast Z3_API Z3_mk_str_to_regex(Z3_context c, Z3_string str); + + /** + \brief Create a regular expression membership predicate. + def_API('Z3_mk_str_in_regex', AST, (_in(CONTEXT), _in(AST), _in(AST))) + */ + Z3_ast Z3_API Z3_mk_str_in_regex(Z3_context c, Z3_ast str, Z3_ast regex); + + /** + \brief Create a regex concatenation term. + def_API('Z3_mk_regex_concat', AST, (_in(CONTEXT), _in(AST), _in(AST))) + */ + Z3_ast Z3_API Z3_mk_regex_concat(Z3_context c, Z3_ast r1, Z3_ast r2); + + /** + \brief Create a regex union term. + def_API('Z3_mk_regex_union', AST, (_in(CONTEXT), _in(AST), _in(AST))) + */ + Z3_ast Z3_API Z3_mk_regex_union(Z3_context c, Z3_ast r1, Z3_ast r2); + + /** + \brief Create a regex Kleene star term. + def_API('Z3_mk_regex_star', AST, (_in(CONTEXT), _in(AST))) + */ + Z3_ast Z3_API Z3_mk_regex_star(Z3_context c, Z3_ast r); + + /** + \brief Create a regex plus term. + def_API('Z3_mk_regex_plus', AST, (_in(CONTEXT), _in(AST))) + */ + Z3_ast Z3_API Z3_mk_regex_plus(Z3_context c, Z3_ast r); + + /** + \brief Create a regex character range term. + def_API('Z3_mk_regex_range', AST, (_in(CONTEXT), _in(STRING), _in(STRING))) + */ + Z3_ast Z3_API Z3_mk_regex_range(Z3_context c, Z3_string start, Z3_string end); /*@}*/ From ef0f6f1de346ddac51a5ff1bb114120312bde0f1 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Thu, 20 Oct 2016 16:01:51 -0400 Subject: [PATCH 238/410] add str.to-int in theory_str WIP --- src/ast/rewriter/str_rewriter.cpp | 39 +++++++++++++++++++++++++++++++ src/ast/rewriter/str_rewriter.h | 1 + src/ast/str_decl_plugin.cpp | 7 ++++++ src/ast/str_decl_plugin.h | 3 +++ 4 files changed, 50 insertions(+) diff --git a/src/ast/rewriter/str_rewriter.cpp b/src/ast/rewriter/str_rewriter.cpp index 015898a64..db3885f28 100644 --- a/src/ast/rewriter/str_rewriter.cpp +++ b/src/ast/rewriter/str_rewriter.cpp @@ -25,6 +25,7 @@ Notes: #include #include #include +#include // Convert a regular expression to an e-NFA using Thompson's construction void nfa::convert_re(expr * e, unsigned & start, unsigned & end, str_util & m_strutil) { @@ -374,6 +375,41 @@ br_status str_rewriter::mk_str_Replace(expr * base, expr * source, expr * target } } +br_status str_rewriter::mk_str_to_int(expr * arg0, expr_ref & result) { + TRACE("t_str_rw", tout << "rewrite (str.to-int " << mk_pp(arg0, m()) << ")" << std::endl;); + + if (m_strutil.is_string(arg0)) { + std::string str = m_strutil.get_string_constant_value(arg0); + if (str.length() == 0) { + result = m_autil.mk_numeral(rational::zero(), true); + return BR_DONE; + } + + // interpret str as a natural number and rewrite to the corresponding integer. + // if this is not valid, rewrite to -1 + // TODO leading zeroes? + rational convertedRepresentation(0); + rational ten(10); + for (unsigned i = 0; i < str.length(); ++i) { + char digit = str.at(i); + if (isdigit((int)digit)) { + std::string sDigit(1, digit); + int val = atoi(sDigit.c_str()); + convertedRepresentation = (ten * convertedRepresentation) + rational(val); + } else { + // not a digit, invalid + TRACE("t_str_rw", tout << "str.to-int argument contains non-digit character '" << digit << "'" << std::endl;); + convertedRepresentation = rational::minus_one(); + break; + } + } + result = m_autil.mk_numeral(convertedRepresentation, true); + return BR_DONE; + } + return BR_FAILED; + +} + br_status str_rewriter::mk_str_Substr(expr * base, expr * start, expr * len, expr_ref & result) { TRACE("t_str_rw", tout << "rewrite (Substr " << mk_pp(base, m()) << " " << mk_pp(start, m()) << " " << mk_pp(len, m()) << ")" << std::endl;); rational startVal, lenVal; @@ -520,6 +556,9 @@ br_status str_rewriter::mk_app_core(func_decl * f, unsigned num_args, expr * con case OP_STR_REPLACE: SASSERT(num_args == 3); return mk_str_Replace(args[0], args[1], args[2], result); + case OP_STR_STR2INT: + SASSERT(num_args == 1); + return mk_str_to_int(args[0], result); case OP_STR_SUBSTR: SASSERT(num_args == 3); return mk_str_Substr(args[0], args[1], args[2], result); diff --git a/src/ast/rewriter/str_rewriter.h b/src/ast/rewriter/str_rewriter.h index d147e82e8..10898eae7 100644 --- a/src/ast/rewriter/str_rewriter.h +++ b/src/ast/rewriter/str_rewriter.h @@ -53,6 +53,7 @@ public: br_status mk_str_LastIndexof(expr * haystack, expr * needle, expr_ref & result); br_status mk_str_Replace(expr * base, expr * source, expr * target, expr_ref & result); br_status mk_str_Substr(expr * base, expr * start, expr * len, expr_ref & result); + br_status mk_str_to_int(expr * arg0, expr_ref & result); br_status mk_re_Str2Reg(expr * str, expr_ref & result); br_status mk_re_RegexIn(expr * str, expr * re, expr_ref & result); diff --git a/src/ast/str_decl_plugin.cpp b/src/ast/str_decl_plugin.cpp index aa12e5946..e8455ffbd 100644 --- a/src/ast/str_decl_plugin.cpp +++ b/src/ast/str_decl_plugin.cpp @@ -36,6 +36,7 @@ str_decl_plugin::str_decl_plugin(): m_lastindexof_decl(0), m_substr_decl(0), m_replace_decl(0), + m_str2int_decl(0), m_re_str2regex_decl(0), m_re_regexin_decl(0), m_re_regexconcat_decl(0), @@ -67,6 +68,7 @@ void str_decl_plugin::finalize(void) { DEC_REF(m_lastindexof_decl); DEC_REF(m_substr_decl); DEC_REF(m_replace_decl); + DEC_REF(m_str2int_decl); DEC_REF(m_re_str2regex_decl); DEC_REF(m_re_regexin_decl); DEC_REF(m_re_regexconcat_decl); @@ -145,6 +147,9 @@ void str_decl_plugin::set_manager(ast_manager * m, family_id id) { m_manager->inc_ref(m_replace_decl); } + m_str2int_decl = m->mk_func_decl(symbol("str.to-int"), s, i, func_decl_info(id, OP_STR_STR2INT)); + m_manager->inc_ref(m_str2int_decl); + m_re_str2regex_decl = m->mk_func_decl(symbol("Str2Reg"), s, re, func_decl_info(id, OP_RE_STR2REGEX)); m_manager->inc_ref(m_re_str2regex_decl); @@ -196,6 +201,7 @@ func_decl * str_decl_plugin::mk_func_decl(decl_kind k) { case OP_STR_LASTINDEXOF: return m_lastindexof_decl; case OP_STR_SUBSTR: return m_substr_decl; case OP_STR_REPLACE: return m_replace_decl; + case OP_STR_STR2INT: return m_str2int_decl; case OP_RE_STR2REGEX: return m_re_str2regex_decl; case OP_RE_REGEXIN: return m_re_regexin_decl; case OP_RE_REGEXCONCAT: return m_re_regexconcat_decl; @@ -269,6 +275,7 @@ void str_decl_plugin::get_op_names(svector & op_names, symbol cons op_names.push_back(builtin_name("LastIndexof", OP_STR_LASTINDEXOF)); op_names.push_back(builtin_name("Substring", OP_STR_SUBSTR)); op_names.push_back(builtin_name("Replace", OP_STR_REPLACE)); + op_names.push_back(builtin_name("str.to-int", OP_STR_STR2INT)); op_names.push_back(builtin_name("Str2Reg", OP_RE_STR2REGEX)); op_names.push_back(builtin_name("RegexIn", OP_RE_REGEXIN)); op_names.push_back(builtin_name("RegexConcat", OP_RE_REGEXCONCAT)); diff --git a/src/ast/str_decl_plugin.h b/src/ast/str_decl_plugin.h index aa8204459..ba2b4f751 100644 --- a/src/ast/str_decl_plugin.h +++ b/src/ast/str_decl_plugin.h @@ -41,6 +41,8 @@ enum str_op_kind { OP_STR_LASTINDEXOF, OP_STR_SUBSTR, OP_STR_REPLACE, + // string-integer conversion + OP_STR_STR2INT, // regular expression operators OP_RE_STR2REGEX, OP_RE_REGEXIN, @@ -73,6 +75,7 @@ protected: func_decl * m_lastindexof_decl; func_decl * m_substr_decl; func_decl * m_replace_decl; + func_decl * m_str2int_decl; func_decl * m_re_str2regex_decl; func_decl * m_re_regexin_decl; From b06b9f9264ec75c3dcbe324d666e0dc3e61193fd Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Fri, 21 Oct 2016 13:35:35 -0400 Subject: [PATCH 239/410] str.to-int WIP --- src/smt/theory_str.cpp | 120 ++++++++++++++++++++++++++++++++++++++++- src/smt/theory_str.h | 12 +++++ 2 files changed, 131 insertions(+), 1 deletion(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 23b2af0fb..ffaf098f7 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -55,6 +55,7 @@ theory_str::theory_str(ast_manager & m): avoidLoopCut(true), loopDetected(false), contains_map(m), + string_int_conversion_terms(m), m_find(*this), m_trail_stack(*this) { @@ -821,7 +822,7 @@ bool theory_str::can_propagate() { || !m_axiom_CharAt_todo.empty() || !m_axiom_StartsWith_todo.empty() || !m_axiom_EndsWith_todo.empty() || !m_axiom_Contains_todo.empty() || !m_axiom_Indexof_todo.empty() || !m_axiom_Indexof2_todo.empty() || !m_axiom_LastIndexof_todo.empty() || !m_axiom_Substr_todo.empty() || !m_axiom_Replace_todo.empty() - || !m_axiom_RegexIn_todo.empty() + || !m_axiom_RegexIn_todo.empty() || !m_library_aware_axiom_todo.empty() || !m_delayed_axiom_setup_terms.empty(); ; } @@ -904,6 +905,17 @@ void theory_str::propagate() { } m_axiom_RegexIn_todo.reset(); + for (unsigned i = 0; i < m_library_aware_axiom_todo.size(); ++i) { + enode * e = m_library_aware_axiom_todo[i]; + if (is_str_to_int(e)) { + instantiate_axiom_str_to_int(e); + } else { + TRACE("t_str", tout << "BUG: unhandled library-aware term " << mk_pp(e->get_owner(), get_manager()) << std::endl;); + NOT_IMPLEMENTED_YET(); + } + } + m_library_aware_axiom_todo.reset(); + for (unsigned i = 0; i < m_delayed_axiom_setup_terms.size(); ++i) { // I think this is okay ctx.internalize(m_delayed_axiom_setup_terms[i].get(), false); @@ -1563,6 +1575,53 @@ void theory_str::instantiate_axiom_Replace(enode * e) { assert_axiom(finalAxiom); } +void theory_str::instantiate_axiom_str_to_int(enode * e) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + app * ex = e->get_owner(); + if (axiomatized_terms.contains(ex)) { + TRACE("t_str_detail", tout << "already set up str.to-int axiom for " << mk_pp(ex, m) << std::endl;); + return; + } + axiomatized_terms.insert(ex); + + TRACE("t_str_detail", tout << "instantiate str.to-int axiom for " << mk_pp(ex, m) << std::endl;); + + // let expr = (str.to-int S) + // axiom 1: expr >= -1 + // axiom 2: expr = 0 <==> S = "0" + // axiom 3: expr >= 1 ==> len(S) > 0 AND S[0] != "0" + + expr * S = ex->get_arg(0); + { + expr_ref axiom1(m_autil.mk_ge(ex, m_autil.mk_numeral(rational::minus_one(), true)), m); + SASSERT(axiom1); + assert_axiom(axiom1); + } + + { + expr_ref lhs(ctx.mk_eq_atom(ex, m_autil.mk_numeral(rational::zero(), true)), m); + expr_ref rhs(ctx.mk_eq_atom(S, m_strutil.mk_string("0")), m); + expr_ref axiom2(ctx.mk_eq_atom(lhs, rhs), m); + SASSERT(axiom2); + assert_axiom(axiom2); + } + + { + expr_ref premise(m_autil.mk_ge(ex, m_autil.mk_numeral(rational::one(), true)), m); + expr_ref hd(mk_str_var("hd"), m); + expr_ref tl(mk_str_var("tl"), m); + expr_ref conclusion1(ctx.mk_eq_atom(S, mk_concat(hd, tl)), m); + expr_ref conclusion2(ctx.mk_eq_atom(mk_strlen(hd), m_autil.mk_numeral(rational::one(), true)), m); + expr_ref conclusion3(m.mk_not(ctx.mk_eq_atom(hd, m_strutil.mk_string("0"))), m); + expr_ref conclusion(m.mk_and(conclusion1, conclusion2, conclusion3), m); + SASSERT(premise); + SASSERT(conclusion); + assert_implication(premise, conclusion); + } +} + expr * theory_str::mk_RegexIn(expr * str, expr * regexp) { expr * args[2] = {str, regexp}; app * regexIn = get_manager().mk_app(get_id(), OP_RE_REGEXIN, 0, 0, 2, args); @@ -6438,6 +6497,9 @@ void theory_str::set_up_axioms(expr * ex) { m_axiom_Indexof2_todo.push_back(n); } else if (is_LastIndexof(ap)) { m_axiom_LastIndexof_todo.push_back(n); + } else if (is_str_to_int(ap)) { + string_int_conversion_terms.push_back(ap); + m_library_aware_axiom_todo.push_back(n); } } } else { @@ -7570,6 +7632,42 @@ int theory_str::ctx_dep_analysis(std::map & strVarMap, std::mapget_arg(0); + + // check integer theory + rational Ival; + bool Ival_exists = get_value(a, Ival); + if (Ival_exists) { + TRACE("t_str_detail", tout << "integer theory assigns " << mk_pp(a, m) << " = " << Ival.to_string() << std::endl;); + // if that value is not -1, we can assert (str.to-int S) = Ival --> S = "Ival" + if (!Ival.is_minus_one()) { + std::string Ival_str = Ival.to_string(); + expr_ref premise(ctx.mk_eq_atom(a, m_autil.mk_numeral(Ival, true)), m); + expr_ref conclusion(ctx.mk_eq_atom(S, m_strutil.mk_string(Ival_str)), m); + expr_ref axiom(m.mk_or(m.mk_not(premise), conclusion), m); + if (!string_int_axioms.contains(axiom)) { + string_int_axioms.insert(axiom); + assert_axiom(axiom); + m_trail_stack.push(insert_obj_trail(string_int_axioms, axiom)); + axiomAdd = true; + } + } + } else { + TRACE("t_str_detail", tout << "integer theory has no assignment for " << mk_pp(a, m) << std::endl;); + NOT_IMPLEMENTED_YET(); + } + // TODO also check assignment in string theory + + return axiomAdd; +} + final_check_status theory_str::final_check_eh() { context & ctx = get_context(); ast_manager & m = get_manager(); @@ -7671,6 +7769,26 @@ final_check_status theory_str::final_check_eh() { } if (!needToAssignFreeVars) { + + // check string-int terms + bool addedStrIntAxioms = false; + for (unsigned i = 0; i < string_int_conversion_terms.size(); ++i) { + app * ex = to_app(string_int_conversion_terms[i].get()); + if (is_str_to_int(ex)) { + bool axiomAdd = finalcheck_str2int(ex); + if (axiomAdd) { + addedStrIntAxioms = true; + } + } else { + // TODO int.to-str + NOT_IMPLEMENTED_YET(); + } + } + if (addedStrIntAxioms) { + TRACE("t_str", tout << "Resuming search due to addition of string-integer conversion axioms." << std::endl;); + return FC_CONTINUE; + } + if (unused_internal_variables.empty()) { TRACE("t_str", tout << "All variables are assigned. Done!" << std::endl;); return FC_DONE; diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 85209c631..5fd2e980b 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -201,6 +201,9 @@ namespace smt { ptr_vector m_axiom_Replace_todo; ptr_vector m_axiom_RegexIn_todo; + // TODO refactor everything to use this worklist + ptr_vector m_library_aware_axiom_todo; + // hashtable of all exprs for which we've already set up term-specific axioms -- // this prevents infinite recursive descent with respect to axioms that // include an occurrence of the term for which axioms are being generated @@ -260,6 +263,10 @@ namespace smt { obj_pair_map concat_astNode_map; + // all (str.to-int) and (int.to-str) terms + expr_ref_vector string_int_conversion_terms; + obj_hashtable string_int_axioms; + th_union_find m_find; th_trail_stack m_trail_stack; theory_var get_var(expr * n) const; @@ -320,6 +327,8 @@ namespace smt { bool is_Substr(enode const * n) const { return is_Substr(n->get_owner()); } bool is_Replace(app const * a) const { return a->is_app_of(get_id(), OP_STR_REPLACE); } bool is_Replace(enode const * n) const { return is_Replace(n->get_owner()); } + bool is_str_to_int(app const * a) const { return a->is_app_of(get_id(), OP_STR_STR2INT); } + bool is_str_to_int(enode const * n) const { return is_str_to_int(n->get_owner()); } bool is_RegexIn(app const * a) const { return a->is_app_of(get_id(), OP_RE_REGEXIN); } bool is_RegexIn(enode const * n) const { return is_RegexIn(n->get_owner()); } @@ -348,6 +357,7 @@ namespace smt { void instantiate_axiom_LastIndexof(enode * e); void instantiate_axiom_Substr(enode * e); void instantiate_axiom_Replace(enode * e); + void instantiate_axiom_str_to_int(enode * e); expr * mk_RegexIn(expr * str, expr * regexp); void instantiate_axiom_RegexIn(enode * e); @@ -469,6 +479,8 @@ namespace smt { void get_const_str_asts_in_node(expr * node, expr_ref_vector & constList); expr * eval_concat(expr * n1, expr * n2); + bool finalcheck_str2int(app * a); + // strRegex void get_eqc_allUnroll(expr * n, expr * &constStr, std::set & unrollFuncSet); From 452eed662603e658372712f37250fd3a1bde2832 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sat, 29 Oct 2016 12:19:24 -0400 Subject: [PATCH 240/410] move get_std_regex_str to str_util --- src/ast/str_decl_plugin.cpp | 43 +++++++++++++++++++++++++++++++++++ src/ast/str_decl_plugin.h | 2 ++ src/smt/theory_str.cpp | 45 +------------------------------------ src/smt/theory_str.h | 1 - 4 files changed, 46 insertions(+), 45 deletions(-) diff --git a/src/ast/str_decl_plugin.cpp b/src/ast/str_decl_plugin.cpp index e8455ffbd..333ae2a02 100644 --- a/src/ast/str_decl_plugin.cpp +++ b/src/ast/str_decl_plugin.cpp @@ -331,3 +331,46 @@ str_util::str_util(ast_manager &m) : m_plugin = static_cast(m.get_plugin(m.mk_family_id(symbol("str")))); m_fid = m_plugin->get_family_id(); } + +static std::string str2RegexStr(std::string str) { + std::string res = ""; + int len = str.size(); + for (int i = 0; i < len; i++) { + char nc = str[i]; + // 12 special chars + if (nc == '\\' || nc == '^' || nc == '$' || nc == '.' || nc == '|' || nc == '?' + || nc == '*' || nc == '+' || nc == '(' || nc == ')' || nc == '[' || nc == '{') { + res.append(1, '\\'); + } + res.append(1, str[i]); + } + return res; +} + +std::string str_util::get_std_regex_str(expr * regex) { + app * a_regex = to_app(regex); + if (is_re_Str2Reg(a_regex)) { + expr * regAst = a_regex->get_arg(0); + std::string regStr = str2RegexStr(get_string_constant_value(regAst)); + return regStr; + } else if (is_re_RegexConcat(a_regex)) { + expr * reg1Ast = a_regex->get_arg(0); + expr * reg2Ast = a_regex->get_arg(1); + std::string reg1Str = get_std_regex_str(reg1Ast); + std::string reg2Str = get_std_regex_str(reg2Ast); + return "(" + reg1Str + ")(" + reg2Str + ")"; + } else if (is_re_RegexUnion(a_regex)) { + expr * reg1Ast = a_regex->get_arg(0); + expr * reg2Ast = a_regex->get_arg(1); + std::string reg1Str = get_std_regex_str(reg1Ast); + std::string reg2Str = get_std_regex_str(reg2Ast); + return "(" + reg1Str + ")|(" + reg2Str + ")"; + } else if (is_re_RegexStar(a_regex)) { + expr * reg1Ast = a_regex->get_arg(0); + std::string reg1Str = get_std_regex_str(reg1Ast); + return "(" + reg1Str + ")*"; + } else { + TRACE("t_str", tout << "BUG: unrecognized regex term " << mk_pp(regex, get_manager()) << std::endl;); + UNREACHABLE(); return ""; + } +} diff --git a/src/ast/str_decl_plugin.h b/src/ast/str_decl_plugin.h index ba2b4f751..e9ab43865 100644 --- a/src/ast/str_decl_plugin.h +++ b/src/ast/str_decl_plugin.h @@ -182,6 +182,8 @@ public: return m_manager.mk_app(get_fid(), OP_RE_REGEXSTAR, 1, es); } + std::string get_std_regex_str(expr * regex); + }; #endif /* _STR_DECL_PLUGIN_H_ */ diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 23b2af0fb..1050e4a66 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -1572,49 +1572,6 @@ expr * theory_str::mk_RegexIn(expr * str, expr * regexp) { return regexIn; } -static std::string str2RegexStr(std::string str) { - std::string res = ""; - int len = str.size(); - for (int i = 0; i < len; i++) { - char nc = str[i]; - // 12 special chars - if (nc == '\\' || nc == '^' || nc == '$' || nc == '.' || nc == '|' || nc == '?' - || nc == '*' || nc == '+' || nc == '(' || nc == ')' || nc == '[' || nc == '{') { - res.append(1, '\\'); - } - res.append(1, str[i]); - } - return res; -} - -std::string theory_str::get_std_regex_str(expr * regex) { - app * a_regex = to_app(regex); - if (is_Str2Reg(a_regex)) { - expr * regAst = a_regex->get_arg(0); - std::string regStr = str2RegexStr(m_strutil.get_string_constant_value(regAst)); - return regStr; - } else if (is_RegexConcat(a_regex)) { - expr * reg1Ast = a_regex->get_arg(0); - expr * reg2Ast = a_regex->get_arg(1); - std::string reg1Str = get_std_regex_str(reg1Ast); - std::string reg2Str = get_std_regex_str(reg2Ast); - return "(" + reg1Str + ")(" + reg2Str + ")"; - } else if (is_RegexUnion(a_regex)) { - expr * reg1Ast = a_regex->get_arg(0); - expr * reg2Ast = a_regex->get_arg(1); - std::string reg1Str = get_std_regex_str(reg1Ast); - std::string reg2Str = get_std_regex_str(reg2Ast); - return "(" + reg1Str + ")|(" + reg2Str + ")"; - } else if (is_RegexStar(a_regex)) { - expr * reg1Ast = a_regex->get_arg(0); - std::string reg1Str = get_std_regex_str(reg1Ast); - return "(" + reg1Str + ")*"; - } else { - TRACE("t_str", tout << "BUG: unrecognized regex term " << mk_pp(regex, get_manager()) << std::endl;); - UNREACHABLE(); return ""; - } -} - void theory_str::instantiate_axiom_RegexIn(enode * e) { context & ctx = get_context(); ast_manager & m = get_manager(); @@ -1629,7 +1586,7 @@ void theory_str::instantiate_axiom_RegexIn(enode * e) { TRACE("t_str_detail", tout << "instantiate RegexIn axiom for " << mk_pp(ex, m) << std::endl;); { - std::string regexStr = get_std_regex_str(ex->get_arg(1)); + std::string regexStr = m_strutil.get_std_regex_str(ex->get_arg(1)); std::pair key1(ex->get_arg(0), regexStr); // skip Z3str's map check, because we already check if we set up axioms on this term regex_in_bool_map[key1] = ex; diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 85209c631..43552f31a 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -478,7 +478,6 @@ namespace smt { expr * gen_unroll_conditional_options(expr * var, std::set & unrolls, std::string lcmStr); expr * gen_unroll_assign(expr * var, std::string lcmStr, expr * testerVar, int l, int h); void reduce_virtual_regex_in(expr * var, expr * regex, expr_ref_vector & items); - std::string get_std_regex_str(expr * regex); void check_regex_in(expr * nn1, expr * nn2); void dump_assignments(); From 3da78f9d8015676b36e3869a3f9a665aa29f1d10 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 1 Nov 2016 20:35:01 -0400 Subject: [PATCH 241/410] experimental cached length testers in theory_str --- src/smt/theory_str.cpp | 24 +++++++++++++++++++++--- src/smt/theory_str.h | 12 ++++++++++++ 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 1050e4a66..45b715247 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -40,6 +40,7 @@ theory_str::theory_str(ast_manager & m): opt_DisableIntegerTheoryIntegration(false), opt_DeferEQCConsistencyCheck(false), opt_CheckVariableScope(true), + opt_UseFastLengthTesterCache(true), /* Internal setup */ search_started(false), m_autil(m), @@ -8536,9 +8537,25 @@ expr * theory_str::gen_len_test_options(expr * freeVar, expr * indicator, int tr ); for (int i = l; i < h; ++i) { - std::string i_str = int_to_string(i); - expr_ref str_indicator(m_strutil.mk_string(i_str), m); - expr_ref or_expr(ctx.mk_eq_atom(indicator, str_indicator), m); // ARGUMENT 2 IS BOGUS! WRONG SORT + expr_ref str_indicator(m); + if (opt_UseFastLengthTesterCache) { + rational ri(i); + expr * lookup_val; + if(lengthTesterCache.find(ri, lookup_val)) { + str_indicator = expr_ref(lookup_val, m); + } else { + // no match; create and insert + std::string i_str = int_to_string(i); + expr_ref new_val(m_strutil.mk_string(i_str), m); + lengthTesterCache.insert(ri, new_val); + m_trail.push_back(new_val); + str_indicator = expr_ref(new_val, m); + } + } else { + std::string i_str = int_to_string(i); + expr_ref str_indicator(m_strutil.mk_string(i_str), m); + } + expr_ref or_expr(ctx.mk_eq_atom(indicator, str_indicator), m); orList.push_back(or_expr); if (opt_AggressiveLengthTesting) { @@ -8551,6 +8568,7 @@ expr * theory_str::gen_len_test_options(expr * freeVar, expr * indicator, int tr andList.push_back(and_expr); } + // TODO cache mk_string("more") orList.push_back(m.mk_eq(indicator, m_strutil.mk_string("more"))); if (opt_AggressiveLengthTesting) { literal l = mk_eq(indicator, m_strutil.mk_string("more"), false); diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 43552f31a..9b41c583b 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -88,6 +88,8 @@ namespace smt { typedef trail_stack th_trail_stack; typedef union_find th_union_find; + typedef map, default_eq > rational_map; + protected: // Some options that control how the solver operates. @@ -167,6 +169,13 @@ namespace smt { */ bool opt_CheckVariableScope; + /* + * If UseFastLengthTesterCache is set to true, + * length tester terms will not be generated from scratch each time they are needed, + * but will be saved in a map and looked up. + */ + bool opt_UseFastLengthTesterCache; + bool search_started; arith_util m_autil; str_util m_strutil; @@ -260,6 +269,9 @@ namespace smt { obj_pair_map concat_astNode_map; + // used when opt_FastLengthTesterCache is true + rational_map lengthTesterCache; + th_union_find m_find; th_trail_stack m_trail_stack; theory_var get_var(expr * n) const; From a61e1f17e872075cc784e06de4061a5b24941d5e Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 2 Nov 2016 12:35:14 -0400 Subject: [PATCH 242/410] fix crash in gen_len_test_options when fast length testers are disabled --- src/smt/theory_str.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 45b715247..5f024dead 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -8553,7 +8553,7 @@ expr * theory_str::gen_len_test_options(expr * freeVar, expr * indicator, int tr } } else { std::string i_str = int_to_string(i); - expr_ref str_indicator(m_strutil.mk_string(i_str), m); + str_indicator = expr_ref(m_strutil.mk_string(i_str), m); } expr_ref or_expr(ctx.mk_eq_atom(indicator, str_indicator), m); orList.push_back(or_expr); From 3ae336fa6f178aa41403140c98d061a2ea560411 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 2 Nov 2016 13:05:16 -0400 Subject: [PATCH 243/410] add experimental value tester caching to theory_str --- src/smt/theory_str.cpp | 13 ++++++++++++- src/smt/theory_str.h | 15 +++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 5f024dead..c7cbff04e 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -41,6 +41,7 @@ theory_str::theory_str(ast_manager & m): opt_DeferEQCConsistencyCheck(false), opt_CheckVariableScope(true), opt_UseFastLengthTesterCache(true), + opt_UseFastValueTesterCache(true), /* Internal setup */ search_started(false), m_autil(m), @@ -7967,6 +7968,7 @@ expr * theory_str::gen_val_options(expr * freeVar, expr * len_indicator, expr * ptr_vector andList; for (long long i = l; i < h; i++) { + // TODO can we share the val_indicator constants with the length tester cache? orList.push_back(m.mk_eq(val_indicator, m_strutil.mk_string(longlong_to_string(i).c_str()) )); if (opt_AggressiveValueTesting) { literal l = mk_eq(val_indicator, m_strutil.mk_string(longlong_to_string(i).c_str()), false); @@ -7975,7 +7977,16 @@ expr * theory_str::gen_val_options(expr * freeVar, expr * len_indicator, expr * } std::string aStr = gen_val_string(len, options[i - l]); - expr * strAst = m_strutil.mk_string(aStr); + expr * strAst; + if (opt_UseFastValueTesterCache) { + if (!valueTesterCache.find(aStr, strAst)) { + strAst = m_strutil.mk_string(aStr); + valueTesterCache.insert(aStr, strAst); + m_trail.push_back(strAst); + } + } else { + strAst = m_strutil.mk_string(aStr); + } andList.push_back(m.mk_eq(orList[orList.size() - 1], m.mk_eq(freeVar, strAst))); } if (!coverAll) { diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 9b41c583b..b04e21fca 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -89,6 +89,12 @@ namespace smt { typedef union_find th_union_find; typedef map, default_eq > rational_map; + struct str_hash_proc { + unsigned operator()(std::string const & s) const { + return string_hash(s.c_str(), static_cast(s.length()), 17); + } + }; + typedef map > string_map; protected: // Some options that control how the solver operates. @@ -176,6 +182,13 @@ namespace smt { */ bool opt_UseFastLengthTesterCache; + /* + * If UseFastValueTesterCache is set to true, + * value tester terms will not be generated from scratch each time they are needed, + * but will be saved in a map and looked up. + */ + bool opt_UseFastValueTesterCache; + bool search_started; arith_util m_autil; str_util m_strutil; @@ -271,6 +284,8 @@ namespace smt { // used when opt_FastLengthTesterCache is true rational_map lengthTesterCache; + // used when opt_FastValueTesterCache is true + string_map valueTesterCache; th_union_find m_find; th_trail_stack m_trail_stack; From 521e0e175be66fcb5140ff3ac766ebb35ef3cd56 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 8 Nov 2016 14:23:10 -0500 Subject: [PATCH 244/410] refresh reused split vars in theory_str this fixes kaluza/unsat/big/7907, now SAT in ~30s --- src/smt/theory_str.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index c7cbff04e..a9a290ab1 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -2726,6 +2726,9 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { t2 = varForBreakConcat[key2][1]; xorFlag = varForBreakConcat[key2][2]; } + // TODO do I need to refresh the xorFlag, which is an integer var, and if so, how? + refresh_theory_var(t1); + refresh_theory_var(t2); } // For split types 0 through 2, we can get away with providing @@ -3048,6 +3051,8 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { temp1 = varForBreakConcat[key2][0]; xorFlag = varForBreakConcat[key2][1]; } + // TODO refresh xorFlag? + refresh_theory_var(temp1); } int splitType = -1; @@ -3361,6 +3366,7 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { temp1 = varForBreakConcat[key2][0]; xorFlag = varForBreakConcat[key2][1]; } + refresh_theory_var(temp1); } @@ -3857,6 +3863,7 @@ void theory_str::process_concat_eq_type6(expr * concatAst1, expr * concatAst2) { commonVar = (entry2->second)[0]; xorFlag = (entry2->second)[1]; } + refresh_theory_var(commonVar); } expr ** or_item = alloc_svect(expr*, (overlapLen.size() + 1)); From 61d1d5e8b0a87381ac429a06786c3db28c4e9aa2 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 8 Nov 2016 15:20:47 -0500 Subject: [PATCH 245/410] add cache for length terms to theory_str, but it seems to slow things down so I disabled it --- src/smt/theory_str.cpp | 17 +++++++++++++++-- src/smt/theory_str.h | 3 +++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index a9a290ab1..4e5e45ce7 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -696,8 +696,21 @@ app * theory_str::mk_strlen(expr * e) { int len = strlen(strval); return m_autil.mk_numeral(rational(len), true); } else { - expr * args[1] = {e}; - return get_manager().mk_app(get_id(), OP_STRLEN, 0, 0, 1, args); + if (false) { + // use cache + app * lenTerm = NULL; + if (!length_ast_map.find(e, lenTerm)) { + expr * args[1] = {e}; + lenTerm = get_manager().mk_app(get_id(), OP_STRLEN, 0, 0, 1, args); + length_ast_map.insert(e, lenTerm); + m_trail.push_back(lenTerm); + } + return lenTerm; + } else { + // always regen + expr * args[1] = {e}; + return get_manager().mk_app(get_id(), OP_STRLEN, 0, 0, 1, args); + } } } diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index b04e21fca..48ebf049b 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -287,6 +287,9 @@ namespace smt { // used when opt_FastValueTesterCache is true string_map valueTesterCache; + // cache mapping each string S to Length(S) + obj_map length_ast_map; + th_union_find m_find; th_trail_stack m_trail_stack; theory_var get_var(expr * n) const; From fff1fadf3b4d4b84c8bf925fab82762182d0366f Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 9 Nov 2016 15:54:22 -0500 Subject: [PATCH 246/410] add str.from-int in theory_str rewriter --- src/ast/rewriter/str_rewriter.cpp | 22 ++++++++++++++++++++++ src/ast/rewriter/str_rewriter.h | 1 + src/ast/str_decl_plugin.cpp | 7 +++++++ src/ast/str_decl_plugin.h | 2 ++ 4 files changed, 32 insertions(+) diff --git a/src/ast/rewriter/str_rewriter.cpp b/src/ast/rewriter/str_rewriter.cpp index db3885f28..875343655 100644 --- a/src/ast/rewriter/str_rewriter.cpp +++ b/src/ast/rewriter/str_rewriter.cpp @@ -410,6 +410,25 @@ br_status str_rewriter::mk_str_to_int(expr * arg0, expr_ref & result) { } +br_status str_rewriter::mk_str_from_int(expr * arg0, expr_ref & result) { + TRACE("t_str_rw", tout << "rewrite (str.from-int " << mk_pp(arg0, m()) << ")" << std::endl;); + rational arg0Int; + if (m_autil.is_numeral(arg0, arg0Int)) { + // (str.from-int N) with N non-negative is the corresponding string in decimal notation. + // otherwise it is the empty string + if (arg0Int.is_nonneg()) { + std::string str = arg0Int.to_string(); + result = m_strutil.mk_string(str); + TRACE("t_str_rw", tout << "convert non-negative integer constant to " << str << std::endl;); + } else { + result = m_strutil.mk_string(""); + TRACE("t_str_rw", tout << "convert invalid integer constant to empty string" << std::endl;); + } + return BR_DONE; + } + return BR_FAILED; +} + br_status str_rewriter::mk_str_Substr(expr * base, expr * start, expr * len, expr_ref & result) { TRACE("t_str_rw", tout << "rewrite (Substr " << mk_pp(base, m()) << " " << mk_pp(start, m()) << " " << mk_pp(len, m()) << ")" << std::endl;); rational startVal, lenVal; @@ -559,6 +578,9 @@ br_status str_rewriter::mk_app_core(func_decl * f, unsigned num_args, expr * con case OP_STR_STR2INT: SASSERT(num_args == 1); return mk_str_to_int(args[0], result); + case OP_STR_INT2STR: + SASSERT(num_args == 1); + return mk_str_from_int(args[0], result); case OP_STR_SUBSTR: SASSERT(num_args == 3); return mk_str_Substr(args[0], args[1], args[2], result); diff --git a/src/ast/rewriter/str_rewriter.h b/src/ast/rewriter/str_rewriter.h index 10898eae7..822fb1ea8 100644 --- a/src/ast/rewriter/str_rewriter.h +++ b/src/ast/rewriter/str_rewriter.h @@ -54,6 +54,7 @@ public: br_status mk_str_Replace(expr * base, expr * source, expr * target, expr_ref & result); br_status mk_str_Substr(expr * base, expr * start, expr * len, expr_ref & result); br_status mk_str_to_int(expr * arg0, expr_ref & result); + br_status mk_str_from_int(expr * arg0, expr_ref & result); br_status mk_re_Str2Reg(expr * str, expr_ref & result); br_status mk_re_RegexIn(expr * str, expr * re, expr_ref & result); diff --git a/src/ast/str_decl_plugin.cpp b/src/ast/str_decl_plugin.cpp index 333ae2a02..4f9dcb7aa 100644 --- a/src/ast/str_decl_plugin.cpp +++ b/src/ast/str_decl_plugin.cpp @@ -37,6 +37,7 @@ str_decl_plugin::str_decl_plugin(): m_substr_decl(0), m_replace_decl(0), m_str2int_decl(0), + m_int2str_decl(0), m_re_str2regex_decl(0), m_re_regexin_decl(0), m_re_regexconcat_decl(0), @@ -69,6 +70,7 @@ void str_decl_plugin::finalize(void) { DEC_REF(m_substr_decl); DEC_REF(m_replace_decl); DEC_REF(m_str2int_decl); + DEC_REF(m_int2str_decl); DEC_REF(m_re_str2regex_decl); DEC_REF(m_re_regexin_decl); DEC_REF(m_re_regexconcat_decl); @@ -150,6 +152,9 @@ void str_decl_plugin::set_manager(ast_manager * m, family_id id) { m_str2int_decl = m->mk_func_decl(symbol("str.to-int"), s, i, func_decl_info(id, OP_STR_STR2INT)); m_manager->inc_ref(m_str2int_decl); + m_int2str_decl = m->mk_func_decl(symbol("str.from-int"), i, s, func_decl_info(id, OP_STR_INT2STR)); + m_manager->inc_ref(m_int2str_decl); + m_re_str2regex_decl = m->mk_func_decl(symbol("Str2Reg"), s, re, func_decl_info(id, OP_RE_STR2REGEX)); m_manager->inc_ref(m_re_str2regex_decl); @@ -202,6 +207,7 @@ func_decl * str_decl_plugin::mk_func_decl(decl_kind k) { case OP_STR_SUBSTR: return m_substr_decl; case OP_STR_REPLACE: return m_replace_decl; case OP_STR_STR2INT: return m_str2int_decl; + case OP_STR_INT2STR: return m_int2str_decl; case OP_RE_STR2REGEX: return m_re_str2regex_decl; case OP_RE_REGEXIN: return m_re_regexin_decl; case OP_RE_REGEXCONCAT: return m_re_regexconcat_decl; @@ -276,6 +282,7 @@ void str_decl_plugin::get_op_names(svector & op_names, symbol cons op_names.push_back(builtin_name("Substring", OP_STR_SUBSTR)); op_names.push_back(builtin_name("Replace", OP_STR_REPLACE)); op_names.push_back(builtin_name("str.to-int", OP_STR_STR2INT)); + op_names.push_back(builtin_name("str.from-int", OP_STR_INT2STR)); op_names.push_back(builtin_name("Str2Reg", OP_RE_STR2REGEX)); op_names.push_back(builtin_name("RegexIn", OP_RE_REGEXIN)); op_names.push_back(builtin_name("RegexConcat", OP_RE_REGEXCONCAT)); diff --git a/src/ast/str_decl_plugin.h b/src/ast/str_decl_plugin.h index e9ab43865..29b2ce9c3 100644 --- a/src/ast/str_decl_plugin.h +++ b/src/ast/str_decl_plugin.h @@ -43,6 +43,7 @@ enum str_op_kind { OP_STR_REPLACE, // string-integer conversion OP_STR_STR2INT, + OP_STR_INT2STR, // regular expression operators OP_RE_STR2REGEX, OP_RE_REGEXIN, @@ -76,6 +77,7 @@ protected: func_decl * m_substr_decl; func_decl * m_replace_decl; func_decl * m_str2int_decl; + func_decl * m_int2str_decl; func_decl * m_re_str2regex_decl; func_decl * m_re_regexin_decl; From 5635016205279623e2c00420934525c9fe22d801 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 9 Nov 2016 18:06:02 -0500 Subject: [PATCH 247/410] theory_str str.from-int very WIP --- src/smt/theory_str.cpp | 98 ++++++++++++++++++++++++++++++++++++++++-- src/smt/theory_str.h | 4 ++ 2 files changed, 98 insertions(+), 4 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 40dc3f42f..881045815 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -924,6 +924,8 @@ void theory_str::propagate() { enode * e = m_library_aware_axiom_todo[i]; if (is_str_to_int(e)) { instantiate_axiom_str_to_int(e); + } else if (is_int_to_str(e)) { + instantiate_axiom_int_to_str(e); } else { TRACE("t_str", tout << "BUG: unhandled library-aware term " << mk_pp(e->get_owner(), get_manager()) << std::endl;); NOT_IMPLEMENTED_YET(); @@ -1637,6 +1639,30 @@ void theory_str::instantiate_axiom_str_to_int(enode * e) { } } +void theory_str::instantiate_axiom_int_to_str(enode * e) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + app * ex = e->get_owner(); + if (axiomatized_terms.contains(ex)) { + TRACE("t_str_detail", tout << "already set up str.from-int axiom for " << mk_pp(ex, m) << std::endl;); + return; + } + axiomatized_terms.insert(ex); + + TRACE("t_str_detail", tout << "instantiate str.from-int axiom for " << mk_pp(ex, m) << std::endl;); + + // axiom 1: N < 0 <==> (str.from-int N) = "" + expr * N = ex->get_arg(0); + { + expr_ref axiom1_lhs(m.mk_not(m_autil.mk_ge(N, m_autil.mk_numeral(rational::zero(), true))), m); + expr_ref axiom1_rhs(ctx.mk_eq_atom(ex, m_strutil.mk_string("")), m); + expr_ref axiom1(ctx.mk_eq_atom(axiom1_lhs, axiom1_rhs), m); + SASSERT(axiom1); + assert_axiom(axiom1); + } +} + expr * theory_str::mk_RegexIn(expr * str, expr * regexp) { expr * args[2] = {str, regexp}; app * regexIn = get_manager().mk_app(get_id(), OP_RE_REGEXIN, 0, 0, 2, args); @@ -6476,7 +6502,7 @@ void theory_str::set_up_axioms(expr * ex) { m_axiom_Indexof2_todo.push_back(n); } else if (is_LastIndexof(ap)) { m_axiom_LastIndexof_todo.push_back(n); - } else if (is_str_to_int(ap)) { + } else if (is_str_to_int(ap) || is_int_to_str(ap)) { string_int_conversion_terms.push_back(ap); m_library_aware_axiom_todo.push_back(n); } @@ -7630,7 +7656,7 @@ bool theory_str::finalcheck_str2int(app * a) { std::string Ival_str = Ival.to_string(); expr_ref premise(ctx.mk_eq_atom(a, m_autil.mk_numeral(Ival, true)), m); expr_ref conclusion(ctx.mk_eq_atom(S, m_strutil.mk_string(Ival_str)), m); - expr_ref axiom(m.mk_or(m.mk_not(premise), conclusion), m); + expr_ref axiom(rewrite_implication(premise, conclusion), m); if (!string_int_axioms.contains(axiom)) { string_int_axioms.insert(axiom); assert_axiom(axiom); @@ -7647,6 +7673,66 @@ bool theory_str::finalcheck_str2int(app * a) { return axiomAdd; } +bool theory_str::finalcheck_int2str(app * a) { + bool axiomAdd = false; + context & ctx = get_context(); + ast_manager & m = get_manager(); + + expr * N = a->get_arg(0); + + // check string theory + bool Sval_expr_exists; + expr * Sval_expr = get_eqc_value(a, Sval_expr_exists); + if (Sval_expr_exists) { + std::string Sval = m_strutil.get_string_constant_value(Sval_expr); + TRACE("t_str_detail", tout << "string theory assigns \"" << mk_pp(a, m) << " = " << Sval << std::endl;); + // empty string --> integer value < 0 + if (Sval.empty()) { + // ignore this. we should already assert the axiom for what happens when the string is "" + } else { + // nonempty string --> convert to correct integer value, or disallow it + // TODO think about whether we need to persist the axiom in this case? + rational convertedRepresentation(0); + rational ten(10); + bool conversionOK = true; + for (unsigned i = 0; i < Sval.length(); ++i) { + char digit = Sval.at(i); + if (isdigit((int)digit)) { + std::string sDigit(1, digit); + int val = atoi(sDigit.c_str()); + convertedRepresentation = (ten * convertedRepresentation) + rational(val); + } else { + // not a digit, invalid + TRACE("t_str_rw", tout << "str.to-int argument contains non-digit character '" << digit << "'" << std::endl;); + conversionOK = false; + break; + } + } + if (conversionOK) { + expr_ref premise(ctx.mk_eq_atom(a, m_strutil.mk_string(Sval)), m); + expr_ref conclusion(ctx.mk_eq_atom(N, m_autil.mk_numeral(convertedRepresentation, true)), m); + expr_ref axiom(rewrite_implication(premise, conclusion), m); + if (!string_int_axioms.contains(axiom)) { + string_int_axioms.insert(axiom); + assert_axiom(axiom); + m_trail_stack.push(insert_obj_trail(string_int_axioms, axiom)); + axiomAdd = true; + } + } else { + expr_ref axiom(m.mk_not(ctx.mk_eq_atom(a, m_strutil.mk_string(Sval))), m); + // always assert this axiom because this is a conflict clause + assert_axiom(axiom); + axiomAdd = true; + } + } + } else { + TRACE("t_str_detail", tout << "string theory has no assignment for " << mk_pp(a, m) << std::endl;); + NOT_IMPLEMENTED_YET(); + } + // TODO also check assignment in integer theory + return axiomAdd; +} + final_check_status theory_str::final_check_eh() { context & ctx = get_context(); ast_manager & m = get_manager(); @@ -7758,9 +7844,13 @@ final_check_status theory_str::final_check_eh() { if (axiomAdd) { addedStrIntAxioms = true; } + } else if (is_int_to_str(ex)) { + bool axiomAdd = finalcheck_int2str(ex); + if (axiomAdd) { + addedStrIntAxioms = true; + } } else { - // TODO int.to-str - NOT_IMPLEMENTED_YET(); + UNREACHABLE(); } } if (addedStrIntAxioms) { diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index e99774034..c7d931d1e 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -359,6 +359,8 @@ namespace smt { bool is_Replace(enode const * n) const { return is_Replace(n->get_owner()); } bool is_str_to_int(app const * a) const { return a->is_app_of(get_id(), OP_STR_STR2INT); } bool is_str_to_int(enode const * n) const { return is_str_to_int(n->get_owner()); } + bool is_int_to_str(app const * a) const { return a->is_app_of(get_id(), OP_STR_INT2STR); } + bool is_int_to_str(enode const * n) const { return is_int_to_str(n->get_owner()); } bool is_RegexIn(app const * a) const { return a->is_app_of(get_id(), OP_RE_REGEXIN); } bool is_RegexIn(enode const * n) const { return is_RegexIn(n->get_owner()); } @@ -388,6 +390,7 @@ namespace smt { void instantiate_axiom_Substr(enode * e); void instantiate_axiom_Replace(enode * e); void instantiate_axiom_str_to_int(enode * e); + void instantiate_axiom_int_to_str(enode * e); expr * mk_RegexIn(expr * str, expr * regexp); void instantiate_axiom_RegexIn(enode * e); @@ -510,6 +513,7 @@ namespace smt { expr * eval_concat(expr * n1, expr * n2); bool finalcheck_str2int(app * a); + bool finalcheck_int2str(app * a); // strRegex From fbaee080b202380b65a5df39c5c038ca68ed0442 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Fri, 11 Nov 2016 00:32:50 -0500 Subject: [PATCH 248/410] fix performance regression introduced with theory_str str.from-int more investigation is required to understand why this works. --- src/ast/str_decl_plugin.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ast/str_decl_plugin.h b/src/ast/str_decl_plugin.h index 29b2ce9c3..8905d66bc 100644 --- a/src/ast/str_decl_plugin.h +++ b/src/ast/str_decl_plugin.h @@ -43,7 +43,7 @@ enum str_op_kind { OP_STR_REPLACE, // string-integer conversion OP_STR_STR2INT, - OP_STR_INT2STR, + OP_STR_INT2STR, OP_STR_PLACEHOLDER1, OP_STR_PLACEHOLDER2, // regular expression operators OP_RE_STR2REGEX, OP_RE_REGEXIN, From 02aacab04e58d5fd8e6f1c79beb8259a1e294b24 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Fri, 11 Nov 2016 17:52:18 -0500 Subject: [PATCH 249/410] add z3str2-style free variable check to theory_str --- src/smt/theory_str.cpp | 62 ++++++++++++++++++++++++++++++------------ 1 file changed, 45 insertions(+), 17 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 881045815..fa205ac32 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -7811,26 +7811,54 @@ final_check_status theory_str::final_check_eh() { return FC_DONE; } - // Check every variable to see if it's eq. to some string constant. - // If not, mark it as free. bool needToAssignFreeVars = false; std::set free_variables; std::set unused_internal_variables; - TRACE("t_str_detail", tout << variable_set.size() << " variables in variable_set" << std::endl;); - for (std::set::iterator it = variable_set.begin(); it != variable_set.end(); ++it) { - TRACE("t_str_detail", tout << "checking eqc of variable " << mk_ismt2_pp(*it, m) << std::endl;); - bool has_eqc_value = false; - get_eqc_value(*it, has_eqc_value); - if (!has_eqc_value) { - // if this is an internal variable, it can be ignored...I think - if (internal_variable_set.find(*it) != internal_variable_set.end() || regex_variable_set.find(*it) != regex_variable_set.end()) { - TRACE("t_str_detail", tout << "WARNING: free internal variable " << mk_ismt2_pp(*it, m) << std::endl;); - //unused_internal_variables.insert(*it); - } else { - needToAssignFreeVars = true; - free_variables.insert(*it); - } - } + if (true) { // Z3str2 free variables check + std::map::iterator itor = varAppearInAssign.begin(); + for (; itor != varAppearInAssign.end(); ++itor) { + /* + std::string vName = std::string(Z3_ast_to_string(ctx, itor->first)); + if (vName.length() >= 3 && vName.substr(0, 3) == "$$_") + continue; + */ + if (internal_variable_set.find(itor->first) != internal_variable_set.end() + || regex_variable_set.find(itor->first) != regex_variable_set.end()) { + // this can be ignored, I think + TRACE("t_str_detail", tout << "free internal variable " << mk_pp(itor->first, m) << " ignored" << std::endl;); + continue; + } + bool hasEqcValue = false; + expr * eqcString = get_eqc_value(itor->first, hasEqcValue); + if (!hasEqcValue) { + TRACE("t_str_detail", tout << "found free variable " << mk_pp(itor->first, m) << std::endl;); + needToAssignFreeVars = true; + free_variables.insert(itor->first); + // break; + } else { + // debug + TRACE("t_str_detail", tout << "variable " << mk_pp(itor->first, m) << " = " << mk_pp(eqcString, m) << std::endl;); + } + } + } else { // new, possibly incorrect free variables check + // Check every variable to see if it's eq. to some string constant. + // If not, mark it as free. + TRACE("t_str_detail", tout << variable_set.size() << " variables in variable_set" << std::endl;); + for (std::set::iterator it = variable_set.begin(); it != variable_set.end(); ++it) { + TRACE("t_str_detail", tout << "checking eqc of variable " << mk_ismt2_pp(*it, m) << std::endl;); + bool has_eqc_value = false; + get_eqc_value(*it, has_eqc_value); + if (!has_eqc_value) { + // if this is an internal variable, it can be ignored...I think + if (internal_variable_set.find(*it) != internal_variable_set.end() || regex_variable_set.find(*it) != regex_variable_set.end()) { + TRACE("t_str_detail", tout << "WARNING: free internal variable " << mk_ismt2_pp(*it, m) << std::endl;); + //unused_internal_variables.insert(*it); + } else { + needToAssignFreeVars = true; + free_variables.insert(*it); + } + } + } } if (!needToAssignFreeVars) { From df6b4611174ad4159ca816bd9f791d9c6448f15a Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Mon, 14 Nov 2016 12:33:23 -0500 Subject: [PATCH 250/410] enhanced backpropagation in theory_str final_check for var=concat terms fixes kaluza sat/big/709.smt2 --- src/smt/theory_str.cpp | 47 ++++++++++++++++++++++++++++++++++++------ src/smt/theory_str.h | 2 +- 2 files changed, 42 insertions(+), 7 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index fa205ac32..21a9ace97 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -2028,7 +2028,6 @@ void theory_str::simplify_parent(expr * nn, expr * eq_str) { expr_ref eq_ast2(m); eq_ast2 = ctx.mk_eq_atom(arg1, arg1Value); SASSERT(eq_ast2); - implyL = m.mk_and(eq_ast1, eq_ast2); } else { implyL = ctx.mk_eq_atom(n_eqNode, eq_str); @@ -2070,7 +2069,6 @@ void theory_str::simplify_parent(expr * nn, expr * eq_str) { << "* |arg0| = " << rational_to_string_if_exists(arg0Len, arg0Len_exists) << std::endl << "* |arg1| = " << rational_to_string_if_exists(arg1Len, arg1Len_exists) << std::endl; ); - if (parentLen_exists && !arg0Len_exists) { TRACE("t_str_detail", tout << "make up len for arg0" << std::endl;); expr_ref implyL11(m.mk_and(ctx.mk_eq_atom(mk_strlen(a_parent), mk_int(parentLen)), @@ -2096,7 +2094,6 @@ void theory_str::simplify_parent(expr * nn, expr * eq_str) { expr_ref eq_ast1(m); eq_ast1 = ctx.mk_eq_atom(n_eqNode, eq_str); SASSERT(eq_ast1); - expr_ref eq_ast2(m); eq_ast2 = ctx.mk_eq_atom(arg0, arg0Value); SASSERT(eq_ast2); @@ -7049,13 +7046,12 @@ void theory_str::trace_ctx_dep(std::ofstream & tout, * > should split the unroll function so that var2 and var3 are bounded by new unrolls */ int theory_str::ctx_dep_analysis(std::map & strVarMap, std::map & freeVarMap, - std::map > & unrollGroupMap) { + std::map > & unrollGroupMap, std::map > & var_eq_concat_map) { std::map concatMap; std::map unrollMap; std::map aliasIndexMap; std::map var_eq_constStr_map; std::map concat_eq_constStr_map; - std::map > var_eq_concat_map; std::map > var_eq_unroll_map; std::map > concat_eq_concat_map; std::map > depMap; @@ -7805,12 +7801,51 @@ final_check_status theory_str::final_check_eh() { std::map varAppearInAssign; std::map freeVar_map; std::map > unrollGroup_map; - int conflictInDep = ctx_dep_analysis(varAppearInAssign, freeVar_map, unrollGroup_map); + std::map > var_eq_concat_map; + int conflictInDep = ctx_dep_analysis(varAppearInAssign, freeVar_map, unrollGroup_map, var_eq_concat_map); if (conflictInDep == -1) { // return Z3_TRUE; return FC_DONE; } + // enhancement: improved backpropagation of string constants into var=concat terms + bool backpropagation_occurred = false; + for (std::map >::iterator veqc_map_it = var_eq_concat_map.begin(); + veqc_map_it != var_eq_concat_map.end(); ++veqc_map_it) { + expr * var = veqc_map_it->first; + for (std::map::iterator concat_map_it = veqc_map_it->second.begin(); + concat_map_it != veqc_map_it->second.end(); ++concat_map_it) { + app * concat = to_app(concat_map_it->first); + expr * concat_lhs = concat->get_arg(0); + expr * concat_rhs = concat->get_arg(1); + // If the concat LHS and RHS both have a string constant in their EQC, + // but the var does not, then we assert an axiom of the form + // (lhs = "lhs" AND rhs = "rhs") --> (Concat lhs rhs) = "lhsrhs" + bool concat_lhs_haseqc, concat_rhs_haseqc, var_haseqc; + expr * concat_lhs_str = get_eqc_value(concat_lhs, concat_lhs_haseqc); + expr * concat_rhs_str = get_eqc_value(concat_rhs, concat_rhs_haseqc); + expr * var_str = get_eqc_value(var, var_haseqc); + if (concat_lhs_haseqc && concat_rhs_haseqc && !var_haseqc) { + TRACE("t_str_detail", tout << "backpropagate into " << mk_pp(var, m) << " = " << mk_pp(concat, m) << std::endl + << "LHS ~= " << mk_pp(concat_lhs_str, m) << " RHS ~= " << mk_pp(concat_rhs_str, m) << std::endl;); + std::string lhsString = m_strutil.get_string_constant_value(concat_lhs_str); + std::string rhsString = m_strutil.get_string_constant_value(concat_rhs_str); + std::string concatString = lhsString + rhsString; + expr_ref lhs1(ctx.mk_eq_atom(concat_lhs, concat_lhs_str), m); + expr_ref lhs2(ctx.mk_eq_atom(concat_rhs, concat_rhs_str), m); + expr_ref lhs(m.mk_and(lhs1, lhs2), m); + expr_ref rhs(ctx.mk_eq_atom(concat, m_strutil.mk_string(concatString)), m); + assert_implication(lhs, rhs); + backpropagation_occurred = true; + } + } + } + + if (backpropagation_occurred) { + TRACE("t_str", tout << "Resuming search due to axioms added by backpropagation." << std::endl;); + return FC_CONTINUE; + } + bool needToAssignFreeVars = false; std::set free_variables; std::set unused_internal_variables; diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index c7d931d1e..5b8f644eb 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -473,7 +473,7 @@ namespace smt { void group_terms_by_eqc(expr * n, std::set & concats, std::set & vars, std::set & consts); int ctx_dep_analysis(std::map & strVarMap, std::map & freeVarMap, - std::map > & unrollGroupMap); + std::map > & unrollGroupMap, std::map > & var_eq_concat_map); void trace_ctx_dep(std::ofstream & tout, std::map & aliasIndexMap, std::map & var_eq_constStr_map, From 977142860042aaad55c80deda252b50d054ae524 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 15 Nov 2016 15:18:07 -0500 Subject: [PATCH 251/410] experimental modification to simplify_parent call in theory_str, WIP --- src/smt/theory_str.cpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 21a9ace97..3874f9f1d 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -2267,19 +2267,12 @@ expr * theory_str::simplify_concat(expr * node) { if (in_same_eqc(node, resultAst)) { TRACE("t_str_detail", tout << "SKIP: both concats are already in the same equivalence class" << std::endl;); } else { - // TODO refactor - expr ** items = alloc_svect(expr*, resolvedMap.size()); - int pos = 0; + expr_ref_vector items(m); std::map::iterator itor = resolvedMap.begin(); for (; itor != resolvedMap.end(); ++itor) { - items[pos++] = ctx.mk_eq_atom(itor->first, itor->second); - } - expr_ref premise(m); - if (pos == 1) { - premise = items[0]; - } else { - premise = m.mk_and(pos, items); + items.push_back(ctx.mk_eq_atom(itor->first, itor->second)); } + expr_ref premise(mk_and(items), m); expr_ref conclusion(ctx.mk_eq_atom(node, resultAst), m); assert_implication(premise, conclusion); } @@ -6374,11 +6367,18 @@ void theory_str::handle_equality(expr * lhs, expr * rhs) { // we want the Z3str2 eqc check here... expr * nn1_value = z3str2_get_eqc_value(lhs, nn1HasEqcValue); expr * nn2_value = z3str2_get_eqc_value(rhs, nn2HasEqcValue); - if (nn1HasEqcValue && !nn2HasEqcValue) { + + + // modification from z3str2: simplify whenever we see a string constant on either side, + // not only when it's on one side but not the other. + // this may work in cases where a concat is simplified to a string constant in group_terms_by_eqc() + // and we fail to simplify parents because we think a string constant is on both sides + + if (nn1HasEqcValue /* && !nn2HasEqcValue */ ) { simplify_parent(rhs, nn1_value); } - if (!nn1HasEqcValue && nn2HasEqcValue) { + if (/* !nn1HasEqcValue && */ nn2HasEqcValue) { simplify_parent(lhs, nn2_value); } From 55ae83f47ebedd3fea1c04e4c39962fa80b97819 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 16 Nov 2016 13:00:05 -0500 Subject: [PATCH 252/410] Revert "experimental modification to simplify_parent call in theory_str, WIP" This reverts commit 977142860042aaad55c80deda252b50d054ae524. --- src/smt/theory_str.cpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 3874f9f1d..21a9ace97 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -2267,12 +2267,19 @@ expr * theory_str::simplify_concat(expr * node) { if (in_same_eqc(node, resultAst)) { TRACE("t_str_detail", tout << "SKIP: both concats are already in the same equivalence class" << std::endl;); } else { - expr_ref_vector items(m); + // TODO refactor + expr ** items = alloc_svect(expr*, resolvedMap.size()); + int pos = 0; std::map::iterator itor = resolvedMap.begin(); for (; itor != resolvedMap.end(); ++itor) { - items.push_back(ctx.mk_eq_atom(itor->first, itor->second)); + items[pos++] = ctx.mk_eq_atom(itor->first, itor->second); + } + expr_ref premise(m); + if (pos == 1) { + premise = items[0]; + } else { + premise = m.mk_and(pos, items); } - expr_ref premise(mk_and(items), m); expr_ref conclusion(ctx.mk_eq_atom(node, resultAst), m); assert_implication(premise, conclusion); } @@ -6367,18 +6374,11 @@ void theory_str::handle_equality(expr * lhs, expr * rhs) { // we want the Z3str2 eqc check here... expr * nn1_value = z3str2_get_eqc_value(lhs, nn1HasEqcValue); expr * nn2_value = z3str2_get_eqc_value(rhs, nn2HasEqcValue); - - - // modification from z3str2: simplify whenever we see a string constant on either side, - // not only when it's on one side but not the other. - // this may work in cases where a concat is simplified to a string constant in group_terms_by_eqc() - // and we fail to simplify parents because we think a string constant is on both sides - - if (nn1HasEqcValue /* && !nn2HasEqcValue */ ) { + if (nn1HasEqcValue && !nn2HasEqcValue) { simplify_parent(rhs, nn1_value); } - if (/* !nn1HasEqcValue && */ nn2HasEqcValue) { + if (!nn1HasEqcValue && nn2HasEqcValue) { simplify_parent(lhs, nn2_value); } From e2d05578d62251d9c7630ffa6c965f29a664d07a Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Thu, 17 Nov 2016 15:25:39 -0500 Subject: [PATCH 253/410] add extra trace message in smt_context for theory_str results change --- src/smt/smt_context.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/smt/smt_context.cpp b/src/smt/smt_context.cpp index c712135d3..8958eae5f 100644 --- a/src/smt/smt_context.cpp +++ b/src/smt/smt_context.cpp @@ -3105,6 +3105,7 @@ namespace smt { theory_str * str = (theory_str*)th; if (str->overlapping_variables_detected()) { TRACE("t_str", tout << "WARNING: overlapping variables detected, UNSAT changed to UNKNOWN!" << std::endl;); + TRACE("context", tout << "WARNING: overlapping variables detected in theory_str. UNSAT changed to UNKNOWN!" << std::endl;); r = l_undef; } break; From d260218e2be6f1ece2140cfeaea9aa2e8cc177fe Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Thu, 17 Nov 2016 15:28:17 -0500 Subject: [PATCH 254/410] tabs to spaces test --- src/smt/theory_str.cpp | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 21a9ace97..39b221961 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -35,13 +35,13 @@ theory_str::theory_str(ast_manager & m): opt_AggressiveUnrollTesting(true), opt_EagerStringConstantLengthAssertions(true), opt_VerifyFinalCheckProgress(true), - opt_LCMUnrollStep(2), - opt_NoQuickReturn_IntegerTheory(false), - opt_DisableIntegerTheoryIntegration(false), - opt_DeferEQCConsistencyCheck(false), - opt_CheckVariableScope(true), - opt_UseFastLengthTesterCache(true), - opt_UseFastValueTesterCache(true), + opt_LCMUnrollStep(2), + opt_NoQuickReturn_IntegerTheory(false), + opt_DisableIntegerTheoryIntegration(false), + opt_DeferEQCConsistencyCheck(false), + opt_CheckVariableScope(true), + opt_UseFastLengthTesterCache(true), + opt_UseFastValueTesterCache(true), /* Internal setup */ search_started(false), m_autil(m), @@ -51,17 +51,17 @@ theory_str::theory_str(ast_manager & m): m_trail(m), m_delayed_axiom_setup_terms(m), tmpStringVarCount(0), - tmpXorVarCount(0), - tmpLenTestVarCount(0), - tmpValTestVarCount(0), - avoidLoopCut(true), - loopDetected(false), - contains_map(m), - string_int_conversion_terms(m), - m_find(*this), - m_trail_stack(*this) + tmpXorVarCount(0), + tmpLenTestVarCount(0), + tmpValTestVarCount(0), + avoidLoopCut(true), + loopDetected(false), + contains_map(m), + string_int_conversion_terms(m), + m_find(*this), + m_trail_stack(*this) { - initialize_charset(); + initialize_charset(); } theory_str::~theory_str() { From 855037eed765cd4462011fa2562d471fc9cd4bc6 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Thu, 17 Nov 2016 16:25:53 -0500 Subject: [PATCH 255/410] refactor process_concat_eq_type2 in theory_str; fixes unsat/big/8558 --- src/smt/theory_str.cpp | 83 ++++++++++++++++++++---------------------- 1 file changed, 39 insertions(+), 44 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 39b221961..f92939ac7 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -3180,28 +3180,28 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { if (can_two_nodes_eq(y, temp1_strAst)) { if (!avoidLoopCut || !(has_self_cut(m, y))) { // break down option 2-1 - expr ** l_items = alloc_svect(expr*, 3); - l_items[0] = ctx.mk_eq_atom(concatAst1, concatAst2); + expr_ref_vector l_items(mgr); + l_items.push_back(ctx.mk_eq_atom(concatAst1, concatAst2)); - expr ** r_items = alloc_svect(expr*, 3); + expr_ref_vector r_items(mgr); expr_ref x_temp1(mk_concat(x, temp1), mgr); - r_items[0] = ctx.mk_eq_atom(m, x_temp1); - r_items[1] = ctx.mk_eq_atom(y, temp1_strAst); + r_items.push_back(ctx.mk_eq_atom(m, x_temp1)); + r_items.push_back(ctx.mk_eq_atom(y, temp1_strAst)); if (x_len_exists && m_len_exists) { - l_items[1] = ctx.mk_eq_atom(mk_strlen(x), mk_int(x_len)); - l_items[2] = ctx.mk_eq_atom(mk_strlen(m), mk_int(m_len)); + l_items.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_int(x_len))); + l_items.push_back(ctx.mk_eq_atom(mk_strlen(m), mk_int(m_len))); rational m_sub_x = (m_len - x_len); - r_items[2] = ctx.mk_eq_atom(mk_strlen(temp1), mk_int(m_sub_x)); + r_items.push_back(ctx.mk_eq_atom(mk_strlen(temp1), mk_int(m_sub_x))); } else { - l_items[1] = ctx.mk_eq_atom(mk_strlen(y), mk_int(y_len)); - l_items[2] = ctx.mk_eq_atom(mk_strlen(strAst), mk_int(str_len)); + l_items.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_int(y_len))); + l_items.push_back(ctx.mk_eq_atom(mk_strlen(strAst), mk_int(str_len))); rational y_sub_str = (y_len - str_len); - r_items[2] = ctx.mk_eq_atom(mk_strlen(temp1), mk_int(y_sub_str)); + r_items.push_back(ctx.mk_eq_atom(mk_strlen(temp1), mk_int(y_sub_str))); } - expr_ref ax_l(mgr.mk_and(3, l_items), mgr); - expr_ref ax_r(mgr.mk_and(3, r_items), mgr); + expr_ref ax_l(mk_and(l_items), mgr); + expr_ref ax_r(mk_and(r_items), mgr); add_cut_info_merge(temp1, sLevel, y); add_cut_info_merge(temp1, sLevel, m); @@ -3228,16 +3228,16 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { // | x | y | // | m | str | rational lenDelta; - expr ** l_items = alloc_svect(expr*, 3); + expr_ref_vector l_items(mgr); int l_count = 0; - l_items[0] = ctx.mk_eq_atom(concatAst1, concatAst2); + l_items.push_back(ctx.mk_eq_atom(concatAst1, concatAst2)); if (x_len_exists && m_len_exists) { - l_items[1] = ctx.mk_eq_atom(mk_strlen(x), mk_int(x_len)); - l_items[2] = ctx.mk_eq_atom(mk_strlen(m), mk_int(m_len)); + l_items.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_int(x_len))); + l_items.push_back(ctx.mk_eq_atom(mk_strlen(m), mk_int(m_len))); l_count = 3; lenDelta = x_len - m_len; } else { - l_items[1] = ctx.mk_eq_atom(mk_strlen(y), mk_int(y_len)); + l_items.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_int(y_len))); l_count = 2; lenDelta = str_len - y_len; } @@ -3255,35 +3255,32 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { ; ); - TRACE("t_str", tout << "*** MARKER 1 ***" << std::endl;); std::string part1Str = strValue.substr(0, lenDelta.get_unsigned()); - TRACE("t_str", tout << "*** MARKER 2 ***" << std::endl;); std::string part2Str = strValue.substr(lenDelta.get_unsigned(), strValue.length() - lenDelta.get_unsigned()); - TRACE("t_str", tout << "*** MARKER 3 ***" << std::endl;); expr_ref prefixStr(m_strutil.mk_string(part1Str), mgr); expr_ref x_concat(mk_concat(m, prefixStr), mgr); expr_ref cropStr(m_strutil.mk_string(part2Str), mgr); if (can_two_nodes_eq(x, x_concat) && can_two_nodes_eq(y, cropStr)) { - expr ** r_items = alloc_svect(expr*, 2); - r_items[0] = ctx.mk_eq_atom(x, x_concat); - r_items[1] = ctx.mk_eq_atom(y, cropStr); - expr_ref ax_l(mgr.mk_and(l_count, l_items), mgr); - expr_ref ax_r(mgr.mk_and(2, r_items), mgr); + expr_ref_vector r_items(mgr); + r_items.push_back(ctx.mk_eq_atom(x, x_concat)); + r_items.push_back(ctx.mk_eq_atom(y, cropStr)); + expr_ref ax_l(mk_and(l_items), mgr); + expr_ref ax_r(mk_and(r_items), mgr); assert_implication(ax_l, ax_r); } else { // negate! It's impossible to split str with these lengths TRACE("t_str", tout << "CONFLICT: Impossible to split str with these lengths." << std::endl;); - expr_ref ax_l(mgr.mk_and(l_count, l_items), mgr); + expr_ref ax_l(mk_and(l_items), mgr); assert_axiom(mgr.mk_not(ax_l)); } } else { // Split type -1: no idea about the length... int optionTotal = 2 + strValue.length(); - expr ** or_item = alloc_svect(expr*, optionTotal); - expr ** and_item = alloc_svect(expr*, (1 + 6 + 4 * (strValue.length() + 1))); + expr_ref_vector or_item(mgr); + expr_ref_vector and_item(mgr); int option = 0; int pos = 1; @@ -3293,13 +3290,14 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { if (can_two_nodes_eq(y, temp1_strAst)) { if (!avoidLoopCut || !has_self_cut(m, y)) { // break down option 2-1 - or_item[option] = ctx.mk_eq_atom(xorFlag, mk_int(option)); + expr_ref current_or_item_option(ctx.mk_eq_atom(xorFlag, mk_int(option)), mgr); + or_item.push_back(current_or_item_option); expr_ref x_temp1(mk_concat(x, temp1), mgr); - and_item[pos++] = ctx.mk_eq_atom(or_item[option], ctx.mk_eq_atom(m, x_temp1)); - and_item[pos++] = ctx.mk_eq_atom(or_item[option], ctx.mk_eq_atom(y, temp1_strAst)); + and_item.push_back(ctx.mk_eq_atom(current_or_item_option, ctx.mk_eq_atom(m, x_temp1))); + and_item.push_back(ctx.mk_eq_atom(current_or_item_option, ctx.mk_eq_atom(y, temp1_strAst))); - and_item[pos++] = ctx.mk_eq_atom(or_item[option], ctx.mk_eq_atom(mk_strlen(m), - m_autil.mk_add(mk_strlen(x), mk_strlen(temp1)))); + and_item.push_back(ctx.mk_eq_atom(current_or_item_option, ctx.mk_eq_atom(mk_strlen(m), + m_autil.mk_add(mk_strlen(x), mk_strlen(temp1))))); ++option; add_cut_info_merge(temp1, ctx.get_scope_level(), y); @@ -3319,21 +3317,18 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { expr_ref cropStr(m_strutil.mk_string(part2Str), mgr); if (can_two_nodes_eq(x, x_concat) && can_two_nodes_eq(y, cropStr)) { // break down option 2-2 - or_item[option] = ctx.mk_eq_atom(xorFlag, mk_int(option)); - and_item[pos++] = ctx.mk_eq_atom(or_item[option], ctx.mk_eq_atom(x, x_concat)); - and_item[pos++] = ctx.mk_eq_atom(or_item[option], ctx.mk_eq_atom(y, cropStr)); - and_item[pos++] = ctx.mk_eq_atom(or_item[option], ctx.mk_eq_atom(mk_strlen(y), mk_int(part2Str.length()))); + expr_ref current_or_item_option(ctx.mk_eq_atom(xorFlag, mk_int(option)), mgr); + or_item.push_back(current_or_item_option); + and_item.push_back(ctx.mk_eq_atom(current_or_item_option, ctx.mk_eq_atom(x, x_concat))); + and_item.push_back(ctx.mk_eq_atom(current_or_item_option, ctx.mk_eq_atom(y, cropStr))); + and_item.push_back(ctx.mk_eq_atom(current_or_item_option, ctx.mk_eq_atom(mk_strlen(y), mk_int(part2Str.length())))); ++option; } } if (option > 0) { - if (option == 1) { - and_item[0] = or_item[0]; - } else { - and_item[0] = mgr.mk_or(option, or_item); - } - expr_ref implyR(mgr.mk_and(pos, and_item), mgr); + and_item.push_back(mk_or(or_item)); + expr_ref implyR(mk_and(and_item), mgr); assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); } else { TRACE("t_str", tout << "STOP: Should not split two EQ concats." << std::endl;); From 5e37a218025aa5ed1dd7cbf00fbdd47376931f81 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Fri, 18 Nov 2016 16:07:20 -0500 Subject: [PATCH 256/410] fix expr_ref in theory_str splits WIP --- src/smt/theory_str.cpp | 120 +++++++++++++++++++++-------------------- 1 file changed, 61 insertions(+), 59 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index f92939ac7..a34a6b8c1 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -23,6 +23,8 @@ Revision History: #include #include #include + +#include "../ast/ast.h" #include"theory_arith.h" namespace smt { @@ -2834,31 +2836,31 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { // len(x) < len(m) || len(y) > len(n) //-------------------------------------- if (!has_self_cut(m, y)) { - expr ** ax_l_items = alloc_svect(expr*, 3); - expr ** ax_r_items = alloc_svect(expr*, 3); + expr_ref_vector ax_l_items(mgr); + expr_ref_vector ax_r_items(mgr); - ax_l_items[0] = ctx.mk_eq_atom(concatAst1, concatAst2); + ax_l_items.push_back(ctx.mk_eq_atom(concatAst1, concatAst2)); expr_ref x_t1(mk_concat(x, t1), mgr); expr_ref t1_n(mk_concat(t1, n), mgr); - ax_r_items[0] = ctx.mk_eq_atom(m, x_t1); - ax_r_items[1] = ctx.mk_eq_atom(y, t1_n); + ax_r_items.push_back(ctx.mk_eq_atom(m, x_t1)); + ax_r_items.push_back(ctx.mk_eq_atom(y, t1_n)); if (m_len_exists && x_len_exists) { - ax_l_items[1] = ctx.mk_eq_atom(mk_strlen(x), mk_int(x_len)); - ax_l_items[2] = ctx.mk_eq_atom(mk_strlen(m), mk_int(m_len)); + ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_int(x_len))); + ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(m), mk_int(m_len))); rational m_sub_x = m_len - x_len; - ax_r_items[2] = ctx.mk_eq_atom(mk_strlen(t1), mk_int(m_sub_x)); + ax_r_items.push_back(ctx.mk_eq_atom(mk_strlen(t1), mk_int(m_sub_x))); } else { - ax_l_items[1] = ctx.mk_eq_atom(mk_strlen(y), mk_int(y_len)); - ax_l_items[2] = ctx.mk_eq_atom(mk_strlen(n), mk_int(n_len)); + ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_int(y_len))); + ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(n), mk_int(n_len))); rational y_sub_n = y_len - n_len; - ax_r_items[2] = ctx.mk_eq_atom(mk_strlen(t1), mk_int(y_sub_n)); + ax_r_items.push_back(ctx.mk_eq_atom(mk_strlen(t1), mk_int(y_sub_n))); } - expr_ref ax_l(mgr.mk_and(3, ax_l_items), mgr); - expr_ref ax_r(mgr.mk_and(3, ax_r_items), mgr); + expr_ref ax_l(mk_and(ax_l_items), mgr); + expr_ref ax_r(mk_and(ax_r_items), mgr); // Cut Info add_cut_info_merge(t1, sLevel, m); @@ -2885,27 +2887,27 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { expr_ref m_t2(mk_concat(m, t2), mgr); expr_ref t2_y(mk_concat(t2, y), mgr); - expr ** ax_l_items = alloc_svect(expr*, 3); - ax_l_items[0] = ctx.mk_eq_atom(concatAst1, concatAst2); + expr_ref_vector ax_l_items(mgr); + ax_l_items.push_back(ctx.mk_eq_atom(concatAst1, concatAst2)); - expr ** ax_r_items = alloc_svect(expr*, 3); - ax_r_items[0] = ctx.mk_eq_atom(x, m_t2); - ax_r_items[1] = ctx.mk_eq_atom(t2_y, n); + expr_ref_vector ax_r_items(mgr); + ax_r_items.push_back(ctx.mk_eq_atom(x, m_t2)); + ax_r_items.push_back(ctx.mk_eq_atom(t2_y, n)); if (m_len_exists && x_len_exists) { - ax_l_items[1] = ctx.mk_eq_atom(mk_strlen(x), mk_int(x_len)); - ax_l_items[2] = ctx.mk_eq_atom(mk_strlen(m), mk_int(m_len)); + ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_int(x_len))); + ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(m), mk_int(m_len))); rational x_sub_m = x_len - m_len; - ax_r_items[2] = ctx.mk_eq_atom(mk_strlen(t2), mk_int(x_sub_m)); + ax_r_items.push_back(ctx.mk_eq_atom(mk_strlen(t2), mk_int(x_sub_m))); } else { - ax_l_items[1] = ctx.mk_eq_atom(mk_strlen(y), mk_int(y_len)); - ax_l_items[2] = ctx.mk_eq_atom(mk_strlen(n), mk_int(n_len)); + ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_int(y_len))); + ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(n), mk_int(n_len))); rational n_sub_y = n_len - y_len; - ax_r_items[2] = ctx.mk_eq_atom(mk_strlen(t2), mk_int(n_sub_y)); + ax_r_items.push_back(ctx.mk_eq_atom(mk_strlen(t2), mk_int(n_sub_y))); } - expr_ref ax_l(mgr.mk_and(3, ax_l_items), mgr); - expr_ref ax_r(mgr.mk_and(3, ax_r_items), mgr); + expr_ref ax_l(mk_and(ax_l_items), mgr); + expr_ref ax_r(mk_and(ax_r_items), mgr); // Cut Info add_cut_info_merge(t2, sLevel, x); @@ -2919,8 +2921,8 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { } } else if (splitType == -1) { // Here we don't really have a choice. We have no length information at all... - expr ** or_item = alloc_svect(expr*, 3); - expr ** and_item = alloc_svect(expr*, 20); + expr_ref_vector or_item(mgr); + expr_ref_vector and_item(mgr); int option = 0; int pos = 1; @@ -2928,28 +2930,29 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { // len(x) < len(m) || len(y) > len(n) if (!avoidLoopCut || !has_self_cut(m, y)) { // break down option 1-1 - expr * x_t1 = mk_concat(x, t1); - expr * t1_n = mk_concat(t1, n); - or_item[option] = ctx.mk_eq_atom(xorFlag, mk_int(option)); - and_item[pos++] = ctx.mk_eq_atom(or_item[option], ctx.mk_eq_atom(m, x_t1)); - and_item[pos++] = ctx.mk_eq_atom(or_item[option], ctx.mk_eq_atom(y, t1_n)); + expr_ref x_t1(mk_concat(x, t1), mgr); + expr_ref t1_n(mk_concat(t1, n), mgr); + expr_ref or_item_option(ctx.mk_eq_atom(xorFlag, mk_int(option)), mgr); + or_item.push_back(or_item_option); + and_item.push_back(ctx.mk_eq_atom(or_item_option, ctx.mk_eq_atom(m, x_t1))); + and_item.push_back(ctx.mk_eq_atom(or_item_option, ctx.mk_eq_atom(y, t1_n))); expr_ref x_plus_t1(m_autil.mk_add(mk_strlen(x), mk_strlen(t1)), mgr); - and_item[pos++] = ctx.mk_eq_atom(or_item[option], ctx.mk_eq_atom(mk_strlen(m), x_plus_t1)); + and_item.push_back(ctx.mk_eq_atom(or_item_option, ctx.mk_eq_atom(mk_strlen(m), x_plus_t1))); // These were crashing the solver because the integer theory // expects a constant on the right-hand side. // The things we want to assert here are len(m) > len(x) and len(y) > len(n). // We rewrite A > B as A-B > 0 and then as not(A-B <= 0), // and then, *because we aren't allowed to use subtraction*, // as not(A + -1*B <= 0) - and_item[pos++] = ctx.mk_eq_atom(or_item[option], + and_item.push_back(ctx.mk_eq_atom(or_item_option, mgr.mk_not(m_autil.mk_le( m_autil.mk_add(mk_strlen(m), m_autil.mk_mul(mk_int(-1), mk_strlen(x))), - mk_int(0))) ); - and_item[pos++] = ctx.mk_eq_atom(or_item[option], + mk_int(0))) )); + and_item.push_back(ctx.mk_eq_atom(or_item_option, mgr.mk_not(m_autil.mk_le( m_autil.mk_add(mk_strlen(y),m_autil.mk_mul(mk_int(-1), mk_strlen(n))), - mk_int(0))) ); + mk_int(0))) )); option++; @@ -2965,25 +2968,26 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { // x = m || y = n if (!avoidLoopCut || !has_self_cut(x, n)) { // break down option 1-2 - expr * m_t2 = mk_concat(m, t2); - expr * t2_y = mk_concat(t2, y); - or_item[option] = ctx.mk_eq_atom(xorFlag, mk_int(option)); - and_item[pos++] = ctx.mk_eq_atom(or_item[option], ctx.mk_eq_atom(x, m_t2)); - and_item[pos++] = ctx.mk_eq_atom(or_item[option], ctx.mk_eq_atom(n, t2_y)); + expr_ref m_t2(mk_concat(m, t2), mgr); + expr_ref t2_y(mk_concat(t2, y), mgr); + expr_ref or_item_option(ctx.mk_eq_atom(xorFlag, mk_int(option)), mgr); + or_item.push_back(or_item_option); + and_item.push_back(ctx.mk_eq_atom(or_item_option, ctx.mk_eq_atom(x, m_t2))); + and_item.push_back(ctx.mk_eq_atom(or_item_option, ctx.mk_eq_atom(n, t2_y))); expr_ref m_plus_t2(m_autil.mk_add(mk_strlen(m), mk_strlen(t2)), mgr); - and_item[pos++] = ctx.mk_eq_atom(or_item[option], ctx.mk_eq_atom(mk_strlen(x), m_plus_t2)); + and_item.push_back(ctx.mk_eq_atom(or_item_option, ctx.mk_eq_atom(mk_strlen(x), m_plus_t2))); // want len(x) > len(m) and len(n) > len(y) - and_item[pos++] = ctx.mk_eq_atom(or_item[option], + and_item.push_back(ctx.mk_eq_atom(or_item_option, mgr.mk_not(m_autil.mk_le( m_autil.mk_add(mk_strlen(x), m_autil.mk_mul(mk_int(-1), mk_strlen(m))), - mk_int(0))) ); - and_item[pos++] = ctx.mk_eq_atom(or_item[option], + mk_int(0))) )); + and_item.push_back(ctx.mk_eq_atom(or_item_option, mgr.mk_not(m_autil.mk_le( m_autil.mk_add(mk_strlen(n), m_autil.mk_mul(mk_int(-1), mk_strlen(y))), - mk_int(0))) ); + mk_int(0))) )); option++; @@ -2997,22 +3001,20 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { } if (can_two_nodes_eq(x, m) && can_two_nodes_eq(y, n)) { - or_item[option] = ctx.mk_eq_atom(xorFlag, mk_int(option)); - and_item[pos++] = ctx.mk_eq_atom(or_item[option], ctx.mk_eq_atom(x, m)); - and_item[pos++] = ctx.mk_eq_atom(or_item[option], ctx.mk_eq_atom(y, n)); - and_item[pos++] = ctx.mk_eq_atom(or_item[option], ctx.mk_eq_atom(mk_strlen(x), mk_strlen(m))); - and_item[pos++] = ctx.mk_eq_atom(or_item[option], ctx.mk_eq_atom(mk_strlen(y), mk_strlen(n))); + expr_ref or_item_option(ctx.mk_eq_atom(xorFlag, mk_int(option)), mgr); + or_item.push_back(or_item_option); + and_item.push_back(ctx.mk_eq_atom(or_item_option, ctx.mk_eq_atom(x, m))); + and_item.push_back(ctx.mk_eq_atom(or_item_option, ctx.mk_eq_atom(y, n))); + and_item.push_back(ctx.mk_eq_atom(or_item_option, ctx.mk_eq_atom(mk_strlen(x), mk_strlen(m)))); + and_item.push_back(ctx.mk_eq_atom(or_item_option, ctx.mk_eq_atom(mk_strlen(y), mk_strlen(n)))); ++option; } if (option > 0) { - if (option == 1) { - and_item[0] = or_item[0]; - } else { - and_item[0] = mgr.mk_or(option, or_item); - } + and_item.push_back(mk_or(or_item)); + expr_ref premise(ctx.mk_eq_atom(concatAst1, concatAst2), mgr); - expr_ref conclusion(mgr.mk_and(pos, and_item), mgr); + expr_ref conclusion(mk_and(and_item), mgr); assert_implication(premise, conclusion); } else { TRACE("t_str", tout << "STOP: no split option found for two EQ concats." << std::endl;); From 11d8ffc4d430a8bf553a2df84639455a416f7868 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 22 Nov 2016 18:21:40 -0500 Subject: [PATCH 257/410] escape characters in theory_str --- src/ast/str_decl_plugin.cpp | 64 +++++++++++++++++++++++++++++++++ src/ast/str_decl_plugin.h | 7 ++++ src/parsers/smt2/smt2parser.cpp | 2 +- 3 files changed, 72 insertions(+), 1 deletion(-) diff --git a/src/ast/str_decl_plugin.cpp b/src/ast/str_decl_plugin.cpp index 4f9dcb7aa..f17551b94 100644 --- a/src/ast/str_decl_plugin.cpp +++ b/src/ast/str_decl_plugin.cpp @@ -339,6 +339,70 @@ str_util::str_util(ast_manager &m) : m_fid = m_plugin->get_family_id(); } +/* + * Scan through the string 'val' and interpret each instance of "backslash followed by a character" + * as a possible escape sequence. Emit all other characters as-is. + * This exists because the SMT-LIB 2.5 standard does not recognize escape sequences other than "" -> " . + * The escape sequences recognized are as follows: + * \a \b \e \f \n \r \t \v : as specified by the C++ standard + * \ooo : produces the ASCII character corresponding to the octal value "ooo", where each "o" is a + * single octal digit and between 1 and 3 valid digits are given + * \xhh : produces the ASCII character corresponding to the hexadecimal value "hh", where each "h" is a + * single case-insensitive hex digit (0-9A-F) and exactly 2 digits are given + * \C, for any character C that does not start a legal escape sequence : the backslash is ignored and "C" is produced. + */ +app * str_util::mk_string_with_escape_characters(std::string & val) { + std::string parsedStr; + parsedStr.reserve(val.length()); + for (unsigned i = 0; i < val.length(); ++i) { + char nextChar = val.at(i); + + if (nextChar == '\\') { + // check escape sequence + i++; + if (i >= val.length()) { + // TODO illegal escape sequence + NOT_IMPLEMENTED_YET(); + } + char escapeChar1 = val.at(i); + if (escapeChar1 == 'a') { + parsedStr.push_back('\a'); + } else if (escapeChar1 == 'b') { + parsedStr.push_back('\b'); + } else if (escapeChar1 == 'e') { + parsedStr.push_back('\e'); + } else if (escapeChar1 == 'f') { + parsedStr.push_back('\f'); + } else if (escapeChar1 == 'n') { + parsedStr.push_back('\n'); + } else if (escapeChar1 == 'r') { + parsedStr.push_back('\r'); + } else if (escapeChar1 == 't') { + parsedStr.push_back('\t'); + } else if (escapeChar1 == 'v') { + parsedStr.push_back('\v'); + } else if (escapeChar1 == 'x') { + // TODO hex escape + NOT_IMPLEMENTED_YET(); + } else if (escapeChar1 == '0' || escapeChar1 == '1' || escapeChar1 == '2' || escapeChar1 == '3' || + escapeChar1 == '4' || escapeChar1 == '5' || escapeChar1 == '6' || escapeChar1 == '7') { + // TODO octal escape + NOT_IMPLEMENTED_YET(); + } else { + // unrecognized escape sequence -- just emit that character + parsedStr.push_back(escapeChar1); + } + } else { + parsedStr.push_back(nextChar); + } + + // i is incremented at the end of this loop. + // If it is modified, ensure that it points to the index before + // the next character. + } + return mk_string(parsedStr); +} + static std::string str2RegexStr(std::string str) { std::string res = ""; int len = str.size(); diff --git a/src/ast/str_decl_plugin.h b/src/ast/str_decl_plugin.h index 8905d66bc..ff531e942 100644 --- a/src/ast/str_decl_plugin.h +++ b/src/ast/str_decl_plugin.h @@ -156,10 +156,17 @@ public: app * mk_string(std::string & val) { return m_plugin->mk_string(val); } + app * mk_fresh_string() { return m_plugin->mk_fresh_string(); } + app * mk_string_with_escape_characters(const char * val) { + std::string str(val); + return mk_string_with_escape_characters(str); + } + app * mk_string_with_escape_characters(std::string & val); + app * mk_re_Str2Reg(expr * s) { expr * es[1] = {s}; return m_manager.mk_app(get_fid(), OP_RE_STR2REGEX, 1, es); diff --git a/src/parsers/smt2/smt2parser.cpp b/src/parsers/smt2/smt2parser.cpp index c8e9a78b6..cdef41b72 100644 --- a/src/parsers/smt2/smt2parser.cpp +++ b/src/parsers/smt2/smt2parser.cpp @@ -1104,7 +1104,7 @@ namespace smt2 { strncpy(buf, original_token, bufsize); buf[bufsize] = '\0'; TRACE("parse_string", tout << "new string constant: " << buf << " length=" << bufsize << "\n";); - expr_stack().push_back(strutil().mk_string(buf)); + expr_stack().push_back(strutil().mk_string_with_escape_characters(buf)); next(); } From 8e962aa427e7a10ee315b9b3868fac7a8920a497 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 22 Nov 2016 18:32:03 -0500 Subject: [PATCH 258/410] escape chars in smt2 printing of string constants --- src/ast/ast_smt2_pp.cpp | 43 +++++++++++++++++++++++++++++++------ src/ast/str_decl_plugin.cpp | 4 +++- 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/src/ast/ast_smt2_pp.cpp b/src/ast/ast_smt2_pp.cpp index ce7177ec9..eece67a32 100644 --- a/src/ast/ast_smt2_pp.cpp +++ b/src/ast/ast_smt2_pp.cpp @@ -305,14 +305,45 @@ format * smt2_pp_environment::mk_float(rational const & val) const { } format * smt2_pp_environment::pp_str_literal(app * t) { - TRACE("parse_string", tout << "pp_str_literal\n";); - str_util & u = get_strutil(); - SASSERT(u.is_string(t)); - const char * val; - u.is_string(t, &val); ast_manager & m = get_manager(); + str_util & u = get_strutil(); + TRACE("parse_string", tout << "pp_str_literal\n";); + + SASSERT(u.is_string(t)); + std::string strVal = u.get_string_constant_value(t); string_buffer<> buf; - buf << "\"" << val << "\""; + buf << "\""; + + // we want to scan strVal and escape every non-printable character + for (unsigned int i = 0; i < strVal.length(); ++i) { + char c = strVal.at(i); + if (isprint(c)) { + buf << c; + } else if (c == '\a') { + buf << "\\a"; + } else if (c == '\b') { + buf << "\\b"; + } else if (c == '\e') { + buf << "\\e"; + } else if (c == '\f') { + buf << "\\f"; + } else if (c == '\n') { + buf << "\\n"; + } else if (c == '\r') { + buf << "\\r"; + } else if (c == '\t') { + buf << "\\t"; + } else if (c == '\v') { + buf << "\\v"; + } else if (c == '\\') { + buf << "\\" << "\\"; + } else { + // TODO general hex escape + NOT_IMPLEMENTED_YET(); + } + } + + buf << "\""; return mk_string(m, buf.c_str()); } diff --git a/src/ast/str_decl_plugin.cpp b/src/ast/str_decl_plugin.cpp index f17551b94..8ac1f722f 100644 --- a/src/ast/str_decl_plugin.cpp +++ b/src/ast/str_decl_plugin.cpp @@ -344,7 +344,7 @@ str_util::str_util(ast_manager &m) : * as a possible escape sequence. Emit all other characters as-is. * This exists because the SMT-LIB 2.5 standard does not recognize escape sequences other than "" -> " . * The escape sequences recognized are as follows: - * \a \b \e \f \n \r \t \v : as specified by the C++ standard + * \a \b \e \f \n \r \t \v \\ : as specified by the C++ standard * \ooo : produces the ASCII character corresponding to the octal value "ooo", where each "o" is a * single octal digit and between 1 and 3 valid digits are given * \xhh : produces the ASCII character corresponding to the hexadecimal value "hh", where each "h" is a @@ -381,6 +381,8 @@ app * str_util::mk_string_with_escape_characters(std::string & val) { parsedStr.push_back('\t'); } else if (escapeChar1 == 'v') { parsedStr.push_back('\v'); + } else if (escapeChar1 == '\\') { + parsedStr.push_back('\\'); } else if (escapeChar1 == 'x') { // TODO hex escape NOT_IMPLEMENTED_YET(); From 889b6be2c3eb5d78613ceb46df98e290c12c2fde Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 23 Nov 2016 19:03:53 -0500 Subject: [PATCH 259/410] fix smt-lib 2.5 double quotes in pp --- src/ast/ast_smt2_pp.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/ast/ast_smt2_pp.cpp b/src/ast/ast_smt2_pp.cpp index eece67a32..7e178b422 100644 --- a/src/ast/ast_smt2_pp.cpp +++ b/src/ast/ast_smt2_pp.cpp @@ -317,7 +317,10 @@ format * smt2_pp_environment::pp_str_literal(app * t) { // we want to scan strVal and escape every non-printable character for (unsigned int i = 0; i < strVal.length(); ++i) { char c = strVal.at(i); - if (isprint(c)) { + if (c == '"') { + // SMT-LIB 2.5 string escape + buf << "\"\""; + } else if (isprint(c)) { buf << c; } else if (c == '\a') { buf << "\\a"; From 1fa8129c8f063a5508a63f2450c5c0184429e7ca Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Fri, 25 Nov 2016 18:02:24 -0500 Subject: [PATCH 260/410] pretty-printing of general escape sequences for string literals --- src/ast/ast_smt2_pp.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/ast/ast_smt2_pp.cpp b/src/ast/ast_smt2_pp.cpp index 7e178b422..ed634069c 100644 --- a/src/ast/ast_smt2_pp.cpp +++ b/src/ast/ast_smt2_pp.cpp @@ -341,8 +341,14 @@ format * smt2_pp_environment::pp_str_literal(app * t) { } else if (c == '\\') { buf << "\\" << "\\"; } else { - // TODO general hex escape - NOT_IMPLEMENTED_YET(); + // general hex escape + buf << "\\x"; + unsigned int cVal = (unsigned int)c; + const char convtable[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + unsigned int highPart = cVal / 16; + unsigned int lowPart = cVal % 16; + SASSERT(highPart < 16); SASSERT(lowPart < 16); + buf << convtable[highPart] << convtable[lowPart]; } } From 8c33dfab39c380ab5162b712bbab024fecb61cf8 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sun, 27 Nov 2016 20:51:34 -0500 Subject: [PATCH 261/410] fix escape character overflow print --- src/ast/ast_smt2_pp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ast/ast_smt2_pp.cpp b/src/ast/ast_smt2_pp.cpp index ed634069c..db2043320 100644 --- a/src/ast/ast_smt2_pp.cpp +++ b/src/ast/ast_smt2_pp.cpp @@ -343,7 +343,7 @@ format * smt2_pp_environment::pp_str_literal(app * t) { } else { // general hex escape buf << "\\x"; - unsigned int cVal = (unsigned int)c; + unsigned int cVal = ((unsigned int)c) & 0x000000FF; const char convtable[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; unsigned int highPart = cVal / 16; unsigned int lowPart = cVal % 16; From 1e65511a3f86084c8fdcaf1e8e6445091dcef43b Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Mon, 28 Nov 2016 16:21:26 -0500 Subject: [PATCH 262/410] save a few functions to trail in theory_str --- src/smt/theory_str.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index a34a6b8c1..ca27169ed 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -662,6 +662,7 @@ app * theory_str::mk_unroll(expr * n, expr * bound) { expr * args[2] = {n, bound}; app * unrollFunc = get_manager().mk_app(get_id(), OP_RE_UNROLL, 0, 0, 2, args); + m_trail.push_back(unrollFunc); expr_ref_vector items(m); items.push_back(ctx.mk_eq_atom(ctx.mk_eq_atom(bound, mk_int(0)), ctx.mk_eq_atom(unrollFunc, m_strutil.mk_string("")))); @@ -677,6 +678,7 @@ app * theory_str::mk_unroll(expr * n, expr * bound) { app * theory_str::mk_contains(expr * haystack, expr * needle) { expr * args[2] = {haystack, needle}; app * contains = get_manager().mk_app(get_id(), OP_STR_CONTAINS, 0, 0, 2, args); + m_trail.push_back(contains); // immediately force internalization so that axiom setup does not fail get_context().internalize(contains, false); set_up_axioms(contains); @@ -686,6 +688,7 @@ app * theory_str::mk_contains(expr * haystack, expr * needle) { app * theory_str::mk_indexof(expr * haystack, expr * needle) { expr * args[2] = {haystack, needle}; app * indexof = get_manager().mk_app(get_id(), OP_STR_INDEXOF, 0, 0, 2, args); + m_trail.push_back(indexof); // immediately force internalization so that axiom setup does not fail get_context().internalize(indexof, false); set_up_axioms(indexof); From b77f6666dc82cd5976ef4f9916e4e0aba8865955 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Mon, 28 Nov 2016 18:40:28 -0500 Subject: [PATCH 263/410] refactor process_concat_eq_type_6 to use expr_ref_vector --- src/smt/theory_str.cpp | 52 +++++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index ca27169ed..6c584fa2e 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -2272,19 +2272,14 @@ expr * theory_str::simplify_concat(expr * node) { if (in_same_eqc(node, resultAst)) { TRACE("t_str_detail", tout << "SKIP: both concats are already in the same equivalence class" << std::endl;); } else { - // TODO refactor - expr ** items = alloc_svect(expr*, resolvedMap.size()); + expr_ref_vector items(m); int pos = 0; std::map::iterator itor = resolvedMap.begin(); for (; itor != resolvedMap.end(); ++itor) { - items[pos++] = ctx.mk_eq_atom(itor->first, itor->second); - } - expr_ref premise(m); - if (pos == 1) { - premise = items[0]; - } else { - premise = m.mk_and(pos, items); + items.push_back(ctx.mk_eq_atom(itor->first, itor->second)); + pos += 1; } + expr_ref premise(mk_and(items), m); expr_ref conclusion(ctx.mk_eq_atom(node, resultAst), m); assert_implication(premise, conclusion); } @@ -3961,22 +3956,26 @@ void theory_str::process_concat_eq_type6(expr * concatAst1, expr * concatAst2) { refresh_theory_var(commonVar); } - expr ** or_item = alloc_svect(expr*, (overlapLen.size() + 1)); + expr_ref_vector or_item(mgr); int option = 0; - expr ** and_item = alloc_svect(expr*, (1 + 4 * (overlapLen.size() + 1))); + expr_ref_vector and_item(mgr); int pos = 1; if (!avoidLoopCut || !has_self_cut(m, y)) { - or_item[option] = ctx.mk_eq_atom(xorFlag, mk_int(option)); + expr_ref or_item_option(ctx.mk_eq_atom(xorFlag, mk_int(option)), mgr); + or_item.push_back(or_item_option); expr_ref str1_commonVar(mk_concat(str1Ast, commonVar), mgr); - and_item[pos++] = ctx.mk_eq_atom(or_item[option], ctx.mk_eq_atom(m, str1_commonVar)); + and_item.push_back(ctx.mk_eq_atom(or_item_option, ctx.mk_eq_atom(m, str1_commonVar))); + pos += 1; expr_ref commonVar_str2(mk_concat(commonVar, str2Ast), mgr); - and_item[pos++] = ctx.mk_eq_atom(or_item[option], ctx.mk_eq_atom(y, commonVar_str2)); + and_item.push_back(ctx.mk_eq_atom(or_item_option, ctx.mk_eq_atom(y, commonVar_str2))); + pos += 1; - and_item[pos++] = ctx.mk_eq_atom(or_item[option], ctx.mk_eq_atom(mk_strlen(m), - m_autil.mk_add(mk_strlen(str1Ast), mk_strlen(commonVar)) )); + and_item.push_back(ctx.mk_eq_atom(or_item_option, ctx.mk_eq_atom(mk_strlen(m), + m_autil.mk_add(mk_strlen(str1Ast), mk_strlen(commonVar)) ))); + pos += 1; // addItems[0] = mk_length(t, commonVar); // addItems[1] = mk_length(t, str2Ast); @@ -3993,29 +3992,34 @@ void theory_str::process_concat_eq_type6(expr * concatAst1, expr * concatAst2) { int overLen = *itor; std::string prefix = str1Value.substr(0, str1Len - overLen); std::string suffix = str2Value.substr(overLen, str2Len - overLen); - or_item[option] = ctx.mk_eq_atom(xorFlag, mk_int(option)); + expr_ref or_item_option(ctx.mk_eq_atom(xorFlag, mk_int(option)), mgr); + or_item.push_back(or_item_option); expr_ref prefixAst(m_strutil.mk_string(prefix), mgr); expr_ref x_eq_prefix(ctx.mk_eq_atom(m, prefixAst), mgr); - and_item[pos++] = ctx.mk_eq_atom(or_item[option], x_eq_prefix); + and_item.push_back(ctx.mk_eq_atom(or_item_option, x_eq_prefix)); + pos += 1; - and_item[pos++] = ctx.mk_eq_atom(or_item[option], - ctx.mk_eq_atom(mk_strlen(m), mk_strlen(prefixAst))); + and_item.push_back(ctx.mk_eq_atom(or_item_option, + ctx.mk_eq_atom(mk_strlen(m), mk_strlen(prefixAst)))); + pos += 1; // adding length constraint for _ = constStr seems slowing things down. expr_ref suffixAst(m_strutil.mk_string(suffix), mgr); expr_ref y_eq_suffix(ctx.mk_eq_atom(y, suffixAst), mgr); - and_item[pos++] = ctx.mk_eq_atom(or_item[option], y_eq_suffix); + and_item.push_back(ctx.mk_eq_atom(or_item_option, y_eq_suffix)); + pos += 1; - and_item[pos++] = ctx.mk_eq_atom(or_item[option], ctx.mk_eq_atom(mk_strlen(y), mk_strlen(suffixAst))); + and_item.push_back(ctx.mk_eq_atom(or_item_option, ctx.mk_eq_atom(mk_strlen(y), mk_strlen(suffixAst)))); + pos += 1; option++; } // case 6: concat("str1", y) = concat(m, "str2") - and_item[0] = mgr.mk_or(option, or_item); - expr_ref implyR(mgr.mk_and(pos, and_item), mgr); + and_item.push_back(mk_or(or_item)); + expr_ref implyR(mk_and(and_item), mgr); assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); } From f968f79d1c5e4f6bc696a62a77ce140dd61f5a5b Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Mon, 28 Nov 2016 18:47:42 -0500 Subject: [PATCH 264/410] refactor solve_concat_eq_str to use expr_ref_vector --- src/smt/theory_str.cpp | 37 ++++++++++++++++--------------------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 6c584fa2e..ea1ae8677 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -5824,19 +5824,16 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { if (arg1 != a1 || arg2 != a2) { TRACE("t_str", tout << "resolved concat argument(s) to eqc string constants" << std::endl;); int iPos = 0; - app * item1[2]; + expr_ref_vector item1(m); if (a1 != arg1) { - item1[iPos++] = ctx.mk_eq_atom(a1, arg1); + item1.push_back(ctx.mk_eq_atom(a1, arg1)); + iPos += 1; } if (a2 != arg2) { - item1[iPos++] = ctx.mk_eq_atom(a2, arg2); - } - expr_ref implyL1(m); - if (iPos == 1) { - implyL1 = item1[0]; - } else { - implyL1 = m.mk_and(item1[0], item1[1]); + item1.push_back(ctx.mk_eq_atom(a2, arg2)); + iPos += 1; } + expr_ref implyL1(mk_and(item1), m); newConcat = mk_concat(arg1, arg2); if (newConcat != str) { expr_ref implyR1(ctx.mk_eq_atom(concat, newConcat), m); @@ -6091,8 +6088,8 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { int xor_pos = 0; int and_count = 1; - expr ** xor_items = alloc_svect(expr*, (concatStrLen+1)); - expr ** and_items = alloc_svect(expr*, (4 * (concatStrLen+1) + 1)); + expr_ref_vector xor_items(m); + expr_ref_vector and_items(m); for (int i = 0; i < concatStrLen + 1; ++i) { std::string prefixStr = const_str.substr(0, i); @@ -6105,15 +6102,18 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { continue; } expr_ref xorAst(ctx.mk_eq_atom(xorFlag, m_autil.mk_numeral(rational(xor_pos), true)), m); - xor_items[xor_pos++] = xorAst; + xor_items.push_back(xorAst); + xor_pos += 1; expr_ref prefixAst(m_strutil.mk_string(prefixStr), m); expr_ref arg1_eq (ctx.mk_eq_atom(arg1, prefixAst), m); - and_items[and_count++] = ctx.mk_eq_atom(xorAst, arg1_eq); + and_items.push_back(ctx.mk_eq_atom(xorAst, arg1_eq)); + and_count += 1; expr_ref suffixAst(m_strutil.mk_string(suffixStr), m); expr_ref arg2_eq (ctx.mk_eq_atom(arg2, suffixAst), m); - and_items[and_count++] = ctx.mk_eq_atom(xorAst, arg2_eq); + and_items.push_back(ctx.mk_eq_atom(xorAst, arg2_eq)); + and_count += 1; } expr_ref implyL(ctx.mk_eq_atom(concat, str), m); @@ -6124,13 +6124,8 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { expr_ref negate_ast(m.mk_not(concat_eq_str), m); assert_axiom(negate_ast); } else { - if (xor_pos == 1) { - and_items[0] = xor_items[0]; - implyR1 = m.mk_and(and_count, and_items); - } else { - and_items[0] = m.mk_or(xor_pos, xor_items); - implyR1 = m.mk_and(and_count, and_items); - } + and_items.push_back(mk_or(xor_items)); + implyR1 = mk_and(and_items); assert_implication(implyL, implyR1); } } /* (arg1Len != 1 || arg2Len != 1) */ From 361f02ef1dc8ea0c3eeb9c5d993cc4e15e0dbab6 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Mon, 28 Nov 2016 21:34:55 -0500 Subject: [PATCH 265/410] remove assignment refcount hack from theory_str::pop_scope_eh --- src/smt/theory_str.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index ea1ae8677..37be73333 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -6686,8 +6686,9 @@ void theory_str::pop_scope_eh(unsigned num_scopes) { // TODO: figure out what's going out of scope and why context & ctx = get_context(); ast_manager & m = get_manager(); - expr_ref_vector assignments(m); - ctx.get_assignments(assignments); + + // expr_ref_vector assignments(m); + // ctx.get_assignments(assignments); TRACE_CODE(if (is_trace_enabled("t_str_dump_assign_on_scope_change")) { dump_assignments(); }); @@ -8254,6 +8255,7 @@ expr * theory_str::gen_val_options(expr * freeVar, expr * len_indicator, expr * // ---------------------------------------------------------------------------------------- + // TODO refactor this and below to use expr_ref_vector instead of ptr_vector/svect ptr_vector orList; ptr_vector andList; From 947d4437266ae05a397f024ae622406631ffc090 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 29 Nov 2016 19:46:37 -0500 Subject: [PATCH 266/410] improved regex concat rewrite --- src/ast/rewriter/str_rewriter.cpp | 22 ++++++++++++++++++++++ src/ast/rewriter/str_rewriter.h | 1 + 2 files changed, 23 insertions(+) diff --git a/src/ast/rewriter/str_rewriter.cpp b/src/ast/rewriter/str_rewriter.cpp index 875343655..e30e857b2 100644 --- a/src/ast/rewriter/str_rewriter.cpp +++ b/src/ast/rewriter/str_rewriter.cpp @@ -489,6 +489,25 @@ br_status str_rewriter::mk_re_RegexStar(expr * re, expr_ref & result) { } } +br_status str_rewriter::mk_re_RegexConcat(expr * r0, expr * r1, expr_ref & result) { + TRACE("t_str_rw", tout << "rewrite (RegexConcat " << mk_pp(r0, m()) << " " << mk_pp(r1, m()) << ")" << std::endl;); + // (RegexConcat (Str2Reg "A") (Str2Reg "B")) --> (Str2Reg "AB") + if (m_strutil.is_re_Str2Reg(r0) && m_strutil.is_re_Str2Reg(r1)) { + expr * r0str = to_app(r0)->get_arg(0); + expr * r1str = to_app(r1)->get_arg(0); + ENSURE(m_strutil.is_string(r0str)); + ENSURE(m_strutil.is_string(r1str)); + std::string r0val = m_strutil.get_string_constant_value(r0str); + std::string r1val = m_strutil.get_string_constant_value(r1str); + std::string simplifyVal = r0val + r1val; + TRACE("t_str_rw", tout << "RegexConcat fast path: both sides are Str2Reg, simplify to (Str2Reg \"" << simplifyVal << "\")" << std::endl;); + result = m_strutil.mk_re_Str2Reg(simplifyVal); + return BR_DONE; + } + + return BR_FAILED; +} + br_status str_rewriter::mk_re_RegexPlus(expr * re, expr_ref & result) { /* * Two optimizations are possible if we inspect 're'. @@ -596,6 +615,9 @@ br_status str_rewriter::mk_app_core(func_decl * f, unsigned num_args, expr * con case OP_RE_REGEXSTAR: SASSERT(num_args == 1); return mk_re_RegexStar(args[0], result); + case OP_RE_REGEXCONCAT: + SASSERT(num_args == 2); + return mk_re_RegexConcat(args[0], args[1], result); case OP_RE_REGEXCHARRANGE: SASSERT(num_args == 2); return mk_re_RegexCharRange(args[0], args[1], result); diff --git a/src/ast/rewriter/str_rewriter.h b/src/ast/rewriter/str_rewriter.h index 822fb1ea8..145c0193e 100644 --- a/src/ast/rewriter/str_rewriter.h +++ b/src/ast/rewriter/str_rewriter.h @@ -60,6 +60,7 @@ public: br_status mk_re_RegexIn(expr * str, expr * re, expr_ref & result); br_status mk_re_RegexPlus(expr * re, expr_ref & result); br_status mk_re_RegexStar(expr * re, expr_ref & result); + br_status mk_re_RegexConcat(expr * r0, expr * r1, expr_ref & result); br_status mk_re_RegexCharRange(expr * start, expr * end, expr_ref & result); bool reduce_eq(expr * l, expr * r, expr_ref_vector & lhs, expr_ref_vector & rhs, bool & change); From edf151c9a0a3b0b69803f9944c6fc88bf1443a76 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 29 Nov 2016 21:46:00 -0500 Subject: [PATCH 267/410] testing term generation refactor in theory_str::check_length_const_string --- src/smt/theory_str.cpp | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 37be73333..76b605ff9 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -5453,6 +5453,7 @@ bool theory_str::can_two_nodes_eq(expr * n1, expr * n2) { // case 0: n1_curr is const string, n2_curr is const string if (is_string(n1_curr) && is_string(n2_curr)) { if (n1_curr != n2_curr) { + // TODO confirm whether it's okay to compare the pointers like this return false; } } @@ -5494,8 +5495,18 @@ bool theory_str::check_length_const_string(expr * n1, expr * constStr) { rational strLen((unsigned) (m_strutil.get_string_constant_value(constStr).length())); if (is_concat(to_app(n1))) { + /* + * This has been refactored from Z3str2. + * We avoid creating new subexpressions until we actually detect a conflict. + * This may avoid a bit of overhead incurred by creating these terms. + */ + ptr_vector args; - expr_ref_vector items(mgr); + + expr_ref_vector eq_args(mgr); + vector eq_lens; + // foreach (arg, len) in zip(eq_args, eq_lens): + // generate eq(mk_strlen(arg), mk_int(len)) get_nodes_in_concat(n1, args); @@ -5505,12 +5516,20 @@ bool theory_str::check_length_const_string(expr * n1, expr * constStr) { bool argLen_exists = get_len_value(args[i], argLen); if (argLen_exists) { if (!m_strutil.is_string(args[i])) { - items.push_back(ctx.mk_eq_atom(mk_strlen(args[i]), mk_int(argLen))); + // items.push_back(ctx.mk_eq_atom(mk_strlen(args[i]), mk_int(argLen))); + eq_args.push_back(args[i]); + eq_lens.push_back(rational(argLen)); } TRACE("t_str_detail", tout << "concat arg: " << mk_pp(args[i], mgr) << " has len = " << argLen.to_string() << std::endl;); sumLen += argLen; if (sumLen > strLen) { + expr_ref_vector items(mgr); items.push_back(ctx.mk_eq_atom(n1, constStr)); + for (unsigned int z = 0; z < eq_args.size(); ++z) { + expr * arg = eq_args.get(z); + rational len = eq_lens.get(z); + items.push_back(ctx.mk_eq_atom(mk_strlen(arg), mk_int(len))); + } expr_ref toAssert(mgr.mk_not(mk_and(items)), mgr); TRACE("t_str_detail", tout << "inconsistent length: concat (len = " << sumLen << ") <==> string constant (len = " << strLen << ")" << std::endl;); assert_axiom(toAssert); From 599cc1e75d616c4640c1813bf2d26076372e18eb Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 30 Nov 2016 13:08:42 -0500 Subject: [PATCH 268/410] ref_vector refactoring in theory_str::check_length_concat_concat --- src/smt/theory_str.cpp | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 76b605ff9..84adf819d 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -5569,7 +5569,13 @@ bool theory_str::check_length_concat_concat(expr * n1, expr * n2) { bool concat1LenFixed = true; bool concat2LenFixed = true; - expr_ref_vector items(mgr); + /* + * Refactored from the Z3str2 version. + * We delay creation of new terms until a conflict + * is actually detected. + */ + expr_ref_vector eq_args(mgr); + vector eq_lens; rational sum1(0), sum2(0); @@ -5580,7 +5586,9 @@ bool theory_str::check_length_concat_concat(expr * n1, expr * n2) { if (argLen_exists) { sum1 += argLen; if (!m_strutil.is_string(oneArg)) { - items.push_back(ctx.mk_eq_atom(mk_strlen(oneArg), mk_int(argLen))); + // items.push_back(ctx.mk_eq_atom(mk_strlen(oneArg), mk_int(argLen))); + eq_args.push_back(oneArg); + eq_lens.push_back(rational(argLen)); } } else { concat1LenFixed = false; @@ -5594,15 +5602,15 @@ bool theory_str::check_length_concat_concat(expr * n1, expr * n2) { if (argLen_exists) { sum2 += argLen; if (!m_strutil.is_string(oneArg)) { - items.push_back(ctx.mk_eq_atom(mk_strlen(oneArg), mk_int(argLen))); + // items.push_back(ctx.mk_eq_atom(mk_strlen(oneArg), mk_int(argLen))); + eq_args.push_back(oneArg); + eq_lens.push_back(rational(argLen)); } } else { concat2LenFixed = false; } } - items.push_back(ctx.mk_eq_atom(n1, n2)); - bool conflict = false; if (concat1LenFixed && concat2LenFixed) { @@ -5621,6 +5629,13 @@ bool theory_str::check_length_concat_concat(expr * n1, expr * n2) { if (conflict) { TRACE("t_str_detail", tout << "inconsistent length detected in concat <==> concat" << std::endl;); + expr_ref_vector items(mgr); + for (unsigned int z = 0; z < eq_args.size(); ++z) { + expr * arg = eq_args.get(z); + rational len = eq_lens.get(z); + items.push_back(ctx.mk_eq_atom(mk_strlen(arg), mk_int(len))); + } + items.push_back(ctx.mk_eq_atom(n1, n2)); expr_ref toAssert(mgr.mk_not(mk_and(items)), mgr); assert_axiom(toAssert); return false; From fd1bf65b6472b883203ea3f0fecb33ee028c66df Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 30 Nov 2016 15:52:58 -0500 Subject: [PATCH 269/410] experimental non-reuse of XOR vars in theory_str --- src/smt/theory_str.cpp | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 84adf819d..ec0a432d1 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -2815,13 +2815,12 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { if (entry1InScope) { t1 = varForBreakConcat[key1][0]; t2 = varForBreakConcat[key1][1]; - xorFlag = varForBreakConcat[key1][2]; + xorFlag = mk_internal_xor_var(); } else { t1 = varForBreakConcat[key2][0]; t2 = varForBreakConcat[key2][1]; - xorFlag = varForBreakConcat[key2][2]; + xorFlag = mk_internal_xor_var(); } - // TODO do I need to refresh the xorFlag, which is an integer var, and if so, how? refresh_theory_var(t1); refresh_theory_var(t2); } @@ -3141,12 +3140,11 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { } else { if (entry1InScope) { temp1 = varForBreakConcat[key1][0]; - xorFlag = varForBreakConcat[key1][1]; + xorFlag = mk_internal_xor_var(); } else if (entry2InScope) { temp1 = varForBreakConcat[key2][0]; - xorFlag = varForBreakConcat[key2][1]; + xorFlag = mk_internal_xor_var(); } - // TODO refresh xorFlag? refresh_theory_var(temp1); } @@ -3451,10 +3449,10 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { } else { if (entry1InScope) { temp1 = varForBreakConcat[key1][0]; - xorFlag = varForBreakConcat[key1][1]; + xorFlag = mk_internal_xor_var(); } else if (varForBreakConcat.find(key2) != varForBreakConcat.end()) { temp1 = varForBreakConcat[key2][0]; - xorFlag = varForBreakConcat[key2][1]; + xorFlag = mk_internal_xor_var(); } refresh_theory_var(temp1); } @@ -3948,10 +3946,10 @@ void theory_str::process_concat_eq_type6(expr * concatAst1, expr * concatAst2) { } else { if (entry1InScope) { commonVar = (entry1->second)[0]; - xorFlag = (entry1->second)[1]; + xorFlag = mk_internal_xor_var(); } else { commonVar = (entry2->second)[0]; - xorFlag = (entry2->second)[1]; + xorFlag = mk_internal_xor_var(); } refresh_theory_var(commonVar); } @@ -6113,9 +6111,9 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { xorFlag = mk_internal_xor_var(); varForBreakConcat[key1][0] = xorFlag; } else if (entry1InScope) { - xorFlag = varForBreakConcat[key1][0]; + xorFlag = mk_internal_xor_var(); } else { // entry2InScope - xorFlag = varForBreakConcat[key2][0]; + xorFlag = mk_internal_xor_var(); } int concatStrLen = const_str.length(); From 170e2b4e2a56b1bbcafcf104ead8f16baa2fc054 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 30 Nov 2016 19:41:00 -0500 Subject: [PATCH 270/410] refactor theory_str::check_length_concat_var --- src/smt/theory_str.cpp | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index ec0a432d1..bc73db405 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -5652,7 +5652,15 @@ bool theory_str::check_length_concat_var(expr * concat, expr * var) { } else { rational sumLen(0); ptr_vector args; - expr_ref_vector items(mgr); + + /* + * Refactor from the Z3str2 version. + * Only generate new terms if a conflict is actually detected. + */ + + expr_ref_vector eq_args(mgr); + vector eq_lens; + get_nodes_in_concat(concat, args); for (unsigned int i = 0; i < args.size(); ++i) { expr * oneArg = args[i]; @@ -5660,11 +5668,19 @@ bool theory_str::check_length_concat_var(expr * concat, expr * var) { bool argLen_exists = get_len_value(oneArg, argLen); if (argLen_exists) { if (!m_strutil.is_string(oneArg) && !argLen.is_zero()) { - items.push_back(ctx.mk_eq_atom(mk_strlen(oneArg), mk_int(argLen))); + // items.push_back(ctx.mk_eq_atom(mk_strlen(oneArg), mk_int(argLen))); + eq_args.push_back(oneArg); + eq_lens.push_back(rational(argLen)); } sumLen += argLen; if (sumLen > varLen) { TRACE("t_str_detail", tout << "inconsistent length detected in concat <==> var" << std::endl;); + expr_ref_vector items(mgr); + for (unsigned int z = 0; z < eq_args.size(); ++z) { + expr * arg = eq_args.get(z); + rational len = eq_lens.get(z); + items.push_back(ctx.mk_eq_atom(mk_strlen(arg), mk_int(len))); + } items.push_back(ctx.mk_eq_atom(mk_strlen(var), mk_int(varLen))); items.push_back(ctx.mk_eq_atom(concat, var)); expr_ref toAssert(mgr.mk_not(mk_and(items)), mgr); From 10c0d94cf2a7834efed103d01f2c3d498045040e Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Thu, 1 Dec 2016 15:19:50 -0500 Subject: [PATCH 271/410] Revert "refactor theory_str::check_length_concat_var" This reverts commit 170e2b4e2a56b1bbcafcf104ead8f16baa2fc054. --- src/smt/theory_str.cpp | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index bc73db405..ec0a432d1 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -5652,15 +5652,7 @@ bool theory_str::check_length_concat_var(expr * concat, expr * var) { } else { rational sumLen(0); ptr_vector args; - - /* - * Refactor from the Z3str2 version. - * Only generate new terms if a conflict is actually detected. - */ - - expr_ref_vector eq_args(mgr); - vector eq_lens; - + expr_ref_vector items(mgr); get_nodes_in_concat(concat, args); for (unsigned int i = 0; i < args.size(); ++i) { expr * oneArg = args[i]; @@ -5668,19 +5660,11 @@ bool theory_str::check_length_concat_var(expr * concat, expr * var) { bool argLen_exists = get_len_value(oneArg, argLen); if (argLen_exists) { if (!m_strutil.is_string(oneArg) && !argLen.is_zero()) { - // items.push_back(ctx.mk_eq_atom(mk_strlen(oneArg), mk_int(argLen))); - eq_args.push_back(oneArg); - eq_lens.push_back(rational(argLen)); + items.push_back(ctx.mk_eq_atom(mk_strlen(oneArg), mk_int(argLen))); } sumLen += argLen; if (sumLen > varLen) { TRACE("t_str_detail", tout << "inconsistent length detected in concat <==> var" << std::endl;); - expr_ref_vector items(mgr); - for (unsigned int z = 0; z < eq_args.size(); ++z) { - expr * arg = eq_args.get(z); - rational len = eq_lens.get(z); - items.push_back(ctx.mk_eq_atom(mk_strlen(arg), mk_int(len))); - } items.push_back(ctx.mk_eq_atom(mk_strlen(var), mk_int(varLen))); items.push_back(ctx.mk_eq_atom(concat, var)); expr_ref toAssert(mgr.mk_not(mk_and(items)), mgr); From 548f635f7ed1094d8aa84ead6f6acd80951eb4d4 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Thu, 1 Dec 2016 15:19:50 -0500 Subject: [PATCH 272/410] Revert "experimental non-reuse of XOR vars in theory_str" This reverts commit fd1bf65b6472b883203ea3f0fecb33ee028c66df. --- src/smt/theory_str.cpp | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index ec0a432d1..84adf819d 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -2815,12 +2815,13 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { if (entry1InScope) { t1 = varForBreakConcat[key1][0]; t2 = varForBreakConcat[key1][1]; - xorFlag = mk_internal_xor_var(); + xorFlag = varForBreakConcat[key1][2]; } else { t1 = varForBreakConcat[key2][0]; t2 = varForBreakConcat[key2][1]; - xorFlag = mk_internal_xor_var(); + xorFlag = varForBreakConcat[key2][2]; } + // TODO do I need to refresh the xorFlag, which is an integer var, and if so, how? refresh_theory_var(t1); refresh_theory_var(t2); } @@ -3140,11 +3141,12 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { } else { if (entry1InScope) { temp1 = varForBreakConcat[key1][0]; - xorFlag = mk_internal_xor_var(); + xorFlag = varForBreakConcat[key1][1]; } else if (entry2InScope) { temp1 = varForBreakConcat[key2][0]; - xorFlag = mk_internal_xor_var(); + xorFlag = varForBreakConcat[key2][1]; } + // TODO refresh xorFlag? refresh_theory_var(temp1); } @@ -3449,10 +3451,10 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { } else { if (entry1InScope) { temp1 = varForBreakConcat[key1][0]; - xorFlag = mk_internal_xor_var(); + xorFlag = varForBreakConcat[key1][1]; } else if (varForBreakConcat.find(key2) != varForBreakConcat.end()) { temp1 = varForBreakConcat[key2][0]; - xorFlag = mk_internal_xor_var(); + xorFlag = varForBreakConcat[key2][1]; } refresh_theory_var(temp1); } @@ -3946,10 +3948,10 @@ void theory_str::process_concat_eq_type6(expr * concatAst1, expr * concatAst2) { } else { if (entry1InScope) { commonVar = (entry1->second)[0]; - xorFlag = mk_internal_xor_var(); + xorFlag = (entry1->second)[1]; } else { commonVar = (entry2->second)[0]; - xorFlag = mk_internal_xor_var(); + xorFlag = (entry2->second)[1]; } refresh_theory_var(commonVar); } @@ -6111,9 +6113,9 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { xorFlag = mk_internal_xor_var(); varForBreakConcat[key1][0] = xorFlag; } else if (entry1InScope) { - xorFlag = mk_internal_xor_var(); + xorFlag = varForBreakConcat[key1][0]; } else { // entry2InScope - xorFlag = mk_internal_xor_var(); + xorFlag = varForBreakConcat[key2][0]; } int concatStrLen = const_str.length(); From b020c71f8a8ddbd01addb88269cb582459ecc204 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Thu, 1 Dec 2016 15:19:51 -0500 Subject: [PATCH 273/410] Revert "ref_vector refactoring in theory_str::check_length_concat_concat" This reverts commit 599cc1e75d616c4640c1813bf2d26076372e18eb. --- src/smt/theory_str.cpp | 25 +++++-------------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 84adf819d..76b605ff9 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -5569,13 +5569,7 @@ bool theory_str::check_length_concat_concat(expr * n1, expr * n2) { bool concat1LenFixed = true; bool concat2LenFixed = true; - /* - * Refactored from the Z3str2 version. - * We delay creation of new terms until a conflict - * is actually detected. - */ - expr_ref_vector eq_args(mgr); - vector eq_lens; + expr_ref_vector items(mgr); rational sum1(0), sum2(0); @@ -5586,9 +5580,7 @@ bool theory_str::check_length_concat_concat(expr * n1, expr * n2) { if (argLen_exists) { sum1 += argLen; if (!m_strutil.is_string(oneArg)) { - // items.push_back(ctx.mk_eq_atom(mk_strlen(oneArg), mk_int(argLen))); - eq_args.push_back(oneArg); - eq_lens.push_back(rational(argLen)); + items.push_back(ctx.mk_eq_atom(mk_strlen(oneArg), mk_int(argLen))); } } else { concat1LenFixed = false; @@ -5602,15 +5594,15 @@ bool theory_str::check_length_concat_concat(expr * n1, expr * n2) { if (argLen_exists) { sum2 += argLen; if (!m_strutil.is_string(oneArg)) { - // items.push_back(ctx.mk_eq_atom(mk_strlen(oneArg), mk_int(argLen))); - eq_args.push_back(oneArg); - eq_lens.push_back(rational(argLen)); + items.push_back(ctx.mk_eq_atom(mk_strlen(oneArg), mk_int(argLen))); } } else { concat2LenFixed = false; } } + items.push_back(ctx.mk_eq_atom(n1, n2)); + bool conflict = false; if (concat1LenFixed && concat2LenFixed) { @@ -5629,13 +5621,6 @@ bool theory_str::check_length_concat_concat(expr * n1, expr * n2) { if (conflict) { TRACE("t_str_detail", tout << "inconsistent length detected in concat <==> concat" << std::endl;); - expr_ref_vector items(mgr); - for (unsigned int z = 0; z < eq_args.size(); ++z) { - expr * arg = eq_args.get(z); - rational len = eq_lens.get(z); - items.push_back(ctx.mk_eq_atom(mk_strlen(arg), mk_int(len))); - } - items.push_back(ctx.mk_eq_atom(n1, n2)); expr_ref toAssert(mgr.mk_not(mk_and(items)), mgr); assert_axiom(toAssert); return false; From 406b622f59c92caa59f7e2713f4d6f8d67d32ae4 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Thu, 1 Dec 2016 15:19:51 -0500 Subject: [PATCH 274/410] Revert "testing term generation refactor in theory_str::check_length_const_string" This reverts commit edf151c9a0a3b0b69803f9944c6fc88bf1443a76. --- src/smt/theory_str.cpp | 23 ++--------------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 76b605ff9..37be73333 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -5453,7 +5453,6 @@ bool theory_str::can_two_nodes_eq(expr * n1, expr * n2) { // case 0: n1_curr is const string, n2_curr is const string if (is_string(n1_curr) && is_string(n2_curr)) { if (n1_curr != n2_curr) { - // TODO confirm whether it's okay to compare the pointers like this return false; } } @@ -5495,18 +5494,8 @@ bool theory_str::check_length_const_string(expr * n1, expr * constStr) { rational strLen((unsigned) (m_strutil.get_string_constant_value(constStr).length())); if (is_concat(to_app(n1))) { - /* - * This has been refactored from Z3str2. - * We avoid creating new subexpressions until we actually detect a conflict. - * This may avoid a bit of overhead incurred by creating these terms. - */ - ptr_vector args; - - expr_ref_vector eq_args(mgr); - vector eq_lens; - // foreach (arg, len) in zip(eq_args, eq_lens): - // generate eq(mk_strlen(arg), mk_int(len)) + expr_ref_vector items(mgr); get_nodes_in_concat(n1, args); @@ -5516,20 +5505,12 @@ bool theory_str::check_length_const_string(expr * n1, expr * constStr) { bool argLen_exists = get_len_value(args[i], argLen); if (argLen_exists) { if (!m_strutil.is_string(args[i])) { - // items.push_back(ctx.mk_eq_atom(mk_strlen(args[i]), mk_int(argLen))); - eq_args.push_back(args[i]); - eq_lens.push_back(rational(argLen)); + items.push_back(ctx.mk_eq_atom(mk_strlen(args[i]), mk_int(argLen))); } TRACE("t_str_detail", tout << "concat arg: " << mk_pp(args[i], mgr) << " has len = " << argLen.to_string() << std::endl;); sumLen += argLen; if (sumLen > strLen) { - expr_ref_vector items(mgr); items.push_back(ctx.mk_eq_atom(n1, constStr)); - for (unsigned int z = 0; z < eq_args.size(); ++z) { - expr * arg = eq_args.get(z); - rational len = eq_lens.get(z); - items.push_back(ctx.mk_eq_atom(mk_strlen(arg), mk_int(len))); - } expr_ref toAssert(mgr.mk_not(mk_and(items)), mgr); TRACE("t_str_detail", tout << "inconsistent length: concat (len = " << sumLen << ") <==> string constant (len = " << strLen << ")" << std::endl;); assert_axiom(toAssert); From 35ad68d9b5726f60f75a790164ccd69786182277 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Mon, 5 Dec 2016 15:13:48 -0500 Subject: [PATCH 275/410] assert stronger arrangements theory_str --- src/smt/theory_str.cpp | 128 +++++++++++++++++++++++++++++++++++------ src/smt/theory_str.h | 9 +++ 2 files changed, 120 insertions(+), 17 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 37be73333..3c6ad60ca 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -44,6 +44,7 @@ theory_str::theory_str(ast_manager & m): opt_CheckVariableScope(true), opt_UseFastLengthTesterCache(true), opt_UseFastValueTesterCache(true), + opt_AssertStrongerArrangements(true), /* Internal setup */ search_started(false), m_autil(m), @@ -2864,7 +2865,12 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { add_cut_info_merge(t1, sLevel, m); add_cut_info_merge(t1, sLevel, y); - assert_implication(ax_l, ax_r); + if (opt_AssertStrongerArrangements) { + expr_ref ax_strong(ctx.mk_eq_atom(ax_l, ax_r), mgr); + assert_axiom(ax_strong); + } else { + assert_implication(ax_l, ax_r); + } } else { loopDetected = true; TRACE("t_str", tout << "AVOID LOOP: SKIPPED" << std::endl;); @@ -2911,7 +2917,12 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { add_cut_info_merge(t2, sLevel, x); add_cut_info_merge(t2, sLevel, n); - assert_implication(ax_l, ax_r); + if (opt_AssertStrongerArrangements) { + expr_ref ax_strong(ctx.mk_eq_atom(ax_l, ax_r), mgr); + assert_axiom(ax_strong); + } else { + assert_implication(ax_l, ax_r); + } } else { loopDetected = true; TRACE("t_str", tout << "AVOID LOOP: SKIPPED" << std::endl;); @@ -3013,7 +3024,12 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { expr_ref premise(ctx.mk_eq_atom(concatAst1, concatAst2), mgr); expr_ref conclusion(mk_and(and_item), mgr); - assert_implication(premise, conclusion); + if (opt_AssertStrongerArrangements) { + expr_ref ax_strong(ctx.mk_eq_atom(premise, conclusion), mgr); + assert_axiom(ax_strong); + } else { + assert_implication(premise, conclusion); + } } else { TRACE("t_str", tout << "STOP: no split option found for two EQ concats." << std::endl;); } @@ -3206,7 +3222,12 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { add_cut_info_merge(temp1, sLevel, y); add_cut_info_merge(temp1, sLevel, m); - assert_implication(ax_l, ax_r); + if (opt_AssertStrongerArrangements) { + expr_ref ax_strong(ctx.mk_eq_atom(ax_l, ax_r), mgr); + assert_axiom(ax_strong); + } else { + assert_implication(ax_l, ax_r); + } } else { loopDetected = true; TRACE("t_str", tout << "AVOID LOOP: SKIP" << std::endl;); @@ -3269,7 +3290,12 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { expr_ref ax_l(mk_and(l_items), mgr); expr_ref ax_r(mk_and(r_items), mgr); - assert_implication(ax_l, ax_r); + if (opt_AssertStrongerArrangements) { + expr_ref ax_strong(ctx.mk_eq_atom(ax_l, ax_r), mgr); + assert_axiom(ax_strong); + } else { + assert_implication(ax_l, ax_r); + } } else { // negate! It's impossible to split str with these lengths TRACE("t_str", tout << "CONFLICT: Impossible to split str with these lengths." << std::endl;); @@ -3329,7 +3355,14 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { if (option > 0) { and_item.push_back(mk_or(or_item)); expr_ref implyR(mk_and(and_item), mgr); - assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); + + if (opt_AssertStrongerArrangements) { + expr_ref implyLHS(ctx.mk_eq_atom(concatAst1, concatAst2), mgr); + expr_ref ax_strong(ctx.mk_eq_atom(implyLHS, implyR), mgr); + assert_axiom(ax_strong); + } else { + assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); + } } else { TRACE("t_str", tout << "STOP: Should not split two EQ concats." << std::endl;); } @@ -3508,7 +3541,13 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { expr_ref_vector r_items(mgr); r_items.push_back(ctx.mk_eq_atom(x, prefixAst)); r_items.push_back(ctx.mk_eq_atom(y, suf_n_concat)); - assert_implication(ax_l, mk_and(r_items)); + + if (opt_AssertStrongerArrangements) { + expr_ref ax_strong(ctx.mk_eq_atom(ax_l, mk_and(r_items)), mgr); + assert_axiom(ax_strong); + } else { + assert_implication(ax_l, mk_and(r_items)); + } } else { // negate! It's impossible to split str with these lengths TRACE("t_str", tout << "CONFLICT: Impossible to split str with these lengths." << std::endl;); @@ -3522,7 +3561,13 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { ctx.mk_eq_atom(mk_strlen(y), mk_strlen(n))), mgr); expr_ref ax_l(mgr.mk_and(ax_l1, ax_l2), mgr); expr_ref ax_r(mgr.mk_and(ctx.mk_eq_atom(x, strAst), ctx.mk_eq_atom(y, n)), mgr); - assert_implication(ax_l, ax_r); + + if (opt_AssertStrongerArrangements) { + expr_ref ax_strong(ctx.mk_eq_atom(ax_l, ax_r), mgr); + assert_axiom(ax_strong); + } else { + assert_implication(ax_l, ax_r); + } } else if (splitType == 2) { // | x | y | @@ -3555,7 +3600,12 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { add_cut_info_merge(temp1, sLevel, x); add_cut_info_merge(temp1, sLevel, n); - assert_implication(ax_l, ax_r); + if (opt_AssertStrongerArrangements) { + expr_ref ax_strong(ctx.mk_eq_atom(ax_l, ax_r), mgr); + assert_axiom(ax_strong); + } else { + assert_implication(ax_l, ax_r); + } } else { loopDetected = true; TRACE("t_str", tout << "AVOID LOOP: SKIPPED" << std::endl;); @@ -3633,7 +3683,14 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { and_item.push_back(mk_or(or_item)); } expr_ref implyR(mk_and(and_item), mgr); - assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); + + if (opt_AssertStrongerArrangements) { + expr_ref ax_lhs(ctx.mk_eq_atom(concatAst1, concatAst2), mgr); + expr_ref ax_strong(ctx.mk_eq_atom(ax_lhs, implyR), mgr); + assert_axiom(ax_strong); + } else { + assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); + } } else { TRACE("t_str", tout << "STOP: should not split two eq. concats" << std::endl;); } @@ -3708,13 +3765,24 @@ void theory_str::process_concat_eq_type4(expr * concatAst1, expr * concatAst2) { if (!in_same_eqc(tmpAst, n)) { // break down option 4-1 expr_ref implyR(ctx.mk_eq_atom(n, tmpAst), mgr); - assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); + if (opt_AssertStrongerArrangements) { + expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr); + assert_axiom(ax_strong); + } else { + assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); + } } } else if (str1Len == str2Len) { if (!in_same_eqc(n, y)) { //break down option 4-2 expr_ref implyR(ctx.mk_eq_atom(n, y), mgr); - assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); + + if (opt_AssertStrongerArrangements) { + expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr); + assert_axiom(ax_strong); + } else { + assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); + } } } else { std::string deltaStr = str2Value.substr(str1Len, str2Len - str1Len); @@ -3722,7 +3790,12 @@ void theory_str::process_concat_eq_type4(expr * concatAst1, expr * concatAst2) { if (!in_same_eqc(y, tmpAst)) { //break down option 4-3 expr_ref implyR(ctx.mk_eq_atom(y, tmpAst), mgr); - assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); + if (opt_AssertStrongerArrangements) { + expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr); + assert_axiom(ax_strong); + } else { + assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); + } } } } @@ -3794,20 +3867,35 @@ void theory_str::process_concat_eq_type5(expr * concatAst1, expr * concatAst2) { expr_ref x_deltaStr(mk_concat(x, m_strutil.mk_string(deltaStr)), mgr); if (!in_same_eqc(m, x_deltaStr)) { expr_ref implyR(ctx.mk_eq_atom(m, x_deltaStr), mgr); - assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); + if (opt_AssertStrongerArrangements) { + expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr); + assert_axiom(ax_strong); + } else { + assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); + } } } else if (str1Len == str2Len) { // test if (!in_same_eqc(x, m)) { expr_ref implyR(ctx.mk_eq_atom(x, m), mgr); - assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); + if (opt_AssertStrongerArrangements) { + expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr); + assert_axiom(ax_strong); + } else { + assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); + } } } else { std::string deltaStr = str2Value.substr(0, str2Len - str1Len); expr_ref m_deltaStr(mk_concat(m, m_strutil.mk_string(deltaStr)), mgr); if (!in_same_eqc(x, m_deltaStr)) { expr_ref implyR(ctx.mk_eq_atom(x, m_deltaStr), mgr); - assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); + if (opt_AssertStrongerArrangements) { + expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr); + assert_axiom(ax_strong); + } else { + assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); + } } } } @@ -4020,7 +4108,13 @@ void theory_str::process_concat_eq_type6(expr * concatAst1, expr * concatAst2) { // case 6: concat("str1", y) = concat(m, "str2") and_item.push_back(mk_or(or_item)); expr_ref implyR(mk_and(and_item), mgr); - assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); + + if (opt_AssertStrongerArrangements) { + expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr); + assert_axiom(ax_strong); + } else { + assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); + } } void theory_str::process_unroll_eq_const_str(expr * unrollFunc, expr * constStr) { diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 5b8f644eb..8168d0632 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -189,6 +189,15 @@ namespace smt { */ bool opt_UseFastValueTesterCache; + /* + * If AssertStrongerArrangements is set to true, + * the implications that would normally be asserted during arrangement generation + * will instead be asserted as equivalences. + * This is a stronger version of the regular axiom. + * The default (Z3str2) behaviour is to set this to false. + */ + bool opt_AssertStrongerArrangements; + bool search_started; arith_util m_autil; str_util m_strutil; From be9cb8db82d56493401817ef5bac6a5ee4affd47 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Mon, 5 Dec 2016 20:17:43 -0500 Subject: [PATCH 276/410] regex tracing theory_str --- src/ast/rewriter/str_rewriter.cpp | 2 +- src/smt/theory_str.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ast/rewriter/str_rewriter.cpp b/src/ast/rewriter/str_rewriter.cpp index e30e857b2..bc64e7218 100644 --- a/src/ast/rewriter/str_rewriter.cpp +++ b/src/ast/rewriter/str_rewriter.cpp @@ -456,10 +456,10 @@ br_status str_rewriter::mk_re_RegexIn(expr * str, expr * re, expr_ref & result) // fast path: // (RegexIn E (Str2Reg S)) --> (= E S) if (m_strutil.is_re_Str2Reg(re)) { - TRACE("t_str_rw", tout << "RegexIn fast path: " << mk_pp(str, m()) << " in " << mk_pp(re, m()) << std::endl;); expr * regexStr = to_app(re)->get_arg(0); ENSURE(m_strutil.is_string(regexStr)); result = m().mk_eq(str, regexStr); + TRACE("t_str_rw", tout << "RegexIn fast path: " << mk_pp(str, m()) << " in " << mk_pp(re, m()) << " ==> " << mk_pp(result, m()) << std::endl;); return BR_REWRITE_FULL; } diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 37be73333..543adcc03 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -1708,6 +1708,7 @@ void theory_str::instantiate_axiom_RegexIn(enode * e) { expr_ref finalAxiom(m.mk_iff(ex, rhs), m); SASSERT(finalAxiom); assert_axiom(finalAxiom); + TRACE("t_str", tout << "set up Str2Reg: (RegexIn " << mk_pp(str, m) << " " << mk_pp(regex, m) << ")" << std::endl;); } else if (is_RegexConcat(regex)) { expr_ref var1(mk_regex_rep_var(), m); expr_ref var2(mk_regex_rep_var(), m); From da61c99f9e880edf1e7b6540e4b3012c96f62ce5 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 6 Dec 2016 12:52:48 -0500 Subject: [PATCH 277/410] experimental boolean case split in theory_str process_concat_eq_type1 WIP --- src/smt/theory_str.cpp | 93 +++++++++++++++++++++++++++--------------- src/smt/theory_str.h | 2 + 2 files changed, 62 insertions(+), 33 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 84091e2da..3d22427d5 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -2443,6 +2443,30 @@ void theory_str::infer_len_concat_equality(expr * nn1, expr * nn2) { */ } +expr_ref theory_str::generate_mutual_exclusion(expr_ref_vector & terms) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + expr_ref_vector result(m); + + // TODO this can probably be made more efficient + + for (unsigned int majorIndex = 0; majorIndex < terms.size(); ++majorIndex) { + for (unsigned int minorIndex = 0; minorIndex < terms.size(); ++minorIndex) { + if (majorIndex == minorIndex) { + continue; + } + // generate an expression of the form + // terms[majorIndex] --> NOT(terms[minorIndex]) + expr_ref ex(rewrite_implication(terms.get(majorIndex), m.mk_not(terms.get(minorIndex))), m); + result.push_back(ex); + } + } + + expr_ref final_result(mk_and(result), m); + return final_result; +} + /* * Handle two equivalent Concats. */ @@ -2931,40 +2955,42 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { } } else if (splitType == -1) { // Here we don't really have a choice. We have no length information at all... - expr_ref_vector or_item(mgr); - expr_ref_vector and_item(mgr); + + // This vector will eventually contain one term for each possible arrangement we explore. + expr_ref_vector arrangement_disjunction(mgr); + int option = 0; int pos = 1; // break option 1: m cuts y // len(x) < len(m) || len(y) > len(n) if (!avoidLoopCut || !has_self_cut(m, y)) { + expr_ref_vector and_item(mgr); // break down option 1-1 expr_ref x_t1(mk_concat(x, t1), mgr); expr_ref t1_n(mk_concat(t1, n), mgr); - expr_ref or_item_option(ctx.mk_eq_atom(xorFlag, mk_int(option)), mgr); - or_item.push_back(or_item_option); - and_item.push_back(ctx.mk_eq_atom(or_item_option, ctx.mk_eq_atom(m, x_t1))); - and_item.push_back(ctx.mk_eq_atom(or_item_option, ctx.mk_eq_atom(y, t1_n))); + + and_item.push_back(ctx.mk_eq_atom(m, x_t1)); + and_item.push_back(ctx.mk_eq_atom(y, t1_n)); expr_ref x_plus_t1(m_autil.mk_add(mk_strlen(x), mk_strlen(t1)), mgr); - and_item.push_back(ctx.mk_eq_atom(or_item_option, ctx.mk_eq_atom(mk_strlen(m), x_plus_t1))); + and_item.push_back(ctx.mk_eq_atom(mk_strlen(m), x_plus_t1)); // These were crashing the solver because the integer theory // expects a constant on the right-hand side. // The things we want to assert here are len(m) > len(x) and len(y) > len(n). // We rewrite A > B as A-B > 0 and then as not(A-B <= 0), // and then, *because we aren't allowed to use subtraction*, // as not(A + -1*B <= 0) - and_item.push_back(ctx.mk_eq_atom(or_item_option, + and_item.push_back( mgr.mk_not(m_autil.mk_le( m_autil.mk_add(mk_strlen(m), m_autil.mk_mul(mk_int(-1), mk_strlen(x))), - mk_int(0))) )); - and_item.push_back(ctx.mk_eq_atom(or_item_option, + mk_int(0))) ); + and_item.push_back( mgr.mk_not(m_autil.mk_le( m_autil.mk_add(mk_strlen(y),m_autil.mk_mul(mk_int(-1), mk_strlen(n))), - mk_int(0))) )); + mk_int(0))) ); - option++; + arrangement_disjunction.push_back(mk_and(and_item)); add_cut_info_merge(t1, ctx.get_scope_level(), m); add_cut_info_merge(t1, ctx.get_scope_level(), y); @@ -2977,30 +3003,30 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { // break option 2: // x = m || y = n if (!avoidLoopCut || !has_self_cut(x, n)) { + expr_ref_vector and_item(mgr); // break down option 1-2 expr_ref m_t2(mk_concat(m, t2), mgr); expr_ref t2_y(mk_concat(t2, y), mgr); - expr_ref or_item_option(ctx.mk_eq_atom(xorFlag, mk_int(option)), mgr); - or_item.push_back(or_item_option); - and_item.push_back(ctx.mk_eq_atom(or_item_option, ctx.mk_eq_atom(x, m_t2))); - and_item.push_back(ctx.mk_eq_atom(or_item_option, ctx.mk_eq_atom(n, t2_y))); + + and_item.push_back(ctx.mk_eq_atom(x, m_t2)); + and_item.push_back(ctx.mk_eq_atom(n, t2_y)); expr_ref m_plus_t2(m_autil.mk_add(mk_strlen(m), mk_strlen(t2)), mgr); - and_item.push_back(ctx.mk_eq_atom(or_item_option, ctx.mk_eq_atom(mk_strlen(x), m_plus_t2))); + and_item.push_back(ctx.mk_eq_atom(mk_strlen(x), m_plus_t2)); // want len(x) > len(m) and len(n) > len(y) - and_item.push_back(ctx.mk_eq_atom(or_item_option, + and_item.push_back( mgr.mk_not(m_autil.mk_le( m_autil.mk_add(mk_strlen(x), m_autil.mk_mul(mk_int(-1), mk_strlen(m))), - mk_int(0))) )); - and_item.push_back(ctx.mk_eq_atom(or_item_option, + mk_int(0))) ); + and_item.push_back( mgr.mk_not(m_autil.mk_le( m_autil.mk_add(mk_strlen(n), m_autil.mk_mul(mk_int(-1), mk_strlen(y))), - mk_int(0))) )); + mk_int(0))) ); - option++; + arrangement_disjunction.push_back(mk_and(and_item)); add_cut_info_merge(t2, ctx.get_scope_level(), x); add_cut_info_merge(t2, ctx.get_scope_level(), n); @@ -3011,26 +3037,27 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { } if (can_two_nodes_eq(x, m) && can_two_nodes_eq(y, n)) { - expr_ref or_item_option(ctx.mk_eq_atom(xorFlag, mk_int(option)), mgr); - or_item.push_back(or_item_option); - and_item.push_back(ctx.mk_eq_atom(or_item_option, ctx.mk_eq_atom(x, m))); - and_item.push_back(ctx.mk_eq_atom(or_item_option, ctx.mk_eq_atom(y, n))); - and_item.push_back(ctx.mk_eq_atom(or_item_option, ctx.mk_eq_atom(mk_strlen(x), mk_strlen(m)))); - and_item.push_back(ctx.mk_eq_atom(or_item_option, ctx.mk_eq_atom(mk_strlen(y), mk_strlen(n)))); - ++option; + expr_ref_vector and_item(mgr); + + and_item.push_back(ctx.mk_eq_atom(x, m)); + and_item.push_back(ctx.mk_eq_atom(y, n)); + and_item.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_strlen(m))); + and_item.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_strlen(n))); + + arrangement_disjunction.push_back(mk_and(and_item)); } - if (option > 0) { - and_item.push_back(mk_or(or_item)); - + if (!arrangement_disjunction.empty()) { expr_ref premise(ctx.mk_eq_atom(concatAst1, concatAst2), mgr); - expr_ref conclusion(mk_and(and_item), mgr); + expr_ref conclusion(mk_or(arrangement_disjunction), mgr); if (opt_AssertStrongerArrangements) { expr_ref ax_strong(ctx.mk_eq_atom(premise, conclusion), mgr); assert_axiom(ax_strong); } else { assert_implication(premise, conclusion); } + // assert mutual exclusion between each branch of the arrangement + assert_axiom(generate_mutual_exclusion(arrangement_disjunction)); } else { TRACE("t_str", tout << "STOP: no split option found for two EQ concats." << std::endl;); } diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 8168d0632..29f5c2336 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -478,6 +478,8 @@ namespace smt { void process_concat_eq_type5(expr * concatAst1, expr * concatAst2); void process_concat_eq_type6(expr * concatAst1, expr * concatAst2); + expr_ref generate_mutual_exclusion(expr_ref_vector & exprs); + bool new_eq_check(expr * lhs, expr * rhs); void group_terms_by_eqc(expr * n, std::set & concats, std::set & vars, std::set & consts); From b57f04e2d2c74d124d72765964cae95475287f3b Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 6 Dec 2016 12:59:40 -0500 Subject: [PATCH 278/410] optimize generate_mutual_exclusion in theory_str to make only half as many subterms --- src/smt/theory_str.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 3d22427d5..4b38d02d3 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -2449,13 +2449,8 @@ expr_ref theory_str::generate_mutual_exclusion(expr_ref_vector & terms) { expr_ref_vector result(m); - // TODO this can probably be made more efficient - for (unsigned int majorIndex = 0; majorIndex < terms.size(); ++majorIndex) { - for (unsigned int minorIndex = 0; minorIndex < terms.size(); ++minorIndex) { - if (majorIndex == minorIndex) { - continue; - } + for (unsigned int minorIndex = majorIndex + 1; minorIndex < terms.size(); ++minorIndex) { // generate an expression of the form // terms[majorIndex] --> NOT(terms[minorIndex]) expr_ref ex(rewrite_implication(terms.get(majorIndex), m.mk_not(terms.get(minorIndex))), m); From 225b527d5832848bc80bc3406b3fafde36d581a8 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 6 Dec 2016 16:09:38 -0500 Subject: [PATCH 279/410] boolean case split theory_str process_concat_eq_type2 --- src/smt/theory_str.cpp | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 4b38d02d3..ef86be313 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -3328,8 +3328,8 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { } else { // Split type -1: no idea about the length... int optionTotal = 2 + strValue.length(); - expr_ref_vector or_item(mgr); - expr_ref_vector and_item(mgr); + expr_ref_vector arrangement_disjunction(mgr); + int option = 0; int pos = 1; @@ -3339,16 +3339,16 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { if (can_two_nodes_eq(y, temp1_strAst)) { if (!avoidLoopCut || !has_self_cut(m, y)) { // break down option 2-1 - expr_ref current_or_item_option(ctx.mk_eq_atom(xorFlag, mk_int(option)), mgr); - or_item.push_back(current_or_item_option); + expr_ref_vector and_item(mgr); + expr_ref x_temp1(mk_concat(x, temp1), mgr); - and_item.push_back(ctx.mk_eq_atom(current_or_item_option, ctx.mk_eq_atom(m, x_temp1))); - and_item.push_back(ctx.mk_eq_atom(current_or_item_option, ctx.mk_eq_atom(y, temp1_strAst))); + and_item.push_back(ctx.mk_eq_atom(m, x_temp1)); + and_item.push_back(ctx.mk_eq_atom(y, temp1_strAst)); - and_item.push_back(ctx.mk_eq_atom(current_or_item_option, ctx.mk_eq_atom(mk_strlen(m), - m_autil.mk_add(mk_strlen(x), mk_strlen(temp1))))); + and_item.push_back(ctx.mk_eq_atom(mk_strlen(m), + m_autil.mk_add(mk_strlen(x), mk_strlen(temp1)))); - ++option; + arrangement_disjunction.push_back(mk_and(and_item)); add_cut_info_merge(temp1, ctx.get_scope_level(), y); add_cut_info_merge(temp1, ctx.get_scope_level(), m); } else { @@ -3366,18 +3366,16 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { expr_ref cropStr(m_strutil.mk_string(part2Str), mgr); if (can_two_nodes_eq(x, x_concat) && can_two_nodes_eq(y, cropStr)) { // break down option 2-2 - expr_ref current_or_item_option(ctx.mk_eq_atom(xorFlag, mk_int(option)), mgr); - or_item.push_back(current_or_item_option); - and_item.push_back(ctx.mk_eq_atom(current_or_item_option, ctx.mk_eq_atom(x, x_concat))); - and_item.push_back(ctx.mk_eq_atom(current_or_item_option, ctx.mk_eq_atom(y, cropStr))); - and_item.push_back(ctx.mk_eq_atom(current_or_item_option, ctx.mk_eq_atom(mk_strlen(y), mk_int(part2Str.length())))); - ++option; + expr_ref_vector and_item(mgr); + and_item.push_back(ctx.mk_eq_atom(x, x_concat)); + and_item.push_back(ctx.mk_eq_atom(y, cropStr)); + and_item.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_int(part2Str.length()))); + arrangement_disjunction.push_back(mk_and(and_item)); } } - if (option > 0) { - and_item.push_back(mk_or(or_item)); - expr_ref implyR(mk_and(and_item), mgr); + if (!arrangement_disjunction.empty()) { + expr_ref implyR(mk_or(arrangement_disjunction), mgr); if (opt_AssertStrongerArrangements) { expr_ref implyLHS(ctx.mk_eq_atom(concatAst1, concatAst2), mgr); @@ -3386,6 +3384,7 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { } else { assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); } + assert_axiom(generate_mutual_exclusion(arrangement_disjunction)); } else { TRACE("t_str", tout << "STOP: Should not split two EQ concats." << std::endl;); } From 7b0aaf874554704af8561810285c69c42a451d97 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 6 Dec 2016 16:22:42 -0500 Subject: [PATCH 280/410] boolean case split theory_str concat_eq remaining cases --- src/smt/theory_str.cpp | 77 ++++++++++++++++++++---------------------- 1 file changed, 37 insertions(+), 40 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index ef86be313..d524bffe7 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -3643,9 +3643,9 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { else { // Split type -1. We know nothing about the length... - expr_ref_vector or_item(mgr); + expr_ref_vector arrangement_disjunction(mgr); unsigned option = 0; - expr_ref_vector and_item(mgr); + int pos = 1; for (int i = 0; i <= (int) strValue.size(); i++) { std::string part1Str = strValue.substr(0, i); @@ -3655,17 +3655,18 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { expr_ref y_concat(mk_concat(suffixStr, n), mgr); if (can_two_nodes_eq(x, cropStr) && can_two_nodes_eq(y, y_concat)) { + expr_ref_vector and_item(mgr); // break down option 3-1 expr_ref x_eq_str(ctx.mk_eq_atom(x, cropStr), mgr); - or_item.push_back(ctx.mk_eq_atom(xorFlag, mk_int(option))); - and_item.push_back(ctx.mk_eq_atom(or_item.get(option), x_eq_str)); ++pos; - and_item.push_back(ctx.mk_eq_atom(or_item.get(option), ctx.mk_eq_atom(y, y_concat))); - and_item.push_back(ctx.mk_eq_atom(or_item.get(option), ctx.mk_eq_atom(mk_strlen(x), mk_strlen(cropStr)))); ++pos; + and_item.push_back(x_eq_str); ++pos; + and_item.push_back(ctx.mk_eq_atom(y, y_concat)); + and_item.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_strlen(cropStr))); ++pos; + // and_item[pos++] = Z3_mk_eq(ctx, or_item[option], Z3_mk_eq(ctx, mk_length(t, y), mk_length(t, y_concat))); - // adding length constraint for _ = constStr seems slowing things down. - option++; + + arrangement_disjunction.push_back(mk_and(and_item)); } } @@ -3678,15 +3679,16 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { if (can_two_nodes_eq(x, strAst_temp1)) { if (!avoidLoopCut || !(has_self_cut(x, n))) { // break down option 3-2 - or_item.push_back(ctx.mk_eq_atom(xorFlag, mk_int(option))); + expr_ref_vector and_item(mgr); expr_ref temp1_y(mk_concat(temp1, y), mgr); - and_item.push_back(ctx.mk_eq_atom(or_item.get(option), ctx.mk_eq_atom(x, strAst_temp1))); ++pos; - and_item.push_back(ctx.mk_eq_atom(or_item.get(option), ctx.mk_eq_atom(n, temp1_y))); ++pos; + and_item.push_back(ctx.mk_eq_atom(x, strAst_temp1)); ++pos; + and_item.push_back(ctx.mk_eq_atom(n, temp1_y)); ++pos; - and_item.push_back(ctx.mk_eq_atom(or_item.get(option), ctx.mk_eq_atom(mk_strlen(x), - m_autil.mk_add(mk_strlen(strAst), mk_strlen(temp1)) )) ); ++pos; - option++; + and_item.push_back(ctx.mk_eq_atom(mk_strlen(x), + m_autil.mk_add(mk_strlen(strAst), mk_strlen(temp1)) ) ); ++pos; + + arrangement_disjunction.push_back(mk_and(and_item)); add_cut_info_merge(temp1, sLevel, x); add_cut_info_merge(temp1, sLevel, n); @@ -3698,13 +3700,8 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { } - if (option > 0) { - if (option == 1) { - and_item.push_back(or_item.get(0)); - } else { - and_item.push_back(mk_or(or_item)); - } - expr_ref implyR(mk_and(and_item), mgr); + if (!arrangement_disjunction.empty()) { + expr_ref implyR(mk_or(arrangement_disjunction), mgr); if (opt_AssertStrongerArrangements) { expr_ref ax_lhs(ctx.mk_eq_atom(concatAst1, concatAst2), mgr); @@ -3713,6 +3710,7 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { } else { assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); } + assert_axiom(generate_mutual_exclusion(arrangement_disjunction)); } else { TRACE("t_str", tout << "STOP: should not split two eq. concats" << std::endl;); } @@ -4066,32 +4064,30 @@ void theory_str::process_concat_eq_type6(expr * concatAst1, expr * concatAst2) { refresh_theory_var(commonVar); } - expr_ref_vector or_item(mgr); + expr_ref_vector arrangement_disjunction(mgr); int option = 0; - expr_ref_vector and_item(mgr); int pos = 1; if (!avoidLoopCut || !has_self_cut(m, y)) { - expr_ref or_item_option(ctx.mk_eq_atom(xorFlag, mk_int(option)), mgr); - or_item.push_back(or_item_option); + expr_ref_vector and_item(mgr); expr_ref str1_commonVar(mk_concat(str1Ast, commonVar), mgr); - and_item.push_back(ctx.mk_eq_atom(or_item_option, ctx.mk_eq_atom(m, str1_commonVar))); + and_item.push_back(ctx.mk_eq_atom(m, str1_commonVar)); pos += 1; expr_ref commonVar_str2(mk_concat(commonVar, str2Ast), mgr); - and_item.push_back(ctx.mk_eq_atom(or_item_option, ctx.mk_eq_atom(y, commonVar_str2))); + and_item.push_back(ctx.mk_eq_atom(y, commonVar_str2)); pos += 1; - and_item.push_back(ctx.mk_eq_atom(or_item_option, ctx.mk_eq_atom(mk_strlen(m), - m_autil.mk_add(mk_strlen(str1Ast), mk_strlen(commonVar)) ))); + and_item.push_back(ctx.mk_eq_atom(mk_strlen(m), + m_autil.mk_add(mk_strlen(str1Ast), mk_strlen(commonVar)) )); pos += 1; // addItems[0] = mk_length(t, commonVar); // addItems[1] = mk_length(t, str2Ast); // and_item[pos++] = Z3_mk_eq(ctx, or_item[option], Z3_mk_eq(ctx, mk_length(t, y), Z3_mk_add(ctx, 2, addItems))); - option++; + arrangement_disjunction.push_back(mk_and(and_item)); } else { loopDetected = true; TRACE("t_str", tout << "AVOID LOOP: SKIPPED." << std::endl;); @@ -4102,34 +4098,34 @@ void theory_str::process_concat_eq_type6(expr * concatAst1, expr * concatAst2) { int overLen = *itor; std::string prefix = str1Value.substr(0, str1Len - overLen); std::string suffix = str2Value.substr(overLen, str2Len - overLen); - expr_ref or_item_option(ctx.mk_eq_atom(xorFlag, mk_int(option)), mgr); - or_item.push_back(or_item_option); + + expr_ref_vector and_item(mgr); expr_ref prefixAst(m_strutil.mk_string(prefix), mgr); expr_ref x_eq_prefix(ctx.mk_eq_atom(m, prefixAst), mgr); - and_item.push_back(ctx.mk_eq_atom(or_item_option, x_eq_prefix)); + and_item.push_back(x_eq_prefix); pos += 1; - and_item.push_back(ctx.mk_eq_atom(or_item_option, - ctx.mk_eq_atom(mk_strlen(m), mk_strlen(prefixAst)))); + and_item.push_back( + ctx.mk_eq_atom(mk_strlen(m), mk_strlen(prefixAst))); pos += 1; // adding length constraint for _ = constStr seems slowing things down. expr_ref suffixAst(m_strutil.mk_string(suffix), mgr); expr_ref y_eq_suffix(ctx.mk_eq_atom(y, suffixAst), mgr); - and_item.push_back(ctx.mk_eq_atom(or_item_option, y_eq_suffix)); + and_item.push_back(y_eq_suffix); pos += 1; - and_item.push_back(ctx.mk_eq_atom(or_item_option, ctx.mk_eq_atom(mk_strlen(y), mk_strlen(suffixAst)))); + and_item.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_strlen(suffixAst))); pos += 1; - option++; + arrangement_disjunction.push_back(mk_and(and_item)); } // case 6: concat("str1", y) = concat(m, "str2") - and_item.push_back(mk_or(or_item)); - expr_ref implyR(mk_and(and_item), mgr); + + expr_ref implyR(mk_or(arrangement_disjunction), mgr); if (opt_AssertStrongerArrangements) { expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr); @@ -4137,6 +4133,7 @@ void theory_str::process_concat_eq_type6(expr * concatAst1, expr * concatAst2) { } else { assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); } + assert_axiom(generate_mutual_exclusion(arrangement_disjunction)); } void theory_str::process_unroll_eq_const_str(expr * unrollFunc, expr * constStr) { From 515cd4a3f33cf2e4509cce349dc6cabb8260ee5c Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Thu, 8 Dec 2016 14:49:38 -0500 Subject: [PATCH 281/410] add boolean case split in theory_str::solve_concat_eq_str --- src/smt/theory_str.cpp | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index d524bffe7..0f434900e 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -6201,10 +6201,10 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { int xor_pos = 0; int and_count = 1; - expr_ref_vector xor_items(m); - expr_ref_vector and_items(m); + expr_ref_vector arrangement_disjunction(m); for (int i = 0; i < concatStrLen + 1; ++i) { + expr_ref_vector and_items(m); std::string prefixStr = const_str.substr(0, i); std::string suffixStr = const_str.substr(i, concatStrLen - i); // skip invalid options @@ -6214,32 +6214,36 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { if (is_concat(to_app(arg2)) && !can_concat_eq_str(arg2, suffixStr)) { continue; } - expr_ref xorAst(ctx.mk_eq_atom(xorFlag, m_autil.mk_numeral(rational(xor_pos), true)), m); - xor_items.push_back(xorAst); - xor_pos += 1; expr_ref prefixAst(m_strutil.mk_string(prefixStr), m); expr_ref arg1_eq (ctx.mk_eq_atom(arg1, prefixAst), m); - and_items.push_back(ctx.mk_eq_atom(xorAst, arg1_eq)); + and_items.push_back(arg1_eq); and_count += 1; expr_ref suffixAst(m_strutil.mk_string(suffixStr), m); expr_ref arg2_eq (ctx.mk_eq_atom(arg2, suffixAst), m); - and_items.push_back(ctx.mk_eq_atom(xorAst, arg2_eq)); + and_items.push_back(arg2_eq); and_count += 1; + + arrangement_disjunction.push_back(mk_and(and_items)); } expr_ref implyL(ctx.mk_eq_atom(concat, str), m); expr_ref implyR1(m); - if (xor_pos == 0) { + if (arrangement_disjunction.empty()) { // negate expr_ref concat_eq_str(ctx.mk_eq_atom(concat, str), m); expr_ref negate_ast(m.mk_not(concat_eq_str), m); assert_axiom(negate_ast); } else { - and_items.push_back(mk_or(xor_items)); - implyR1 = mk_and(and_items); - assert_implication(implyL, implyR1); + implyR1 = mk_or(arrangement_disjunction); + if (opt_AssertStrongerArrangements) { + expr_ref ax_strong(ctx.mk_eq_atom(implyL, implyR1), m); + assert_axiom(ax_strong); + } else { + assert_implication(implyL, implyR1); + } + assert_axiom(generate_mutual_exclusion(arrangement_disjunction)); } } /* (arg1Len != 1 || arg2Len != 1) */ } /* if (Concat(arg1, arg2) == NULL) */ From 737565180fbe21c20e7395b32fe40b51d93aeba2 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Fri, 9 Dec 2016 16:55:34 -0500 Subject: [PATCH 282/410] disable stronger arrangements in theory_str for now --- src/smt/theory_str.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 0f434900e..92920c220 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -44,7 +44,7 @@ theory_str::theory_str(ast_manager & m): opt_CheckVariableScope(true), opt_UseFastLengthTesterCache(true), opt_UseFastValueTesterCache(true), - opt_AssertStrongerArrangements(true), + opt_AssertStrongerArrangements(false), /* Internal setup */ search_started(false), m_autil(m), From e9411e5b8c6d80de3f8866071ae89cc4f7df431c Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Fri, 9 Dec 2016 17:12:29 -0500 Subject: [PATCH 283/410] explicitly re-introduce string axioms on refreshed string theory vars this fixes at least one case (kaluza/unsat/big/9650.smt2) where a string could have a negative length value due to a constraint that went out of scope --- src/smt/theory_str.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 92920c220..a6d93e70b 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -320,6 +320,9 @@ void theory_str::refresh_theory_var(expr * e) { enode * en = ensure_enode(e); theory_var v = mk_var(en); TRACE("t_str_detail", tout << "refresh " << mk_pp(e, get_manager()) << ": v#" << v << std::endl;); + // TODO this is probably sub-optimal + // TODO case where the refreshed var must be non-empty? + m_basicstr_axiom_todo.push_back(en); } theory_var theory_str::mk_var(enode* n) { From 09053b831dd6bb4948045d6a43ed24b636e00382 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Fri, 9 Dec 2016 17:23:39 -0500 Subject: [PATCH 284/410] enforce nonempty string constraint on refreshed nonempty string vars --- src/smt/theory_str.cpp | 34 ++++++++++++++++++++++++++++++---- src/smt/theory_str.h | 1 + 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index a6d93e70b..b9e9e748f 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -321,7 +321,6 @@ void theory_str::refresh_theory_var(expr * e) { theory_var v = mk_var(en); TRACE("t_str_detail", tout << "refresh " << mk_pp(e, get_manager()) << ": v#" << v << std::endl;); // TODO this is probably sub-optimal - // TODO case where the refreshed var must be non-empty? m_basicstr_axiom_todo.push_back(en); } @@ -617,6 +616,28 @@ app * theory_str::mk_regex_rep_var() { return a; } +void theory_str::add_nonempty_constraint(expr * s) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + expr_ref ax1(m.mk_not(ctx.mk_eq_atom(s, m_strutil.mk_string(""))), m); + assert_axiom(ax1); + + { + // build LHS + expr_ref len_str(mk_strlen(s), m); + SASSERT(len_str); + // build RHS + expr_ref zero(m_autil.mk_numeral(rational(0), true), m); + SASSERT(zero); + // build LHS > RHS and assert + // we have to build !(LHS <= RHS) instead + expr_ref lhs_gt_rhs(m.mk_not(m_autil.mk_le(len_str, zero)), m); + SASSERT(lhs_gt_rhs); + assert_axiom(lhs_gt_rhs); + } +} + app * theory_str::mk_nonempty_str_var() { context & ctx = get_context(); ast_manager & m = get_manager(); @@ -639,14 +660,14 @@ app * theory_str::mk_nonempty_str_var() { // assert a variation of the basic string axioms that ensures this string is nonempty { // build LHS - expr * len_str = mk_strlen(a); + expr_ref len_str(mk_strlen(a), m); SASSERT(len_str); // build RHS - expr * zero = m_autil.mk_numeral(rational(0), true); + expr_ref zero(m_autil.mk_numeral(rational(0), true), m); SASSERT(zero); // build LHS > RHS and assert // we have to build !(LHS <= RHS) instead - app * lhs_gt_rhs = m.mk_not(m_autil.mk_le(len_str, zero)); + expr_ref lhs_gt_rhs(m.mk_not(m_autil.mk_le(len_str, zero)), m); SASSERT(lhs_gt_rhs); assert_axiom(lhs_gt_rhs); } @@ -2847,7 +2868,9 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { } // TODO do I need to refresh the xorFlag, which is an integer var, and if so, how? refresh_theory_var(t1); + add_nonempty_constraint(t1); refresh_theory_var(t2); + add_nonempty_constraint(t2); } // For split types 0 through 2, we can get away with providing @@ -3190,6 +3213,7 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { } // TODO refresh xorFlag? refresh_theory_var(temp1); + add_nonempty_constraint(temp1); } int splitType = -1; @@ -3515,6 +3539,7 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { xorFlag = varForBreakConcat[key2][1]; } refresh_theory_var(temp1); + add_nonempty_constraint(temp1); } @@ -4065,6 +4090,7 @@ void theory_str::process_concat_eq_type6(expr * concatAst1, expr * concatAst2) { xorFlag = (entry2->second)[1]; } refresh_theory_var(commonVar); + add_nonempty_constraint(commonVar); } expr_ref_vector arrangement_disjunction(mgr); diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 29f5c2336..b3667bdec 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -341,6 +341,7 @@ namespace smt { app * mk_regex_rep_var(); app * mk_unroll_bound_var(); app * mk_unroll_test_var(); + void add_nonempty_constraint(expr * s); bool is_concat(app const * a) const { return a->is_app_of(get_id(), OP_STRCAT); } bool is_concat(enode const * n) const { return is_concat(n->get_owner()); } From f5bc17b864a7989b339bbe921aae1bf18f3ecbf0 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 13 Dec 2016 16:12:57 -0500 Subject: [PATCH 285/410] theory_str params module, WIP --- src/smt/params/smt_params.h | 2 ++ src/smt/params/smt_params_helper.pyg | 3 +- src/smt/params/theory_str_params.cpp | 24 ++++++++++++++++ src/smt/params/theory_str_params.h | 42 ++++++++++++++++++++++++++++ src/smt/smt_setup.cpp | 4 +-- src/smt/theory_str.cpp | 40 +++++++++++++------------- src/smt/theory_str.h | 16 +++-------- 7 files changed, 96 insertions(+), 35 deletions(-) create mode 100644 src/smt/params/theory_str_params.cpp create mode 100644 src/smt/params/theory_str_params.h diff --git a/src/smt/params/smt_params.h b/src/smt/params/smt_params.h index 9c1eec649..27071bd9e 100644 --- a/src/smt/params/smt_params.h +++ b/src/smt/params/smt_params.h @@ -25,6 +25,7 @@ Revision History: #include"theory_arith_params.h" #include"theory_array_params.h" #include"theory_bv_params.h" +#include"theory_str_params.h" #include"theory_pb_params.h" #include"theory_datatype_params.h" #include"preprocessor_params.h" @@ -75,6 +76,7 @@ struct smt_params : public preprocessor_params, public theory_arith_params, public theory_array_params, public theory_bv_params, + public theory_str_params, public theory_pb_params, public theory_datatype_params { bool m_display_proof; diff --git a/src/smt/params/smt_params_helper.pyg b/src/smt/params/smt_params_helper.pyg index a9f6ccc18..49a786e69 100644 --- a/src/smt/params/smt_params_helper.pyg +++ b/src/smt/params/smt_params_helper.pyg @@ -61,5 +61,6 @@ def_module_params(module_name='smt', ('dack.gc', UINT, 2000, 'Dynamic ackermannization garbage collection frequency (per conflict)'), ('dack.gc_inv_decay', DOUBLE, 0.8, 'Dynamic ackermannization garbage collection decay'), ('dack.threshold', UINT, 10, ' number of times the congruence rule must be used before Leibniz\'s axiom is expanded'), - ('core.validate', BOOL, False, 'validate unsat core produced by SMT context') + ('core.validate', BOOL, False, 'validate unsat core produced by SMT context'), + ('str.strong_arrangements', BOOL, True, 'assert equivalences instead of implications when generating string arrangement axioms') )) diff --git a/src/smt/params/theory_str_params.cpp b/src/smt/params/theory_str_params.cpp new file mode 100644 index 000000000..c1fcb0412 --- /dev/null +++ b/src/smt/params/theory_str_params.cpp @@ -0,0 +1,24 @@ +/*++ +Module Name: + + theory_str_params.cpp + +Abstract: + + Parameters for string theory plugin + +Author: + + Murphy Berzish (mtrberzi) 2016-12-13 + +Revision History: + +--*/ + +#include"theory_str_params.h" +#include"smt_params_helper.hpp" + +void theory_str_params::updt_params(params_ref const & _p) { + smt_params_helper p(_p); + m_AssertStrongerArrangements = p.str_strong_arrangements(); +} diff --git a/src/smt/params/theory_str_params.h b/src/smt/params/theory_str_params.h new file mode 100644 index 000000000..480ad1479 --- /dev/null +++ b/src/smt/params/theory_str_params.h @@ -0,0 +1,42 @@ +/*++ +Module Name: + + theory_str_params.h + +Abstract: + + Parameters for string theory plugin + +Author: + + Murphy Berzish (mtrberzi) 2016-12-13 + +Revision History: + +--*/ + +#ifndef THEORY_STR_PARAMS_H +#define THEORY_STR_PARAMS_H + +#include"params.h" + +struct theory_str_params { + /* + * If AssertStrongerArrangements is set to true, + * the implications that would normally be asserted during arrangement generation + * will instead be asserted as equivalences. + * This is a stronger version of the standard axiom. + * The Z3str2 axioms can be simulated by setting this to false. + */ + bool m_AssertStrongerArrangements; + + theory_str_params(params_ref const & p = params_ref()): + m_AssertStrongerArrangements(true) + { + updt_params(p); + } + + void updt_params(params_ref const & p); +}; + +#endif /* THEORY_STR_PARAMS_H */ diff --git a/src/smt/smt_setup.cpp b/src/smt/smt_setup.cpp index 117b606fd..7cbfd0b2e 100644 --- a/src/smt/smt_setup.cpp +++ b/src/smt/smt_setup.cpp @@ -707,7 +707,7 @@ namespace smt { void setup::setup_QF_S() { m_context.register_plugin(alloc(smt::theory_mi_arith, m_manager, m_params)); - m_context.register_plugin(alloc(smt::theory_str, m_manager)); + m_context.register_plugin(alloc(smt::theory_str, m_manager, m_params)); } bool is_arith(static_features const & st) { @@ -839,7 +839,7 @@ namespace smt { void setup::setup_str() { setup_arith(); - m_context.register_plugin(alloc(theory_str, m_manager)); + m_context.register_plugin(alloc(theory_str, m_manager, m_params)); } void setup::setup_unknown() { diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index b9e9e748f..4eb15d6ad 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -29,8 +29,9 @@ Revision History: namespace smt { -theory_str::theory_str(ast_manager & m): +theory_str::theory_str(ast_manager & m, theory_str_params const & params): theory(m.mk_family_id("str")), + m_params(params), /* Options */ opt_AggressiveLengthTesting(false), opt_AggressiveValueTesting(false), @@ -44,7 +45,6 @@ theory_str::theory_str(ast_manager & m): opt_CheckVariableScope(true), opt_UseFastLengthTesterCache(true), opt_UseFastValueTesterCache(true), - opt_AssertStrongerArrangements(false), /* Internal setup */ search_started(false), m_autil(m), @@ -2911,7 +2911,7 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { add_cut_info_merge(t1, sLevel, m); add_cut_info_merge(t1, sLevel, y); - if (opt_AssertStrongerArrangements) { + if (m_params.m_AssertStrongerArrangements) { expr_ref ax_strong(ctx.mk_eq_atom(ax_l, ax_r), mgr); assert_axiom(ax_strong); } else { @@ -2963,7 +2963,7 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { add_cut_info_merge(t2, sLevel, x); add_cut_info_merge(t2, sLevel, n); - if (opt_AssertStrongerArrangements) { + if (m_params.m_AssertStrongerArrangements) { expr_ref ax_strong(ctx.mk_eq_atom(ax_l, ax_r), mgr); assert_axiom(ax_strong); } else { @@ -3071,7 +3071,7 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { if (!arrangement_disjunction.empty()) { expr_ref premise(ctx.mk_eq_atom(concatAst1, concatAst2), mgr); expr_ref conclusion(mk_or(arrangement_disjunction), mgr); - if (opt_AssertStrongerArrangements) { + if (m_params.m_AssertStrongerArrangements) { expr_ref ax_strong(ctx.mk_eq_atom(premise, conclusion), mgr); assert_axiom(ax_strong); } else { @@ -3272,7 +3272,7 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { add_cut_info_merge(temp1, sLevel, y); add_cut_info_merge(temp1, sLevel, m); - if (opt_AssertStrongerArrangements) { + if (m_params.m_AssertStrongerArrangements) { expr_ref ax_strong(ctx.mk_eq_atom(ax_l, ax_r), mgr); assert_axiom(ax_strong); } else { @@ -3340,7 +3340,7 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { expr_ref ax_l(mk_and(l_items), mgr); expr_ref ax_r(mk_and(r_items), mgr); - if (opt_AssertStrongerArrangements) { + if (m_params.m_AssertStrongerArrangements) { expr_ref ax_strong(ctx.mk_eq_atom(ax_l, ax_r), mgr); assert_axiom(ax_strong); } else { @@ -3404,7 +3404,7 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { if (!arrangement_disjunction.empty()) { expr_ref implyR(mk_or(arrangement_disjunction), mgr); - if (opt_AssertStrongerArrangements) { + if (m_params.m_AssertStrongerArrangements) { expr_ref implyLHS(ctx.mk_eq_atom(concatAst1, concatAst2), mgr); expr_ref ax_strong(ctx.mk_eq_atom(implyLHS, implyR), mgr); assert_axiom(ax_strong); @@ -3592,7 +3592,7 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { r_items.push_back(ctx.mk_eq_atom(x, prefixAst)); r_items.push_back(ctx.mk_eq_atom(y, suf_n_concat)); - if (opt_AssertStrongerArrangements) { + if (m_params.m_AssertStrongerArrangements) { expr_ref ax_strong(ctx.mk_eq_atom(ax_l, mk_and(r_items)), mgr); assert_axiom(ax_strong); } else { @@ -3612,7 +3612,7 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { expr_ref ax_l(mgr.mk_and(ax_l1, ax_l2), mgr); expr_ref ax_r(mgr.mk_and(ctx.mk_eq_atom(x, strAst), ctx.mk_eq_atom(y, n)), mgr); - if (opt_AssertStrongerArrangements) { + if (m_params.m_AssertStrongerArrangements) { expr_ref ax_strong(ctx.mk_eq_atom(ax_l, ax_r), mgr); assert_axiom(ax_strong); } else { @@ -3650,7 +3650,7 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { add_cut_info_merge(temp1, sLevel, x); add_cut_info_merge(temp1, sLevel, n); - if (opt_AssertStrongerArrangements) { + if (m_params.m_AssertStrongerArrangements) { expr_ref ax_strong(ctx.mk_eq_atom(ax_l, ax_r), mgr); assert_axiom(ax_strong); } else { @@ -3731,7 +3731,7 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { if (!arrangement_disjunction.empty()) { expr_ref implyR(mk_or(arrangement_disjunction), mgr); - if (opt_AssertStrongerArrangements) { + if (m_params.m_AssertStrongerArrangements) { expr_ref ax_lhs(ctx.mk_eq_atom(concatAst1, concatAst2), mgr); expr_ref ax_strong(ctx.mk_eq_atom(ax_lhs, implyR), mgr); assert_axiom(ax_strong); @@ -3813,7 +3813,7 @@ void theory_str::process_concat_eq_type4(expr * concatAst1, expr * concatAst2) { if (!in_same_eqc(tmpAst, n)) { // break down option 4-1 expr_ref implyR(ctx.mk_eq_atom(n, tmpAst), mgr); - if (opt_AssertStrongerArrangements) { + if (m_params.m_AssertStrongerArrangements) { expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr); assert_axiom(ax_strong); } else { @@ -3825,7 +3825,7 @@ void theory_str::process_concat_eq_type4(expr * concatAst1, expr * concatAst2) { //break down option 4-2 expr_ref implyR(ctx.mk_eq_atom(n, y), mgr); - if (opt_AssertStrongerArrangements) { + if (m_params.m_AssertStrongerArrangements) { expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr); assert_axiom(ax_strong); } else { @@ -3838,7 +3838,7 @@ void theory_str::process_concat_eq_type4(expr * concatAst1, expr * concatAst2) { if (!in_same_eqc(y, tmpAst)) { //break down option 4-3 expr_ref implyR(ctx.mk_eq_atom(y, tmpAst), mgr); - if (opt_AssertStrongerArrangements) { + if (m_params.m_AssertStrongerArrangements) { expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr); assert_axiom(ax_strong); } else { @@ -3915,7 +3915,7 @@ void theory_str::process_concat_eq_type5(expr * concatAst1, expr * concatAst2) { expr_ref x_deltaStr(mk_concat(x, m_strutil.mk_string(deltaStr)), mgr); if (!in_same_eqc(m, x_deltaStr)) { expr_ref implyR(ctx.mk_eq_atom(m, x_deltaStr), mgr); - if (opt_AssertStrongerArrangements) { + if (m_params.m_AssertStrongerArrangements) { expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr); assert_axiom(ax_strong); } else { @@ -3926,7 +3926,7 @@ void theory_str::process_concat_eq_type5(expr * concatAst1, expr * concatAst2) { // test if (!in_same_eqc(x, m)) { expr_ref implyR(ctx.mk_eq_atom(x, m), mgr); - if (opt_AssertStrongerArrangements) { + if (m_params.m_AssertStrongerArrangements) { expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr); assert_axiom(ax_strong); } else { @@ -3938,7 +3938,7 @@ void theory_str::process_concat_eq_type5(expr * concatAst1, expr * concatAst2) { expr_ref m_deltaStr(mk_concat(m, m_strutil.mk_string(deltaStr)), mgr); if (!in_same_eqc(x, m_deltaStr)) { expr_ref implyR(ctx.mk_eq_atom(x, m_deltaStr), mgr); - if (opt_AssertStrongerArrangements) { + if (m_params.m_AssertStrongerArrangements) { expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr); assert_axiom(ax_strong); } else { @@ -4156,7 +4156,7 @@ void theory_str::process_concat_eq_type6(expr * concatAst1, expr * concatAst2) { expr_ref implyR(mk_or(arrangement_disjunction), mgr); - if (opt_AssertStrongerArrangements) { + if (m_params.m_AssertStrongerArrangements) { expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr); assert_axiom(ax_strong); } else { @@ -6266,7 +6266,7 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { assert_axiom(negate_ast); } else { implyR1 = mk_or(arrangement_disjunction); - if (opt_AssertStrongerArrangements) { + if (m_params.m_AssertStrongerArrangements) { expr_ref ax_strong(ctx.mk_eq_atom(implyL, implyR1), m); assert_axiom(ax_strong); } else { diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index b3667bdec..30bf0b080 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -18,6 +18,7 @@ Revision History: #define _THEORY_STR_H_ #include"smt_theory.h" +#include"theory_str_params.h" #include"trail.h" #include"th_rewriter.h" #include"value_factory.h" @@ -97,7 +98,7 @@ namespace smt { typedef map > string_map; protected: - // Some options that control how the solver operates. + theory_str_params const & m_params; /* * If AggressiveLengthTesting is true, we manipulate the phase of length tester equalities @@ -189,15 +190,6 @@ namespace smt { */ bool opt_UseFastValueTesterCache; - /* - * If AssertStrongerArrangements is set to true, - * the implications that would normally be asserted during arrangement generation - * will instead be asserted as equivalences. - * This is a stronger version of the regular axiom. - * The default (Z3str2) behaviour is to set this to false. - */ - bool opt_AssertStrongerArrangements; - bool search_started; arith_util m_autil; str_util m_strutil; @@ -548,7 +540,7 @@ namespace smt { void refresh_theory_var(expr * e); public: - theory_str(ast_manager & m); + theory_str(ast_manager & m, theory_str_params const & params); virtual ~theory_str(); virtual char const * get_name() const { return "strings"; } @@ -569,7 +561,7 @@ namespace smt { virtual void new_eq_eh(theory_var, theory_var); virtual void new_diseq_eh(theory_var, theory_var); - virtual theory* mk_fresh(context*) { return alloc(theory_str, get_manager()); } + virtual theory* mk_fresh(context*) { return alloc(theory_str, get_manager(), m_params); } virtual void init_search_eh(); virtual void relevant_eh(app * n); virtual void assign_eh(bool_var v, bool is_true); From bced5828f7c1dbcab586709a2a6f067a97fab1f7 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 13 Dec 2016 17:20:58 -0500 Subject: [PATCH 286/410] theory_str parameters --- src/smt/params/smt_params_helper.pyg | 4 +++- src/smt/params/theory_str_params.cpp | 2 ++ src/smt/params/theory_str_params.h | 16 +++++++++++++++- src/smt/theory_str.cpp | 14 ++++++-------- src/smt/theory_str.h | 12 ------------ 5 files changed, 26 insertions(+), 22 deletions(-) diff --git a/src/smt/params/smt_params_helper.pyg b/src/smt/params/smt_params_helper.pyg index 49a786e69..feec8b01c 100644 --- a/src/smt/params/smt_params_helper.pyg +++ b/src/smt/params/smt_params_helper.pyg @@ -62,5 +62,7 @@ def_module_params(module_name='smt', ('dack.gc_inv_decay', DOUBLE, 0.8, 'Dynamic ackermannization garbage collection decay'), ('dack.threshold', UINT, 10, ' number of times the congruence rule must be used before Leibniz\'s axiom is expanded'), ('core.validate', BOOL, False, 'validate unsat core produced by SMT context'), - ('str.strong_arrangements', BOOL, True, 'assert equivalences instead of implications when generating string arrangement axioms') + ('str.strong_arrangements', BOOL, True, 'assert equivalences instead of implications when generating string arrangement axioms'), + ('str.aggressive_length_testing', BOOL, False, 'prioritize testing concrete length values over generating more options'), + ('str.aggressive_value_testing', BOOL, False, 'prioritize testing concrete string constant values over generating more options') )) diff --git a/src/smt/params/theory_str_params.cpp b/src/smt/params/theory_str_params.cpp index c1fcb0412..f7a562842 100644 --- a/src/smt/params/theory_str_params.cpp +++ b/src/smt/params/theory_str_params.cpp @@ -21,4 +21,6 @@ Revision History: void theory_str_params::updt_params(params_ref const & _p) { smt_params_helper p(_p); m_AssertStrongerArrangements = p.str_strong_arrangements(); + m_AggressiveLengthTesting = p.str_aggressive_length_testing(); + m_AggressiveValueTesting = p.str_aggressive_value_testing(); } diff --git a/src/smt/params/theory_str_params.h b/src/smt/params/theory_str_params.h index 480ad1479..78c78089e 100644 --- a/src/smt/params/theory_str_params.h +++ b/src/smt/params/theory_str_params.h @@ -30,8 +30,22 @@ struct theory_str_params { */ bool m_AssertStrongerArrangements; + /* + * If AggressiveLengthTesting is true, we manipulate the phase of length tester equalities + * to prioritize trying concrete length options over choosing the "more" option. + */ + bool m_AggressiveLengthTesting; + + /* + * Similarly, if AggressiveValueTesting is true, we manipulate the phase of value tester equalities + * to prioritize trying concrete value options over choosing the "more" option. + */ + bool m_AggressiveValueTesting; + theory_str_params(params_ref const & p = params_ref()): - m_AssertStrongerArrangements(true) + m_AssertStrongerArrangements(true), + m_AggressiveLengthTesting(false), + m_AggressiveValueTesting(false) { updt_params(p); } diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 4eb15d6ad..fe89b4662 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -33,8 +33,6 @@ theory_str::theory_str(ast_manager & m, theory_str_params const & params): theory(m.mk_family_id("str")), m_params(params), /* Options */ - opt_AggressiveLengthTesting(false), - opt_AggressiveValueTesting(false), opt_AggressiveUnrollTesting(true), opt_EagerStringConstantLengthAssertions(true), opt_VerifyFinalCheckProgress(true), @@ -8364,7 +8362,7 @@ expr * theory_str::gen_val_options(expr * freeVar, expr * len_indicator, expr * << "val_indicator = " << mk_ismt2_pp(val_indicator, m) << std::endl << "lenstr = " << lenStr << std::endl << "tries = " << tries << std::endl; - if (opt_AggressiveValueTesting) { + if (m_params.m_AggressiveValueTesting) { tout << "note: aggressive value testing is enabled" << std::endl; } ); @@ -8408,7 +8406,7 @@ expr * theory_str::gen_val_options(expr * freeVar, expr * len_indicator, expr * for (long long i = l; i < h; i++) { // TODO can we share the val_indicator constants with the length tester cache? orList.push_back(m.mk_eq(val_indicator, m_strutil.mk_string(longlong_to_string(i).c_str()) )); - if (opt_AggressiveValueTesting) { + if (m_params.m_AggressiveValueTesting) { literal l = mk_eq(val_indicator, m_strutil.mk_string(longlong_to_string(i).c_str()), false); ctx.mark_as_relevant(l); ctx.force_phase(l); @@ -8429,7 +8427,7 @@ expr * theory_str::gen_val_options(expr * freeVar, expr * len_indicator, expr * } if (!coverAll) { orList.push_back(m.mk_eq(val_indicator, m_strutil.mk_string("more"))); - if (opt_AggressiveValueTesting) { + if (m_params.m_AggressiveValueTesting) { literal l = mk_eq(val_indicator, m_strutil.mk_string("more"), false); ctx.mark_as_relevant(l); ctx.force_phase(~l); @@ -8980,7 +8978,7 @@ expr * theory_str::gen_len_test_options(expr * freeVar, expr * indicator, int tr TRACE("t_str_detail", tout << "building andList and orList" << std::endl; - if (opt_AggressiveLengthTesting) { + if (m_params.m_AggressiveLengthTesting) { tout << "note: aggressive length testing is active" << std::endl; } ); @@ -9007,7 +9005,7 @@ expr * theory_str::gen_len_test_options(expr * freeVar, expr * indicator, int tr expr_ref or_expr(ctx.mk_eq_atom(indicator, str_indicator), m); orList.push_back(or_expr); - if (opt_AggressiveLengthTesting) { + if (m_params.m_AggressiveLengthTesting) { literal l = mk_eq(indicator, str_indicator, false); ctx.mark_as_relevant(l); ctx.force_phase(l); @@ -9019,7 +9017,7 @@ expr * theory_str::gen_len_test_options(expr * freeVar, expr * indicator, int tr // TODO cache mk_string("more") orList.push_back(m.mk_eq(indicator, m_strutil.mk_string("more"))); - if (opt_AggressiveLengthTesting) { + if (m_params.m_AggressiveLengthTesting) { literal l = mk_eq(indicator, m_strutil.mk_string("more"), false); ctx.mark_as_relevant(l); ctx.force_phase(~l); diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 30bf0b080..02b351167 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -100,18 +100,6 @@ namespace smt { protected: theory_str_params const & m_params; - /* - * If AggressiveLengthTesting is true, we manipulate the phase of length tester equalities - * to prioritize trying concrete length options over choosing the "more" option. - */ - bool opt_AggressiveLengthTesting; - - /* - * Similarly, if AggressiveValueTesting is true, we manipulate the phase of value tester equalities - * to prioritize trying concrete value options over choosing the "more" option. - */ - bool opt_AggressiveValueTesting; - /* * If AggressiveUnrollTesting is true, we manipulate the phase of regex unroll tester equalities * to prioritize trying concrete unroll counts over choosing the "more" option. From 27a2c20c1cf38e55cc4995749de4475864b5ef39 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 13 Dec 2016 19:38:40 -0500 Subject: [PATCH 287/410] add more parameters for theory_str --- src/smt/params/smt_params_helper.pyg | 6 +++++- src/smt/params/theory_str_params.cpp | 3 +++ src/smt/params/theory_str_params.h | 25 ++++++++++++++++++++++++- src/smt/theory_str.cpp | 13 +++++-------- src/smt/theory_str.h | 20 -------------------- 5 files changed, 37 insertions(+), 30 deletions(-) diff --git a/src/smt/params/smt_params_helper.pyg b/src/smt/params/smt_params_helper.pyg index feec8b01c..cf861a28a 100644 --- a/src/smt/params/smt_params_helper.pyg +++ b/src/smt/params/smt_params_helper.pyg @@ -64,5 +64,9 @@ def_module_params(module_name='smt', ('core.validate', BOOL, False, 'validate unsat core produced by SMT context'), ('str.strong_arrangements', BOOL, True, 'assert equivalences instead of implications when generating string arrangement axioms'), ('str.aggressive_length_testing', BOOL, False, 'prioritize testing concrete length values over generating more options'), - ('str.aggressive_value_testing', BOOL, False, 'prioritize testing concrete string constant values over generating more options') + ('str.aggressive_value_testing', BOOL, False, 'prioritize testing concrete string constant values over generating more options'), + ('str.aggressive_unroll_testing', BOOL, True, 'prioritize testing concrete regex unroll counts over generating more options'), + ('str.fast_length_tester_cache', BOOL, False, 'cache length tester constants instead of regenerating them'), + ('str.fast_value_tester_cache', BOOL, True, 'cache value tester constants instead of regenerating them') + )) diff --git a/src/smt/params/theory_str_params.cpp b/src/smt/params/theory_str_params.cpp index f7a562842..f952c6c87 100644 --- a/src/smt/params/theory_str_params.cpp +++ b/src/smt/params/theory_str_params.cpp @@ -23,4 +23,7 @@ void theory_str_params::updt_params(params_ref const & _p) { m_AssertStrongerArrangements = p.str_strong_arrangements(); m_AggressiveLengthTesting = p.str_aggressive_length_testing(); m_AggressiveValueTesting = p.str_aggressive_value_testing(); + m_AggressiveUnrollTesting = p.str_aggressive_unroll_testing(); + m_UseFastLengthTesterCache = p.str_fast_length_tester_cache(); + m_UseFastValueTesterCache = p.str_fast_value_tester_cache(); } diff --git a/src/smt/params/theory_str_params.h b/src/smt/params/theory_str_params.h index 78c78089e..f4e7ecf33 100644 --- a/src/smt/params/theory_str_params.h +++ b/src/smt/params/theory_str_params.h @@ -42,10 +42,33 @@ struct theory_str_params { */ bool m_AggressiveValueTesting; + /* + * If AggressiveUnrollTesting is true, we manipulate the phase of regex unroll tester equalities + * to prioritize trying concrete unroll counts over choosing the "more" option. + */ + bool m_AggressiveUnrollTesting; + + /* + * If UseFastLengthTesterCache is set to true, + * length tester terms will not be generated from scratch each time they are needed, + * but will be saved in a map and looked up. + */ + bool m_UseFastLengthTesterCache; + + /* + * If UseFastValueTesterCache is set to true, + * value tester terms will not be generated from scratch each time they are needed, + * but will be saved in a map and looked up. + */ + bool m_UseFastValueTesterCache; + theory_str_params(params_ref const & p = params_ref()): m_AssertStrongerArrangements(true), m_AggressiveLengthTesting(false), - m_AggressiveValueTesting(false) + m_AggressiveValueTesting(false), + m_AggressiveUnrollTesting(true), + m_UseFastLengthTesterCache(false), + m_UseFastValueTesterCache(true) { updt_params(p); } diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index fe89b4662..b18d51a98 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -33,7 +33,6 @@ theory_str::theory_str(ast_manager & m, theory_str_params const & params): theory(m.mk_family_id("str")), m_params(params), /* Options */ - opt_AggressiveUnrollTesting(true), opt_EagerStringConstantLengthAssertions(true), opt_VerifyFinalCheckProgress(true), opt_LCMUnrollStep(2), @@ -41,8 +40,6 @@ theory_str::theory_str(ast_manager & m, theory_str_params const & params): opt_DisableIntegerTheoryIntegration(false), opt_DeferEQCConsistencyCheck(false), opt_CheckVariableScope(true), - opt_UseFastLengthTesterCache(true), - opt_UseFastValueTesterCache(true), /* Internal setup */ search_started(false), m_autil(m), @@ -8414,7 +8411,7 @@ expr * theory_str::gen_val_options(expr * freeVar, expr * len_indicator, expr * std::string aStr = gen_val_string(len, options[i - l]); expr * strAst; - if (opt_UseFastValueTesterCache) { + if (m_params.m_UseFastValueTesterCache) { if (!valueTesterCache.find(aStr, strAst)) { strAst = m_strutil.mk_string(aStr); valueTesterCache.insert(aStr, strAst); @@ -8905,7 +8902,7 @@ expr * theory_str::gen_unroll_assign(expr * var, std::string lcmStr, expr * test TRACE("t_str_detail", tout << "entry: var = " << mk_pp(var, mgr) << ", lcmStr = " << lcmStr << ", l = " << l << ", h = " << h << std::endl;); - if (opt_AggressiveUnrollTesting) { + if (m_params.m_AggressiveUnrollTesting) { TRACE("t_str_detail", tout << "note: aggressive unroll testing is active" << std::endl;); } @@ -8916,7 +8913,7 @@ expr * theory_str::gen_unroll_assign(expr * var, std::string lcmStr, expr * test std::string iStr = int_to_string(i); expr_ref testerEqAst(ctx.mk_eq_atom(testerVar, m_strutil.mk_string(iStr)), mgr); TRACE("t_str_detail", tout << "testerEqAst = " << mk_pp(testerEqAst, mgr) << std::endl;); - if (opt_AggressiveUnrollTesting) { + if (m_params.m_AggressiveUnrollTesting) { literal l = mk_eq(testerVar, m_strutil.mk_string(iStr), false); ctx.mark_as_relevant(l); ctx.force_phase(l); @@ -8935,7 +8932,7 @@ expr * theory_str::gen_unroll_assign(expr * var, std::string lcmStr, expr * test } expr_ref testerEqMore(ctx.mk_eq_atom(testerVar, m_strutil.mk_string("more")), mgr); TRACE("t_str_detail", tout << "testerEqMore = " << mk_pp(testerEqMore, mgr) << std::endl;); - if (opt_AggressiveUnrollTesting) { + if (m_params.m_AggressiveUnrollTesting) { literal l = mk_eq(testerVar, m_strutil.mk_string("more"), false); ctx.mark_as_relevant(l); ctx.force_phase(~l); @@ -8985,7 +8982,7 @@ expr * theory_str::gen_len_test_options(expr * freeVar, expr * indicator, int tr for (int i = l; i < h; ++i) { expr_ref str_indicator(m); - if (opt_UseFastLengthTesterCache) { + if (m_params.m_UseFastLengthTesterCache) { rational ri(i); expr * lookup_val; if(lengthTesterCache.find(ri, lookup_val)) { diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 02b351167..2a9997517 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -100,12 +100,6 @@ namespace smt { protected: theory_str_params const & m_params; - /* - * If AggressiveUnrollTesting is true, we manipulate the phase of regex unroll tester equalities - * to prioritize trying concrete unroll counts over choosing the "more" option. - */ - bool opt_AggressiveUnrollTesting; - /* * Setting EagerStringConstantLengthAssertions to true allows some methods, * in particular internalize_term(), to add @@ -164,20 +158,6 @@ namespace smt { */ bool opt_CheckVariableScope; - /* - * If UseFastLengthTesterCache is set to true, - * length tester terms will not be generated from scratch each time they are needed, - * but will be saved in a map and looked up. - */ - bool opt_UseFastLengthTesterCache; - - /* - * If UseFastValueTesterCache is set to true, - * value tester terms will not be generated from scratch each time they are needed, - * but will be saved in a map and looked up. - */ - bool opt_UseFastValueTesterCache; - bool search_started; arith_util m_autil; str_util m_strutil; From 67e73077773b6fa136c8a4896f5f2e55cfd77e9b Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 14 Dec 2016 15:00:17 -0500 Subject: [PATCH 288/410] add cut var debug info, wip --- src/smt/theory_str.cpp | 39 ++++++++++++++++++++++++++++++++++++++- src/smt/theory_str.h | 2 ++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index b18d51a98..503485293 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -2481,6 +2481,43 @@ expr_ref theory_str::generate_mutual_exclusion(expr_ref_vector & terms) { return final_result; } +void theory_str::print_cut_var(expr * node, std::ofstream & xout) { + ast_manager & m = get_manager(); + /* +#ifdef DEBUGLOG + __debugPrint(logFile, "\n>> CUT info of ["); + printZ3Node(t, node); + __debugPrint(logFile, "]\n"); + + if (cut_VARMap.find(node) != cut_VARMap.end()) { + if (!cut_VARMap[node].empty()) { + __debugPrint(logFile, "[%2d] {", cut_VARMap[node].top()->level); + std::map::iterator itor = cut_VARMap[node].top()->vars.begin(); + for (; itor != cut_VARMap[node].top()->vars.end(); itor++) { + printZ3Node(t, itor->first); + __debugPrint(logFile, ", "); + } + __debugPrint(logFile, "}\n"); + } else { + + } + } + __debugPrint(logFile, "------------------------\n\n"); +#endif +*/ + xout << "Cut info of " << mk_pp(node, m) << std::endl; + if (cut_var_map.contains(node)) { + if (!cut_var_map[node].empty()) { + xout << "[" << cut_var_map[node].top()->level << "] "; + std::map::iterator itor = cut_var_map[node].top()->vars.begin(); + for (; itor != cut_var_map[node].top()->vars.end(); ++itor) { + xout << mk_pp(itor->first, m) << ", "; + } + xout << std::endl; + } + } +} + /* * Handle two equivalent Concats. */ @@ -3013,7 +3050,7 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { } else { loopDetected = true; TRACE("t_str", tout << "AVOID LOOP: SKIPPED" << std::endl;); - // TODO printCutVar(m, y); + TRACE("t_str_detail", {print_cut_var(m, tout); print_cut_var(y, tout);}); } // break option 2: diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 2a9997517..73f8d9dcc 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -439,6 +439,8 @@ namespace smt { void process_concat_eq_type5(expr * concatAst1, expr * concatAst2); void process_concat_eq_type6(expr * concatAst1, expr * concatAst2); + void print_cut_var(expr * node, std::ofstream & xout); + expr_ref generate_mutual_exclusion(expr_ref_vector & exprs); bool new_eq_check(expr * lhs, expr * rhs); From dd8cd8199ba06db25e34b7539aff8dc212e28881 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Fri, 16 Dec 2016 14:37:34 -0500 Subject: [PATCH 289/410] theory_str refcount debug messages and beginning theory case split --- src/ast/ast.cpp | 1 + src/smt/params/smt_params.cpp | 1 + src/smt/params/smt_params.h | 2 ++ src/smt/params/smt_params_helper.pyg | 4 ++-- src/smt/smt_context.cpp | 11 ++++++++++- src/smt/theory_str.cpp | 12 ++++++++++++ 6 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/ast/ast.cpp b/src/ast/ast.cpp index a9a91ab2a..a822be37a 100644 --- a/src/ast/ast.cpp +++ b/src/ast/ast.cpp @@ -1767,6 +1767,7 @@ void ast_manager::delete_node(ast * n) { TRACE("ast", tout << "Deleting object " << n->m_id << " " << n << "\n";); CTRACE("del_quantifier", is_quantifier(n), tout << "deleting quantifier " << n->m_id << " " << n << "\n";); TRACE("mk_var_bug", tout << "del_ast: " << n->m_id << "\n";); + TRACE("t_str_refcount_hack", tout << "delete ast " << n->m_id << std::endl;); TRACE("ast_delete_node", tout << mk_bounded_pp(n, *this) << "\n";); SASSERT(m_ast_table.contains(n)); diff --git a/src/smt/params/smt_params.cpp b/src/smt/params/smt_params.cpp index 8222c3d60..a5b3e4867 100644 --- a/src/smt/params/smt_params.cpp +++ b/src/smt/params/smt_params.cpp @@ -31,6 +31,7 @@ void smt_params::updt_local_params(params_ref const & _p) { m_restart_strategy = static_cast(p.restart_strategy()); m_restart_factor = p.restart_factor(); m_case_split_strategy = static_cast(p.case_split()); + m_theory_case_split = p.theory_case_split(); m_delay_units = p.delay_units(); m_delay_units_threshold = p.delay_units_threshold(); m_preprocess = _p.get_bool("preprocess", true); // hidden parameter diff --git a/src/smt/params/smt_params.h b/src/smt/params/smt_params.h index 27071bd9e..55346d34f 100644 --- a/src/smt/params/smt_params.h +++ b/src/smt/params/smt_params.h @@ -111,6 +111,7 @@ struct smt_params : public preprocessor_params, case_split_strategy m_case_split_strategy; unsigned m_rel_case_split_order; bool m_lookahead_diseq; + bool m_theory_case_split; // ----------------------------------- // @@ -241,6 +242,7 @@ struct smt_params : public preprocessor_params, m_case_split_strategy(CS_ACTIVITY_DELAY_NEW), m_rel_case_split_order(0), m_lookahead_diseq(false), + m_theory_case_split(false), m_delay_units(false), m_delay_units_threshold(32), m_theory_resolve(false), diff --git a/src/smt/params/smt_params_helper.pyg b/src/smt/params/smt_params_helper.pyg index cf861a28a..3f2c6a54a 100644 --- a/src/smt/params/smt_params_helper.pyg +++ b/src/smt/params/smt_params_helper.pyg @@ -67,6 +67,6 @@ def_module_params(module_name='smt', ('str.aggressive_value_testing', BOOL, False, 'prioritize testing concrete string constant values over generating more options'), ('str.aggressive_unroll_testing', BOOL, True, 'prioritize testing concrete regex unroll counts over generating more options'), ('str.fast_length_tester_cache', BOOL, False, 'cache length tester constants instead of regenerating them'), - ('str.fast_value_tester_cache', BOOL, True, 'cache value tester constants instead of regenerating them') - + ('str.fast_value_tester_cache', BOOL, True, 'cache value tester constants instead of regenerating them'), + ('theory_case_split', BOOL, False, 'Allow the context to use heuristics involving theory case splits, which are a set of literals of which exactly one can be assigned True. If this option is false, the context will generate extra axioms to enforce this instead.') )) diff --git a/src/smt/smt_context.cpp b/src/smt/smt_context.cpp index 8958eae5f..741525dd2 100644 --- a/src/smt/smt_context.cpp +++ b/src/smt/smt_context.cpp @@ -2377,6 +2377,9 @@ namespace smt { */ unsigned context::pop_scope_core(unsigned num_scopes) { + TRACE("t_str_refcount_hack", tout << "begin pop_scope_core in smt_context" << std::endl;); + + if (m_manager.has_trace_stream()) m_manager.trace_stream() << "[pop] " << num_scopes << " " << m_scope_lvl << "\n"; @@ -2423,8 +2426,11 @@ namespace smt { ptr_vector::iterator it = m_theory_set.begin(); ptr_vector::iterator end = m_theory_set.end(); - for (; it != end; ++it) + for (; it != end; ++it) { + TRACE("t_str_refcount_hack", tout << "begin theory pop_scope_eh" << std::endl;); (*it)->pop_scope_eh(num_scopes); + TRACE("t_str_refcount_hack", tout << "end theory pop_scope_eh" << std::endl;); + } del_justifications(m_justifications, s.m_justifications_lim); @@ -2450,6 +2456,9 @@ namespace smt { reassert_units(units_to_reassert_lim); TRACE("pop_scope_detail", tout << "end of pop_scope: \n"; display(tout);); CASSERT("context", check_invariant()); + + TRACE("t_str_refcount_hack", tout << "end pop_scope_core in smt_context" << std::endl;); + return num_bool_vars; } diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 503485293..25a045ee8 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -1805,6 +1805,7 @@ void theory_str::reset_eh() { * Then add an assertion: (y2 == (Concat ce m2)) AND ("str3" == (Concat abc x2)) -> (y2 != "str3") */ bool theory_str::new_eq_check(expr * lhs, expr * rhs) { + TRACE("t_str_refcount_hack", tout << "begin new_eq_check in theory_str" << std::endl;); context & ctx = get_context(); ast_manager & m = get_manager(); @@ -1830,6 +1831,7 @@ bool theory_str::new_eq_check(expr * lhs, expr * rhs) { expr_ref to_assert(m.mk_not(ctx.mk_eq_atom(eqc_nn1, eqc_nn2)), m); assert_axiom(to_assert); // this shouldn't use the integer theory at all, so we don't allow the option of quick-return + TRACE("t_str_refcount_hack", tout << "end new_eq_check in theory_str" << std::endl;); return false; } if (!check_length_consistency(eqc_nn1, eqc_nn2)) { @@ -1837,6 +1839,7 @@ bool theory_str::new_eq_check(expr * lhs, expr * rhs) { if (opt_NoQuickReturn_IntegerTheory){ TRACE("t_str_detail", tout << "continuing in new_eq_check() due to opt_NoQuickReturn_IntegerTheory" << std::endl;); } else { + TRACE("t_str_refcount_hack", tout << "end new_eq_check in theory_str" << std::endl;); return false; } } @@ -1855,6 +1858,7 @@ bool theory_str::new_eq_check(expr * lhs, expr * rhs) { } // okay, all checks here passed + TRACE("t_str_refcount_hack", tout << "end new_eq_check in theory_str" << std::endl;); return true; } @@ -6859,14 +6863,20 @@ void theory_str::check_variable_scope() { } void theory_str::pop_scope_eh(unsigned num_scopes) { + TRACE("t_str_refcount_hack", tout << "begin pop_scope_eh in theory_str" << std::endl;); + sLevel -= num_scopes; TRACE("t_str", tout << "pop " << num_scopes << " to " << sLevel << std::endl;); // TODO: figure out what's going out of scope and why context & ctx = get_context(); ast_manager & m = get_manager(); + // { // expr_ref_vector assignments(m); // ctx.get_assignments(assignments); + // TRACE("t_str_refcount_hack", tout << "assignment vector about to go out of scope" << std::endl;); + // } + // TRACE("t_str_refcount_hack", tout << "assignment vector has gone out of scope" << std::endl;); TRACE_CODE(if (is_trace_enabled("t_str_dump_assign_on_scope_change")) { dump_assignments(); }); @@ -6937,6 +6947,8 @@ void theory_str::pop_scope_eh(unsigned num_scopes) { theory::pop_scope_eh(num_scopes); //check_variable_scope(); + + TRACE("t_str_refcount_hack", tout << "end pop_scope_eh in theory_str" << std::endl;); } void theory_str::dump_assignments() { From e85f9d33c4dceccded5436955a84f125a1e712d8 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Fri, 16 Dec 2016 15:50:03 -0500 Subject: [PATCH 290/410] add "legacy" support for theory case splits this replicates what was done in theory_str to add axioms excluding each pair of literals from being assigned True at the same time; no new heuristics are being used in smt_context (yet) --- src/smt/smt_context.cpp | 21 +++++++++++++++++++++ src/smt/smt_context.h | 8 ++++++++ src/smt/theory_str.cpp | 26 +++++++++++++++++++------- src/smt/theory_str.h | 2 +- 4 files changed, 49 insertions(+), 8 deletions(-) diff --git a/src/smt/smt_context.cpp b/src/smt/smt_context.cpp index 741525dd2..907ea876b 100644 --- a/src/smt/smt_context.cpp +++ b/src/smt/smt_context.cpp @@ -2939,6 +2939,27 @@ namespace smt { assert_expr_core(e, pr); } + void context::mk_th_case_split(unsigned num_lits, literal * lits) { + TRACE("theory_case_split", display_literals_verbose(tout << "theory case split: ", num_lits, lits); tout << std::endl;); + // If we don't use the theory case split heuristic, + // for each pair of literals (l1, l2) we add the clause (~l1 OR ~l2) + // to enforce the condition that more than one literal can't be + // assigned 'true' simultaneously. + if (!m_fparams.m_theory_case_split) { + for (unsigned i = 0; i < num_lits; ++i) { + for (unsigned j = i+1; j < num_lits; ++j) { + literal l1 = lits[i]; + literal l2 = lits[j]; + literal excl[2] = {~l1, ~l2}; + justification * j_excl = 0; + mk_clause(2, excl, j_excl); + } + } + } else { + NOT_IMPLEMENTED_YET(); + } + } + bool context::reduce_assertions() { if (!m_asserted_formulas.inconsistent()) { SASSERT(at_base_level()); diff --git a/src/smt/smt_context.h b/src/smt/smt_context.h index 8b2453e31..5c52adc73 100644 --- a/src/smt/smt_context.h +++ b/src/smt/smt_context.h @@ -805,6 +805,14 @@ namespace smt { void mk_th_axiom(theory_id tid, literal l1, literal l2, literal l3, unsigned num_params = 0, parameter * params = 0); + /* + * Provide a hint to the core solver that the specified literals form a "theory case split". + * The core solver will enforce the condition that exactly one of these literals can be + * assigned 'true' at any time. + * We assume that the theory solver has already asserted the disjunction of these literals + * or some other axiom that means at least one of them must be assigned 'true'. + */ + void mk_th_case_split(unsigned num_lits, literal * lits); bool_var mk_bool_var(expr * n); diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 25a045ee8..89f31db5a 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -2466,8 +2466,19 @@ void theory_str::infer_len_concat_equality(expr * nn1, expr * nn2) { */ } -expr_ref theory_str::generate_mutual_exclusion(expr_ref_vector & terms) { +void theory_str::generate_mutual_exclusion(expr_ref_vector & terms) { context & ctx = get_context(); + // pull each literal out of the arrangement disjunction + literal_vector ls; + for (unsigned i = 0; i < terms.size(); ++i) { + expr * e = terms.get(i); + literal l = ctx.get_literal(e); + ls.push_back(l); + } + ctx.mk_th_case_split(ls.size(), ls.c_ptr()); + + // old version, without special support in the context + /* ast_manager & m = get_manager(); expr_ref_vector result(m); @@ -2482,7 +2493,8 @@ expr_ref theory_str::generate_mutual_exclusion(expr_ref_vector & terms) { } expr_ref final_result(mk_and(result), m); - return final_result; + assert_axiom(final_result); + */ } void theory_str::print_cut_var(expr * node, std::ofstream & xout) { @@ -3114,7 +3126,7 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { assert_implication(premise, conclusion); } // assert mutual exclusion between each branch of the arrangement - assert_axiom(generate_mutual_exclusion(arrangement_disjunction)); + generate_mutual_exclusion(arrangement_disjunction); } else { TRACE("t_str", tout << "STOP: no split option found for two EQ concats." << std::endl;); } @@ -3447,7 +3459,7 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { } else { assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); } - assert_axiom(generate_mutual_exclusion(arrangement_disjunction)); + generate_mutual_exclusion(arrangement_disjunction); } else { TRACE("t_str", tout << "STOP: Should not split two EQ concats." << std::endl;); } @@ -3774,7 +3786,7 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { } else { assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); } - assert_axiom(generate_mutual_exclusion(arrangement_disjunction)); + generate_mutual_exclusion(arrangement_disjunction); } else { TRACE("t_str", tout << "STOP: should not split two eq. concats" << std::endl;); } @@ -4198,7 +4210,7 @@ void theory_str::process_concat_eq_type6(expr * concatAst1, expr * concatAst2) { } else { assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); } - assert_axiom(generate_mutual_exclusion(arrangement_disjunction)); + generate_mutual_exclusion(arrangement_disjunction); } void theory_str::process_unroll_eq_const_str(expr * unrollFunc, expr * constStr) { @@ -6308,7 +6320,7 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { } else { assert_implication(implyL, implyR1); } - assert_axiom(generate_mutual_exclusion(arrangement_disjunction)); + generate_mutual_exclusion(arrangement_disjunction); } } /* (arg1Len != 1 || arg2Len != 1) */ } /* if (Concat(arg1, arg2) == NULL) */ diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 73f8d9dcc..ffeea34e8 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -441,7 +441,7 @@ namespace smt { void print_cut_var(expr * node, std::ofstream & xout); - expr_ref generate_mutual_exclusion(expr_ref_vector & exprs); + void generate_mutual_exclusion(expr_ref_vector & exprs); bool new_eq_check(expr * lhs, expr * rhs); void group_terms_by_eqc(expr * n, std::set & concats, std::set & vars, std::set & consts); From e5d3e425f10aba1380018d68711154c3588face8 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sun, 18 Dec 2016 15:23:05 -0500 Subject: [PATCH 291/410] theory_str caching of all string constants --- src/smt/theory_str.cpp | 155 ++++++++++++++++++++++++----------------- src/smt/theory_str.h | 8 +++ 2 files changed, 101 insertions(+), 62 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 89f31db5a..19e677acb 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -56,6 +56,9 @@ theory_str::theory_str(ast_manager & m, theory_str_params const & params): loopDetected(false), contains_map(m), string_int_conversion_terms(m), + totalCacheAccessCount(0), + cacheHitCount(0), + cacheMissCount(0), m_find(*this), m_trail_stack(*this) { @@ -66,6 +69,34 @@ theory_str::~theory_str() { m_trail_stack.reset(); } +expr * theory_str::mk_string(std::string str) { + ++totalCacheAccessCount; + expr * val; + if (stringConstantCache.find(str, val)) { + // cache hit + ++cacheHitCount; + TRACE("t_str_cache", tout << "cache hit: \"" << str << "\" (" + << cacheHitCount << " hits, " << cacheMissCount << " misses out of " + << totalCacheAccessCount << " accesses)" << std::endl;); + return val; + } else { + // cache miss + ++cacheMissCount; + TRACE("t_str_cache", tout << "cache miss: \"" << str << "\" (" + << cacheHitCount << " hits, " << cacheMissCount << " misses out of " + << totalCacheAccessCount << " accesses)" << std::endl;); + val = m_strutil.mk_string(str); + m_trail.push_back(val); + stringConstantCache.insert(str, val); + return val; + } +} + +expr * theory_str::mk_string(const char * str) { + std::string valStr(str); + return mk_string(valStr); +} + void theory_str::initialize_charset() { bool defaultCharset = true; if (defaultCharset) { @@ -615,7 +646,7 @@ void theory_str::add_nonempty_constraint(expr * s) { context & ctx = get_context(); ast_manager & m = get_manager(); - expr_ref ax1(m.mk_not(ctx.mk_eq_atom(s, m_strutil.mk_string(""))), m); + expr_ref ax1(m.mk_not(ctx.mk_eq_atom(s, mk_string(""))), m); assert_axiom(ax1); { @@ -685,7 +716,7 @@ app * theory_str::mk_unroll(expr * n, expr * bound) { m_trail.push_back(unrollFunc); expr_ref_vector items(m); - items.push_back(ctx.mk_eq_atom(ctx.mk_eq_atom(bound, mk_int(0)), ctx.mk_eq_atom(unrollFunc, m_strutil.mk_string("")))); + items.push_back(ctx.mk_eq_atom(ctx.mk_eq_atom(bound, mk_int(0)), ctx.mk_eq_atom(unrollFunc, mk_string("")))); items.push_back(m_autil.mk_ge(bound, mk_int(0))); items.push_back(m_autil.mk_ge(mk_strlen(unrollFunc), mk_int(0))); @@ -760,7 +791,7 @@ expr * theory_str::mk_concat_const_str(expr * n1, expr * n2) { m_strutil.is_string(v2, & n2_str_tmp); std::string n2_str(n2_str_tmp); std::string result = n1_str + n2_str; - return m_strutil.mk_string(result); + return mk_string(result); } else if (n1HasEqcValue && !n2HasEqcValue) { const char * n1_str_tmp; m_strutil.is_string(v1, & n1_str_tmp); @@ -1013,7 +1044,7 @@ void theory_str::try_eval_concat(enode * cat) { } if (constOK) { TRACE("t_str_detail", tout << "flattened to \"" << flattenedString << "\"" << std::endl;); - expr_ref constStr(m_strutil.mk_string(flattenedString), m); + expr_ref constStr(mk_string(flattenedString), m); expr_ref axiom(ctx.mk_eq_atom(a_cat, constStr), m); assert_axiom(axiom); } @@ -1132,7 +1163,7 @@ void theory_str::instantiate_basic_string_axioms(enode * str) { SASSERT(lhs); // build RHS of iff expr_ref empty_str(m); - empty_str = m_strutil.mk_string(""); + empty_str = mk_string(""); SASSERT(empty_str); expr_ref rhs(m); rhs = ctx.mk_eq_atom(a_str, empty_str); @@ -1203,7 +1234,7 @@ void theory_str::instantiate_axiom_CharAt(enode * e) { and_item.push_back(ctx.mk_eq_atom(mk_strlen(ts1), mk_int(1))); expr_ref thenBranch(m.mk_and(and_item.size(), and_item.c_ptr()), m); - expr_ref elseBranch(ctx.mk_eq_atom(ts1, m_strutil.mk_string("")), m); + expr_ref elseBranch(ctx.mk_eq_atom(ts1, mk_string("")), m); expr_ref axiom(m.mk_ite(cond, thenBranch, elseBranch), m); expr_ref reductionVar(ctx.mk_eq_atom(expr, ts1), m); @@ -1644,7 +1675,7 @@ void theory_str::instantiate_axiom_str_to_int(enode * e) { { expr_ref lhs(ctx.mk_eq_atom(ex, m_autil.mk_numeral(rational::zero(), true)), m); - expr_ref rhs(ctx.mk_eq_atom(S, m_strutil.mk_string("0")), m); + expr_ref rhs(ctx.mk_eq_atom(S, mk_string("0")), m); expr_ref axiom2(ctx.mk_eq_atom(lhs, rhs), m); SASSERT(axiom2); assert_axiom(axiom2); @@ -1656,7 +1687,7 @@ void theory_str::instantiate_axiom_str_to_int(enode * e) { expr_ref tl(mk_str_var("tl"), m); expr_ref conclusion1(ctx.mk_eq_atom(S, mk_concat(hd, tl)), m); expr_ref conclusion2(ctx.mk_eq_atom(mk_strlen(hd), m_autil.mk_numeral(rational::one(), true)), m); - expr_ref conclusion3(m.mk_not(ctx.mk_eq_atom(hd, m_strutil.mk_string("0"))), m); + expr_ref conclusion3(m.mk_not(ctx.mk_eq_atom(hd, mk_string("0"))), m); expr_ref conclusion(m.mk_and(conclusion1, conclusion2, conclusion3), m); SASSERT(premise); SASSERT(conclusion); @@ -1681,7 +1712,7 @@ void theory_str::instantiate_axiom_int_to_str(enode * e) { expr * N = ex->get_arg(0); { expr_ref axiom1_lhs(m.mk_not(m_autil.mk_ge(N, m_autil.mk_numeral(rational::zero(), true))), m); - expr_ref axiom1_rhs(ctx.mk_eq_atom(ex, m_strutil.mk_string("")), m); + expr_ref axiom1_rhs(ctx.mk_eq_atom(ex, mk_string("")), m); expr_ref axiom1(ctx.mk_eq_atom(axiom1_lhs, axiom1_rhs), m); SASSERT(axiom1); assert_axiom(axiom1); @@ -1766,7 +1797,7 @@ void theory_str::instantiate_axiom_RegexIn(enode * e) { expr_ref unrollFunc(mk_unroll(regex1, unrollCount), m); expr_ref_vector items(m); items.push_back(ctx.mk_eq_atom(ex, ctx.mk_eq_atom(str, unrollFunc))); - items.push_back(ctx.mk_eq_atom(ctx.mk_eq_atom(unrollCount, mk_int(0)), ctx.mk_eq_atom(unrollFunc, m_strutil.mk_string("")))); + items.push_back(ctx.mk_eq_atom(ctx.mk_eq_atom(unrollCount, mk_int(0)), ctx.mk_eq_atom(unrollFunc, mk_string("")))); expr_ref finalAxiom(mk_and(items), m); SASSERT(finalAxiom); assert_axiom(finalAxiom); @@ -1947,7 +1978,7 @@ expr * theory_str::eval_concat(expr * n1, expr * n2) { std::string n1_str = m_strutil.get_string_constant_value(v1); std::string n2_str = m_strutil.get_string_constant_value(v2); std::string result = n1_str + n2_str; - return m_strutil.mk_string(result); + return mk_string(result); } else if (n1HasEqcValue && !n2HasEqcValue) { if (m_strutil.get_string_constant_value(v1) == "") { return n2; @@ -2286,7 +2317,7 @@ expr * theory_str::simplify_concat(expr * node) { // no simplification possible return node; } else { - expr * resultAst = m_strutil.mk_string(""); + expr * resultAst = mk_string(""); for (unsigned i = 0; i < argVec.size(); ++i) { bool vArgHasEqcValue = false; expr * vArg = get_eqc_value(argVec[i], vArgHasEqcValue); @@ -3377,9 +3408,9 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { std::string part1Str = strValue.substr(0, lenDelta.get_unsigned()); std::string part2Str = strValue.substr(lenDelta.get_unsigned(), strValue.length() - lenDelta.get_unsigned()); - expr_ref prefixStr(m_strutil.mk_string(part1Str), mgr); + expr_ref prefixStr(mk_string(part1Str), mgr); expr_ref x_concat(mk_concat(m, prefixStr), mgr); - expr_ref cropStr(m_strutil.mk_string(part2Str), mgr); + expr_ref cropStr(mk_string(part2Str), mgr); if (can_two_nodes_eq(x, x_concat) && can_two_nodes_eq(y, cropStr)) { expr_ref_vector r_items(mgr); @@ -3436,9 +3467,9 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { for (int i = 0; i <= (int)strValue.size(); ++i) { std::string part1Str = strValue.substr(0, i); std::string part2Str = strValue.substr(i, strValue.size() - i); - expr_ref prefixStr(m_strutil.mk_string(part1Str), mgr); + expr_ref prefixStr(mk_string(part1Str), mgr); expr_ref x_concat(mk_concat(m, prefixStr), mgr); - expr_ref cropStr(m_strutil.mk_string(part2Str), mgr); + expr_ref cropStr(mk_string(part2Str), mgr); if (can_two_nodes_eq(x, x_concat) && can_two_nodes_eq(y, cropStr)) { // break down option 2-2 expr_ref_vector and_item(mgr); @@ -3630,8 +3661,8 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { std::string prefixStr = strValue.substr(0, prefixLen.get_unsigned()); rational str_sub_prefix = str_len - prefixLen; std::string suffixStr = strValue.substr(prefixLen.get_unsigned(), str_sub_prefix.get_unsigned()); - expr_ref prefixAst(m_strutil.mk_string(prefixStr), mgr); - expr_ref suffixAst(m_strutil.mk_string(suffixStr), mgr); + expr_ref prefixAst(mk_string(prefixStr), mgr); + expr_ref suffixAst(mk_string(suffixStr), mgr); expr_ref ax_l(mgr.mk_and(litems.size(), litems.c_ptr()), mgr); expr_ref suf_n_concat(mk_concat(suffixAst, n), mgr); @@ -3726,8 +3757,8 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { for (int i = 0; i <= (int) strValue.size(); i++) { std::string part1Str = strValue.substr(0, i); std::string part2Str = strValue.substr(i, strValue.size() - i); - expr_ref cropStr(m_strutil.mk_string(part1Str), mgr); - expr_ref suffixStr(m_strutil.mk_string(part2Str), mgr); + expr_ref cropStr(mk_string(part1Str), mgr); + expr_ref suffixStr(mk_string(part2Str), mgr); expr_ref y_concat(mk_concat(suffixStr, n), mgr); if (can_two_nodes_eq(x, cropStr) && can_two_nodes_eq(y, y_concat)) { @@ -3857,7 +3888,7 @@ void theory_str::process_concat_eq_type4(expr * concatAst1, expr * concatAst2) { } else { if (str1Len > str2Len) { std::string deltaStr = str1Value.substr(str2Len, str1Len - str2Len); - expr_ref tmpAst(mk_concat(m_strutil.mk_string(deltaStr), y), mgr); + expr_ref tmpAst(mk_concat(mk_string(deltaStr), y), mgr); if (!in_same_eqc(tmpAst, n)) { // break down option 4-1 expr_ref implyR(ctx.mk_eq_atom(n, tmpAst), mgr); @@ -3882,7 +3913,7 @@ void theory_str::process_concat_eq_type4(expr * concatAst1, expr * concatAst2) { } } else { std::string deltaStr = str2Value.substr(str1Len, str2Len - str1Len); - expr_ref tmpAst(mk_concat(m_strutil.mk_string(deltaStr), n), mgr); + expr_ref tmpAst(mk_concat(mk_string(deltaStr), n), mgr); if (!in_same_eqc(y, tmpAst)) { //break down option 4-3 expr_ref implyR(ctx.mk_eq_atom(y, tmpAst), mgr); @@ -3960,7 +3991,7 @@ void theory_str::process_concat_eq_type5(expr * concatAst1, expr * concatAst2) { } else { if (str1Len > str2Len) { std::string deltaStr = str1Value.substr(0, str1Len - str2Len); - expr_ref x_deltaStr(mk_concat(x, m_strutil.mk_string(deltaStr)), mgr); + expr_ref x_deltaStr(mk_concat(x, mk_string(deltaStr)), mgr); if (!in_same_eqc(m, x_deltaStr)) { expr_ref implyR(ctx.mk_eq_atom(m, x_deltaStr), mgr); if (m_params.m_AssertStrongerArrangements) { @@ -3983,7 +4014,7 @@ void theory_str::process_concat_eq_type5(expr * concatAst1, expr * concatAst2) { } } else { std::string deltaStr = str2Value.substr(0, str2Len - str1Len); - expr_ref m_deltaStr(mk_concat(m, m_strutil.mk_string(deltaStr)), mgr); + expr_ref m_deltaStr(mk_concat(m, mk_string(deltaStr)), mgr); if (!in_same_eqc(x, m_deltaStr)) { expr_ref implyR(ctx.mk_eq_atom(x, m_deltaStr), mgr); if (m_params.m_AssertStrongerArrangements) { @@ -4178,7 +4209,7 @@ void theory_str::process_concat_eq_type6(expr * concatAst1, expr * concatAst2) { expr_ref_vector and_item(mgr); - expr_ref prefixAst(m_strutil.mk_string(prefix), mgr); + expr_ref prefixAst(mk_string(prefix), mgr); expr_ref x_eq_prefix(ctx.mk_eq_atom(m, prefixAst), mgr); and_item.push_back(x_eq_prefix); pos += 1; @@ -4189,7 +4220,7 @@ void theory_str::process_concat_eq_type6(expr * concatAst1, expr * concatAst2) { // adding length constraint for _ = constStr seems slowing things down. - expr_ref suffixAst(m_strutil.mk_string(suffix), mgr); + expr_ref suffixAst(mk_string(suffix), mgr); expr_ref y_eq_suffix(ctx.mk_eq_atom(y, suffixAst), mgr); and_item.push_back(y_eq_suffix); pos += 1; @@ -4262,7 +4293,7 @@ void theory_str::process_concat_eq_unroll(expr * concat, expr * unroll) { expr_ref t2(mk_unroll_bound_var(), mgr); expr_ref t3(mk_unroll_bound_var(), mgr); - expr_ref emptyStr(m_strutil.mk_string(""), mgr); + expr_ref emptyStr(mk_string(""), mgr); expr_ref unroll1(mk_unroll(r1, t2), mgr); expr_ref unroll2(mk_unroll(r1, t3), mgr); @@ -6093,7 +6124,7 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { assert_axiom(diseq); return; } else { - expr_ref tmpStrConst(m_strutil.mk_string(firstPart), m); + expr_ref tmpStrConst(mk_string(firstPart), m); expr_ref premise(ctx.mk_eq_atom(newConcat, str), m); expr_ref conclusion(ctx.mk_eq_atom(arg1, tmpStrConst), m); assert_implication(premise, conclusion); @@ -6133,7 +6164,7 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { assert_axiom(diseq); return; } else { - expr_ref tmpStrConst(m_strutil.mk_string(secondPart), m); + expr_ref tmpStrConst(mk_string(secondPart), m); expr_ref premise(ctx.mk_eq_atom(newConcat, str), m); expr_ref conclusion(ctx.mk_eq_atom(arg2, tmpStrConst), m); assert_implication(premise, conclusion); @@ -6200,8 +6231,8 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { return; } expr_ref_vector r_items(m); - r_items.push_back(ctx.mk_eq_atom(arg1, m_strutil.mk_string(prefixStr))); - r_items.push_back(ctx.mk_eq_atom(arg2, m_strutil.mk_string(suffixStr))); + r_items.push_back(ctx.mk_eq_atom(arg1, mk_string(prefixStr))); + r_items.push_back(ctx.mk_eq_atom(arg2, mk_string(suffixStr))); if (!arg1Len_exists) { r_items.push_back(ctx.mk_eq_atom(mk_strlen(arg1), mk_int(prefixStr.size()))); } @@ -6292,12 +6323,12 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { continue; } - expr_ref prefixAst(m_strutil.mk_string(prefixStr), m); + expr_ref prefixAst(mk_string(prefixStr), m); expr_ref arg1_eq (ctx.mk_eq_atom(arg1, prefixAst), m); and_items.push_back(arg1_eq); and_count += 1; - expr_ref suffixAst(m_strutil.mk_string(suffixStr), m); + expr_ref suffixAst(mk_string(suffixStr), m); expr_ref arg2_eq (ctx.mk_eq_atom(arg2, suffixAst), m); and_items.push_back(arg2_eq); and_count += 1; @@ -6450,7 +6481,7 @@ void theory_str::handle_equality(expr * lhs, expr * rhs) { rational nn1Len, nn2Len; bool nn1Len_exists = get_len_value(lhs, nn1Len); bool nn2Len_exists = get_len_value(rhs, nn2Len); - expr * emptyStr = m_strutil.mk_string(""); + expr * emptyStr = mk_string(""); if (nn1Len_exists && nn1Len.is_zero()) { if (!in_same_eqc(lhs, emptyStr) && rhs != emptyStr) { @@ -7853,7 +7884,7 @@ bool theory_str::finalcheck_str2int(app * a) { if (!Ival.is_minus_one()) { std::string Ival_str = Ival.to_string(); expr_ref premise(ctx.mk_eq_atom(a, m_autil.mk_numeral(Ival, true)), m); - expr_ref conclusion(ctx.mk_eq_atom(S, m_strutil.mk_string(Ival_str)), m); + expr_ref conclusion(ctx.mk_eq_atom(S, mk_string(Ival_str)), m); expr_ref axiom(rewrite_implication(premise, conclusion), m); if (!string_int_axioms.contains(axiom)) { string_int_axioms.insert(axiom); @@ -7907,7 +7938,7 @@ bool theory_str::finalcheck_int2str(app * a) { } } if (conversionOK) { - expr_ref premise(ctx.mk_eq_atom(a, m_strutil.mk_string(Sval)), m); + expr_ref premise(ctx.mk_eq_atom(a, mk_string(Sval)), m); expr_ref conclusion(ctx.mk_eq_atom(N, m_autil.mk_numeral(convertedRepresentation, true)), m); expr_ref axiom(rewrite_implication(premise, conclusion), m); if (!string_int_axioms.contains(axiom)) { @@ -7917,7 +7948,7 @@ bool theory_str::finalcheck_int2str(app * a) { axiomAdd = true; } } else { - expr_ref axiom(m.mk_not(ctx.mk_eq_atom(a, m_strutil.mk_string(Sval))), m); + expr_ref axiom(m.mk_not(ctx.mk_eq_atom(a, mk_string(Sval))), m); // always assert this axiom because this is a conflict clause assert_axiom(axiom); axiomAdd = true; @@ -8036,7 +8067,7 @@ final_check_status theory_str::final_check_eh() { expr_ref lhs1(ctx.mk_eq_atom(concat_lhs, concat_lhs_str), m); expr_ref lhs2(ctx.mk_eq_atom(concat_rhs, concat_rhs_str), m); expr_ref lhs(m.mk_and(lhs1, lhs2), m); - expr_ref rhs(ctx.mk_eq_atom(concat, m_strutil.mk_string(concatString)), m); + expr_ref rhs(ctx.mk_eq_atom(concat, mk_string(concatString)), m); assert_implication(lhs, rhs); backpropagation_occurred = true; } @@ -8130,7 +8161,7 @@ final_check_status theory_str::final_check_eh() { TRACE("t_str", tout << "Assigning decoy values to free internal variables." << std::endl;); for (std::set::iterator it = unused_internal_variables.begin(); it != unused_internal_variables.end(); ++it) { expr * var = *it; - expr_ref assignment(m.mk_eq(var, m_strutil.mk_string("**unused**")), m); + expr_ref assignment(m.mk_eq(var, mk_string("**unused**")), m); assert_axiom(assignment); } return FC_CONTINUE; @@ -8463,9 +8494,9 @@ expr * theory_str::gen_val_options(expr * freeVar, expr * len_indicator, expr * for (long long i = l; i < h; i++) { // TODO can we share the val_indicator constants with the length tester cache? - orList.push_back(m.mk_eq(val_indicator, m_strutil.mk_string(longlong_to_string(i).c_str()) )); + orList.push_back(m.mk_eq(val_indicator, mk_string(longlong_to_string(i).c_str()) )); if (m_params.m_AggressiveValueTesting) { - literal l = mk_eq(val_indicator, m_strutil.mk_string(longlong_to_string(i).c_str()), false); + literal l = mk_eq(val_indicator, mk_string(longlong_to_string(i).c_str()), false); ctx.mark_as_relevant(l); ctx.force_phase(l); } @@ -8474,19 +8505,19 @@ expr * theory_str::gen_val_options(expr * freeVar, expr * len_indicator, expr * expr * strAst; if (m_params.m_UseFastValueTesterCache) { if (!valueTesterCache.find(aStr, strAst)) { - strAst = m_strutil.mk_string(aStr); + strAst = mk_string(aStr); valueTesterCache.insert(aStr, strAst); m_trail.push_back(strAst); } } else { - strAst = m_strutil.mk_string(aStr); + strAst = mk_string(aStr); } andList.push_back(m.mk_eq(orList[orList.size() - 1], m.mk_eq(freeVar, strAst))); } if (!coverAll) { - orList.push_back(m.mk_eq(val_indicator, m_strutil.mk_string("more"))); + orList.push_back(m.mk_eq(val_indicator, mk_string("more"))); if (m_params.m_AggressiveValueTesting) { - literal l = mk_eq(val_indicator, m_strutil.mk_string("more"), false); + literal l = mk_eq(val_indicator, mk_string("more"), false); ctx.mark_as_relevant(l); ctx.force_phase(~l); } @@ -8513,11 +8544,11 @@ expr * theory_str::gen_val_options(expr * freeVar, expr * len_indicator, expr * // Should add ($$_len_x_j = 16) /\ ($$_val_x_16_i = "more") // --------------------------------------- andList.reset(); - andList.push_back(m.mk_eq(len_indicator, m_strutil.mk_string(lenStr.c_str()))); + andList.push_back(m.mk_eq(len_indicator, mk_string(lenStr.c_str()))); for (int i = 0; i < tries; i++) { expr * vTester = fvar_valueTester_map[freeVar][len][i].second; if (vTester != val_indicator) - andList.push_back(m.mk_eq(vTester, m_strutil.mk_string("more"))); + andList.push_back(m.mk_eq(vTester, mk_string("more"))); } expr * assertL = NULL; if (andList.size() == 1) { @@ -8772,7 +8803,7 @@ void theory_str::gen_assign_unroll_reg(std::set & unrolls) { // option 0 expr_ref op0(ctx.mk_eq_atom(cntInUnr, mk_int(0)), mgr); - expr_ref ast1(ctx.mk_eq_atom(unrFunc, m_strutil.mk_string("")), mgr); + expr_ref ast1(ctx.mk_eq_atom(unrFunc, mk_string("")), mgr); expr_ref ast2(ctx.mk_eq_atom(mk_strlen(unrFunc), mk_int(0)), mgr); expr_ref and1(mgr.mk_and(ast1, ast2), mgr); @@ -8856,7 +8887,7 @@ expr * theory_str::gen_assign_unroll_Str2Reg(expr * n, std::set & unrolls return gen_unroll_conditional_options(n, unrolls, lcmStr); } else { expr_ref implyL(mk_and(litems), mgr); - expr_ref implyR(ctx.mk_eq_atom(n, m_strutil.mk_string("")), mgr); + expr_ref implyR(ctx.mk_eq_atom(n, mk_string("")), mgr); // want to return (implyL -> implyR) expr * final_axiom = rewrite_implication(implyL, implyR); return final_axiom; @@ -8869,7 +8900,7 @@ expr * theory_str::gen_unroll_conditional_options(expr * var, std::set & int dist = opt_LCMUnrollStep; expr_ref_vector litems(mgr); - expr_ref moreAst(m_strutil.mk_string("more"), mgr); + expr_ref moreAst(mk_string("more"), mgr); for (std::set::iterator itor = unrolls.begin(); itor != unrolls.end(); itor++) { expr_ref item(ctx.mk_eq_atom(var, *itor), mgr); TRACE("t_str_detail", tout << "considering unroll " << mk_pp(item, mgr) << std::endl;); @@ -8972,10 +9003,10 @@ expr * theory_str::gen_unroll_assign(expr * var, std::string lcmStr, expr * test for (int i = l; i < h; i++) { std::string iStr = int_to_string(i); - expr_ref testerEqAst(ctx.mk_eq_atom(testerVar, m_strutil.mk_string(iStr)), mgr); + expr_ref testerEqAst(ctx.mk_eq_atom(testerVar, mk_string(iStr)), mgr); TRACE("t_str_detail", tout << "testerEqAst = " << mk_pp(testerEqAst, mgr) << std::endl;); if (m_params.m_AggressiveUnrollTesting) { - literal l = mk_eq(testerVar, m_strutil.mk_string(iStr), false); + literal l = mk_eq(testerVar, mk_string(iStr), false); ctx.mark_as_relevant(l); ctx.force_phase(l); } @@ -8983,7 +9014,7 @@ expr * theory_str::gen_unroll_assign(expr * var, std::string lcmStr, expr * test orItems.push_back(testerEqAst); std::string unrollStrInstance = get_unrolled_string(lcmStr, i); - expr_ref x1(ctx.mk_eq_atom(testerEqAst, ctx.mk_eq_atom(var, m_strutil.mk_string(unrollStrInstance))), mgr); + expr_ref x1(ctx.mk_eq_atom(testerEqAst, ctx.mk_eq_atom(var, mk_string(unrollStrInstance))), mgr); TRACE("t_str_detail", tout << "x1 = " << mk_pp(x1, mgr) << std::endl;); andItems.push_back(x1); @@ -8991,10 +9022,10 @@ expr * theory_str::gen_unroll_assign(expr * var, std::string lcmStr, expr * test TRACE("t_str_detail", tout << "x2 = " << mk_pp(x2, mgr) << std::endl;); andItems.push_back(x2); } - expr_ref testerEqMore(ctx.mk_eq_atom(testerVar, m_strutil.mk_string("more")), mgr); + expr_ref testerEqMore(ctx.mk_eq_atom(testerVar, mk_string("more")), mgr); TRACE("t_str_detail", tout << "testerEqMore = " << mk_pp(testerEqMore, mgr) << std::endl;); if (m_params.m_AggressiveUnrollTesting) { - literal l = mk_eq(testerVar, m_strutil.mk_string("more"), false); + literal l = mk_eq(testerVar, mk_string("more"), false); ctx.mark_as_relevant(l); ctx.force_phase(~l); } @@ -9051,14 +9082,14 @@ expr * theory_str::gen_len_test_options(expr * freeVar, expr * indicator, int tr } else { // no match; create and insert std::string i_str = int_to_string(i); - expr_ref new_val(m_strutil.mk_string(i_str), m); + expr_ref new_val(mk_string(i_str), m); lengthTesterCache.insert(ri, new_val); m_trail.push_back(new_val); str_indicator = expr_ref(new_val, m); } } else { std::string i_str = int_to_string(i); - str_indicator = expr_ref(m_strutil.mk_string(i_str), m); + str_indicator = expr_ref(mk_string(i_str), m); } expr_ref or_expr(ctx.mk_eq_atom(indicator, str_indicator), m); orList.push_back(or_expr); @@ -9074,9 +9105,9 @@ expr * theory_str::gen_len_test_options(expr * freeVar, expr * indicator, int tr } // TODO cache mk_string("more") - orList.push_back(m.mk_eq(indicator, m_strutil.mk_string("more"))); + orList.push_back(m.mk_eq(indicator, mk_string("more"))); if (m_params.m_AggressiveLengthTesting) { - literal l = mk_eq(indicator, m_strutil.mk_string("more"), false); + literal l = mk_eq(indicator, mk_string("more"), false); ctx.mark_as_relevant(l); ctx.force_phase(~l); } @@ -9104,7 +9135,7 @@ expr * theory_str::gen_len_test_options(expr * freeVar, expr * indicator, int tr int testerCount = tries - 1; if (testerCount > 0) { expr_ref_vector and_items_LHS(m); - expr_ref moreAst(m_strutil.mk_string("more"), m); + expr_ref moreAst(mk_string("more"), m); for (int i = 0; i < testerCount; ++i) { expr * indicator = fvar_lenTester_map[freeVar][i]; if (internal_variable_set.find(indicator) == internal_variable_set.end()) { @@ -9530,7 +9561,7 @@ app * theory_str::mk_value_helper(app * n) { std::string a0_s(a0_str); std::string a1_s(a1_str); std::string result = a0_s + a1_s; - return m_strutil.mk_string(result); + return to_app(mk_string(result)); } } // fallback path @@ -9562,7 +9593,7 @@ model_value_proc * theory_str::mk_value(enode * n, model_generator & mg) { TRACE("t_str", tout << "WARNING: failed to find a concrete value, falling back" << std::endl;); // TODO make absolutely sure the reason we can't find a concrete value is because of an unassigned temporary // e.g. for an expression like (Concat X $$_str0) - return alloc(expr_wrapper_proc, m_strutil.mk_string("**UNUSED**")); + return alloc(expr_wrapper_proc, to_app(mk_string("**UNUSED**"))); } } diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index ffeea34e8..e77c955f2 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -263,6 +263,11 @@ namespace smt { // used when opt_FastValueTesterCache is true string_map valueTesterCache; + string_map stringConstantCache; + unsigned long totalCacheAccessCount; + unsigned long cacheHitCount; + unsigned long cacheMissCount; + // cache mapping each string S to Length(S) obj_map length_ast_map; @@ -277,6 +282,9 @@ namespace smt { void assert_implication(expr * premise, expr * conclusion); expr * rewrite_implication(expr * premise, expr * conclusion); + expr * mk_string(std::string str); + expr * mk_string(const char * str); + app * mk_strlen(expr * e); expr * mk_concat(expr * n1, expr * n2); expr * mk_concat_const_str(expr * n1, expr * n2); From 94762d276d7a6cac121e72fae0d39be046701ac9 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sun, 18 Dec 2016 18:47:38 -0500 Subject: [PATCH 292/410] add string constant cache to theory_str and associated param --- src/smt/params/smt_params_helper.pyg | 1 + src/smt/params/theory_str_params.cpp | 1 + src/smt/params/theory_str_params.h | 9 ++++++- src/smt/theory_str.cpp | 40 +++++++++++++++------------- 4 files changed, 32 insertions(+), 19 deletions(-) diff --git a/src/smt/params/smt_params_helper.pyg b/src/smt/params/smt_params_helper.pyg index 3f2c6a54a..3bcb867b4 100644 --- a/src/smt/params/smt_params_helper.pyg +++ b/src/smt/params/smt_params_helper.pyg @@ -68,5 +68,6 @@ def_module_params(module_name='smt', ('str.aggressive_unroll_testing', BOOL, True, 'prioritize testing concrete regex unroll counts over generating more options'), ('str.fast_length_tester_cache', BOOL, False, 'cache length tester constants instead of regenerating them'), ('str.fast_value_tester_cache', BOOL, True, 'cache value tester constants instead of regenerating them'), + ('str.string_constant_cache', BOOL, True, 'cache all generated string constants generated from anywhere in theory_str'), ('theory_case_split', BOOL, False, 'Allow the context to use heuristics involving theory case splits, which are a set of literals of which exactly one can be assigned True. If this option is false, the context will generate extra axioms to enforce this instead.') )) diff --git a/src/smt/params/theory_str_params.cpp b/src/smt/params/theory_str_params.cpp index f952c6c87..dae7765cc 100644 --- a/src/smt/params/theory_str_params.cpp +++ b/src/smt/params/theory_str_params.cpp @@ -26,4 +26,5 @@ void theory_str_params::updt_params(params_ref const & _p) { m_AggressiveUnrollTesting = p.str_aggressive_unroll_testing(); m_UseFastLengthTesterCache = p.str_fast_length_tester_cache(); m_UseFastValueTesterCache = p.str_fast_value_tester_cache(); + m_StringConstantCache = p.str_string_constant_cache(); } diff --git a/src/smt/params/theory_str_params.h b/src/smt/params/theory_str_params.h index f4e7ecf33..dc4e1aa89 100644 --- a/src/smt/params/theory_str_params.h +++ b/src/smt/params/theory_str_params.h @@ -62,13 +62,20 @@ struct theory_str_params { */ bool m_UseFastValueTesterCache; + /* + * If StringConstantCache is set to true, + * all string constants in theory_str generated from anywhere will be cached and saved. + */ + bool m_StringConstantCache; + theory_str_params(params_ref const & p = params_ref()): m_AssertStrongerArrangements(true), m_AggressiveLengthTesting(false), m_AggressiveValueTesting(false), m_AggressiveUnrollTesting(true), m_UseFastLengthTesterCache(false), - m_UseFastValueTesterCache(true) + m_UseFastValueTesterCache(true), + m_StringConstantCache(true) { updt_params(p); } diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 19e677acb..3a3d36c36 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -70,25 +70,29 @@ theory_str::~theory_str() { } expr * theory_str::mk_string(std::string str) { - ++totalCacheAccessCount; - expr * val; - if (stringConstantCache.find(str, val)) { - // cache hit - ++cacheHitCount; - TRACE("t_str_cache", tout << "cache hit: \"" << str << "\" (" - << cacheHitCount << " hits, " << cacheMissCount << " misses out of " - << totalCacheAccessCount << " accesses)" << std::endl;); - return val; + if (m_params.m_StringConstantCache) { + ++totalCacheAccessCount; + expr * val; + if (stringConstantCache.find(str, val)) { + // cache hit + ++cacheHitCount; + TRACE("t_str_cache", tout << "cache hit: \"" << str << "\" (" + << cacheHitCount << " hits, " << cacheMissCount << " misses out of " + << totalCacheAccessCount << " accesses)" << std::endl;); + return val; + } else { + // cache miss + ++cacheMissCount; + TRACE("t_str_cache", tout << "cache miss: \"" << str << "\" (" + << cacheHitCount << " hits, " << cacheMissCount << " misses out of " + << totalCacheAccessCount << " accesses)" << std::endl;); + val = m_strutil.mk_string(str); + m_trail.push_back(val); + stringConstantCache.insert(str, val); + return val; + } } else { - // cache miss - ++cacheMissCount; - TRACE("t_str_cache", tout << "cache miss: \"" << str << "\" (" - << cacheHitCount << " hits, " << cacheMissCount << " misses out of " - << totalCacheAccessCount << " accesses)" << std::endl;); - val = m_strutil.mk_string(str); - m_trail.push_back(val); - stringConstantCache.insert(str, val); - return val; + return m_strutil.mk_string(str); } } From a04bc9974b2d2847505a37f3e9e640a082f4ed84 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 20 Dec 2016 11:14:42 -0500 Subject: [PATCH 293/410] theory case split WIP --- src/smt/smt_context.cpp | 26 +++++++++++++++++++++++++- src/smt/smt_context.h | 12 ++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/src/smt/smt_context.cpp b/src/smt/smt_context.cpp index 907ea876b..45beebc15 100644 --- a/src/smt/smt_context.cpp +++ b/src/smt/smt_context.cpp @@ -1750,6 +1750,8 @@ namespace smt { if (inconsistent()) return false; unsigned qhead = m_qhead; + if (!propagate_th_case_split()) + return false; if (!bcp()) return false; if (get_cancel_flag()) @@ -2956,10 +2958,32 @@ namespace smt { } } } else { - NOT_IMPLEMENTED_YET(); + int_set new_case_split; // TODO is it okay to allocate this on the stack? + for (unsigned i = 0; i < num_lits; ++i) { + literal l = lits[i]; + // TODO do we need to enforce this invariant? can we make undo information work without it? + SASSERT(!m_all_th_case_split_literals.contains(l.index())); + m_all_th_case_split_literals.insert(l.index()); + // TODO add undo information for this insert + new_case_split.insert(l.index()); + } + m_th_case_split_sets.push_back(new_case_split); + push_trail(push_back_vector >(m_th_case_split_sets)); + for (unsigned i = 0; i < num_lits; ++i) { + literal l = lits[i]; + m_literal2casesplitsets[l.index()].push_back(new_case_split); + push_trail(push_back_vector >(m_literal2casesplitsets[l.index()])); + } } } + bool context::propagate_th_case_split() { + if (m_all_th_case_split_literals.empty()) + return true; + + NOT_IMPLEMENTED_YET(); return true; + } + bool context::reduce_assertions() { if (!m_asserted_formulas.inconsistent()) { SASSERT(at_base_level()); diff --git a/src/smt/smt_context.h b/src/smt/smt_context.h index 5c52adc73..cdc52dc67 100644 --- a/src/smt/smt_context.h +++ b/src/smt/smt_context.h @@ -212,6 +212,16 @@ namespace smt { literal2assumption m_literal2assumption; // maps an expression associated with a literal to the original assumption expr_ref_vector m_unsat_core; + // ----------------------------------- + // + // Theory case split + // + // ----------------------------------- + typedef int_hashtable > int_set; + int_set m_all_th_case_split_literals; + vector m_th_case_split_sets; + u_map< vector > m_literal2casesplitsets; // returns the case split literal sets that a literal participates in + // ----------------------------------- // // Accessors @@ -814,6 +824,8 @@ namespace smt { */ void mk_th_case_split(unsigned num_lits, literal * lits); + bool propagate_th_case_split(); + bool_var mk_bool_var(expr * n); enode * mk_enode(app * n, bool suppress_args, bool merge_tf, bool cgc_enabled); From ab0fcc42f9107931a414fff39c1769dfd7be14d1 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 20 Dec 2016 16:21:07 -0500 Subject: [PATCH 294/410] theory case split heuristic --- src/smt/smt_context.cpp | 93 +++++++++++++++++++++++++++++++++++++---- src/smt/smt_context.h | 8 +++- 2 files changed, 91 insertions(+), 10 deletions(-) diff --git a/src/smt/smt_context.cpp b/src/smt/smt_context.cpp index 45beebc15..93461584f 100644 --- a/src/smt/smt_context.cpp +++ b/src/smt/smt_context.cpp @@ -63,6 +63,7 @@ namespace smt { m_is_diseq_tmp(0), m_units_to_reassert(m_manager), m_qhead(0), + m_th_case_split_qhead(0), m_simp_qhead(0), m_simp_counter(0), m_bvar_inc(1.0), @@ -325,6 +326,7 @@ namespace smt { bool context::bcp() { SASSERT(!inconsistent()); + m_th_case_split_qhead = m_qhead; while (m_qhead < m_assigned_literals.size()) { if (get_cancel_flag()) { return true; @@ -1750,10 +1752,10 @@ namespace smt { if (inconsistent()) return false; unsigned qhead = m_qhead; - if (!propagate_th_case_split()) - return false; if (!bcp()) return false; + if (!propagate_th_case_split()) + return false; if (get_cancel_flag()) return true; SASSERT(!inconsistent()); @@ -2941,6 +2943,18 @@ namespace smt { assert_expr_core(e, pr); } + class case_split_insert_trail : public trail { + literal l; + public: + case_split_insert_trail(literal l): + l(l) { + + } + virtual void undo(context & ctx) { + ctx.undo_th_case_split(l); + } + }; + void context::mk_th_case_split(unsigned num_lits, literal * lits) { TRACE("theory_case_split", display_literals_verbose(tout << "theory case split: ", num_lits, lits); tout << std::endl;); // If we don't use the theory case split heuristic, @@ -2958,30 +2972,93 @@ namespace smt { } } } else { - int_set new_case_split; // TODO is it okay to allocate this on the stack? + literal_vector new_case_split; // TODO is it okay to allocate this on the stack? for (unsigned i = 0; i < num_lits; ++i) { literal l = lits[i]; // TODO do we need to enforce this invariant? can we make undo information work without it? SASSERT(!m_all_th_case_split_literals.contains(l.index())); m_all_th_case_split_literals.insert(l.index()); - // TODO add undo information for this insert - new_case_split.insert(l.index()); + push_trail(case_split_insert_trail(l)); + new_case_split.push_back(l); } m_th_case_split_sets.push_back(new_case_split); - push_trail(push_back_vector >(m_th_case_split_sets)); + push_trail(push_back_vector >(m_th_case_split_sets)); for (unsigned i = 0; i < num_lits; ++i) { literal l = lits[i]; + if (!m_literal2casesplitsets.contains(l.index())) { + m_literal2casesplitsets.insert(l.index(), vector()); + } m_literal2casesplitsets[l.index()].push_back(new_case_split); - push_trail(push_back_vector >(m_literal2casesplitsets[l.index()])); + push_trail(push_back_vector >(m_literal2casesplitsets[l.index()])); } + TRACE("theory_case_split", tout << "tracking case split literal set { "; + for (unsigned i = 0; i < num_lits; ++i) { + tout << lits[i].index() << " "; + } + tout << "}" << std::endl; + ); } } + void context::undo_th_case_split(literal l) { + m_all_th_case_split_literals.remove(l.index()); + } + bool context::propagate_th_case_split() { if (m_all_th_case_split_literals.empty()) return true; - NOT_IMPLEMENTED_YET(); return true; + // iterate over all literals assigned since the last time this method was called, + // not counting any literals that get assigned by this method + // this relies on bcp() to give us its old m_qhead and therefore + // bcp() should always be called before this method + unsigned assigned_literal_idx = m_th_case_split_qhead; + unsigned assigned_literal_end = m_assigned_literals.size(); + while(assigned_literal_idx < assigned_literal_end) { + literal l = m_assigned_literals[assigned_literal_idx]; + TRACE("theory_case_split", tout << "check literal " << l.index() << std::endl; display_literal_verbose(tout, l); tout << std::endl;); + ++assigned_literal_idx; + // check if this literal participates in any theory case split + if (m_all_th_case_split_literals.contains(l.index())) { + TRACE("theory_case_split", tout << "assigned literal " << l.index() << " is a theory case split literal" << std::endl;); + // now find the sets of literals which contain l + vector case_split_sets = m_literal2casesplitsets.get(l.index(), vector()); + for (vector::const_iterator it = case_split_sets.begin(); it != case_split_sets.end(); ++it) { + literal_vector case_split_set = *it; + TRACE("theory_case_split", tout << "found case split set { "; + for(literal_vector::iterator set_it = case_split_set.begin(); set_it != case_split_set.end(); ++set_it) { + tout << set_it->index() << " "; + } + tout << "}" << std::endl;); + for(literal_vector::iterator set_it = case_split_set.begin(); set_it != case_split_set.end(); ++set_it) { + literal l2 = *set_it; + if (l2 != l) { + b_justification js(l); + switch (get_assignment(l2)) { + case l_false: + TRACE("theory_case_split", tout << "case split literal " << l2.index() << " is already assigned False" << std::endl;); + break; + // TODO these next two cases can be combined. I'm doing this for debugging purposes + case l_undef: + TRACE("theory_case_split", tout << "case split literal " << l2.index() << " is not assigned" << std::endl;); + assign(~l2, js); + break; + case l_true: + TRACE("theory_case_split", tout << "case split literal " << l2.index() << " is already assigned True" << std::endl;); + assign(~l2, js); + break; + } + if (inconsistent()) { + TRACE("theory_case_split", tout << "conflict detected!" << std::endl;); + return false; + } + } + } + } + } + } + // if we get here without detecting a conflict, we're fine + return true; } bool context::reduce_assertions() { diff --git a/src/smt/smt_context.h b/src/smt/smt_context.h index cdc52dc67..8016eb587 100644 --- a/src/smt/smt_context.h +++ b/src/smt/smt_context.h @@ -219,8 +219,9 @@ namespace smt { // ----------------------------------- typedef int_hashtable > int_set; int_set m_all_th_case_split_literals; - vector m_th_case_split_sets; - u_map< vector > m_literal2casesplitsets; // returns the case split literal sets that a literal participates in + vector m_th_case_split_sets; + u_map< vector > m_literal2casesplitsets; // returns the case split literal sets that a literal participates in + unsigned m_th_case_split_qhead; // ----------------------------------- // @@ -824,6 +825,9 @@ namespace smt { */ void mk_th_case_split(unsigned num_lits, literal * lits); + // helper function for trail + void undo_th_case_split(literal l); + bool propagate_th_case_split(); bool_var mk_bool_var(expr * n); From df63b62763ef06c27fef3bc9c3d5d5fac17437ff Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 20 Dec 2016 17:32:51 -0500 Subject: [PATCH 295/410] fix vector manip bug in theory case split --- src/smt/smt_context.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/smt/smt_context.cpp b/src/smt/smt_context.cpp index 93461584f..6c0a89d4f 100644 --- a/src/smt/smt_context.cpp +++ b/src/smt/smt_context.cpp @@ -2989,7 +2989,6 @@ namespace smt { m_literal2casesplitsets.insert(l.index(), vector()); } m_literal2casesplitsets[l.index()].push_back(new_case_split); - push_trail(push_back_vector >(m_literal2casesplitsets[l.index()])); } TRACE("theory_case_split", tout << "tracking case split literal set { "; for (unsigned i = 0; i < num_lits; ++i) { @@ -3002,6 +3001,11 @@ namespace smt { void context::undo_th_case_split(literal l) { m_all_th_case_split_literals.remove(l.index()); + if (m_literal2casesplitsets.contains(l.index())) { + if (!m_literal2casesplitsets[l.index()].empty()) { + m_literal2casesplitsets[l.index()].pop_back(); + } + } } bool context::propagate_th_case_split() { From 2dc9b486d3d4962850e2899836b64cf2a07266f9 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Thu, 22 Dec 2016 19:17:42 -0500 Subject: [PATCH 296/410] theory_str binary search heuristic WIP --- src/smt/params/smt_params_helper.pyg | 2 + src/smt/params/theory_str_params.cpp | 2 + src/smt/params/theory_str_params.h | 7 +- src/smt/theory_str.cpp | 546 +++++++++++++++++++-------- src/smt/theory_str.h | 53 +++ 5 files changed, 442 insertions(+), 168 deletions(-) diff --git a/src/smt/params/smt_params_helper.pyg b/src/smt/params/smt_params_helper.pyg index 3bcb867b4..4e3bec57d 100644 --- a/src/smt/params/smt_params_helper.pyg +++ b/src/smt/params/smt_params_helper.pyg @@ -69,5 +69,7 @@ def_module_params(module_name='smt', ('str.fast_length_tester_cache', BOOL, False, 'cache length tester constants instead of regenerating them'), ('str.fast_value_tester_cache', BOOL, True, 'cache value tester constants instead of regenerating them'), ('str.string_constant_cache', BOOL, True, 'cache all generated string constants generated from anywhere in theory_str'), + ('str.use_binary_search', BOOL, False, 'use a binary search heuristic for finding concrete length values for free variables in theory_str (set to False to use linear search)'), + ('str.binary_search_start', UINT, 64, 'initial upper bound for theory_str binary search'), ('theory_case_split', BOOL, False, 'Allow the context to use heuristics involving theory case splits, which are a set of literals of which exactly one can be assigned True. If this option is false, the context will generate extra axioms to enforce this instead.') )) diff --git a/src/smt/params/theory_str_params.cpp b/src/smt/params/theory_str_params.cpp index dae7765cc..2e98a4394 100644 --- a/src/smt/params/theory_str_params.cpp +++ b/src/smt/params/theory_str_params.cpp @@ -27,4 +27,6 @@ void theory_str_params::updt_params(params_ref const & _p) { m_UseFastLengthTesterCache = p.str_fast_length_tester_cache(); m_UseFastValueTesterCache = p.str_fast_value_tester_cache(); m_StringConstantCache = p.str_string_constant_cache(); + m_UseBinarySearch = p.str_use_binary_search(); + m_BinarySearchInitialUpperBound = p.str_binary_search_start(); } diff --git a/src/smt/params/theory_str_params.h b/src/smt/params/theory_str_params.h index dc4e1aa89..39c553780 100644 --- a/src/smt/params/theory_str_params.h +++ b/src/smt/params/theory_str_params.h @@ -68,6 +68,9 @@ struct theory_str_params { */ bool m_StringConstantCache; + bool m_UseBinarySearch; + unsigned m_BinarySearchInitialUpperBound; + theory_str_params(params_ref const & p = params_ref()): m_AssertStrongerArrangements(true), m_AggressiveLengthTesting(false), @@ -75,7 +78,9 @@ struct theory_str_params { m_AggressiveUnrollTesting(true), m_UseFastLengthTesterCache(false), m_UseFastValueTesterCache(true), - m_StringConstantCache(true) + m_StringConstantCache(true), + m_UseBinarySearch(false), + m_BinarySearchInitialUpperBound(64) { updt_params(p); } diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 3a3d36c36..754d258bc 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -6379,27 +6379,53 @@ void theory_str::more_value_tests(expr * valTester, std::string valTesterValue) ast_manager & m = get_manager(); expr * fVar = valueTester_fvar_map[valTester]; - int lenTesterCount = fvar_lenTester_map[fVar].size(); - - expr * effectiveLenInd = NULL; - std::string effectiveLenIndiStr = ""; - for (int i = 0; i < lenTesterCount; ++i) { - expr * len_indicator_pre = fvar_lenTester_map[fVar][i]; - bool indicatorHasEqcValue = false; - expr * len_indicator_value = get_eqc_value(len_indicator_pre, indicatorHasEqcValue); - if (indicatorHasEqcValue) { - std::string len_pIndiStr = m_strutil.get_string_constant_value(len_indicator_value); - if (len_pIndiStr != "more") { - effectiveLenInd = len_indicator_pre; - effectiveLenIndiStr = len_pIndiStr; - break; + if (m_params.m_UseBinarySearch) { + if (!binary_search_len_tester_stack.contains(fVar) || binary_search_len_tester_stack[fVar].empty()) { + TRACE("t_str_binary_search", tout << "WARNING: no active length testers for " << mk_pp(fVar, m) << std::endl;); + // TODO handle this? + NOT_IMPLEMENTED_YET(); + } + expr * effectiveLenInd = binary_search_len_tester_stack[fVar].back(); + bool hasEqcValue; + expr * len_indicator_value = get_eqc_value(effectiveLenInd, hasEqcValue); + if (!hasEqcValue) { + TRACE("t_str_binary_search", tout << "WARNING: length tester " << mk_pp(effectiveLenInd, m) << " at top of stack for " << mk_pp(fVar, m) << " has no EQC value" << std::endl;); + } else { + // safety check + std::string effectiveLenIndiStr = m_strutil.get_string_constant_value(len_indicator_value); + if (effectiveLenIndiStr == "more" || effectiveLenIndiStr == "less") { + TRACE("t_str_binary_search", tout << "ERROR: illegal state -- requesting 'more value tests' but a length tester is not yet concrete!" << std::endl;); + UNREACHABLE(); + } + expr * valueAssert = gen_free_var_options(fVar, effectiveLenInd, effectiveLenIndiStr, valTester, valTesterValue); + TRACE("t_str_detail", tout << "asserting more value tests for free variable " << mk_ismt2_pp(fVar, m) << std::endl;); + if (valueAssert != NULL) { + assert_axiom(valueAssert); } } - } - expr * valueAssert = gen_free_var_options(fVar, effectiveLenInd, effectiveLenIndiStr, valTester, valTesterValue); - TRACE("t_str_detail", tout << "asserting more value tests for free variable " << mk_ismt2_pp(fVar, m) << std::endl;); - if (valueAssert != NULL) { - assert_axiom(valueAssert); + } else { + int lenTesterCount = fvar_lenTester_map[fVar].size(); + + expr * effectiveLenInd = NULL; + std::string effectiveLenIndiStr = ""; + for (int i = 0; i < lenTesterCount; ++i) { + expr * len_indicator_pre = fvar_lenTester_map[fVar][i]; + bool indicatorHasEqcValue = false; + expr * len_indicator_value = get_eqc_value(len_indicator_pre, indicatorHasEqcValue); + if (indicatorHasEqcValue) { + std::string len_pIndiStr = m_strutil.get_string_constant_value(len_indicator_value); + if (len_pIndiStr != "more") { + effectiveLenInd = len_indicator_pre; + effectiveLenIndiStr = len_pIndiStr; + break; + } + } + } + expr * valueAssert = gen_free_var_options(fVar, effectiveLenInd, effectiveLenIndiStr, valTester, valTesterValue); + TRACE("t_str_detail", tout << "asserting more value tests for free variable " << mk_ismt2_pp(fVar, m) << std::endl;); + if (valueAssert != NULL) { + assert_axiom(valueAssert); + } } } @@ -9163,6 +9189,186 @@ expr * theory_str::gen_len_test_options(expr * freeVar, expr * indicator, int tr } } +// Return an expression of the form +// (tester = "less" | tester = "N" | tester = "more") & +// (tester = "less" iff len(freeVar) < N) & (tester = "more" iff len(freeVar) > N) & (tester = "N" iff len(freeVar) = N)) +expr_ref theory_str::binary_search_case_split(expr * freeVar, expr * tester, binary_search_info & bounds) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + rational N = bounds.midPoint; + rational N_minus_one = N - rational::one(); + rational N_plus_one = N + rational::one(); + expr_ref lenFreeVar(mk_strlen(freeVar), m); + + TRACE("t_str_binary_search", tout << "create case split for free var " << mk_pp(freeVar, m) + << " over " << mk_pp(tester, m) << " with midpoint " << N << std::endl;); + + expr_ref_vector combinedCaseSplit(m); + expr_ref_vector testerCases(m); + + expr_ref caseLess(ctx.mk_eq_atom(tester, mk_string("less")), m); + testerCases.push_back(caseLess); + combinedCaseSplit.push_back(ctx.mk_eq_atom(caseLess, m_autil.mk_le(lenFreeVar, m_autil.mk_numeral(N_minus_one, true) ))); + + expr_ref caseMore(ctx.mk_eq_atom(tester, mk_string("more")), m); + testerCases.push_back(caseMore); + combinedCaseSplit.push_back(ctx.mk_eq_atom(caseMore, m_autil.mk_ge(lenFreeVar, m_autil.mk_numeral(N_plus_one, true) ))); + + expr_ref caseEq(ctx.mk_eq_atom(tester, mk_string(N.to_string())), m); + testerCases.push_back(caseEq); + combinedCaseSplit.push_back(ctx.mk_eq_atom(caseEq, ctx.mk_eq_atom(lenFreeVar, m_autil.mk_numeral(N, true)))); + + combinedCaseSplit.push_back(mk_or(testerCases)); + + expr_ref final_term(mk_and(combinedCaseSplit), m); + SASSERT(final_term); + TRACE("t_str_binary_search", tout << "final term: " << mk_pp(final_term, m) << std::endl;); + return final_term; +} + +expr * theory_str::binary_search_length_test(expr * freeVar, expr * previousLenTester, std::string previousLenTesterValue) { + ast_manager & m = get_manager(); + + if (binary_search_len_tester_stack.contains(freeVar)) { + TRACE("t_str_binary_search", tout << "checking existing length testers for " << mk_pp(freeVar, m) << std::endl; + for (ptr_vector::const_iterator it = binary_search_len_tester_stack[freeVar].begin(); + it != binary_search_len_tester_stack[freeVar].end(); ++it) { + expr * tester = *it; + tout << mk_pp(tester, m) << ": "; + if (binary_search_len_tester_info.contains(tester)) { + binary_search_info & bounds = binary_search_len_tester_info[tester]; + tout << "[" << bounds.lowerBound << " | " << bounds.midPoint << " | " << bounds.upperBound << "]!" << bounds.windowSize; + } else { + tout << "[WARNING: no bounds info available]"; + } + bool hasEqcValue; + expr * testerEqcValue = get_eqc_value(tester, hasEqcValue); + if (hasEqcValue) { + tout << " = " << mk_pp(testerEqcValue, m); + } else { + tout << " [no eqc value]"; + } + tout << std::endl; + } + ); + expr * lastTester = binary_search_len_tester_stack[freeVar].back(); + bool lastTesterHasEqcValue; + expr * lastTesterValue = get_eqc_value(lastTester, lastTesterHasEqcValue); + std::string lastTesterConstant; + if (!lastTesterHasEqcValue) { + TRACE("t_str_binary_search", tout << "length tester " << mk_pp(lastTester, m) << " at top of stack doesn't have an EQC value yet" << std::endl;); + // check previousLenTester + if (previousLenTester == lastTester) { + lastTesterConstant = previousLenTesterValue; + TRACE("t_str_binary_search", tout << "invoked with previousLenTester info matching top of stack" << std::endl;); + } else { + // this is a bit unexpected + TRACE("t_str_binary_search", tout << "WARNING: unexpected reordering of length testers!" << std::endl;); + // TODO resolve this case + NOT_IMPLEMENTED_YET(); return NULL; + } + } else { + lastTesterConstant = m_strutil.get_string_constant_value(lastTesterValue); + } + TRACE("t_str_binary_search", tout << "last length tester is assigned \"" << lastTesterConstant << "\"" << std::endl;); + if (lastTesterConstant == "more" || lastTesterConstant == "less") { + // use the previous bounds info to generate a new midpoint + binary_search_info lastBounds; + if (!binary_search_len_tester_info.find(lastTester, lastBounds)) { + // unexpected + TRACE("t_str_binary_search", tout << "WARNING: no bounds information available for last tester!" << std::endl;); + // TODO resolve this + NOT_IMPLEMENTED_YET(); + } + TRACE("t_str_binary_search", tout << "last bounds are [" << lastBounds.lowerBound << " | " << lastBounds.midPoint << " | " << lastBounds.upperBound << "]!" << lastBounds.windowSize << std::endl;); + binary_search_info newBounds; + expr * newTester; + if (lastTesterConstant == "more") { + // special case: if the midpoint, upper bound, and window size are all equal, + // we double the window size and adjust the bounds + if (lastBounds.midPoint == lastBounds.upperBound && lastBounds.upperBound == lastBounds.windowSize) { + TRACE("t_str_binary_search", tout << "search hit window size; expanding" << std::endl;); + // TODO is this correct? + newBounds.lowerBound = lastBounds.windowSize + rational::one(); + newBounds.windowSize = lastBounds.windowSize * rational(2); + newBounds.upperBound = newBounds.windowSize; + newBounds.calculate_midpoint(); + } else if (false) { + // TODO handle the case where the midpoint can't be increased further + // (e.g. a window like [50 | 50 | 50]!64 and we don't answer "50") + } else { + // general case + newBounds.lowerBound = lastBounds.midPoint + rational::one(); + newBounds.windowSize = lastBounds.windowSize; + newBounds.upperBound = lastBounds.upperBound; + newBounds.calculate_midpoint(); + } + if (!binary_search_next_var_high.find(lastTester, newTester)) { + newTester = mk_internal_lenTest_var(freeVar, newBounds.midPoint.get_int32()); + binary_search_next_var_high.insert(lastTester, newTester); + } + refresh_theory_var(newTester); + } else if (lastTesterConstant == "less") { + if (false) { + // TODO handle the case where the midpoint can't be decreased further + // (e.g. a window like [0 | 0 | 0]!64 and we don't answer "0" + } else { + // general case + newBounds.upperBound = lastBounds.midPoint - rational::one(); + newBounds.windowSize = lastBounds.windowSize; + newBounds.lowerBound = lastBounds.lowerBound; + newBounds.calculate_midpoint(); + } + if (!binary_search_next_var_low.find(lastTester, newTester)) { + newTester = mk_internal_lenTest_var(freeVar, newBounds.midPoint.get_int32()); + binary_search_next_var_low.insert(lastTester, newTester); + } + refresh_theory_var(newTester); + } + TRACE("t_str_binary_search", tout << "new bounds are [" << newBounds.lowerBound << " | " << newBounds.midPoint << " | " << newBounds.upperBound << "]!" << newBounds.windowSize << std::endl;); + binary_search_len_tester_stack[freeVar].push_back(newTester); + m_trail_stack.push(binary_search_trail(binary_search_len_tester_stack, freeVar)); + binary_search_len_tester_info.insert(newTester, newBounds); + m_trail_stack.push(insert_obj_map(binary_search_len_tester_info, newTester)); + + expr_ref next_case_split(binary_search_case_split(freeVar, newTester, newBounds)); + m_trail.push_back(next_case_split); + // TODO assert a precondition about all previous length testers that got us here + return next_case_split; + } else { // lastTesterConstant is a concrete value + TRACE("t_str", tout << "length is fixed; generating models for free var" << std::endl;); + // length is fixed + expr * valueAssert = gen_free_var_options(freeVar, lastTester, lastTesterConstant, NULL, ""); + return valueAssert; + } + } else { + // no length testers yet + TRACE("t_str_binary_search", tout << "no length testers for " << mk_pp(freeVar, m) << std::endl;); + binary_search_len_tester_stack.insert(freeVar, ptr_vector()); + + expr * firstTester; + rational lowerBound(0); + rational upperBound(m_params.m_BinarySearchInitialUpperBound); + rational windowSize(upperBound); + rational midPoint(floor(upperBound / rational(2))); + if (!binary_search_starting_len_tester.find(freeVar, firstTester)) { + firstTester = mk_internal_lenTest_var(freeVar, midPoint.get_int32()); + binary_search_starting_len_tester.insert(freeVar, firstTester); + } + refresh_theory_var(firstTester); + + binary_search_len_tester_stack[freeVar].push_back(firstTester); + m_trail_stack.push(binary_search_trail(binary_search_len_tester_stack, freeVar)); + binary_search_info new_info(lowerBound, midPoint, upperBound, windowSize); + binary_search_len_tester_info.insert(firstTester, new_info); + m_trail_stack.push(insert_obj_map(binary_search_len_tester_info, firstTester)); + + expr_ref initial_case_split(binary_search_case_split(freeVar, firstTester, new_info)); + m_trail.push_back(initial_case_split); + return initial_case_split; + } +} + // ----------------------------------------------------------------------------------------------------- // True branch will be taken in final_check: // - When we discover a variable is "free" for the first time @@ -9180,161 +9386,167 @@ expr * theory_str::gen_len_val_options_for_free_var(expr * freeVar, expr * lenTe TRACE("t_str_detail", tout << "gen for free var " << mk_ismt2_pp(freeVar, m) << std::endl;); - bool map_effectively_empty = false; - if (fvar_len_count_map.find(freeVar) == fvar_len_count_map.end()) { - TRACE("t_str_detail", tout << "fvar_len_count_map is empty" << std::endl;); - map_effectively_empty = true; - } - - if (!map_effectively_empty) { - // check whether any entries correspond to variables that went out of scope; - // if every entry is out of scope then the map counts as being empty - // TODO: maybe remove them from the map instead? either here or in pop_scope_eh() - - // assume empty and find a counterexample - map_effectively_empty = true; - ptr_vector indicator_set = fvar_lenTester_map[freeVar]; - for (ptr_vector::iterator it = indicator_set.begin(); it != indicator_set.end(); ++it) { - expr * indicator = *it; - if (internal_variable_set.find(indicator) != internal_variable_set.end()) { - TRACE("t_str_detail", tout <<"found active internal variable " << mk_ismt2_pp(indicator, m) - << " in fvar_lenTester_map[freeVar]" << std::endl;); - map_effectively_empty = false; - break; - } - } - CTRACE("t_str_detail", map_effectively_empty, tout << "all variables in fvar_lenTester_map[freeVar] out of scope" << std::endl;); - } - - if (map_effectively_empty) { - // no length assertions for this free variable have ever been added. - TRACE("t_str_detail", tout << "no length assertions yet" << std::endl;); - - fvar_len_count_map[freeVar] = 1; - unsigned int testNum = fvar_len_count_map[freeVar]; - - expr_ref indicator(mk_internal_lenTest_var(freeVar, testNum), m); - SASSERT(indicator); - - // since the map is "effectively empty", we can remove those variables that have left scope... - fvar_lenTester_map[freeVar].shrink(0); - fvar_lenTester_map[freeVar].push_back(indicator); - lenTester_fvar_map[indicator] = freeVar; - - expr * lenTestAssert = gen_len_test_options(freeVar, indicator, testNum); - SASSERT(lenTestAssert != NULL); - return lenTestAssert; + if (m_params.m_UseBinarySearch) { + TRACE("t_str_detail", tout << "using binary search heuristic" << std::endl;); + return binary_search_length_test(freeVar, lenTesterInCbEq, lenTesterValue); } else { - TRACE("t_str_detail", tout << "found previous in-scope length assertions" << std::endl;); + bool map_effectively_empty = false; + if (fvar_len_count_map.find(freeVar) == fvar_len_count_map.end()) { + TRACE("t_str_detail", tout << "fvar_len_count_map is empty" << std::endl;); + map_effectively_empty = true; + } - expr * effectiveLenInd = NULL; - std::string effectiveLenIndiStr = ""; - int lenTesterCount = (int) fvar_lenTester_map[freeVar].size(); + if (!map_effectively_empty) { + // check whether any entries correspond to variables that went out of scope; + // if every entry is out of scope then the map counts as being empty + // TODO: maybe remove them from the map instead? either here or in pop_scope_eh() - TRACE("t_str_detail", - tout << lenTesterCount << " length testers in fvar_lenTester_map[" << mk_pp(freeVar, m) << "]:" << std::endl; - for (int i = 0; i < lenTesterCount; ++i) { - expr * len_indicator = fvar_lenTester_map[freeVar][i]; - tout << mk_pp(len_indicator, m) << ": "; - bool effectiveInScope = (internal_variable_set.find(len_indicator) != internal_variable_set.end()); - tout << (effectiveInScope ? "in scope" : "NOT in scope"); - tout << std::endl; - } - ); + // assume empty and find a counterexample + map_effectively_empty = true; + ptr_vector indicator_set = fvar_lenTester_map[freeVar]; + for (ptr_vector::iterator it = indicator_set.begin(); it != indicator_set.end(); ++it) { + expr * indicator = *it; + if (internal_variable_set.find(indicator) != internal_variable_set.end()) { + TRACE("t_str_detail", tout <<"found active internal variable " << mk_ismt2_pp(indicator, m) + << " in fvar_lenTester_map[freeVar]" << std::endl;); + map_effectively_empty = false; + break; + } + } + CTRACE("t_str_detail", map_effectively_empty, tout << "all variables in fvar_lenTester_map[freeVar] out of scope" << std::endl;); + } - int i = 0; - for (; i < lenTesterCount; ++i) { - expr * len_indicator_pre = fvar_lenTester_map[freeVar][i]; - // check whether this is in scope as well - if (internal_variable_set.find(len_indicator_pre) == internal_variable_set.end()) { - TRACE("t_str_detail", tout << "length indicator " << mk_pp(len_indicator_pre, m) << " not in scope" << std::endl;); - continue; - } + if (map_effectively_empty) { + // no length assertions for this free variable have ever been added. + TRACE("t_str_detail", tout << "no length assertions yet" << std::endl;); - bool indicatorHasEqcValue = false; - expr * len_indicator_value = get_eqc_value(len_indicator_pre, indicatorHasEqcValue); - TRACE("t_str_detail", tout << "length indicator " << mk_ismt2_pp(len_indicator_pre, m) << - " = " << mk_ismt2_pp(len_indicator_value, m) << std::endl;); - if (indicatorHasEqcValue) { - const char * val = 0; - m_strutil.is_string(len_indicator_value, & val); - std::string len_pIndiStr(val); - if (len_pIndiStr != "more") { - effectiveLenInd = len_indicator_pre; - effectiveLenIndiStr = len_pIndiStr; - break; - } - } else { - if (lenTesterInCbEq != len_indicator_pre) { - TRACE("t_str", tout << "WARNING: length indicator " << mk_ismt2_pp(len_indicator_pre, m) - << " does not have an equivalence class value." - << " i = " << i << ", lenTesterCount = " << lenTesterCount << std::endl;); - if (i > 0) { - effectiveLenInd = fvar_lenTester_map[freeVar][i - 1]; - bool effectiveHasEqcValue; - expr * effective_eqc_value = get_eqc_value(effectiveLenInd, effectiveHasEqcValue); - bool effectiveInScope = (internal_variable_set.find(effectiveLenInd) != internal_variable_set.end()); - TRACE("t_str_detail", tout << "checking effective length indicator " << mk_pp(effectiveLenInd, m) << ": " - << (effectiveInScope ? "in scope" : "NOT in scope") << ", "; - if (effectiveHasEqcValue) { - tout << "~= " << mk_pp(effective_eqc_value, m); - } else { - tout << "no eqc string constant"; - } - tout << std::endl;); - if (effectiveLenInd == lenTesterInCbEq) { - effectiveLenIndiStr = lenTesterValue; - } else { - if (effectiveHasEqcValue) { - effectiveLenIndiStr = m_strutil.get_string_constant_value(effective_eqc_value); - } else { - // TODO this should be unreachable, but can we really do anything here? - NOT_IMPLEMENTED_YET(); - } - } - } - break; - } - // lenTesterInCbEq == len_indicator_pre - else { - if (lenTesterValue != "more") { - effectiveLenInd = len_indicator_pre; - effectiveLenIndiStr = lenTesterValue; - break; - } - } - } // !indicatorHasEqcValue - } // for (i : [0..lenTesterCount-1]) - if (effectiveLenIndiStr == "more" || effectiveLenIndiStr == "") { - TRACE("t_str", tout << "length is not fixed; generating length tester options for free var" << std::endl;); - expr_ref indicator(m); - unsigned int testNum = 0; + fvar_len_count_map[freeVar] = 1; + unsigned int testNum = fvar_len_count_map[freeVar]; - TRACE("t_str", tout << "effectiveLenIndiStr = " << effectiveLenIndiStr - << ", i = " << i << ", lenTesterCount = " << lenTesterCount << std::endl;); + expr_ref indicator(mk_internal_lenTest_var(freeVar, testNum), m); + SASSERT(indicator); - if (i == lenTesterCount) { - fvar_len_count_map[freeVar] = fvar_len_count_map[freeVar] + 1; - testNum = fvar_len_count_map[freeVar]; - indicator = mk_internal_lenTest_var(freeVar, testNum); - fvar_lenTester_map[freeVar].push_back(indicator); - lenTester_fvar_map[indicator] = freeVar; - } else { - // TODO make absolutely sure this is safe to do if 'indicator' is technically out of scope - indicator = fvar_lenTester_map[freeVar][i]; - refresh_theory_var(indicator); - testNum = i + 1; - } - expr * lenTestAssert = gen_len_test_options(freeVar, indicator, testNum); - SASSERT(lenTestAssert != NULL); - return lenTestAssert; - } else { - TRACE("t_str", tout << "length is fixed; generating models for free var" << std::endl;); - // length is fixed - expr * valueAssert = gen_free_var_options(freeVar, effectiveLenInd, effectiveLenIndiStr, NULL, ""); - return valueAssert; - } - } // fVarLenCountMap.find(...) + // since the map is "effectively empty", we can remove those variables that have left scope... + fvar_lenTester_map[freeVar].shrink(0); + fvar_lenTester_map[freeVar].push_back(indicator); + lenTester_fvar_map[indicator] = freeVar; + + expr * lenTestAssert = gen_len_test_options(freeVar, indicator, testNum); + SASSERT(lenTestAssert != NULL); + return lenTestAssert; + } else { + TRACE("t_str_detail", tout << "found previous in-scope length assertions" << std::endl;); + + expr * effectiveLenInd = NULL; + std::string effectiveLenIndiStr = ""; + int lenTesterCount = (int) fvar_lenTester_map[freeVar].size(); + + TRACE("t_str_detail", + tout << lenTesterCount << " length testers in fvar_lenTester_map[" << mk_pp(freeVar, m) << "]:" << std::endl; + for (int i = 0; i < lenTesterCount; ++i) { + expr * len_indicator = fvar_lenTester_map[freeVar][i]; + tout << mk_pp(len_indicator, m) << ": "; + bool effectiveInScope = (internal_variable_set.find(len_indicator) != internal_variable_set.end()); + tout << (effectiveInScope ? "in scope" : "NOT in scope"); + tout << std::endl; + } + ); + + int i = 0; + for (; i < lenTesterCount; ++i) { + expr * len_indicator_pre = fvar_lenTester_map[freeVar][i]; + // check whether this is in scope as well + if (internal_variable_set.find(len_indicator_pre) == internal_variable_set.end()) { + TRACE("t_str_detail", tout << "length indicator " << mk_pp(len_indicator_pre, m) << " not in scope" << std::endl;); + continue; + } + + bool indicatorHasEqcValue = false; + expr * len_indicator_value = get_eqc_value(len_indicator_pre, indicatorHasEqcValue); + TRACE("t_str_detail", tout << "length indicator " << mk_ismt2_pp(len_indicator_pre, m) << + " = " << mk_ismt2_pp(len_indicator_value, m) << std::endl;); + if (indicatorHasEqcValue) { + const char * val = 0; + m_strutil.is_string(len_indicator_value, & val); + std::string len_pIndiStr(val); + if (len_pIndiStr != "more") { + effectiveLenInd = len_indicator_pre; + effectiveLenIndiStr = len_pIndiStr; + break; + } + } else { + if (lenTesterInCbEq != len_indicator_pre) { + TRACE("t_str", tout << "WARNING: length indicator " << mk_ismt2_pp(len_indicator_pre, m) + << " does not have an equivalence class value." + << " i = " << i << ", lenTesterCount = " << lenTesterCount << std::endl;); + if (i > 0) { + effectiveLenInd = fvar_lenTester_map[freeVar][i - 1]; + bool effectiveHasEqcValue; + expr * effective_eqc_value = get_eqc_value(effectiveLenInd, effectiveHasEqcValue); + bool effectiveInScope = (internal_variable_set.find(effectiveLenInd) != internal_variable_set.end()); + TRACE("t_str_detail", tout << "checking effective length indicator " << mk_pp(effectiveLenInd, m) << ": " + << (effectiveInScope ? "in scope" : "NOT in scope") << ", "; + if (effectiveHasEqcValue) { + tout << "~= " << mk_pp(effective_eqc_value, m); + } else { + tout << "no eqc string constant"; + } + tout << std::endl;); + if (effectiveLenInd == lenTesterInCbEq) { + effectiveLenIndiStr = lenTesterValue; + } else { + if (effectiveHasEqcValue) { + effectiveLenIndiStr = m_strutil.get_string_constant_value(effective_eqc_value); + } else { + // TODO this should be unreachable, but can we really do anything here? + NOT_IMPLEMENTED_YET(); + } + } + } + break; + } + // lenTesterInCbEq == len_indicator_pre + else { + if (lenTesterValue != "more") { + effectiveLenInd = len_indicator_pre; + effectiveLenIndiStr = lenTesterValue; + break; + } + } + } // !indicatorHasEqcValue + } // for (i : [0..lenTesterCount-1]) + if (effectiveLenIndiStr == "more" || effectiveLenIndiStr == "") { + TRACE("t_str", tout << "length is not fixed; generating length tester options for free var" << std::endl;); + expr_ref indicator(m); + unsigned int testNum = 0; + + TRACE("t_str", tout << "effectiveLenIndiStr = " << effectiveLenIndiStr + << ", i = " << i << ", lenTesterCount = " << lenTesterCount << std::endl;); + + if (i == lenTesterCount) { + fvar_len_count_map[freeVar] = fvar_len_count_map[freeVar] + 1; + testNum = fvar_len_count_map[freeVar]; + indicator = mk_internal_lenTest_var(freeVar, testNum); + fvar_lenTester_map[freeVar].push_back(indicator); + lenTester_fvar_map[indicator] = freeVar; + } else { + // TODO make absolutely sure this is safe to do if 'indicator' is technically out of scope + indicator = fvar_lenTester_map[freeVar][i]; + refresh_theory_var(indicator); + testNum = i + 1; + } + expr * lenTestAssert = gen_len_test_options(freeVar, indicator, testNum); + SASSERT(lenTestAssert != NULL); + return lenTestAssert; + } else { + TRACE("t_str", tout << "length is fixed; generating models for free var" << std::endl;); + // length is fixed + expr * valueAssert = gen_free_var_options(freeVar, effectiveLenInd, effectiveLenIndiStr, NULL, ""); + return valueAssert; + } + } // fVarLenCountMap.find(...) + + } // !UseBinarySearch } void theory_str::get_concats_in_eqc(expr * n, std::set & concats) { diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index e77c955f2..4ac054c52 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -75,6 +75,27 @@ namespace smt { } }; + template + class binary_search_trail : public trail { + obj_map > & target; + expr * entry; + public: + binary_search_trail(obj_map > & target, expr * entry) : + target(target), entry(entry) {} + virtual ~binary_search_trail() {} + virtual void undo(Ctx & ctx) { + if (target.contains(entry)) { + if (!target[entry].empty()) { + target[entry].pop_back(); + } else { + TRACE("t_str_binary_search", tout << "WARNING: attempt to remove length tester from an empty stack" << std::endl;); + } + } else { + TRACE("t_str_binary_search", tout << "WARNING: attempt to access length tester map via invalid key" << std::endl;); + } + } + }; + class theory_str : public theory { struct T_cut { @@ -277,6 +298,34 @@ namespace smt { expr * get_eqc_next(expr * n); app * get_ast(theory_var i); + // binary search heuristic data + struct binary_search_info { + rational lowerBound; + rational midPoint; + rational upperBound; + rational windowSize; + + binary_search_info() : lowerBound(rational::zero()), midPoint(rational::zero()), + upperBound(rational::zero()), windowSize(rational::zero()) {} + binary_search_info(rational lower, rational mid, rational upper, rational windowSize) : + lowerBound(lower), midPoint(mid), upperBound(upper), windowSize(windowSize) {} + + void calculate_midpoint() { + midPoint = floor(lowerBound + ((upperBound - lowerBound) / rational(2)) ); + } + }; + // maps a free string var to a stack of active length testers. + // can use binary_search_trail to record changes to this object + obj_map > binary_search_len_tester_stack; + // maps a length tester var to the *active* search window + obj_map binary_search_len_tester_info; + // maps a free string var to the first length tester to be (re)used + obj_map binary_search_starting_len_tester; + // maps a length tester to the next length tester to be (re)used if the split is "low" + obj_map binary_search_next_var_low; + // maps a length tester to the next length tester to be (re)used if the split is "high" + obj_map binary_search_next_var_high; + protected: void assert_axiom(expr * e); void assert_implication(expr * premise, expr * conclusion); @@ -482,6 +531,10 @@ namespace smt { bool get_next_val_encode(int_vector & base, int_vector & next); std::string gen_val_string(int len, int_vector & encoding); + // binary search heuristic + expr * binary_search_length_test(expr * freeVar, expr * previousLenTester, std::string previousLenTesterValue); + expr_ref binary_search_case_split(expr * freeVar, expr * tester, binary_search_info & bounds); + bool free_var_attempt(expr * nn1, expr * nn2); void more_len_tests(expr * lenTester, std::string lenTesterValue); void more_value_tests(expr * valTester, std::string valTesterValue); From 0a6c23148fa1723d6582513b22acc9c632c97e97 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Thu, 22 Dec 2016 19:33:38 -0500 Subject: [PATCH 297/410] fix empty vector edge case in binary search --- src/smt/theory_str.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 754d258bc..0edd2726d 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -9229,7 +9229,7 @@ expr_ref theory_str::binary_search_case_split(expr * freeVar, expr * tester, bin expr * theory_str::binary_search_length_test(expr * freeVar, expr * previousLenTester, std::string previousLenTesterValue) { ast_manager & m = get_manager(); - if (binary_search_len_tester_stack.contains(freeVar)) { + if (binary_search_len_tester_stack.contains(freeVar) && !binary_search_len_tester_stack[freeVar].empty()) { TRACE("t_str_binary_search", tout << "checking existing length testers for " << mk_pp(freeVar, m) << std::endl; for (ptr_vector::const_iterator it = binary_search_len_tester_stack[freeVar].begin(); it != binary_search_len_tester_stack[freeVar].end(); ++it) { From f3e064cb077a417ba97f0198e18097ad3ec10caf Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sat, 31 Dec 2016 13:28:32 -0500 Subject: [PATCH 298/410] theory_str binary search crash avoidance when a negative length is reached --- src/smt/theory_str.cpp | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 0edd2726d..d3d680717 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -8426,6 +8426,13 @@ std::string theory_str::gen_val_string(int len, int_vector & encoding) { bool theory_str::get_next_val_encode(int_vector & base, int_vector & next) { SASSERT(charSetSize > 0); + TRACE("t_str_value_test_bug", tout << "base vector: [ "; + for (unsigned i = 0; i < base.size(); ++i) { + tout << base[i] << " "; + } + tout << "]" << std::endl; + ); + int s = 0; int carry = 0; next.reset(); @@ -9228,6 +9235,7 @@ expr_ref theory_str::binary_search_case_split(expr * freeVar, expr * tester, bin expr * theory_str::binary_search_length_test(expr * freeVar, expr * previousLenTester, std::string previousLenTesterValue) { ast_manager & m = get_manager(); + context & ctx = get_context(); if (binary_search_len_tester_stack.contains(freeVar) && !binary_search_len_tester_stack[freeVar].empty()) { TRACE("t_str_binary_search", tout << "checking existing length testers for " << mk_pp(freeVar, m) << std::endl; @@ -9337,6 +9345,19 @@ expr * theory_str::binary_search_length_test(expr * freeVar, expr * previousLenT return next_case_split; } else { // lastTesterConstant is a concrete value TRACE("t_str", tout << "length is fixed; generating models for free var" << std::endl;); + // defensive check that this length did not converge on a negative value. + binary_search_info lastBounds; + if (!binary_search_len_tester_info.find(lastTester, lastBounds)) { + // unexpected + TRACE("t_str_binary_search", tout << "WARNING: no bounds information available for last tester!" << std::endl;); + // TODO resolve this + NOT_IMPLEMENTED_YET(); + } + if (lastBounds.midPoint.is_neg()) { + TRACE("t_str_binary_search", tout << "WARNING: length search converged on a negative value. Negating this constraint." << std::endl;); + expr_ref axiom(m.mk_not(ctx.mk_eq_atom(mk_strlen(freeVar), m_autil.mk_numeral(lastBounds.midPoint, true))), m); + return axiom; + } // length is fixed expr * valueAssert = gen_free_var_options(freeVar, lastTester, lastTesterConstant, NULL, ""); return valueAssert; From f9d7981c1eb81aa7121c0ae5f637ca712864847a Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 3 Jan 2017 15:45:04 -0500 Subject: [PATCH 299/410] add theory case split to theory_str binary search --- src/smt/theory_str.cpp | 22 ++++++++++++++++++---- src/smt/theory_str.h | 3 ++- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index d3d680717..278f692f8 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -2507,6 +2507,7 @@ void theory_str::generate_mutual_exclusion(expr_ref_vector & terms) { literal_vector ls; for (unsigned i = 0; i < terms.size(); ++i) { expr * e = terms.get(i); + // TODO make sure the terms are internalized, etc.? literal l = ctx.get_literal(e); ls.push_back(l); } @@ -9199,7 +9200,7 @@ expr * theory_str::gen_len_test_options(expr * freeVar, expr * indicator, int tr // Return an expression of the form // (tester = "less" | tester = "N" | tester = "more") & // (tester = "less" iff len(freeVar) < N) & (tester = "more" iff len(freeVar) > N) & (tester = "N" iff len(freeVar) = N)) -expr_ref theory_str::binary_search_case_split(expr * freeVar, expr * tester, binary_search_info & bounds) { +expr_ref theory_str::binary_search_case_split(expr * freeVar, expr * tester, binary_search_info & bounds, literal_vector & case_split) { context & ctx = get_context(); ast_manager & m = get_manager(); rational N = bounds.midPoint; @@ -9227,6 +9228,16 @@ expr_ref theory_str::binary_search_case_split(expr * freeVar, expr * tester, bin combinedCaseSplit.push_back(mk_or(testerCases)); + // force internalization on all terms in testerCases so we can extract literals + for (unsigned i = 0; i < testerCases.size(); ++i) { + expr * testerCase = testerCases.get(i); + if (!ctx.b_internalized(testerCase)) { + ctx.internalize(testerCase, false); + } + literal l = ctx.get_literal(testerCase); + case_split.push_back(l); + } + expr_ref final_term(mk_and(combinedCaseSplit), m); SASSERT(final_term); TRACE("t_str_binary_search", tout << "final term: " << mk_pp(final_term, m) << std::endl;); @@ -9339,9 +9350,10 @@ expr * theory_str::binary_search_length_test(expr * freeVar, expr * previousLenT binary_search_len_tester_info.insert(newTester, newBounds); m_trail_stack.push(insert_obj_map(binary_search_len_tester_info, newTester)); - expr_ref next_case_split(binary_search_case_split(freeVar, newTester, newBounds)); + literal_vector case_split_literals; + expr_ref next_case_split(binary_search_case_split(freeVar, newTester, newBounds, case_split_literals)); m_trail.push_back(next_case_split); - // TODO assert a precondition about all previous length testers that got us here + ctx.mk_th_case_split(case_split_literals.size(), case_split_literals.c_ptr()); return next_case_split; } else { // lastTesterConstant is a concrete value TRACE("t_str", tout << "length is fixed; generating models for free var" << std::endl;); @@ -9384,8 +9396,10 @@ expr * theory_str::binary_search_length_test(expr * freeVar, expr * previousLenT binary_search_len_tester_info.insert(firstTester, new_info); m_trail_stack.push(insert_obj_map(binary_search_len_tester_info, firstTester)); - expr_ref initial_case_split(binary_search_case_split(freeVar, firstTester, new_info)); + literal_vector case_split_literals; + expr_ref initial_case_split(binary_search_case_split(freeVar, firstTester, new_info, case_split_literals)); m_trail.push_back(initial_case_split); + ctx.mk_th_case_split(case_split_literals.size(), case_split_literals.c_ptr()); return initial_case_split; } } diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 4ac054c52..fdd1a9c84 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -84,6 +84,7 @@ namespace smt { target(target), entry(entry) {} virtual ~binary_search_trail() {} virtual void undo(Ctx & ctx) { + TRACE("t_str_binary_search", tout << "in binary_search_trail::undo()" << std::endl;); if (target.contains(entry)) { if (!target[entry].empty()) { target[entry].pop_back(); @@ -533,7 +534,7 @@ namespace smt { // binary search heuristic expr * binary_search_length_test(expr * freeVar, expr * previousLenTester, std::string previousLenTesterValue); - expr_ref binary_search_case_split(expr * freeVar, expr * tester, binary_search_info & bounds); + expr_ref binary_search_case_split(expr * freeVar, expr * tester, binary_search_info & bounds, literal_vector & case_split_lits); bool free_var_attempt(expr * nn1, expr * nn2); void more_len_tests(expr * lenTester, std::string lenTesterValue); From c190d458596803fabc7db00d006c143e93b58e5d Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 4 Jan 2017 15:56:16 -0500 Subject: [PATCH 300/410] fix binary search string length axiom --- src/smt/theory_str.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 278f692f8..bfa439e03 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -9367,7 +9367,7 @@ expr * theory_str::binary_search_length_test(expr * freeVar, expr * previousLenT } if (lastBounds.midPoint.is_neg()) { TRACE("t_str_binary_search", tout << "WARNING: length search converged on a negative value. Negating this constraint." << std::endl;); - expr_ref axiom(m.mk_not(ctx.mk_eq_atom(mk_strlen(freeVar), m_autil.mk_numeral(lastBounds.midPoint, true))), m); + expr_ref axiom(m_autil.mk_ge(mk_strlen(freeVar), m_autil.mk_numeral(rational::zero(), true)), m); return axiom; } // length is fixed From 6f5c1942f0529afe2a40193cfbff9a625696ef60 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sun, 8 Jan 2017 20:15:45 -0500 Subject: [PATCH 301/410] theory_str length propagation --- src/smt/theory_str.cpp | 169 ++++++++++++++++++++++++++++++++++++++++- src/smt/theory_str.h | 7 +- 2 files changed, 174 insertions(+), 2 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index bfa439e03..120bf426a 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -9,7 +9,7 @@ Abstract: Author: - Murphy Berzish (mtrberzi) 2015-09-03 + Murphy Berzish and Yunhui Zheng Revision History: @@ -7993,6 +7993,160 @@ bool theory_str::finalcheck_int2str(app * a) { return axiomAdd; } +void theory_str::collect_var_concat(expr * node, std::set & varSet, std::set & concatSet) { + if (variable_set.find(node) != variable_set.end()) { + if (internal_lenTest_vars.find(node) == internal_lenTest_vars.end()) { + varSet.insert(node); + } + } + else if (is_app(node)) { + app * aNode = to_app(node); + if (is_strlen(aNode)) { + // Length + return; + } + if (is_concat(aNode)) { + expr * arg0 = aNode->get_arg(0); + expr * arg1 = aNode->get_arg(1); + if (concatSet.find(node) == concatSet.end()) { + concatSet.insert(node); + } + } + // recursively visit all arguments + for (unsigned i = 0; i < aNode->get_num_args(); ++i) { + expr * arg = aNode->get_arg(i); + collect_var_concat(arg, varSet, concatSet); + } + } +} + +bool theory_str::propagate_length_within_eqc(expr * var) { + bool res = false; + ast_manager & m = get_manager(); + context & ctx = get_context(); + + TRACE("t_str_length", tout << "propagate_length_within_eqc: " << mk_ismt2_pp(var, m) << std::endl ;); + + enode * n_eq_enode = ctx.get_enode(var); + rational varLen; + if (! get_len_value(var, varLen)) { + bool hasLen = false; + expr * nodeWithLen= var; + do { + if (get_len_value(nodeWithLen, varLen)) { + hasLen = true; + break; + } + nodeWithLen = get_eqc_next(nodeWithLen); + } while (nodeWithLen != var); + + if (hasLen) { + // var = nodeWithLen --> |var| = |nodeWithLen| + expr_ref_vector l_items(m); + expr_ref varEqNode(ctx.mk_eq_atom(var, nodeWithLen), m); + l_items.push_back(varEqNode); + + expr_ref nodeWithLenExpr (mk_strlen(nodeWithLen), m); + expr_ref varLenExpr (mk_int(varLen), m); + expr_ref lenEqNum(ctx.mk_eq_atom(nodeWithLenExpr, varLenExpr), m); + l_items.push_back(lenEqNum); + + expr_ref axl(m.mk_and(l_items.size(), l_items.c_ptr()), m); + expr_ref varLen(mk_strlen(var), m); + expr_ref axr(ctx.mk_eq_atom(varLen, mk_int(varLen)), m); + assert_implication(axl, axr); + TRACE("t_str_length", tout << mk_ismt2_pp(axl, m) << std::endl << " ---> " << std::endl << mk_ismt2_pp(axr, m);); + res = true; + } + } + return res; +} + +bool theory_str::propagate_length(std::set & varSet, std::set & concatSet, std::map & exprLenMap) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + expr_ref_vector assignments(m); + ctx.get_assignments(assignments); + bool axiomAdded = false; + // collect all concats in context + for (expr_ref_vector::iterator it = assignments.begin(); it != assignments.end(); ++it) { + if (! ctx.is_relevant(*it)) { + continue; + } + if (m.is_eq(*it)) { + collect_var_concat(*it, varSet, concatSet); + } + } + // iterate each concat + // if a concat doesn't have length info, check if the length of all leaf nodes can be resolved + for (std::set::iterator it = concatSet.begin(); it != concatSet.end(); it++) { + expr * concat = *it; + rational lenValue; + expr_ref concatlenExpr (mk_strlen(concat), m) ; + bool allLeafResolved = true; + if (! get_value(concatlenExpr, lenValue)) { + // the length fo concat is unresolved yet + if (get_len_value(concat, lenValue)) { + // but all leaf nodes have length information + TRACE("t_str_length", tout << "* length pop-up: " << mk_ismt2_pp(concat, m) << "| = " << lenValue << std::endl;); + std::set leafNodes; + get_unique_non_concat_nodes(concat, leafNodes); + expr_ref_vector l_items(m); + for (std::set::iterator leafIt = leafNodes.begin(); leafIt != leafNodes.end(); ++leafIt) { + rational leafLenValue; + if (get_len_value(*leafIt, leafLenValue)) { + expr_ref leafItLenExpr (mk_strlen(*leafIt), m); + expr_ref leafLenValueExpr (mk_int(leafLenValue), m); + expr_ref lcExpr (ctx.mk_eq_atom(leafItLenExpr, leafLenValueExpr), m); + l_items.push_back(lcExpr); + } else { + allLeafResolved = false; + break; + } + } + if (allLeafResolved) { + expr_ref axl(m.mk_and(l_items.size(), l_items.c_ptr()), m); + expr_ref lenValueExpr (mk_int(lenValue), m); + expr_ref axr(ctx.mk_eq_atom(concatlenExpr, lenValueExpr), m); + assert_implication(axl, axr); + TRACE("t_str_length", tout << mk_ismt2_pp(axl, m) << std::endl << " ---> " << std::endl << mk_ismt2_pp(axr, m)<< std::endl;); + axiomAdded = true; + } + } + } + } + // if no concat length is propagated, check the length of variables. + if (! axiomAdded) { + for (std::set::iterator it = varSet.begin(); it != varSet.end(); it++) { + expr * var = *it; + rational lenValue; + expr_ref varlen (mk_strlen(var), m) ; + bool allLeafResolved = true; + if (! get_value(varlen, lenValue)) { + if (propagate_length_within_eqc(var)) { + axiomAdded = true; + } + } + } + + } + return axiomAdded; +} + +void theory_str::get_unique_non_concat_nodes(expr * node, std::set & argSet) { + app * a_node = to_app(node); + if (!is_concat(a_node)) { + argSet.insert(node); + return; + } else { + SASSERT(a_node->get_num_args() == 2); + expr * leftArg = a_node->get_arg(0); + expr * rightArg = a_node->get_arg(1); + get_unique_non_concat_nodes(leftArg, argSet); + get_unique_non_concat_nodes(rightArg, argSet); + } +} + final_check_status theory_str::final_check_eh() { context & ctx = get_context(); ast_manager & m = get_manager(); @@ -8110,6 +8264,19 @@ final_check_status theory_str::final_check_eh() { return FC_CONTINUE; } + // enhancement: improved backpropagation of length information + { + std::set varSet; + std::set concatSet; + std::map exprLenMap; + + bool length_propagation_occurred = propagate_length(varSet, concatSet, exprLenMap); + if (length_propagation_occurred) { + TRACE("t_str", tout << "Resuming search due to axioms added by length propagation." << std::endl;); + return FC_CONTINUE; + } + } + bool needToAssignFreeVars = false; std::set free_variables; std::set unused_internal_variables; diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index fdd1a9c84..b7229a72e 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -9,7 +9,7 @@ Abstract: Author: - Murphy Berzish (mtrberzi) 2015-09-03 + Murphy Berzish and Yunhui Zheng Revision History: @@ -568,6 +568,11 @@ namespace smt { void check_variable_scope(); void recursive_check_variable_scope(expr * ex); + void collect_var_concat(expr * node, std::set & varSet, std::set & concatSet); + bool propagate_length(std::set & varSet, std::set & concatSet, std::map & exprLenMap); + void get_unique_non_concat_nodes(expr * node, std::set & argSet); + bool propagate_length_within_eqc(expr * var); + // TESTING void refresh_theory_var(expr * e); From 5f854c6689b4aa6250a79f28822fdde6c6ea5d48 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Mon, 9 Jan 2017 15:11:56 -0500 Subject: [PATCH 302/410] experimental linear search theory case split in theory_str --- src/smt/theory_str.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 120bf426a..9a71c05a9 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -9277,6 +9277,9 @@ expr * theory_str::gen_len_test_options(expr * freeVar, expr * indicator, int tr } ); + // experimental theory-aware case split support + literal_vector case_split_literals; + for (int i = l; i < h; ++i) { expr_ref str_indicator(m); if (m_params.m_UseFastLengthTesterCache) { @@ -9305,6 +9308,8 @@ expr * theory_str::gen_len_test_options(expr * freeVar, expr * indicator, int tr ctx.force_phase(l); } + case_split_literals.insert(mk_eq(freeVarLen, mk_int(i), false)); + expr_ref and_expr(ctx.mk_eq_atom(orList.get(orList.size() - 1), m.mk_eq(freeVarLen, mk_int(i))), m); andList.push_back(and_expr); } @@ -9319,6 +9324,13 @@ expr * theory_str::gen_len_test_options(expr * freeVar, expr * indicator, int tr andList.push_back(ctx.mk_eq_atom(orList.get(orList.size() - 1), m_autil.mk_ge(freeVarLen, mk_int(h)))); + { // more experimental theory case split support + expr_ref tmp(m_autil.mk_ge(freeVarLen, mk_int(h)), m); + ctx.internalize(m_autil.mk_ge(freeVarLen, mk_int(h)), false); + case_split_literals.push_back(ctx.get_literal(tmp)); + ctx.mk_th_case_split(case_split_literals.size(), case_split_literals.c_ptr()); + } + expr_ref_vector or_items(m); expr_ref_vector and_items(m); From 9004e1b23e9e662d6fde73e1db5cfeedc9399b1b Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 10 Jan 2017 12:34:44 -0500 Subject: [PATCH 303/410] disable length test/theory case split integration theory_str --- src/smt/theory_str.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 9a71c05a9..5a27dcebb 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -9324,12 +9324,14 @@ expr * theory_str::gen_len_test_options(expr * freeVar, expr * indicator, int tr andList.push_back(ctx.mk_eq_atom(orList.get(orList.size() - 1), m_autil.mk_ge(freeVarLen, mk_int(h)))); + /* { // more experimental theory case split support expr_ref tmp(m_autil.mk_ge(freeVarLen, mk_int(h)), m); ctx.internalize(m_autil.mk_ge(freeVarLen, mk_int(h)), false); case_split_literals.push_back(ctx.get_literal(tmp)); ctx.mk_th_case_split(case_split_literals.size(), case_split_literals.c_ptr()); } + */ expr_ref_vector or_items(m); expr_ref_vector and_items(m); @@ -9532,7 +9534,7 @@ expr * theory_str::binary_search_length_test(expr * freeVar, expr * previousLenT literal_vector case_split_literals; expr_ref next_case_split(binary_search_case_split(freeVar, newTester, newBounds, case_split_literals)); m_trail.push_back(next_case_split); - ctx.mk_th_case_split(case_split_literals.size(), case_split_literals.c_ptr()); + // ctx.mk_th_case_split(case_split_literals.size(), case_split_literals.c_ptr()); return next_case_split; } else { // lastTesterConstant is a concrete value TRACE("t_str", tout << "length is fixed; generating models for free var" << std::endl;); @@ -9578,7 +9580,7 @@ expr * theory_str::binary_search_length_test(expr * freeVar, expr * previousLenT literal_vector case_split_literals; expr_ref initial_case_split(binary_search_case_split(freeVar, firstTester, new_info, case_split_literals)); m_trail.push_back(initial_case_split); - ctx.mk_th_case_split(case_split_literals.size(), case_split_literals.c_ptr()); + // ctx.mk_th_case_split(case_split_literals.size(), case_split_literals.c_ptr()); return initial_case_split; } } From 3459c1993ebd8b21745e4b796cc4cbc2b45c4005 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 10 Jan 2017 15:38:33 -0500 Subject: [PATCH 304/410] experimental theory-aware branching code --- src/smt/params/smt_params.cpp | 1 + src/smt/params/smt_params.h | 2 + src/smt/params/smt_params_helper.pyg | 3 +- src/smt/smt_case_split_queue.cpp | 164 ++++++++++++++++++++++++++- src/smt/smt_case_split_queue.h | 3 + 5 files changed, 166 insertions(+), 7 deletions(-) diff --git a/src/smt/params/smt_params.cpp b/src/smt/params/smt_params.cpp index a5b3e4867..f295e260b 100644 --- a/src/smt/params/smt_params.cpp +++ b/src/smt/params/smt_params.cpp @@ -32,6 +32,7 @@ void smt_params::updt_local_params(params_ref const & _p) { m_restart_factor = p.restart_factor(); m_case_split_strategy = static_cast(p.case_split()); m_theory_case_split = p.theory_case_split(); + m_theory_aware_branching = p.theory_aware_branching(); m_delay_units = p.delay_units(); m_delay_units_threshold = p.delay_units_threshold(); m_preprocess = _p.get_bool("preprocess", true); // hidden parameter diff --git a/src/smt/params/smt_params.h b/src/smt/params/smt_params.h index 55346d34f..a0c90a525 100644 --- a/src/smt/params/smt_params.h +++ b/src/smt/params/smt_params.h @@ -112,6 +112,7 @@ struct smt_params : public preprocessor_params, unsigned m_rel_case_split_order; bool m_lookahead_diseq; bool m_theory_case_split; + bool m_theory_aware_branching; // ----------------------------------- // @@ -243,6 +244,7 @@ struct smt_params : public preprocessor_params, m_rel_case_split_order(0), m_lookahead_diseq(false), m_theory_case_split(false), + m_theory_aware_branching(false), m_delay_units(false), m_delay_units_threshold(32), m_theory_resolve(false), diff --git a/src/smt/params/smt_params_helper.pyg b/src/smt/params/smt_params_helper.pyg index 4e3bec57d..8e8e52987 100644 --- a/src/smt/params/smt_params_helper.pyg +++ b/src/smt/params/smt_params_helper.pyg @@ -71,5 +71,6 @@ def_module_params(module_name='smt', ('str.string_constant_cache', BOOL, True, 'cache all generated string constants generated from anywhere in theory_str'), ('str.use_binary_search', BOOL, False, 'use a binary search heuristic for finding concrete length values for free variables in theory_str (set to False to use linear search)'), ('str.binary_search_start', UINT, 64, 'initial upper bound for theory_str binary search'), - ('theory_case_split', BOOL, False, 'Allow the context to use heuristics involving theory case splits, which are a set of literals of which exactly one can be assigned True. If this option is false, the context will generate extra axioms to enforce this instead.') + ('theory_case_split', BOOL, False, 'Allow the context to use heuristics involving theory case splits, which are a set of literals of which exactly one can be assigned True. If this option is false, the context will generate extra axioms to enforce this instead.'), + ('theory_aware_branching', BOOL, False, 'Allow the context to use extra information from theory solvers regarding literal branching prioritization.') )) diff --git a/src/smt/smt_case_split_queue.cpp b/src/smt/smt_case_split_queue.cpp index 06004e3b8..8b02dd6a9 100644 --- a/src/smt/smt_case_split_queue.cpp +++ b/src/smt/smt_case_split_queue.cpp @@ -22,9 +22,13 @@ Revision History: #include"stopwatch.h" #include"for_each_expr.h" #include"ast_pp.h" +#include"map.h" +#include"hashtable.h" namespace smt { + typedef map > theory_var_priority_map; + struct bool_var_act_lt { svector const & m_activity; bool_var_act_lt(svector const & a):m_activity(a) {} @@ -35,6 +39,25 @@ namespace smt { typedef heap bool_var_act_queue; + struct theory_aware_act_lt { + // only take into account theory var priority for now + theory_var_priority_map const & m_theory_var_priority; + theory_aware_act_lt(theory_var_priority_map const & a):m_theory_var_priority(a) {} + bool operator()(bool_var v1, bool_var v2) const { + double p_v1, p_v2; + // safety -- use a large negative number if some var isn't in the map + if (!m_theory_var_priority.find(v1, p_v1)) { + p_v1 = -1000.0; + } + if (!m_theory_var_priority.find(v2, p_v2)) { + p_v2 = -1000.0; + } + return p_v1 > p_v2; + } + }; + + typedef heap theory_aware_act_queue; + /** \brief Case split queue based on activity and random splits. */ @@ -1087,6 +1110,118 @@ namespace smt { } }; + class theory_aware_branching_queue : public case_split_queue { + protected: + context & m_context; + smt_params & m_params; + + theory_var_priority_map m_theory_var_priority; + theory_aware_act_queue m_theory_queue; + case_split_queue * m_base_queue; + int_hashtable > m_theory_vars; + map > m_theory_var_phase; + public: + theory_aware_branching_queue(context & ctx, smt_params & p, case_split_queue * base_queue) : + m_context(ctx), + m_params(p), + m_theory_var_priority(), + m_theory_queue(1024, theory_aware_act_lt(m_theory_var_priority)), + m_base_queue(base_queue) { + } + + virtual void activity_increased_eh(bool_var v) { + if (m_theory_queue.contains(v)) { + m_theory_queue.decreased(v); + } + m_base_queue->activity_increased_eh(v); + } + + virtual void mk_var_eh(bool_var v) { + // do nothing. we only "react" if/when we learn this is an important theory literal + m_base_queue->mk_var_eh(v); + } + + virtual void del_var_eh(bool_var v) { + if (m_theory_queue.contains(v)) { + m_theory_queue.erase(v); + } + m_base_queue->del_var_eh(v); + } + + virtual void assign_lit_eh(literal l) { + m_base_queue->assign_lit_eh(l); + } + + virtual void unassign_var_eh(bool_var v) { + if (m_theory_vars.contains(v) && !m_theory_queue.contains(v)) { + m_theory_queue.insert(v); + } + m_base_queue->unassign_var_eh(v); + } + + virtual void relevant_eh(expr * n) { + m_base_queue->relevant_eh(n); + } + + virtual void init_search_eh() { + m_base_queue->init_search_eh(); + } + + virtual void end_search_eh() { + m_base_queue->end_search_eh(); + } + + virtual void internalize_instance_eh(expr * e, unsigned gen) { + m_base_queue->internalize_instance_eh(e, gen); + } + + virtual void reset() { + m_theory_queue.reset(); + m_theory_vars.reset(); + m_theory_var_phase.reset(); + m_theory_var_priority.reset(); + m_base_queue->reset(); + } + + virtual void push_scope() { + m_base_queue->push_scope(); + } + + virtual void pop_scope(unsigned num_scopes) { + m_base_queue->pop_scope(num_scopes); + } + + virtual void next_case_split(bool_var & next, lbool & phase) { + while (!m_theory_queue.empty()) { + next = m_theory_queue.erase_min(); + // if this literal is unassigned, it is the theory literal with the highest priority, + // so case split on this + if (m_context.get_assignment(next) == l_undef) { + TRACE("theory_aware_branching", tout << "Theory-aware branch on l#" << next << std::endl;); + if (!m_theory_var_phase.find(next, phase)) { + phase = l_undef; + } + return; + } + } + // if we reach this point, the theory literal queue is empty, + // so fall back to the base queue + m_base_queue->next_case_split(next, phase); + } + + virtual void add_theory_aware_branching_info(bool_var v, double priority, lbool phase) { + TRACE("theory_aware_branching", tout << "Add theory-aware branching information for l#" << v << ": priority=" << priority << std::endl;); + m_theory_vars.insert(v); + m_theory_var_phase.insert(v, phase); + m_theory_var_priority.insert(v, priority); + m_theory_queue.insert(v); + } + + virtual void display(std::ostream & out) { + // TODO + m_base_queue->display(out); + } + }; case_split_queue * mk_case_split_queue(context & ctx, smt_params & p) { if (p.m_relevancy_lvl < 2 && (p.m_case_split_strategy == CS_RELEVANCY || p.m_case_split_strategy == CS_RELEVANCY_ACTIVITY || @@ -1099,19 +1234,36 @@ namespace smt { warning_msg("auto configuration (option AUTO_CONFIG) must be disabled to use option CASE_SPLIT=3, 4 or 5"); p.m_case_split_strategy = CS_ACTIVITY; } + + case_split_queue * baseQueue; + switch (p.m_case_split_strategy) { case CS_ACTIVITY_DELAY_NEW: - return alloc(dact_case_split_queue, ctx, p); + baseQueue = alloc(dact_case_split_queue, ctx, p); + break; case CS_ACTIVITY_WITH_CACHE: - return alloc(cact_case_split_queue, ctx, p); + baseQueue = alloc(cact_case_split_queue, ctx, p); + break; case CS_RELEVANCY: - return alloc(rel_case_split_queue, ctx, p); + baseQueue = alloc(rel_case_split_queue, ctx, p); + break; case CS_RELEVANCY_ACTIVITY: - return alloc(rel_act_case_split_queue, ctx, p); + baseQueue = alloc(rel_act_case_split_queue, ctx, p); + break; case CS_RELEVANCY_GOAL: - return alloc(rel_goal_case_split_queue, ctx, p); + baseQueue = alloc(rel_goal_case_split_queue, ctx, p); + break; default: - return alloc(act_case_split_queue, ctx, p); + baseQueue = alloc(act_case_split_queue, ctx, p); + break; + } + + if (p.m_theory_aware_branching) { + TRACE("theory_aware_branching", tout << "Allocating and returning theory-aware branching queue." << std::endl;); + case_split_queue * theory_aware_queue = alloc(theory_aware_branching_queue, ctx, p, baseQueue); + return theory_aware_queue; + } else { + return baseQueue; } } diff --git a/src/smt/smt_case_split_queue.h b/src/smt/smt_case_split_queue.h index e6b217a22..9a3a93cc6 100644 --- a/src/smt/smt_case_split_queue.h +++ b/src/smt/smt_case_split_queue.h @@ -46,6 +46,9 @@ namespace smt { virtual void next_case_split(bool_var & next, lbool & phase) = 0; virtual void display(std::ostream & out) = 0; virtual ~case_split_queue() {} + + // theory-aware branching hint + virtual void add_theory_aware_branching_info(bool_var v, double priority, lbool phase) {} }; case_split_queue * mk_case_split_queue(context & ctx, smt_params & p); From 1363f50e4ffce014dc80a5e757529232c93f6154 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 10 Jan 2017 19:50:46 -0500 Subject: [PATCH 305/410] demonstration of theory-aware branching in theory_str, WIP --- src/smt/smt_context.cpp | 4 ++++ src/smt/smt_context.h | 7 +++++++ src/smt/theory_str.cpp | 40 +++++++++++++++++----------------------- src/smt/theory_str.h | 1 + 4 files changed, 29 insertions(+), 23 deletions(-) diff --git a/src/smt/smt_context.cpp b/src/smt/smt_context.cpp index 6c0a89d4f..2de610772 100644 --- a/src/smt/smt_context.cpp +++ b/src/smt/smt_context.cpp @@ -2999,6 +2999,10 @@ namespace smt { } } + void context::add_theory_aware_branching_info(bool_var v, double priority, lbool phase) { + m_case_split_queue->add_theory_aware_branching_info(v, priority, phase); + } + void context::undo_th_case_split(literal l) { m_all_th_case_split_literals.remove(l.index()); if (m_literal2casesplitsets.contains(l.index())) { diff --git a/src/smt/smt_context.h b/src/smt/smt_context.h index 8016eb587..2aae6c8a5 100644 --- a/src/smt/smt_context.h +++ b/src/smt/smt_context.h @@ -825,6 +825,13 @@ namespace smt { */ void mk_th_case_split(unsigned num_lits, literal * lits); + /* + * Provide a hint to the branching heuristic about the priority of a "theory-aware literal". + * Literals marked in this way will always be branched on before unmarked literals, + * starting with the literal having the highest priority. + */ + void add_theory_aware_branching_info(bool_var v, double priority, lbool phase); + // helper function for trail void undo_th_case_split(literal l); diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 5a27dcebb..f49b539dd 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -2501,6 +2501,13 @@ void theory_str::infer_len_concat_equality(expr * nn1, expr * nn2) { */ } +void theory_str::add_theory_aware_branching_info(expr * term, double priority, lbool phase) { + context & ctx = get_context(); + ctx.internalize(term, false); + bool_var v = ctx.get_bool_var(term); + ctx.add_theory_aware_branching_info(v, priority, phase); +} + void theory_str::generate_mutual_exclusion(expr_ref_vector & terms) { context & ctx = get_context(); // pull each literal out of the arrangement disjunction @@ -2512,25 +2519,6 @@ void theory_str::generate_mutual_exclusion(expr_ref_vector & terms) { ls.push_back(l); } ctx.mk_th_case_split(ls.size(), ls.c_ptr()); - - // old version, without special support in the context - /* - ast_manager & m = get_manager(); - - expr_ref_vector result(m); - - for (unsigned int majorIndex = 0; majorIndex < terms.size(); ++majorIndex) { - for (unsigned int minorIndex = majorIndex + 1; minorIndex < terms.size(); ++minorIndex) { - // generate an expression of the form - // terms[majorIndex] --> NOT(terms[minorIndex]) - expr_ref ex(rewrite_implication(terms.get(majorIndex), m.mk_not(terms.get(minorIndex))), m); - result.push_back(ex); - } - } - - expr_ref final_result(mk_and(result), m); - assert_axiom(final_result); - */ } void theory_str::print_cut_var(expr * node, std::ofstream & xout) { @@ -3095,7 +3083,9 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { m_autil.mk_add(mk_strlen(y),m_autil.mk_mul(mk_int(-1), mk_strlen(n))), mk_int(0))) ); - arrangement_disjunction.push_back(mk_and(and_item)); + expr_ref option1(mk_and(and_item), mgr); + arrangement_disjunction.push_back(option1); + add_theory_aware_branching_info(option1, 0.0, l_true); add_cut_info_merge(t1, ctx.get_scope_level(), m); add_cut_info_merge(t1, ctx.get_scope_level(), y); @@ -3130,8 +3120,9 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { m_autil.mk_add(mk_strlen(n), m_autil.mk_mul(mk_int(-1), mk_strlen(y))), mk_int(0))) ); - - arrangement_disjunction.push_back(mk_and(and_item)); + expr_ref option2(mk_and(and_item), mgr); + arrangement_disjunction.push_back(option2); + add_theory_aware_branching_info(option2, 0.0, l_true); add_cut_info_merge(t2, ctx.get_scope_level(), x); add_cut_info_merge(t2, ctx.get_scope_level(), n); @@ -3149,7 +3140,10 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { and_item.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_strlen(m))); and_item.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_strlen(n))); - arrangement_disjunction.push_back(mk_and(and_item)); + expr_ref option3(mk_and(and_item), mgr); + arrangement_disjunction.push_back(option3); + // prioritize this case, it is easier + add_theory_aware_branching_info(option3, 2.0, l_true); } if (!arrangement_disjunction.empty()) { diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index b7229a72e..1f615cfc5 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -500,6 +500,7 @@ namespace smt { void print_cut_var(expr * node, std::ofstream & xout); void generate_mutual_exclusion(expr_ref_vector & exprs); + void add_theory_aware_branching_info(expr * term, double priority, lbool phase); bool new_eq_check(expr * lhs, expr * rhs); void group_terms_by_eqc(expr * n, std::set & concats, std::set & vars, std::set & consts); From bc5af5873463f9648a093e8e9c21c8a2d0ce487c Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 10 Jan 2017 20:08:35 -0500 Subject: [PATCH 306/410] additional theory-aware branches in theory_str --- src/smt/theory_str.cpp | 59 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 52 insertions(+), 7 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index f49b539dd..0bc9e8dc8 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -3453,7 +3453,9 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { and_item.push_back(ctx.mk_eq_atom(mk_strlen(m), m_autil.mk_add(mk_strlen(x), mk_strlen(temp1)))); - arrangement_disjunction.push_back(mk_and(and_item)); + expr_ref option1(mk_and(and_item), mgr); + arrangement_disjunction.push_back(option1); + add_theory_aware_branching_info(option1, 0.0, l_true); add_cut_info_merge(temp1, ctx.get_scope_level(), y); add_cut_info_merge(temp1, ctx.get_scope_level(), m); } else { @@ -3475,7 +3477,16 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { and_item.push_back(ctx.mk_eq_atom(x, x_concat)); and_item.push_back(ctx.mk_eq_atom(y, cropStr)); and_item.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_int(part2Str.length()))); - arrangement_disjunction.push_back(mk_and(and_item)); + expr_ref option2(mk_and(and_item), mgr); + arrangement_disjunction.push_back(option2); + double priority; + // prioritize the option where y is equal to the original string + if (i == 0) { + priority = 2.0; + } else { + priority = 0.0; + } + add_theory_aware_branching_info(option2, priority, l_true); } } @@ -3772,7 +3783,15 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { // and_item[pos++] = Z3_mk_eq(ctx, or_item[option], Z3_mk_eq(ctx, mk_length(t, y), mk_length(t, y_concat))); // adding length constraint for _ = constStr seems slowing things down. - arrangement_disjunction.push_back(mk_and(and_item)); + expr_ref option1(mk_and(and_item), mgr); + arrangement_disjunction.push_back(option1); + double priority; + if (i == (int)strValue.size()) { + priority = 1.0; + } else { + priority = 0.0; + } + add_theory_aware_branching_info(option1, priority, l_true); } } @@ -3794,7 +3813,9 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { and_item.push_back(ctx.mk_eq_atom(mk_strlen(x), m_autil.mk_add(mk_strlen(strAst), mk_strlen(temp1)) ) ); ++pos; - arrangement_disjunction.push_back(mk_and(and_item)); + expr_ref option2(mk_and(and_item), mgr); + arrangement_disjunction.push_back(option2); + add_theory_aware_branching_info(option2, 0.0, l_true); add_cut_info_merge(temp1, sLevel, x); add_cut_info_merge(temp1, sLevel, n); @@ -4194,7 +4215,9 @@ void theory_str::process_concat_eq_type6(expr * concatAst1, expr * concatAst2) { // addItems[1] = mk_length(t, str2Ast); // and_item[pos++] = Z3_mk_eq(ctx, or_item[option], Z3_mk_eq(ctx, mk_length(t, y), Z3_mk_add(ctx, 2, addItems))); - arrangement_disjunction.push_back(mk_and(and_item)); + expr_ref option1(mk_and(and_item), mgr); + arrangement_disjunction.push_back(option1); + add_theory_aware_branching_info(option1, 0.0, l_true); } else { loopDetected = true; TRACE("t_str", tout << "AVOID LOOP: SKIPPED." << std::endl;); @@ -4227,7 +4250,16 @@ void theory_str::process_concat_eq_type6(expr * concatAst1, expr * concatAst2) { and_item.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_strlen(suffixAst))); pos += 1; - arrangement_disjunction.push_back(mk_and(and_item)); + expr_ref option2(mk_and(and_item), mgr); + arrangement_disjunction.push_back(option2); + double priority; + // prefer the option "str1" = x + if (prefix == str1Value) { + priority = 1.0; + } else { + priority = 0.0; + } + add_theory_aware_branching_info(option2, priority, l_true); } // case 6: concat("str1", y) = concat(m, "str2") @@ -9296,6 +9328,16 @@ expr * theory_str::gen_len_test_options(expr * freeVar, expr * indicator, int tr expr_ref or_expr(ctx.mk_eq_atom(indicator, str_indicator), m); orList.push_back(or_expr); + double priority; + // give high priority to small lengths if this is available + if (i <= 5) { + priority = 3.0; + } else { + // prioritize over "more" + priority = 0.5; + } + add_theory_aware_branching_info(or_expr, priority, l_true); + if (m_params.m_AggressiveLengthTesting) { literal l = mk_eq(indicator, str_indicator, false); ctx.mark_as_relevant(l); @@ -9309,7 +9351,10 @@ expr * theory_str::gen_len_test_options(expr * freeVar, expr * indicator, int tr } // TODO cache mk_string("more") - orList.push_back(m.mk_eq(indicator, mk_string("more"))); + expr_ref more_option(ctx.mk_eq_atom(indicator, mk_string("more")), m); + orList.push_back(more_option); + // decrease priority of this option + add_theory_aware_branching_info(more_option, -1.0, l_true); if (m_params.m_AggressiveLengthTesting) { literal l = mk_eq(indicator, mk_string("more"), false); ctx.mark_as_relevant(l); From 20a8ad9b2101b558191aa650353c421ad7c28ca0 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 10 Jan 2017 22:15:46 -0500 Subject: [PATCH 307/410] correctly reserve entries in theory aware branching queue heap --- src/smt/smt_case_split_queue.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/smt/smt_case_split_queue.cpp b/src/smt/smt_case_split_queue.cpp index 8b02dd6a9..ebe9c2e4e 100644 --- a/src/smt/smt_case_split_queue.cpp +++ b/src/smt/smt_case_split_queue.cpp @@ -1130,9 +1130,6 @@ namespace smt { } virtual void activity_increased_eh(bool_var v) { - if (m_theory_queue.contains(v)) { - m_theory_queue.decreased(v); - } m_base_queue->activity_increased_eh(v); } @@ -1214,6 +1211,7 @@ namespace smt { m_theory_vars.insert(v); m_theory_var_phase.insert(v, phase); m_theory_var_priority.insert(v, priority); + m_theory_queue.reserve(v+1); m_theory_queue.insert(v); } From 6576dabd583c3d8789e519cec5b6aafbc5a5cac8 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Thu, 12 Jan 2017 00:20:34 -0500 Subject: [PATCH 308/410] add tracing info to theory_str cut var map --- src/smt/theory_str.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 0bc9e8dc8..44d13d666 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -416,14 +416,14 @@ void theory_str::add_cut_info_one_node(expr * baseNode, int slevel, expr * node) varInfo->vars[node] = 1; cut_var_map.insert(baseNode, std::stack()); cut_var_map[baseNode].push(varInfo); - TRACE("t_str_cut_var_map", tout << "add var info for baseNode=" << mk_pp(baseNode, get_manager()) << ", node=" << mk_pp(node, get_manager()) << std::endl;); + TRACE("t_str_cut_var_map", tout << "add var info for baseNode=" << mk_pp(baseNode, get_manager()) << ", node=" << mk_pp(node, get_manager()) << " [" << slevel << "]" << std::endl;); } else { if (cut_var_map[baseNode].empty()) { T_cut * varInfo = alloc(T_cut); varInfo->level = slevel; varInfo->vars[node] = 1; cut_var_map[baseNode].push(varInfo); - TRACE("t_str_cut_var_map", tout << "add var info for baseNode=" << mk_pp(baseNode, get_manager()) << ", node=" << mk_pp(node, get_manager()) << std::endl;); + TRACE("t_str_cut_var_map", tout << "add var info for baseNode=" << mk_pp(baseNode, get_manager()) << ", node=" << mk_pp(node, get_manager()) << " [" << slevel << "]" << std::endl;); } else { if (cut_var_map[baseNode].top()->level < slevel) { T_cut * varInfo = alloc(T_cut); @@ -431,10 +431,10 @@ void theory_str::add_cut_info_one_node(expr * baseNode, int slevel, expr * node) cut_vars_map_copy(varInfo->vars, cut_var_map[baseNode].top()->vars); varInfo->vars[node] = 1; cut_var_map[baseNode].push(varInfo); - TRACE("t_str_cut_var_map", tout << "add var info for baseNode=" << mk_pp(baseNode, get_manager()) << ", node=" << mk_pp(node, get_manager()) << std::endl;); + TRACE("t_str_cut_var_map", tout << "add var info for baseNode=" << mk_pp(baseNode, get_manager()) << ", node=" << mk_pp(node, get_manager()) << " [" << slevel << "]" << std::endl;); } else if (cut_var_map[baseNode].top()->level == slevel) { cut_var_map[baseNode].top()->vars[node] = 1; - TRACE("t_str_cut_var_map", tout << "add var info for baseNode=" << mk_pp(baseNode, get_manager()) << ", node=" << mk_pp(node, get_manager()) << std::endl;); + TRACE("t_str_cut_var_map", tout << "add var info for baseNode=" << mk_pp(baseNode, get_manager()) << ", node=" << mk_pp(node, get_manager()) << " [" << slevel << "]" << std::endl;); } else { get_manager().raise_exception("entered illegal state during add_cut_info_one_node()"); } @@ -460,7 +460,7 @@ void theory_str::add_cut_info_merge(expr * destNode, int slevel, expr * srcNode) cut_vars_map_copy(varInfo->vars, cut_var_map[srcNode].top()->vars); cut_var_map.insert(destNode, std::stack()); cut_var_map[destNode].push(varInfo); - TRACE("t_str_cut_var_map", tout << "merge var info for destNode=" << mk_pp(destNode, get_manager()) << ", srcNode=" << mk_pp(srcNode, get_manager()) << std::endl;); + TRACE("t_str_cut_var_map", tout << "merge var info for destNode=" << mk_pp(destNode, get_manager()) << ", srcNode=" << mk_pp(srcNode, get_manager()) << " [" << slevel << "]" << std::endl;); } else { if (cut_var_map[destNode].empty() || cut_var_map[destNode].top()->level < slevel) { T_cut * varInfo = alloc(T_cut); @@ -468,10 +468,10 @@ void theory_str::add_cut_info_merge(expr * destNode, int slevel, expr * srcNode) cut_vars_map_copy(varInfo->vars, cut_var_map[destNode].top()->vars); cut_vars_map_copy(varInfo->vars, cut_var_map[srcNode].top()->vars); cut_var_map[destNode].push(varInfo); - TRACE("t_str_cut_var_map", tout << "merge var info for destNode=" << mk_pp(destNode, get_manager()) << ", srcNode=" << mk_pp(srcNode, get_manager()) << std::endl;); + TRACE("t_str_cut_var_map", tout << "merge var info for destNode=" << mk_pp(destNode, get_manager()) << ", srcNode=" << mk_pp(srcNode, get_manager()) << " [" << slevel << "]" << std::endl;); } else if (cut_var_map[destNode].top()->level == slevel) { cut_vars_map_copy(cut_var_map[destNode].top()->vars, cut_var_map[srcNode].top()->vars); - TRACE("t_str_cut_var_map", tout << "merge var info for destNode=" << mk_pp(destNode, get_manager()) << ", srcNode=" << mk_pp(srcNode, get_manager()) << std::endl;); + TRACE("t_str_cut_var_map", tout << "merge var info for destNode=" << mk_pp(destNode, get_manager()) << ", srcNode=" << mk_pp(srcNode, get_manager()) << " [" << slevel << "]" << std::endl;); } else { get_manager().raise_exception("illegal state in add_cut_info_merge(): inconsistent slevels"); } @@ -4221,7 +4221,7 @@ void theory_str::process_concat_eq_type6(expr * concatAst1, expr * concatAst2) { } else { loopDetected = true; TRACE("t_str", tout << "AVOID LOOP: SKIPPED." << std::endl;); - // TODO printCutVAR(m, y) + TRACE("t_str", print_cut_var(m, tout); print_cut_var(y, tout);); } for (std::list::iterator itor = overlapLen.begin(); itor != overlapLen.end(); itor++) { @@ -6985,8 +6985,10 @@ void theory_str::pop_scope_eh(unsigned num_scopes) { obj_map >::iterator varItor = cut_var_map.begin(); while (varItor != cut_var_map.end()) { + expr * e = varItor->m_key; std::stack & val = cut_var_map[varItor->m_key]; while ((val.size() > 0) && (val.top()->level != 0) && (val.top()->level >= sLevel)) { + TRACE("t_str_cut_var_map", tout << "remove cut info for " << mk_pp(e, m) << std::endl; print_cut_var(e, tout);); T_cut * aCut = val.top(); val.pop(); // dealloc(aCut); // TODO find a safer way to do this, it is causing a crash From 677fcdcb41e93eb450774c97ba497d4368d55066 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Thu, 12 Jan 2017 18:41:30 -0500 Subject: [PATCH 309/410] concat overlap avoid in theory_str --- src/smt/theory_str.cpp | 202 ++++++++++++++++++++++++++++++++++++++++- src/smt/theory_str.h | 11 +++ 2 files changed, 212 insertions(+), 1 deletion(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 44d13d666..03b04d308 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -40,6 +40,7 @@ theory_str::theory_str(ast_manager & m, theory_str_params const & params): opt_DisableIntegerTheoryIntegration(false), opt_DeferEQCConsistencyCheck(false), opt_CheckVariableScope(true), + opt_ConcatOverlapAvoid(true), /* Internal setup */ search_started(false), m_autil(m), @@ -2801,6 +2802,179 @@ void theory_str::simplify_concat_equality(expr * nn1, expr * nn2) { } +/* + * Returns true if attempting to process a concat equality between lhs and rhs + * will result in overlapping variables (false otherwise). + */ +bool theory_str::will_result_in_overlap(expr * lhs, expr * rhs) { + ast_manager & m = get_manager(); + + expr_ref new_nn1(simplify_concat(lhs), m); + expr_ref new_nn2(simplify_concat(rhs), m); + app * a_new_nn1 = to_app(new_nn1); + app * a_new_nn2 = to_app(new_nn2); + + bool n1IsConcat = is_concat(a_new_nn1); + bool n2IsConcat = is_concat(a_new_nn2); + if (!n1IsConcat && !n2IsConcat) { + // we simplified both sides to non-concat expressions... + return false; + } + + expr * v1_arg0 = a_new_nn1->get_arg(0); + expr * v1_arg1 = a_new_nn1->get_arg(1); + expr * v2_arg0 = a_new_nn2->get_arg(0); + expr * v2_arg1 = a_new_nn2->get_arg(1); + + TRACE("t_str_detail", tout << "checking whether " << mk_pp(new_nn1, m) << " and " << mk_pp(new_nn1, m) << " might overlap." << std::endl;); + + check_and_init_cut_var(v1_arg0); + check_and_init_cut_var(v1_arg1); + check_and_init_cut_var(v2_arg0); + check_and_init_cut_var(v2_arg1); + + //************************************************************* + // case 1: concat(x, y) = concat(m, n) + //************************************************************* + if (is_concat_eq_type1(new_nn1, new_nn2)) { + TRACE("t_str_detail", tout << "Type 1 check." << std::endl;); + expr * x = to_app(new_nn1)->get_arg(0); + expr * y = to_app(new_nn1)->get_arg(1); + expr * m = to_app(new_nn2)->get_arg(0); + expr * n = to_app(new_nn2)->get_arg(1); + + // TODO is it too slow to perform length checks here to avoid false positives? + + if (has_self_cut(m, y)) { + TRACE("t_str_detail", tout << "Possible overlap found" << std::endl; print_cut_var(m, tout); print_cut_var(y, tout);); + return true; + } else if (has_self_cut(x, n)) { + TRACE("t_str_detail", tout << "Possible overlap found" << std::endl; print_cut_var(x, tout); print_cut_var(n, tout);); + return true; + } else { + return false; + } + } + + //************************************************************* + // case 2: concat(x, y) = concat(m, "str") + //************************************************************* + if (is_concat_eq_type2(new_nn1, new_nn2)) { + expr * x = NULL; + expr * y = NULL; + expr * strAst = NULL; + expr * m = NULL; + + expr * v1_arg0 = to_app(new_nn1)->get_arg(0); + expr * v1_arg1 = to_app(new_nn1)->get_arg(1); + expr * v2_arg0 = to_app(new_nn2)->get_arg(0); + expr * v2_arg1 = to_app(new_nn2)->get_arg(1); + + if (m_strutil.is_string(v1_arg1) && !m_strutil.is_string(v2_arg1)) { + m = v1_arg0; + strAst = v1_arg1; + x = v2_arg0; + y = v2_arg1; + } else { + m = v2_arg0; + strAst = v2_arg1; + x = v1_arg0; + y = v1_arg1; + } + + if (has_self_cut(m, y)) { + TRACE("t_str_detail", tout << "Possible overlap found" << std::endl; print_cut_var(m, tout); print_cut_var(y, tout);); + return true; + } else { + return false; + } + } + + //************************************************************* + // case 3: concat(x, y) = concat("str", n) + //************************************************************* + if (is_concat_eq_type3(new_nn1, new_nn2)) { + expr * v1_arg0 = to_app(new_nn1)->get_arg(0); + expr * v1_arg1 = to_app(new_nn1)->get_arg(1); + expr * v2_arg0 = to_app(new_nn2)->get_arg(0); + expr * v2_arg1 = to_app(new_nn2)->get_arg(1); + + expr * x = NULL; + expr * y = NULL; + expr * strAst = NULL; + expr * n = NULL; + + if (m_strutil.is_string(v1_arg0) && !m_strutil.is_string(v2_arg0)) { + strAst = v1_arg0; + n = v1_arg1; + x = v2_arg0; + y = v2_arg1; + } else { + strAst = v2_arg0; + n = v2_arg1; + x = v1_arg0; + y = v1_arg1; + } + if (has_self_cut(x, n)) { + TRACE("t_str_detail", tout << "Possible overlap found" << std::endl; print_cut_var(x, tout); print_cut_var(n, tout);); + return true; + } else { + return false; + } + } + + //************************************************************* + // case 4: concat("str1", y) = concat("str2", n) + //************************************************************* + if (is_concat_eq_type4(new_nn1, new_nn2)) { + // This case can never result in an overlap. + return false; + } + + //************************************************************* + // case 5: concat(x, "str1") = concat(m, "str2") + //************************************************************* + if (is_concat_eq_type5(new_nn1, new_nn2)) { + // This case can never result in an overlap. + return false; + } + //************************************************************* + // case 6: concat("str1", y) = concat(m, "str2") + //************************************************************* + if (is_concat_eq_type6(new_nn1, new_nn2)) { + expr * v1_arg0 = to_app(new_nn1)->get_arg(0); + expr * v1_arg1 = to_app(new_nn1)->get_arg(1); + expr * v2_arg0 = to_app(new_nn2)->get_arg(0); + expr * v2_arg1 = to_app(new_nn2)->get_arg(1); + + expr * str1Ast = NULL; + expr * y = NULL; + expr * m = NULL; + expr * str2Ast = NULL; + + if (m_strutil.is_string(v1_arg0)) { + str1Ast = v1_arg0; + y = v1_arg1; + m = v2_arg0; + str2Ast = v2_arg1; + } else { + str1Ast = v2_arg0; + y = v2_arg1; + m = v1_arg0; + str2Ast = v1_arg1; + } + if (has_self_cut(m, y)) { + TRACE("t_str_detail", tout << "Possible overlap found" << std::endl; print_cut_var(m, tout); print_cut_var(y, tout);); + return true; + } else { + return false; + } + } + + TRACE("t_str_detail", tout << "warning: unrecognized concat case" << std::endl;); + return false; +} + /************************************************************* * Type 1: concat(x, y) = concat(m, n) * x, y, m and n all variables @@ -6629,7 +6803,33 @@ void theory_str::handle_equality(expr * lhs, expr * rhs) { } } if (hasCommon == 0) { - simplify_concat_equality(*(eqc_concat_lhs.begin()), *(eqc_concat_rhs.begin())); + if (opt_ConcatOverlapAvoid) { + bool found = false; + // check each pair and take the first ones that won't immediately overlap + for (itor1 = eqc_concat_lhs.begin(); itor1 != eqc_concat_lhs.end() && !found; ++itor1) { + expr * concat_lhs = *itor1; + for (itor2 = eqc_concat_rhs.begin(); itor2 != eqc_concat_rhs.end() && !found; ++itor2) { + expr * concat_rhs = *itor2; + if (will_result_in_overlap(concat_lhs, concat_rhs)) { + TRACE("t_str_detail", tout << "Concats " << mk_pp(concat_lhs, m) << " and " + << mk_pp(concat_rhs, m) << " will result in overlap; skipping." << std::endl;); + } else { + TRACE("t_str_detail", tout << "Concats " << mk_pp(concat_lhs, m) << " and " + << mk_pp(concat_rhs, m) << " won't overlap. Simplifying here." << std::endl;); + simplify_concat_equality(concat_lhs, concat_rhs); + found = true; + break; + } + } + } + if (!found) { + TRACE("t_str_detail", tout << "All pairs of concats expected to overlap, falling back." << std::endl;); + simplify_concat_equality(*(eqc_concat_lhs.begin()), *(eqc_concat_rhs.begin())); + } + } else { + // default behaviour + simplify_concat_equality(*(eqc_concat_lhs.begin()), *(eqc_concat_rhs.begin())); + } } } diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 1f615cfc5..7f1e1dd9c 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -180,6 +180,14 @@ namespace smt { */ bool opt_CheckVariableScope; + /* + * If ConcatOverlapAvoid is set to true, + * the check to simplify Concat = Concat in handle_equality() will + * avoid simplifying wrt. pairs of Concat terms that will immediately + * result in an overlap. (false = Z3str2 behaviour) + */ + bool opt_ConcatOverlapAvoid; + bool search_started; arith_util m_autil; str_util m_strutil; @@ -350,6 +358,9 @@ namespace smt { void add_cut_info_merge(expr * destNode, int slevel, expr * srcNode); bool has_self_cut(expr * n1, expr * n2); + // for ConcatOverlapAvoid + bool will_result_in_overlap(expr * lhs, expr * rhs); + void track_variable_scope(expr * var); app * mk_str_var(std::string name); app * mk_int_var(std::string name); From f033a77faed636ed932ed2b6a811e3255cbca189 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Fri, 13 Jan 2017 12:57:48 -0500 Subject: [PATCH 310/410] modify theory-aware branching to manipulate activity instead of giving absolute priority --- src/smt/smt_case_split_queue.cpp | 137 +++++++++++++++++++++++++++---- src/smt/theory_str.cpp | 30 +++---- 2 files changed, 138 insertions(+), 29 deletions(-) diff --git a/src/smt/smt_case_split_queue.cpp b/src/smt/smt_case_split_queue.cpp index ebe9c2e4e..fa012525b 100644 --- a/src/smt/smt_case_split_queue.cpp +++ b/src/smt/smt_case_split_queue.cpp @@ -40,18 +40,20 @@ namespace smt { typedef heap bool_var_act_queue; struct theory_aware_act_lt { - // only take into account theory var priority for now + svector const & m_activity; theory_var_priority_map const & m_theory_var_priority; - theory_aware_act_lt(theory_var_priority_map const & a):m_theory_var_priority(a) {} + theory_aware_act_lt(svector const & act, theory_var_priority_map const & a):m_activity(act),m_theory_var_priority(a) {} bool operator()(bool_var v1, bool_var v2) const { double p_v1, p_v2; - // safety -- use a large negative number if some var isn't in the map if (!m_theory_var_priority.find(v1, p_v1)) { - p_v1 = -1000.0; - } + p_v1 = 0.0; + } if (!m_theory_var_priority.find(v2, p_v2)) { - p_v2 = -1000.0; + p_v2 = 0.0; } + // add clause activity + p_v1 += m_activity[v1]; + p_v2 += m_activity[v2]; return p_v1 > p_v2; } }; @@ -1109,7 +1111,8 @@ namespace smt { m_params.m_qi_eager_threshold += start_gen; } }; - + + /* class theory_aware_branching_queue : public case_split_queue { protected: context & m_context; @@ -1220,7 +1223,114 @@ namespace smt { m_base_queue->display(out); } }; + */ + class theory_aware_branching_queue : public case_split_queue { + protected: + context & m_context; + smt_params & m_params; + theory_var_priority_map m_theory_var_priority; + theory_aware_act_queue m_queue; + public: + theory_aware_branching_queue(context & ctx, smt_params & p): + m_context(ctx), + m_params(p), + m_theory_var_priority(), + m_queue(1024, theory_aware_act_lt(ctx.get_activity_vector(), m_theory_var_priority)) { + } + + virtual void activity_increased_eh(bool_var v) { + if (m_queue.contains(v)) + m_queue.decreased(v); + } + + virtual void mk_var_eh(bool_var v) { + m_queue.reserve(v+1); + m_queue.insert(v); + } + + virtual void del_var_eh(bool_var v) { + if (m_queue.contains(v)) + m_queue.erase(v); + } + + virtual void unassign_var_eh(bool_var v) { + if (!m_queue.contains(v)) + m_queue.insert(v); + } + + virtual void relevant_eh(expr * n) {} + + virtual void init_search_eh() {} + + virtual void end_search_eh() {} + + virtual void reset() { + m_queue.reset(); + } + + virtual void push_scope() {} + + virtual void pop_scope(unsigned num_scopes) {} + + virtual void next_case_split(bool_var & next, lbool & phase) { + phase = l_undef; + + if (m_context.get_random_value() < static_cast(m_params.m_random_var_freq * random_gen::max_value())) { + next = m_context.get_random_value() % m_context.get_num_b_internalized(); + TRACE("random_split", tout << "next: " << next << " get_assignment(next): " << m_context.get_assignment(next) << "\n";); + if (m_context.get_assignment(next) == l_undef) + return; + } + + while (!m_queue.empty()) { + next = m_queue.erase_min(); + if (m_context.get_assignment(next) == l_undef) + return; + } + + next = null_bool_var; + } + + virtual void add_theory_aware_branching_info(bool_var v, double priority, lbool phase) { + TRACE("theory_aware_branching", tout << "Add theory-aware branching information for l#" << v << ": priority=" << priority << std::endl;); + // m_theory_vars.insert(v); + // m_theory_var_phase.insert(v, phase); + m_theory_var_priority.insert(v, priority); + if (m_queue.contains(v)) { + if (priority > 0.0) { + m_queue.decreased(v); + } else { + m_queue.increased(v); + } + } + // m_theory_queue.reserve(v+1); + // m_theory_queue.insert(v); + } + + virtual void display(std::ostream & out) { + bool first = true; + bool_var_act_queue::const_iterator it = m_queue.begin(); + bool_var_act_queue::const_iterator end = m_queue.end(); + for (; it != end ; ++it) { + unsigned v = *it; + if (m_context.get_assignment(v) == l_undef) { + if (first) { + out << "remaining case-splits:\n"; + first = false; + } + out << "#" << m_context.bool_var2expr(v)->get_id() << " "; + } + } + if (!first) + out << "\n"; + + } + + virtual ~theory_aware_branching_queue() {}; + }; + + case_split_queue * mk_case_split_queue(context & ctx, smt_params & p) { if (p.m_relevancy_lvl < 2 && (p.m_case_split_strategy == CS_RELEVANCY || p.m_case_split_strategy == CS_RELEVANCY_ACTIVITY || p.m_case_split_strategy == CS_RELEVANCY_GOAL)) { @@ -1235,6 +1345,10 @@ namespace smt { case_split_queue * baseQueue; + if (p.m_theory_aware_branching) { + // override + baseQueue = alloc(theory_aware_branching_queue, ctx, p); + } else { switch (p.m_case_split_strategy) { case CS_ACTIVITY_DELAY_NEW: baseQueue = alloc(dact_case_split_queue, ctx, p); @@ -1255,14 +1369,9 @@ namespace smt { baseQueue = alloc(act_case_split_queue, ctx, p); break; } + } - if (p.m_theory_aware_branching) { - TRACE("theory_aware_branching", tout << "Allocating and returning theory-aware branching queue." << std::endl;); - case_split_queue * theory_aware_queue = alloc(theory_aware_branching_queue, ctx, p, baseQueue); - return theory_aware_queue; - } else { - return baseQueue; - } + return baseQueue; } }; diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 44d13d666..2936baf13 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -3085,7 +3085,7 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { expr_ref option1(mk_and(and_item), mgr); arrangement_disjunction.push_back(option1); - add_theory_aware_branching_info(option1, 0.0, l_true); + add_theory_aware_branching_info(option1, 0.1, l_true); add_cut_info_merge(t1, ctx.get_scope_level(), m); add_cut_info_merge(t1, ctx.get_scope_level(), y); @@ -3122,7 +3122,7 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { expr_ref option2(mk_and(and_item), mgr); arrangement_disjunction.push_back(option2); - add_theory_aware_branching_info(option2, 0.0, l_true); + add_theory_aware_branching_info(option2, 0.1, l_true); add_cut_info_merge(t2, ctx.get_scope_level(), x); add_cut_info_merge(t2, ctx.get_scope_level(), n); @@ -3143,7 +3143,7 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { expr_ref option3(mk_and(and_item), mgr); arrangement_disjunction.push_back(option3); // prioritize this case, it is easier - add_theory_aware_branching_info(option3, 2.0, l_true); + add_theory_aware_branching_info(option3, 0.5, l_true); } if (!arrangement_disjunction.empty()) { @@ -3455,7 +3455,7 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { expr_ref option1(mk_and(and_item), mgr); arrangement_disjunction.push_back(option1); - add_theory_aware_branching_info(option1, 0.0, l_true); + add_theory_aware_branching_info(option1, 0.1, l_true); add_cut_info_merge(temp1, ctx.get_scope_level(), y); add_cut_info_merge(temp1, ctx.get_scope_level(), m); } else { @@ -3482,9 +3482,9 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { double priority; // prioritize the option where y is equal to the original string if (i == 0) { - priority = 2.0; + priority = 0.5; } else { - priority = 0.0; + priority = 0.1; } add_theory_aware_branching_info(option2, priority, l_true); } @@ -3787,9 +3787,9 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { arrangement_disjunction.push_back(option1); double priority; if (i == (int)strValue.size()) { - priority = 1.0; + priority = 0.5; } else { - priority = 0.0; + priority = 0.1; } add_theory_aware_branching_info(option1, priority, l_true); } @@ -3815,7 +3815,7 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { expr_ref option2(mk_and(and_item), mgr); arrangement_disjunction.push_back(option2); - add_theory_aware_branching_info(option2, 0.0, l_true); + add_theory_aware_branching_info(option2, 0.1, l_true); add_cut_info_merge(temp1, sLevel, x); add_cut_info_merge(temp1, sLevel, n); @@ -4217,7 +4217,7 @@ void theory_str::process_concat_eq_type6(expr * concatAst1, expr * concatAst2) { expr_ref option1(mk_and(and_item), mgr); arrangement_disjunction.push_back(option1); - add_theory_aware_branching_info(option1, 0.0, l_true); + add_theory_aware_branching_info(option1, 0.1, l_true); } else { loopDetected = true; TRACE("t_str", tout << "AVOID LOOP: SKIPPED." << std::endl;); @@ -4255,9 +4255,9 @@ void theory_str::process_concat_eq_type6(expr * concatAst1, expr * concatAst2) { double priority; // prefer the option "str1" = x if (prefix == str1Value) { - priority = 1.0; + priority = 0.5; } else { - priority = 0.0; + priority = 0.1; } add_theory_aware_branching_info(option2, priority, l_true); } @@ -9333,10 +9333,10 @@ expr * theory_str::gen_len_test_options(expr * freeVar, expr * indicator, int tr double priority; // give high priority to small lengths if this is available if (i <= 5) { - priority = 3.0; + priority = 0.3; } else { // prioritize over "more" - priority = 0.5; + priority = 0.2; } add_theory_aware_branching_info(or_expr, priority, l_true); @@ -9356,7 +9356,7 @@ expr * theory_str::gen_len_test_options(expr * freeVar, expr * indicator, int tr expr_ref more_option(ctx.mk_eq_atom(indicator, mk_string("more")), m); orList.push_back(more_option); // decrease priority of this option - add_theory_aware_branching_info(more_option, -1.0, l_true); + add_theory_aware_branching_info(more_option, -0.1, l_true); if (m_params.m_AggressiveLengthTesting) { literal l = mk_eq(indicator, mk_string("more"), false); ctx.mark_as_relevant(l); From a9ec8666f0c2e310bfe581169538b59e9cb1748d Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sat, 14 Jan 2017 14:43:57 -0500 Subject: [PATCH 311/410] add phase selection to theory-aware branching queue --- src/smt/smt_case_split_queue.cpp | 44 +++++++++++++++++++------------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/src/smt/smt_case_split_queue.cpp b/src/smt/smt_case_split_queue.cpp index fa012525b..c7ef655f2 100644 --- a/src/smt/smt_case_split_queue.cpp +++ b/src/smt/smt_case_split_queue.cpp @@ -1231,14 +1231,17 @@ namespace smt { smt_params & m_params; theory_var_priority_map m_theory_var_priority; theory_aware_act_queue m_queue; + + int_hashtable > m_theory_vars; + map > m_theory_var_phase; public: theory_aware_branching_queue(context & ctx, smt_params & p): m_context(ctx), m_params(p), - m_theory_var_priority(), + m_theory_var_priority(), m_queue(1024, theory_aware_act_lt(ctx.get_activity_vector(), m_theory_var_priority)) { } - + virtual void activity_increased_eh(bool_var v) { if (m_queue.contains(v)) m_queue.decreased(v); @@ -1275,39 +1278,44 @@ namespace smt { virtual void next_case_split(bool_var & next, lbool & phase) { phase = l_undef; - + if (m_context.get_random_value() < static_cast(m_params.m_random_var_freq * random_gen::max_value())) { next = m_context.get_random_value() % m_context.get_num_b_internalized(); TRACE("random_split", tout << "next: " << next << " get_assignment(next): " << m_context.get_assignment(next) << "\n";); if (m_context.get_assignment(next) == l_undef) return; } - + while (!m_queue.empty()) { next = m_queue.erase_min(); if (m_context.get_assignment(next) == l_undef) return; } - + next = null_bool_var; + if (m_theory_vars.contains(next)) { + if (!m_theory_var_phase.find(next, phase)) { + phase = l_undef; + } + } } virtual void add_theory_aware_branching_info(bool_var v, double priority, lbool phase) { TRACE("theory_aware_branching", tout << "Add theory-aware branching information for l#" << v << ": priority=" << priority << std::endl;); - // m_theory_vars.insert(v); - // m_theory_var_phase.insert(v, phase); + m_theory_vars.insert(v); + m_theory_var_phase.insert(v, phase); m_theory_var_priority.insert(v, priority); - if (m_queue.contains(v)) { - if (priority > 0.0) { - m_queue.decreased(v); - } else { - m_queue.increased(v); - } - } + if (m_queue.contains(v)) { + if (priority > 0.0) { + m_queue.decreased(v); + } else { + m_queue.increased(v); + } + } // m_theory_queue.reserve(v+1); // m_theory_queue.insert(v); } - + virtual void display(std::ostream & out) { bool first = true; bool_var_act_queue::const_iterator it = m_queue.begin(); @@ -1330,15 +1338,15 @@ namespace smt { virtual ~theory_aware_branching_queue() {}; }; - + case_split_queue * mk_case_split_queue(context & ctx, smt_params & p) { if (p.m_relevancy_lvl < 2 && (p.m_case_split_strategy == CS_RELEVANCY || p.m_case_split_strategy == CS_RELEVANCY_ACTIVITY || - p.m_case_split_strategy == CS_RELEVANCY_GOAL)) { + p.m_case_split_strategy == CS_RELEVANCY_GOAL)) { warning_msg("relevancy must be enabled to use option CASE_SPLIT=3, 4 or 5"); p.m_case_split_strategy = CS_ACTIVITY; } if (p.m_auto_config && (p.m_case_split_strategy == CS_RELEVANCY || p.m_case_split_strategy == CS_RELEVANCY_ACTIVITY || - p.m_case_split_strategy == CS_RELEVANCY_GOAL)) { + p.m_case_split_strategy == CS_RELEVANCY_GOAL)) { warning_msg("auto configuration (option AUTO_CONFIG) must be disabled to use option CASE_SPLIT=3, 4 or 5"); p.m_case_split_strategy = CS_ACTIVITY; } From aa8bf2668f9942af6ef819e1a9f9af87a227c14e Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sat, 14 Jan 2017 15:28:58 -0500 Subject: [PATCH 312/410] scale theory-aware priority by bvar_inc --- src/smt/smt_case_split_queue.cpp | 19 ++++++++++++------- src/smt/smt_context.h | 1 + 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/smt/smt_case_split_queue.cpp b/src/smt/smt_case_split_queue.cpp index c7ef655f2..2bc3e32df 100644 --- a/src/smt/smt_case_split_queue.cpp +++ b/src/smt/smt_case_split_queue.cpp @@ -42,18 +42,23 @@ namespace smt { struct theory_aware_act_lt { svector const & m_activity; theory_var_priority_map const & m_theory_var_priority; - theory_aware_act_lt(svector const & act, theory_var_priority_map const & a):m_activity(act),m_theory_var_priority(a) {} + double const & m_bvar_inc; + theory_aware_act_lt(svector const & act, + theory_var_priority_map const & a, + double const & bvar_inc):m_activity(act),m_theory_var_priority(a),m_bvar_inc(bvar_inc) {} bool operator()(bool_var v1, bool_var v2) const { double p_v1, p_v2; if (!m_theory_var_priority.find(v1, p_v1)) { - p_v1 = 0.0; - } + p_v1 = 0.0; + } + p_v1 *= m_bvar_inc; if (!m_theory_var_priority.find(v2, p_v2)) { p_v2 = 0.0; } - // add clause activity - p_v1 += m_activity[v1]; - p_v2 += m_activity[v2]; + p_v2 *= m_bvar_inc; + // add clause activity + p_v1 += m_activity[v1]; + p_v2 += m_activity[v2]; return p_v1 > p_v2; } }; @@ -1239,7 +1244,7 @@ namespace smt { m_context(ctx), m_params(p), m_theory_var_priority(), - m_queue(1024, theory_aware_act_lt(ctx.get_activity_vector(), m_theory_var_priority)) { + m_queue(1024, theory_aware_act_lt(ctx.get_activity_vector(), m_theory_var_priority, ctx.get_bvar_inc())) { } virtual void activity_increased_eh(bool_var v) { diff --git a/src/smt/smt_context.h b/src/smt/smt_context.h index 2aae6c8a5..9a8e01b93 100644 --- a/src/smt/smt_context.h +++ b/src/smt/smt_context.h @@ -824,6 +824,7 @@ namespace smt { * or some other axiom that means at least one of them must be assigned 'true'. */ void mk_th_case_split(unsigned num_lits, literal * lits); + double get_bvar_inc() const { return m_bvar_inc; } /* * Provide a hint to the branching heuristic about the priority of a "theory-aware literal". From 0dfaa30ae8f2b143bf90959dea1ba222e923d2ad Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Mon, 16 Jan 2017 14:46:04 -0500 Subject: [PATCH 313/410] experimental z3str2 search order --- src/smt/theory_str.cpp | 22 ++++++++++++++++++++-- src/smt/theory_str.h | 16 +++++++++++++++- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index fd379fd2d..13f2732d8 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -41,6 +41,7 @@ theory_str::theory_str(ast_manager & m, theory_str_params const & params): opt_DeferEQCConsistencyCheck(false), opt_CheckVariableScope(true), opt_ConcatOverlapAvoid(true), + opt_DeferredSearchOrder(true), /* Internal setup */ search_started(false), m_autil(m), @@ -899,7 +900,8 @@ bool theory_str::can_propagate() { || !m_axiom_Contains_todo.empty() || !m_axiom_Indexof_todo.empty() || !m_axiom_Indexof2_todo.empty() || !m_axiom_LastIndexof_todo.empty() || !m_axiom_Substr_todo.empty() || !m_axiom_Replace_todo.empty() || !m_axiom_RegexIn_todo.empty() || !m_library_aware_axiom_todo.empty() - || !m_delayed_axiom_setup_terms.empty(); + || !m_delayed_axiom_setup_terms.empty() + || (opt_DeferredSearchOrder && !m_new_eqs.empty()) ; } @@ -1000,6 +1002,14 @@ void theory_str::propagate() { set_up_axioms(m_delayed_axiom_setup_terms[i].get()); } m_delayed_axiom_setup_terms.reset(); + + if (opt_DeferredSearchOrder) { + for (unsigned i = 0; i < m_new_eqs.size(); ++i) { + var_pair & p = m_new_eqs[i]; + cb_new_eq(p.first, p.second); + } + m_new_eqs.reset(); + } } } @@ -7062,7 +7072,15 @@ void theory_str::init_search_eh() { search_started = true; } -void theory_str::new_eq_eh(theory_var x, theory_var y) { +void theory_str::new_eq_eh(theory_var v1, theory_var v2) { + if (opt_DeferredSearchOrder) { + m_new_eqs.push_back(var_pair(v1,v2)); + } else { + cb_new_eq(v1, v2); + } +} + +void theory_str::cb_new_eq(theory_var x, theory_var y) { //TRACE("t_str_detail", tout << "new eq: v#" << x << " = v#" << y << std::endl;); TRACE("t_str", tout << "new eq: " << mk_ismt2_pp(get_enode(x)->get_owner(), get_manager()) << " = " << mk_ismt2_pp(get_enode(y)->get_owner(), get_manager()) << std::endl;); diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 7f1e1dd9c..598e9d8c9 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -97,6 +97,8 @@ namespace smt { } }; + typedef std::pair var_pair; + class theory_str : public theory { struct T_cut { @@ -188,6 +190,16 @@ namespace smt { */ bool opt_ConcatOverlapAvoid; + /* + * If DeferredSearchOrder is set to true, + * certain behaviours from user_smt_theory will be emulated in order to + * reproduce more faithfully the search order used by Z3str2. + * In particular, new equalities will be saved and processed during propagate(), + * and asserted axioms will be deferred until the end of each propagate() step. + */ + bool opt_DeferredSearchOrder; + svector m_new_eqs; + bool search_started; arith_util m_autil; str_util m_strutil; @@ -585,9 +597,11 @@ namespace smt { void get_unique_non_concat_nodes(expr * node, std::set & argSet); bool propagate_length_within_eqc(expr * var); - // TESTING void refresh_theory_var(expr * e); + // user_smt_theory search order emulation + void cb_new_eq(theory_var v1, theory_var v2); + public: theory_str(ast_manager & m, theory_str_params const & params); virtual ~theory_str(); From 4b6582b8f35f5c5c650cd855d6774a1c0a4463c3 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Mon, 16 Jan 2017 15:46:17 -0500 Subject: [PATCH 314/410] Revert "experimental z3str2 search order" This reverts commit 0dfaa30ae8f2b143bf90959dea1ba222e923d2ad. --- src/smt/theory_str.cpp | 22 ++-------------------- src/smt/theory_str.h | 16 +--------------- 2 files changed, 3 insertions(+), 35 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 13f2732d8..fd379fd2d 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -41,7 +41,6 @@ theory_str::theory_str(ast_manager & m, theory_str_params const & params): opt_DeferEQCConsistencyCheck(false), opt_CheckVariableScope(true), opt_ConcatOverlapAvoid(true), - opt_DeferredSearchOrder(true), /* Internal setup */ search_started(false), m_autil(m), @@ -900,8 +899,7 @@ bool theory_str::can_propagate() { || !m_axiom_Contains_todo.empty() || !m_axiom_Indexof_todo.empty() || !m_axiom_Indexof2_todo.empty() || !m_axiom_LastIndexof_todo.empty() || !m_axiom_Substr_todo.empty() || !m_axiom_Replace_todo.empty() || !m_axiom_RegexIn_todo.empty() || !m_library_aware_axiom_todo.empty() - || !m_delayed_axiom_setup_terms.empty() - || (opt_DeferredSearchOrder && !m_new_eqs.empty()) + || !m_delayed_axiom_setup_terms.empty(); ; } @@ -1002,14 +1000,6 @@ void theory_str::propagate() { set_up_axioms(m_delayed_axiom_setup_terms[i].get()); } m_delayed_axiom_setup_terms.reset(); - - if (opt_DeferredSearchOrder) { - for (unsigned i = 0; i < m_new_eqs.size(); ++i) { - var_pair & p = m_new_eqs[i]; - cb_new_eq(p.first, p.second); - } - m_new_eqs.reset(); - } } } @@ -7072,15 +7062,7 @@ void theory_str::init_search_eh() { search_started = true; } -void theory_str::new_eq_eh(theory_var v1, theory_var v2) { - if (opt_DeferredSearchOrder) { - m_new_eqs.push_back(var_pair(v1,v2)); - } else { - cb_new_eq(v1, v2); - } -} - -void theory_str::cb_new_eq(theory_var x, theory_var y) { +void theory_str::new_eq_eh(theory_var x, theory_var y) { //TRACE("t_str_detail", tout << "new eq: v#" << x << " = v#" << y << std::endl;); TRACE("t_str", tout << "new eq: " << mk_ismt2_pp(get_enode(x)->get_owner(), get_manager()) << " = " << mk_ismt2_pp(get_enode(y)->get_owner(), get_manager()) << std::endl;); diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 598e9d8c9..7f1e1dd9c 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -97,8 +97,6 @@ namespace smt { } }; - typedef std::pair var_pair; - class theory_str : public theory { struct T_cut { @@ -190,16 +188,6 @@ namespace smt { */ bool opt_ConcatOverlapAvoid; - /* - * If DeferredSearchOrder is set to true, - * certain behaviours from user_smt_theory will be emulated in order to - * reproduce more faithfully the search order used by Z3str2. - * In particular, new equalities will be saved and processed during propagate(), - * and asserted axioms will be deferred until the end of each propagate() step. - */ - bool opt_DeferredSearchOrder; - svector m_new_eqs; - bool search_started; arith_util m_autil; str_util m_strutil; @@ -597,11 +585,9 @@ namespace smt { void get_unique_non_concat_nodes(expr * node, std::set & argSet); bool propagate_length_within_eqc(expr * var); + // TESTING void refresh_theory_var(expr * e); - // user_smt_theory search order emulation - void cb_new_eq(theory_var v1, theory_var v2); - public: theory_str(ast_manager & m, theory_str_params const & params); virtual ~theory_str(); From 4e2847dea4e84f2ab4309b3de28309ca8eda41ce Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Mon, 16 Jan 2017 15:46:28 -0500 Subject: [PATCH 315/410] Revert "scale theory-aware priority by bvar_inc" This reverts commit aa8bf2668f9942af6ef819e1a9f9af87a227c14e. --- src/smt/smt_case_split_queue.cpp | 19 +++++++------------ src/smt/smt_context.h | 1 - 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/src/smt/smt_case_split_queue.cpp b/src/smt/smt_case_split_queue.cpp index 2bc3e32df..c7ef655f2 100644 --- a/src/smt/smt_case_split_queue.cpp +++ b/src/smt/smt_case_split_queue.cpp @@ -42,23 +42,18 @@ namespace smt { struct theory_aware_act_lt { svector const & m_activity; theory_var_priority_map const & m_theory_var_priority; - double const & m_bvar_inc; - theory_aware_act_lt(svector const & act, - theory_var_priority_map const & a, - double const & bvar_inc):m_activity(act),m_theory_var_priority(a),m_bvar_inc(bvar_inc) {} + theory_aware_act_lt(svector const & act, theory_var_priority_map const & a):m_activity(act),m_theory_var_priority(a) {} bool operator()(bool_var v1, bool_var v2) const { double p_v1, p_v2; if (!m_theory_var_priority.find(v1, p_v1)) { - p_v1 = 0.0; - } - p_v1 *= m_bvar_inc; + p_v1 = 0.0; + } if (!m_theory_var_priority.find(v2, p_v2)) { p_v2 = 0.0; } - p_v2 *= m_bvar_inc; - // add clause activity - p_v1 += m_activity[v1]; - p_v2 += m_activity[v2]; + // add clause activity + p_v1 += m_activity[v1]; + p_v2 += m_activity[v2]; return p_v1 > p_v2; } }; @@ -1244,7 +1239,7 @@ namespace smt { m_context(ctx), m_params(p), m_theory_var_priority(), - m_queue(1024, theory_aware_act_lt(ctx.get_activity_vector(), m_theory_var_priority, ctx.get_bvar_inc())) { + m_queue(1024, theory_aware_act_lt(ctx.get_activity_vector(), m_theory_var_priority)) { } virtual void activity_increased_eh(bool_var v) { diff --git a/src/smt/smt_context.h b/src/smt/smt_context.h index 9a8e01b93..2aae6c8a5 100644 --- a/src/smt/smt_context.h +++ b/src/smt/smt_context.h @@ -824,7 +824,6 @@ namespace smt { * or some other axiom that means at least one of them must be assigned 'true'. */ void mk_th_case_split(unsigned num_lits, literal * lits); - double get_bvar_inc() const { return m_bvar_inc; } /* * Provide a hint to the branching heuristic about the priority of a "theory-aware literal". From e459617c39b8ef171586f50748126de49dc53f85 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Mon, 16 Jan 2017 18:04:03 -0500 Subject: [PATCH 316/410] experimental finite model finding WIP, first successful run --- src/smt/params/smt_params_helper.pyg | 3 +- src/smt/params/theory_str_params.cpp | 1 + src/smt/params/theory_str_params.h | 8 ++ src/smt/theory_str.cpp | 160 ++++++++++++++++++++++++++- src/smt/theory_str.h | 6 + 5 files changed, 175 insertions(+), 3 deletions(-) diff --git a/src/smt/params/smt_params_helper.pyg b/src/smt/params/smt_params_helper.pyg index 8e8e52987..e23915ab4 100644 --- a/src/smt/params/smt_params_helper.pyg +++ b/src/smt/params/smt_params_helper.pyg @@ -72,5 +72,6 @@ def_module_params(module_name='smt', ('str.use_binary_search', BOOL, False, 'use a binary search heuristic for finding concrete length values for free variables in theory_str (set to False to use linear search)'), ('str.binary_search_start', UINT, 64, 'initial upper bound for theory_str binary search'), ('theory_case_split', BOOL, False, 'Allow the context to use heuristics involving theory case splits, which are a set of literals of which exactly one can be assigned True. If this option is false, the context will generate extra axioms to enforce this instead.'), - ('theory_aware_branching', BOOL, False, 'Allow the context to use extra information from theory solvers regarding literal branching prioritization.') + ('theory_aware_branching', BOOL, False, 'Allow the context to use extra information from theory solvers regarding literal branching prioritization.'), + ('str.finite_overlap_models', BOOL, False, 'attempt a finite model search for overlapping variables instead of completely giving up on the arrangement') )) diff --git a/src/smt/params/theory_str_params.cpp b/src/smt/params/theory_str_params.cpp index 2e98a4394..46302cf82 100644 --- a/src/smt/params/theory_str_params.cpp +++ b/src/smt/params/theory_str_params.cpp @@ -27,6 +27,7 @@ void theory_str_params::updt_params(params_ref const & _p) { m_UseFastLengthTesterCache = p.str_fast_length_tester_cache(); m_UseFastValueTesterCache = p.str_fast_value_tester_cache(); m_StringConstantCache = p.str_string_constant_cache(); + m_FiniteOverlapModels = p.str_finite_overlap_models(); m_UseBinarySearch = p.str_use_binary_search(); m_BinarySearchInitialUpperBound = p.str_binary_search_start(); } diff --git a/src/smt/params/theory_str_params.h b/src/smt/params/theory_str_params.h index 39c553780..4effb0897 100644 --- a/src/smt/params/theory_str_params.h +++ b/src/smt/params/theory_str_params.h @@ -68,6 +68,13 @@ struct theory_str_params { */ bool m_StringConstantCache; + /* + * If FiniteOverlapModels is set to true, + * arrangements that result in overlapping variables will generate a small number of models + * to test instead of completely giving up on the case. + */ + bool m_FiniteOverlapModels; + bool m_UseBinarySearch; unsigned m_BinarySearchInitialUpperBound; @@ -79,6 +86,7 @@ struct theory_str_params { m_UseFastLengthTesterCache(false), m_UseFastValueTesterCache(true), m_StringConstantCache(true), + m_FiniteOverlapModels(false), m_UseBinarySearch(false), m_BinarySearchInitialUpperBound(64) { diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index fd379fd2d..4ff80a613 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -4394,8 +4394,47 @@ void theory_str::process_concat_eq_type6(expr * concatAst1, expr * concatAst2) { add_theory_aware_branching_info(option1, 0.1, l_true); } else { loopDetected = true; - TRACE("t_str", tout << "AVOID LOOP: SKIPPED." << std::endl;); - TRACE("t_str", print_cut_var(m, tout); print_cut_var(y, tout);); + + if (m_params.m_FiniteOverlapModels) { + // TODO refactor this entire segment into its own method. this is really just for experiment purposes + TRACE("t_str", tout << "activating finite model testing for overlapping concats " + << mk_pp(concatAst1, mgr) << " and " << mk_pp(concatAst2, mgr) << std::endl;); + std::map concatMap; + std::map unrollMap; + std::map varMap; + classify_ast_by_type(concatAst1, varMap, concatMap, unrollMap); + classify_ast_by_type(concatAst2, varMap, concatMap, unrollMap); + TRACE("t_str_detail", tout << "found vars:"; + for (std::map::iterator it = varMap.begin(); it != varMap.end(); ++it) { + tout << " " << mk_pp(it->first, mgr); + } + tout << std::endl; + ); + + expr_ref testvar(mk_str_var("finiteModelTest"), mgr); + m_trail.push_back(testvar); + ptr_vector varlist; + + for (std::map::iterator it = varMap.begin(); it != varMap.end(); ++it) { + expr * v = it->first; + varlist.push_back(v); + } + + // make things easy for the core wrt. testvar + expr_ref t1(ctx.mk_eq_atom(testvar, m_strutil.mk_string("")), mgr); + expr_ref t_yes(ctx.mk_eq_atom(testvar, m_strutil.mk_string("yes")), mgr); + expr_ref testvaraxiom(mgr.mk_or(t1, t_yes), mgr); + assert_axiom(testvaraxiom); + + finite_model_test_varlists.insert(testvar, varlist); + m_trail_stack.push(insert_obj_map >(finite_model_test_varlists, testvar) ); + + arrangement_disjunction.push_back(t_yes); + add_theory_aware_branching_info(t_yes, -0.1, l_true); + } else { + TRACE("t_str", tout << "AVOID LOOP: SKIPPED." << std::endl;); + TRACE("t_str", print_cut_var(m, tout); print_cut_var(y, tout);); + } } for (std::list::iterator itor = overlapLen.begin(); itor != overlapLen.end(); itor++) { @@ -6564,6 +6603,114 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { } } +void theory_str::finite_model_test(expr * testvar, expr * str) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + if (!m_strutil.is_string(str)) return; + std::string s = m_strutil.get_string_constant_value(str); + if (s == "yes") { + TRACE("t_str", tout << "start finite model test for " << mk_pp(testvar, m) << std::endl;); + ptr_vector & vars = finite_model_test_varlists[testvar]; + for (ptr_vector::iterator it = vars.begin(); it != vars.end(); ++it) { + expr * v = *it; + // check for any sort of existing length tester we might interfere with + if (m_params.m_UseBinarySearch) { + NOT_IMPLEMENTED_YET(); + } else { + bool map_effectively_empty = false; + if (fvar_len_count_map.find(v) == fvar_len_count_map.end()) { + map_effectively_empty = true; + } + + if (!map_effectively_empty) { + map_effectively_empty = true; + ptr_vector indicator_set = fvar_lenTester_map[v]; + for (ptr_vector::iterator it = indicator_set.begin(); it != indicator_set.end(); ++it) { + expr * indicator = *it; + if (internal_variable_set.find(indicator) != internal_variable_set.end()) { + map_effectively_empty = false; + break; + } + } + } + + if (map_effectively_empty) { + TRACE("t_str_detail", tout << "no existing length testers for " << mk_pp(v, m) << std::endl;); + rational v_len; + if (get_len_value(v, v_len)) { + TRACE("t_str_detail", tout << "length = " << v_len.to_string() << std::endl;); + } else { + expr_ref vLengthExpr(mk_strlen(v), m); + + rational v_lower_bound; + bool lower_bound_exists = lower_bound(vLengthExpr, v_lower_bound); + rational v_upper_bound; + bool upper_bound_exists = upper_bound(vLengthExpr, v_upper_bound); + TRACE("t_str_detail", tout << "bounds = [" << (lower_bound_exists?v_lower_bound.to_string():"?") + << ".." << (upper_bound_exists?v_upper_bound.to_string():"?") << "]" << std::endl;); + + // make sure the bounds are non-negative + if (lower_bound_exists && v_lower_bound.is_neg()) { + v_lower_bound = rational::zero(); + } + if (upper_bound_exists && v_upper_bound.is_neg()) { + v_upper_bound = rational::zero(); + } + + if (lower_bound_exists && upper_bound_exists) { + // easiest case. we will search within these bounds + } else if (upper_bound_exists && !lower_bound_exists) { + // search between 0 and the upper bound + v_lower_bound == rational::zero(); + } else if (lower_bound_exists && !upper_bound_exists) { + // check some finite portion of the search space + // TODO here and below, factor out the increment to a param + v_upper_bound = v_lower_bound + rational(10); + } else { + // no bounds information + v_lower_bound = rational::zero(); + v_upper_bound = v_lower_bound + rational(10); + } + // now create a fake length tester over this finite disjunction of lengths + + fvar_len_count_map[v] = 1; + unsigned int testNum = fvar_len_count_map[v]; + + expr_ref indicator(mk_internal_lenTest_var(v, testNum), m); + SASSERT(indicator); + m_trail.push_back(indicator); + + fvar_lenTester_map[v].shrink(0); + fvar_lenTester_map[v].push_back(indicator); + lenTester_fvar_map[indicator] = v; + + expr_ref_vector orList(m); + expr_ref_vector andList(m); + + for (rational l = v_lower_bound; l <= v_upper_bound; l += rational::one()) { + // TODO integrate with the enhancements in gen_len_test_options() + std::string lStr = l.to_string(); + expr_ref str_indicator(m_strutil.mk_string(lStr), m); + expr_ref or_expr(ctx.mk_eq_atom(indicator, str_indicator), m); + orList.push_back(or_expr); + expr_ref and_expr(ctx.mk_eq_atom(or_expr, ctx.mk_eq_atom(vLengthExpr, m_autil.mk_numeral(l, true))), m); + andList.push_back(and_expr); + } + andList.push_back(mk_or(orList)); + expr_ref implLhs(ctx.mk_eq_atom(testvar, str), m); + expr_ref implRhs(mk_and(andList), m); + assert_implication(implLhs, implRhs); + } + } else { + // TODO figure out this case + NOT_IMPLEMENTED_YET(); + } + } + } // foreach (v in vars) + } // (s == "yes") +} + void theory_str::more_len_tests(expr * lenTester, std::string lenTesterValue) { ast_manager & m = get_manager(); if (lenTester_fvar_map.find(lenTester) != lenTester_fvar_map.end()) { @@ -6666,6 +6813,15 @@ void theory_str::handle_equality(expr * lhs, expr * rhs) { return; } + if (m_params.m_FiniteOverlapModels && !finite_model_test_varlists.empty()) { + // TODO NEXT + if (finite_model_test_varlists.contains(lhs)) { + finite_model_test(lhs, rhs); return; + } else if (finite_model_test_varlists.contains(rhs)) { + finite_model_test(rhs, lhs); return; + } + } + if (free_var_attempt(lhs, rhs) || free_var_attempt(rhs, lhs)) { return; } diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 7f1e1dd9c..050593691 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -335,6 +335,10 @@ namespace smt { // maps a length tester to the next length tester to be (re)used if the split is "high" obj_map binary_search_next_var_high; + // finite model finding data + // maps a finite model tester var to a list of variables that will be tested + obj_map > finite_model_test_varlists; + protected: void assert_axiom(expr * e); void assert_implication(expr * premise, expr * conclusion); @@ -588,6 +592,8 @@ namespace smt { // TESTING void refresh_theory_var(expr * e); + void finite_model_test(expr * v, expr * c); + public: theory_str(ast_manager & m, theory_str_params const & params); virtual ~theory_str(); From 0af834421faa801faa1010b20e085cde4fc4780e Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Mon, 16 Jan 2017 18:24:47 -0500 Subject: [PATCH 317/410] finite model finding for other concat cases in theory_str --- src/smt/theory_str.cpp | 378 ++++++++++++++++++++++++----------------- src/smt/theory_str.h | 1 + 2 files changed, 220 insertions(+), 159 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 4ff80a613..5313b08ce 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -3126,33 +3126,33 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { // Type 0: M cuts Y. // len(x) < len(m) || len(y) > len(n) //-------------------------------------- + expr_ref_vector ax_l_items(mgr); + expr_ref_vector ax_r_items(mgr); + + ax_l_items.push_back(ctx.mk_eq_atom(concatAst1, concatAst2)); + + expr_ref x_t1(mk_concat(x, t1), mgr); + expr_ref t1_n(mk_concat(t1, n), mgr); + + ax_r_items.push_back(ctx.mk_eq_atom(m, x_t1)); + ax_r_items.push_back(ctx.mk_eq_atom(y, t1_n)); + + if (m_len_exists && x_len_exists) { + ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_int(x_len))); + ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(m), mk_int(m_len))); + rational m_sub_x = m_len - x_len; + ax_r_items.push_back(ctx.mk_eq_atom(mk_strlen(t1), mk_int(m_sub_x))); + } else { + ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_int(y_len))); + ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(n), mk_int(n_len))); + rational y_sub_n = y_len - n_len; + ax_r_items.push_back(ctx.mk_eq_atom(mk_strlen(t1), mk_int(y_sub_n))); + } + + expr_ref ax_l(mk_and(ax_l_items), mgr); + expr_ref ax_r(mk_and(ax_r_items), mgr); + if (!has_self_cut(m, y)) { - expr_ref_vector ax_l_items(mgr); - expr_ref_vector ax_r_items(mgr); - - ax_l_items.push_back(ctx.mk_eq_atom(concatAst1, concatAst2)); - - expr_ref x_t1(mk_concat(x, t1), mgr); - expr_ref t1_n(mk_concat(t1, n), mgr); - - ax_r_items.push_back(ctx.mk_eq_atom(m, x_t1)); - ax_r_items.push_back(ctx.mk_eq_atom(y, t1_n)); - - if (m_len_exists && x_len_exists) { - ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_int(x_len))); - ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(m), mk_int(m_len))); - rational m_sub_x = m_len - x_len; - ax_r_items.push_back(ctx.mk_eq_atom(mk_strlen(t1), mk_int(m_sub_x))); - } else { - ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_int(y_len))); - ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(n), mk_int(n_len))); - rational y_sub_n = y_len - n_len; - ax_r_items.push_back(ctx.mk_eq_atom(mk_strlen(t1), mk_int(y_sub_n))); - } - - expr_ref ax_l(mk_and(ax_l_items), mgr); - expr_ref ax_r(mk_and(ax_r_items), mgr); - // Cut Info add_cut_info_merge(t1, sLevel, m); add_cut_info_merge(t1, sLevel, y); @@ -3165,8 +3165,14 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { } } else { loopDetected = true; - TRACE("t_str", tout << "AVOID LOOP: SKIPPED" << std::endl;); - // TODO printCutVar(m, y); + if (m_params.m_FiniteOverlapModels) { + expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2); + assert_implication(ax_l, tester); + add_theory_aware_branching_info(tester, -0.1, l_true); + } else { + TRACE("t_str", tout << "AVOID LOOP: SKIPPED" << std::endl;); + // TODO printCutVar(m, y); + } } } else if (splitType == 1) { // Type 1: @@ -3179,32 +3185,32 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { } else if (splitType == 2) { // Type 2: X cuts N. // len(x) > len(m) || len(y) < len(n) + expr_ref m_t2(mk_concat(m, t2), mgr); + expr_ref t2_y(mk_concat(t2, y), mgr); + + expr_ref_vector ax_l_items(mgr); + ax_l_items.push_back(ctx.mk_eq_atom(concatAst1, concatAst2)); + + expr_ref_vector ax_r_items(mgr); + ax_r_items.push_back(ctx.mk_eq_atom(x, m_t2)); + ax_r_items.push_back(ctx.mk_eq_atom(t2_y, n)); + + if (m_len_exists && x_len_exists) { + ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_int(x_len))); + ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(m), mk_int(m_len))); + rational x_sub_m = x_len - m_len; + ax_r_items.push_back(ctx.mk_eq_atom(mk_strlen(t2), mk_int(x_sub_m))); + } else { + ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_int(y_len))); + ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(n), mk_int(n_len))); + rational n_sub_y = n_len - y_len; + ax_r_items.push_back(ctx.mk_eq_atom(mk_strlen(t2), mk_int(n_sub_y))); + } + + expr_ref ax_l(mk_and(ax_l_items), mgr); + expr_ref ax_r(mk_and(ax_r_items), mgr); + if (!has_self_cut(x, n)) { - expr_ref m_t2(mk_concat(m, t2), mgr); - expr_ref t2_y(mk_concat(t2, y), mgr); - - expr_ref_vector ax_l_items(mgr); - ax_l_items.push_back(ctx.mk_eq_atom(concatAst1, concatAst2)); - - expr_ref_vector ax_r_items(mgr); - ax_r_items.push_back(ctx.mk_eq_atom(x, m_t2)); - ax_r_items.push_back(ctx.mk_eq_atom(t2_y, n)); - - if (m_len_exists && x_len_exists) { - ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_int(x_len))); - ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(m), mk_int(m_len))); - rational x_sub_m = x_len - m_len; - ax_r_items.push_back(ctx.mk_eq_atom(mk_strlen(t2), mk_int(x_sub_m))); - } else { - ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_int(y_len))); - ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(n), mk_int(n_len))); - rational n_sub_y = n_len - y_len; - ax_r_items.push_back(ctx.mk_eq_atom(mk_strlen(t2), mk_int(n_sub_y))); - } - - expr_ref ax_l(mk_and(ax_l_items), mgr); - expr_ref ax_r(mk_and(ax_r_items), mgr); - // Cut Info add_cut_info_merge(t2, sLevel, x); add_cut_info_merge(t2, sLevel, n); @@ -3217,8 +3223,14 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { } } else { loopDetected = true; - TRACE("t_str", tout << "AVOID LOOP: SKIPPED" << std::endl;); - // TODO printCutVar(m, y); + if (m_params.m_FiniteOverlapModels) { + expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2); + assert_implication(ax_l, tester); + add_theory_aware_branching_info(tester, -0.1, l_true); + } else { + TRACE("t_str", tout << "AVOID LOOP: SKIPPED" << std::endl;); + // TODO printCutVar(m, y); + } } } else if (splitType == -1) { // Here we don't really have a choice. We have no length information at all... @@ -3265,12 +3277,19 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { add_cut_info_merge(t1, ctx.get_scope_level(), y); } else { loopDetected = true; - TRACE("t_str", tout << "AVOID LOOP: SKIPPED" << std::endl;); - TRACE("t_str_detail", {print_cut_var(m, tout); print_cut_var(y, tout);}); + if (m_params.m_FiniteOverlapModels) { + expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2); + arrangement_disjunction.push_back(tester); + add_theory_aware_branching_info(tester, -0.1, l_true); + } else { + TRACE("t_str", tout << "AVOID LOOP: SKIPPED" << std::endl;); + TRACE("t_str_detail", {print_cut_var(m, tout); print_cut_var(y, tout);}); + } } // break option 2: - // x = m || y = n + // x = m . t2 + // n = t2 . y if (!avoidLoopCut || !has_self_cut(x, n)) { expr_ref_vector and_item(mgr); // break down option 1-2 @@ -3302,10 +3321,19 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { add_cut_info_merge(t2, ctx.get_scope_level(), n); } else { loopDetected = true; - TRACE("t_str", tout << "AVOID LOOP: SKIPPED" << std::endl;); - // TODO printCutVar(x, n); + if (m_params.m_FiniteOverlapModels) { + // TODO this might repeat the case above, we may wish to avoid doing this twice + expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2); + arrangement_disjunction.push_back(tester); + add_theory_aware_branching_info(tester, -0.1, l_true); + } else { + TRACE("t_str", tout << "AVOID LOOP: SKIPPED" << std::endl;); + // TODO printCutVar(x, n); + } } + // option 3: + // x = m, y = n if (can_two_nodes_eq(x, m) && can_two_nodes_eq(y, n)) { expr_ref_vector and_item(mgr); @@ -3496,31 +3524,31 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { // | m | str | expr_ref temp1_strAst(mk_concat(temp1, strAst), mgr); if (can_two_nodes_eq(y, temp1_strAst)) { + expr_ref_vector l_items(mgr); + l_items.push_back(ctx.mk_eq_atom(concatAst1, concatAst2)); + + expr_ref_vector r_items(mgr); + expr_ref x_temp1(mk_concat(x, temp1), mgr); + r_items.push_back(ctx.mk_eq_atom(m, x_temp1)); + r_items.push_back(ctx.mk_eq_atom(y, temp1_strAst)); + + if (x_len_exists && m_len_exists) { + l_items.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_int(x_len))); + l_items.push_back(ctx.mk_eq_atom(mk_strlen(m), mk_int(m_len))); + rational m_sub_x = (m_len - x_len); + r_items.push_back(ctx.mk_eq_atom(mk_strlen(temp1), mk_int(m_sub_x))); + } else { + l_items.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_int(y_len))); + l_items.push_back(ctx.mk_eq_atom(mk_strlen(strAst), mk_int(str_len))); + rational y_sub_str = (y_len - str_len); + r_items.push_back(ctx.mk_eq_atom(mk_strlen(temp1), mk_int(y_sub_str))); + } + + expr_ref ax_l(mk_and(l_items), mgr); + expr_ref ax_r(mk_and(r_items), mgr); + if (!avoidLoopCut || !(has_self_cut(m, y))) { // break down option 2-1 - expr_ref_vector l_items(mgr); - l_items.push_back(ctx.mk_eq_atom(concatAst1, concatAst2)); - - expr_ref_vector r_items(mgr); - expr_ref x_temp1(mk_concat(x, temp1), mgr); - r_items.push_back(ctx.mk_eq_atom(m, x_temp1)); - r_items.push_back(ctx.mk_eq_atom(y, temp1_strAst)); - - if (x_len_exists && m_len_exists) { - l_items.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_int(x_len))); - l_items.push_back(ctx.mk_eq_atom(mk_strlen(m), mk_int(m_len))); - rational m_sub_x = (m_len - x_len); - r_items.push_back(ctx.mk_eq_atom(mk_strlen(temp1), mk_int(m_sub_x))); - } else { - l_items.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_int(y_len))); - l_items.push_back(ctx.mk_eq_atom(mk_strlen(strAst), mk_int(str_len))); - rational y_sub_str = (y_len - str_len); - r_items.push_back(ctx.mk_eq_atom(mk_strlen(temp1), mk_int(y_sub_str))); - } - - expr_ref ax_l(mk_and(l_items), mgr); - expr_ref ax_r(mk_and(r_items), mgr); - add_cut_info_merge(temp1, sLevel, y); add_cut_info_merge(temp1, sLevel, m); @@ -3532,8 +3560,15 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { } } else { loopDetected = true; - TRACE("t_str", tout << "AVOID LOOP: SKIP" << std::endl;); - // TODO printCutVar(m, y); + + if (m_params.m_FiniteOverlapModels) { + expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2); + assert_implication(ax_l, tester); + add_theory_aware_branching_info(tester, -0.1, l_true); + } else { + TRACE("t_str", tout << "AVOID LOOP: SKIP" << std::endl;); + // TODO printCutVar(m, y); + } } } } else if (splitType == 1) { @@ -3634,8 +3669,14 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { add_cut_info_merge(temp1, ctx.get_scope_level(), m); } else { loopDetected = true; - TRACE("t_str", tout << "AVOID LOOP: SKIPPED" << std::endl;); - // TODO printCutVar(m, y) + if (m_params.m_FiniteOverlapModels) { + expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2); + arrangement_disjunction.push_back(tester); + add_theory_aware_branching_info(tester, -0.1, l_true); + } else { + TRACE("t_str", tout << "AVOID LOOP: SKIPPED" << std::endl;); + // TODO printCutVar(m, y) + } } } @@ -3921,8 +3962,14 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { } } else { loopDetected = true; - TRACE("t_str", tout << "AVOID LOOP: SKIPPED" << std::endl;); - // TODO printCutVar(x, n); + if (m_params.m_FiniteOverlapModels) { + expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2); + assert_implication(ax_l, tester); + add_theory_aware_branching_info(tester, -0.1, l_true); + } else { + TRACE("t_str", tout << "AVOID LOOP: SKIPPED" << std::endl;); + // TODO printCutVar(x, n); + } } } // else { @@ -3995,8 +4042,14 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { add_cut_info_merge(temp1, sLevel, n); } else { loopDetected = true; - TRACE("t_str", tout << "AVOID LOOP: SKIPPED." << std::endl;); - // TODO printCutVAR(x, n) + if (m_params.m_FiniteOverlapModels) { + expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2); + arrangement_disjunction.push_back(tester); + add_theory_aware_branching_info(tester, -0.1, l_true); + } else { + TRACE("t_str", tout << "AVOID LOOP: SKIPPED." << std::endl;); + // TODO printCutVAR(x, n) + } } } @@ -4396,41 +4449,9 @@ void theory_str::process_concat_eq_type6(expr * concatAst1, expr * concatAst2) { loopDetected = true; if (m_params.m_FiniteOverlapModels) { - // TODO refactor this entire segment into its own method. this is really just for experiment purposes - TRACE("t_str", tout << "activating finite model testing for overlapping concats " - << mk_pp(concatAst1, mgr) << " and " << mk_pp(concatAst2, mgr) << std::endl;); - std::map concatMap; - std::map unrollMap; - std::map varMap; - classify_ast_by_type(concatAst1, varMap, concatMap, unrollMap); - classify_ast_by_type(concatAst2, varMap, concatMap, unrollMap); - TRACE("t_str_detail", tout << "found vars:"; - for (std::map::iterator it = varMap.begin(); it != varMap.end(); ++it) { - tout << " " << mk_pp(it->first, mgr); - } - tout << std::endl; - ); - - expr_ref testvar(mk_str_var("finiteModelTest"), mgr); - m_trail.push_back(testvar); - ptr_vector varlist; - - for (std::map::iterator it = varMap.begin(); it != varMap.end(); ++it) { - expr * v = it->first; - varlist.push_back(v); - } - - // make things easy for the core wrt. testvar - expr_ref t1(ctx.mk_eq_atom(testvar, m_strutil.mk_string("")), mgr); - expr_ref t_yes(ctx.mk_eq_atom(testvar, m_strutil.mk_string("yes")), mgr); - expr_ref testvaraxiom(mgr.mk_or(t1, t_yes), mgr); - assert_axiom(testvaraxiom); - - finite_model_test_varlists.insert(testvar, varlist); - m_trail_stack.push(insert_obj_map >(finite_model_test_varlists, testvar) ); - - arrangement_disjunction.push_back(t_yes); - add_theory_aware_branching_info(t_yes, -0.1, l_true); + expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2); + arrangement_disjunction.push_back(tester); + add_theory_aware_branching_info(tester, -0.1, l_true); } else { TRACE("t_str", tout << "AVOID LOOP: SKIPPED." << std::endl;); TRACE("t_str", print_cut_var(m, tout); print_cut_var(y, tout);); @@ -6603,6 +6624,44 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { } } +expr_ref theory_str::set_up_finite_model_test(expr * lhs, expr * rhs) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + TRACE("t_str", tout << "activating finite model testing for overlapping concats " + << mk_pp(lhs, m) << " and " << mk_pp(rhs, m) << std::endl;); + std::map concatMap; + std::map unrollMap; + std::map varMap; + classify_ast_by_type(lhs, varMap, concatMap, unrollMap); + classify_ast_by_type(rhs, varMap, concatMap, unrollMap); + TRACE("t_str_detail", tout << "found vars:"; + for (std::map::iterator it = varMap.begin(); it != varMap.end(); ++it) { + tout << " " << mk_pp(it->first, m); + } + tout << std::endl; + ); + + expr_ref testvar(mk_str_var("finiteModelTest"), m); + m_trail.push_back(testvar); + ptr_vector varlist; + + for (std::map::iterator it = varMap.begin(); it != varMap.end(); ++it) { + expr * v = it->first; + varlist.push_back(v); + } + + // make things easy for the core wrt. testvar + expr_ref t1(ctx.mk_eq_atom(testvar, m_strutil.mk_string("")), m); + expr_ref t_yes(ctx.mk_eq_atom(testvar, m_strutil.mk_string("yes")), m); + expr_ref testvaraxiom(m.mk_or(t1, t_yes), m); + assert_axiom(testvaraxiom); + + finite_model_test_varlists.insert(testvar, varlist); + m_trail_stack.push(insert_obj_map >(finite_model_test_varlists, testvar) ); + return t_yes; +} + void theory_str::finite_model_test(expr * testvar, expr * str) { context & ctx = get_context(); ast_manager & m = get_manager(); @@ -6638,14 +6697,15 @@ void theory_str::finite_model_test(expr * testvar, expr * str) { if (map_effectively_empty) { TRACE("t_str_detail", tout << "no existing length testers for " << mk_pp(v, m) << std::endl;); rational v_len; + rational v_lower_bound; + rational v_upper_bound; + expr_ref vLengthExpr(mk_strlen(v), m); if (get_len_value(v, v_len)) { TRACE("t_str_detail", tout << "length = " << v_len.to_string() << std::endl;); + v_lower_bound = v_len; + v_upper_bound = v_len; } else { - expr_ref vLengthExpr(mk_strlen(v), m); - - rational v_lower_bound; bool lower_bound_exists = lower_bound(vLengthExpr, v_lower_bound); - rational v_upper_bound; bool upper_bound_exists = upper_bound(vLengthExpr, v_upper_bound); TRACE("t_str_detail", tout << "bounds = [" << (lower_bound_exists?v_lower_bound.to_string():"?") << ".." << (upper_bound_exists?v_upper_bound.to_string():"?") << "]" << std::endl;); @@ -6672,36 +6732,36 @@ void theory_str::finite_model_test(expr * testvar, expr * str) { v_lower_bound = rational::zero(); v_upper_bound = v_lower_bound + rational(10); } - // now create a fake length tester over this finite disjunction of lengths - - fvar_len_count_map[v] = 1; - unsigned int testNum = fvar_len_count_map[v]; - - expr_ref indicator(mk_internal_lenTest_var(v, testNum), m); - SASSERT(indicator); - m_trail.push_back(indicator); - - fvar_lenTester_map[v].shrink(0); - fvar_lenTester_map[v].push_back(indicator); - lenTester_fvar_map[indicator] = v; - - expr_ref_vector orList(m); - expr_ref_vector andList(m); - - for (rational l = v_lower_bound; l <= v_upper_bound; l += rational::one()) { - // TODO integrate with the enhancements in gen_len_test_options() - std::string lStr = l.to_string(); - expr_ref str_indicator(m_strutil.mk_string(lStr), m); - expr_ref or_expr(ctx.mk_eq_atom(indicator, str_indicator), m); - orList.push_back(or_expr); - expr_ref and_expr(ctx.mk_eq_atom(or_expr, ctx.mk_eq_atom(vLengthExpr, m_autil.mk_numeral(l, true))), m); - andList.push_back(and_expr); - } - andList.push_back(mk_or(orList)); - expr_ref implLhs(ctx.mk_eq_atom(testvar, str), m); - expr_ref implRhs(mk_and(andList), m); - assert_implication(implLhs, implRhs); } + // now create a fake length tester over this finite disjunction of lengths + + fvar_len_count_map[v] = 1; + unsigned int testNum = fvar_len_count_map[v]; + + expr_ref indicator(mk_internal_lenTest_var(v, testNum), m); + SASSERT(indicator); + m_trail.push_back(indicator); + + fvar_lenTester_map[v].shrink(0); + fvar_lenTester_map[v].push_back(indicator); + lenTester_fvar_map[indicator] = v; + + expr_ref_vector orList(m); + expr_ref_vector andList(m); + + for (rational l = v_lower_bound; l <= v_upper_bound; l += rational::one()) { + // TODO integrate with the enhancements in gen_len_test_options() + std::string lStr = l.to_string(); + expr_ref str_indicator(m_strutil.mk_string(lStr), m); + expr_ref or_expr(ctx.mk_eq_atom(indicator, str_indicator), m); + orList.push_back(or_expr); + expr_ref and_expr(ctx.mk_eq_atom(or_expr, ctx.mk_eq_atom(vLengthExpr, m_autil.mk_numeral(l, true))), m); + andList.push_back(and_expr); + } + andList.push_back(mk_or(orList)); + expr_ref implLhs(ctx.mk_eq_atom(testvar, str), m); + expr_ref implRhs(mk_and(andList), m); + assert_implication(implLhs, implRhs); } else { // TODO figure out this case NOT_IMPLEMENTED_YET(); diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 050593691..3bb093dcd 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -592,6 +592,7 @@ namespace smt { // TESTING void refresh_theory_var(expr * e); + expr_ref set_up_finite_model_test(expr * lhs, expr * rhs); void finite_model_test(expr * v, expr * c); public: From 794e210958df94b313fae287cbfa8951d1c712d7 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Mon, 16 Jan 2017 21:42:11 -0500 Subject: [PATCH 318/410] finite model fix --- src/smt/theory_str.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 5313b08ce..e91709962 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -6673,6 +6673,12 @@ void theory_str::finite_model_test(expr * testvar, expr * str) { ptr_vector & vars = finite_model_test_varlists[testvar]; for (ptr_vector::iterator it = vars.begin(); it != vars.end(); ++it) { expr * v = *it; + bool v_has_eqc = false; + get_eqc_value(v, v_has_eqc); + if (v_has_eqc) { + TRACE("t_str_detail", tout << "variable " << mk_pp(v,m) << " already equivalent to a string constant" << std::endl;); + continue; + } // check for any sort of existing length tester we might interfere with if (m_params.m_UseBinarySearch) { NOT_IMPLEMENTED_YET(); @@ -6763,8 +6769,8 @@ void theory_str::finite_model_test(expr * testvar, expr * str) { expr_ref implRhs(mk_and(andList), m); assert_implication(implLhs, implRhs); } else { - // TODO figure out this case - NOT_IMPLEMENTED_YET(); + TRACE("t_str_detail", tout << "already found existing length testers for " << mk_pp(v, m) << std::endl;); + continue; } } } // foreach (v in vars) From a570149b57e30137d27f647ef59c83ca9fd793fa Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 17 Jan 2017 14:49:57 -0500 Subject: [PATCH 319/410] finite overlap models with binary search --- src/smt/theory_str.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index e91709962..3153fa337 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -6681,7 +6681,15 @@ void theory_str::finite_model_test(expr * testvar, expr * str) { } // check for any sort of existing length tester we might interfere with if (m_params.m_UseBinarySearch) { - NOT_IMPLEMENTED_YET(); + if (binary_search_len_tester_stack.contains(v) && !binary_search_len_tester_stack[v].empty()) { + TRACE("t_str_detail", tout << "already found existing length testers for " << mk_pp(v, m) << std::endl;); + continue; + } else { + // start binary search as normal + expr_ref implLhs(ctx.mk_eq_atom(testvar, str), m); + expr_ref implRhs(binary_search_length_test(v, NULL, ""), m); + assert_implication(implLhs, implRhs); + } } else { bool map_effectively_empty = false; if (fvar_len_count_map.find(v) == fvar_len_count_map.end()) { From 50e2273dbdd67b8d7fa8940f3411d3b0a9d93d57 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Fri, 20 Jan 2017 17:39:32 -0500 Subject: [PATCH 320/410] substr bugfix --- src/ast/rewriter/str_rewriter.cpp | 45 ++++++++++++++--- src/smt/theory_str.cpp | 81 +++++++++++++++++++++++++++++++ 2 files changed, 118 insertions(+), 8 deletions(-) diff --git a/src/ast/rewriter/str_rewriter.cpp b/src/ast/rewriter/str_rewriter.cpp index bc64e7218..2e3c82613 100644 --- a/src/ast/rewriter/str_rewriter.cpp +++ b/src/ast/rewriter/str_rewriter.cpp @@ -431,18 +431,47 @@ br_status str_rewriter::mk_str_from_int(expr * arg0, expr_ref & result) { br_status str_rewriter::mk_str_Substr(expr * base, expr * start, expr * len, expr_ref & result) { TRACE("t_str_rw", tout << "rewrite (Substr " << mk_pp(base, m()) << " " << mk_pp(start, m()) << " " << mk_pp(len, m()) << ")" << std::endl;); - rational startVal, lenVal; - if (m_strutil.is_string(base) && m_autil.is_numeral(start, startVal) && m_autil.is_numeral(len, lenVal)) { - std::string baseStr = m_strutil.get_string_constant_value(base); - // TODO handling for invalid start/len - if (startVal.is_nonneg() && lenVal.is_nonneg() && startVal.get_unsigned() <= baseStr.length()) { - TRACE("t_str_rw", tout << "rewriting constant Substr expression" << std::endl;); - std::string substr = baseStr.substr(startVal.get_unsigned(), lenVal.get_unsigned()); - result = m_strutil.mk_string(substr); + + bool constant_base = m_strutil.is_string(base); + std::string baseStr; + if (constant_base) { + baseStr = m_strutil.get_string_constant_value(base); + } + rational startVal; + bool constant_start = m_autil.is_numeral(start, startVal); + rational lenVal; + bool constant_len = m_autil.is_numeral(len, lenVal); + + // case 1: start < 0 or len < 0 + if ( (constant_start && startVal.is_neg()) || (constant_len && lenVal.is_neg()) ) { + TRACE("t_str_rw", tout << "start/len of substr is negative" << std::endl;); + result = m_strutil.mk_string(""); + return BR_DONE; + } + // case 1.1: start >= length(base) + if (constant_start && constant_base) { + rational baseStrlen((unsigned int)baseStr.length()); + if (startVal >= baseStrlen) { + TRACE("t_str_rw", tout << "start >= strlen for substr" << std::endl;); + result = m_strutil.mk_string(""); return BR_DONE; } } + if (constant_base && constant_start && constant_len) { + rational baseStrlen((unsigned int)baseStr.length()); + std::string retval; + if (startVal + lenVal >= baseStrlen) { + // case 2: pos+len goes past the end of the string + retval = baseStr.substr(startVal.get_unsigned(), std::string::npos); + } else { + // case 3: pos+len still within string + retval = baseStr.substr(startVal.get_unsigned(), lenVal.get_unsigned()); + } + result = m_strutil.mk_string(retval); + return BR_DONE; + } + return BR_FAILED; } diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 3153fa337..706f2cd73 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -1579,6 +1579,86 @@ void theory_str::instantiate_axiom_Substr(enode * e) { TRACE("t_str_detail", tout << "instantiate Substr axiom for " << mk_pp(expr, m) << std::endl;); + expr_ref substrBase(expr->get_arg(0), m); + expr_ref substrPos(expr->get_arg(1), m); + expr_ref substrLen(expr->get_arg(2), m); + SASSERT(substrBase); + SASSERT(substrPos); + SASSERT(substrLen); + + expr_ref zero(m_autil.mk_numeral(rational::zero(), true), m); + expr_ref minusOne(m_autil.mk_numeral(rational::minus_one(), true), m); + SASSERT(zero); + SASSERT(minusOne); + + expr_ref_vector argumentsValid_terms(m); + // pos >= 0 + argumentsValid_terms.push_back(m_autil.mk_ge(substrPos, zero)); + // pos < strlen(base) + // --> pos + -1*strlen(base) < 0 + argumentsValid_terms.push_back(m.mk_not(m_autil.mk_ge( + m_autil.mk_add(substrPos, m_autil.mk_mul(minusOne, substrLen)), + zero))); + // len >= 0 + argumentsValid_terms.push_back(m_autil.mk_ge(substrLen, zero)); + + expr_ref argumentsValid(mk_and(argumentsValid_terms), m); + SASSERT(argumentsValid); + ctx.internalize(argumentsValid, false); + + // (pos+len) >= strlen(base) + // --> pos + len + -1*strlen(base) >= 0 + expr_ref lenOutOfBounds(m_autil.mk_ge( + m_autil.mk_add(substrPos, substrLen, m_autil.mk_mul(minusOne, mk_strlen(substrBase))), + zero), m); + SASSERT(lenOutOfBounds); + ctx.internalize(argumentsValid, false); + + // Case 1: pos < 0 or pos >= strlen(base) or len < 0 + // ==> (Substr ...) = "" + expr_ref case1_premise(m.mk_not(argumentsValid), m); + SASSERT(case1_premise); + ctx.internalize(case1_premise, false); + expr_ref case1_conclusion(ctx.mk_eq_atom(expr, mk_string("")), m); + SASSERT(case1_conclusion); + ctx.internalize(case1_conclusion, false); + expr_ref case1(rewrite_implication(case1_premise, case1_conclusion), m); + SASSERT(case1); + + // Case 2: (pos >= 0 and pos < strlen(base) and len >= 0) and (pos+len) >= strlen(base) + // ==> base = t0.t1 AND len(t0) = pos AND (Substr ...) = t1 + expr_ref t0(mk_str_var("t0"), m); + expr_ref t1(mk_str_var("t1"), m); + expr_ref case2_conclusion(m.mk_and( + ctx.mk_eq_atom(substrBase, mk_concat(t0,t1)), + ctx.mk_eq_atom(mk_strlen(t0), substrPos), + ctx.mk_eq_atom(expr, t1)), m); + expr_ref case2(rewrite_implication(m.mk_and(argumentsValid, lenOutOfBounds), case2_conclusion), m); + SASSERT(case2); + + // Case 3: (pos >= 0 and pos < strlen(base) and len >= 0) and (pos+len) < strlen(base) + // ==> base = t2.t3.t4 AND len(t2) = pos AND len(t3) = len AND (Substr ...) = t3 + expr_ref t2(mk_str_var("t2"), m); + expr_ref t3(mk_str_var("t3"), m); + expr_ref t4(mk_str_var("t4"), m); + expr_ref_vector case3_conclusion_terms(m); + case3_conclusion_terms.push_back(ctx.mk_eq_atom(substrBase, mk_concat(t2, mk_concat(t3, t4)))); + case3_conclusion_terms.push_back(ctx.mk_eq_atom(mk_strlen(t2), substrPos)); + case3_conclusion_terms.push_back(ctx.mk_eq_atom(mk_strlen(t3), substrLen)); + case3_conclusion_terms.push_back(ctx.mk_eq_atom(expr, t3)); + expr_ref case3_conclusion(mk_and(case3_conclusion_terms), m); + expr_ref case3(rewrite_implication(m.mk_and(argumentsValid, m.mk_not(lenOutOfBounds)), case3_conclusion), m); + SASSERT(case3); + + ctx.internalize(case1, false); + ctx.internalize(case2, false); + ctx.internalize(case3, false); + + expr_ref finalAxiom(m.mk_and(case1, case2, case3), m); + SASSERT(finalAxiom); + assert_axiom(finalAxiom); + + /* expr_ref ts0(mk_str_var("ts0"), m); expr_ref ts1(mk_str_var("ts1"), m); expr_ref ts2(mk_str_var("ts2"), m); @@ -1601,6 +1681,7 @@ void theory_str::instantiate_axiom_Substr(enode * e) { expr_ref finalAxiom(m.mk_and(breakdownAssert, reduceToVar), m); SASSERT(finalAxiom); assert_axiom(finalAxiom); + */ } void theory_str::instantiate_axiom_Replace(enode * e) { From 09ac5645e4c947e134e62534b4e44417a99bf771 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sun, 22 Jan 2017 23:21:20 -0500 Subject: [PATCH 321/410] parameterize theory-aware activity of overlap --- src/smt/params/smt_params_helper.pyg | 3 ++- src/smt/params/theory_str_params.cpp | 1 + src/smt/params/theory_str_params.h | 5 ++++- src/smt/theory_str.cpp | 18 +++++++++--------- 4 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/smt/params/smt_params_helper.pyg b/src/smt/params/smt_params_helper.pyg index e23915ab4..75ee20ebd 100644 --- a/src/smt/params/smt_params_helper.pyg +++ b/src/smt/params/smt_params_helper.pyg @@ -73,5 +73,6 @@ def_module_params(module_name='smt', ('str.binary_search_start', UINT, 64, 'initial upper bound for theory_str binary search'), ('theory_case_split', BOOL, False, 'Allow the context to use heuristics involving theory case splits, which are a set of literals of which exactly one can be assigned True. If this option is false, the context will generate extra axioms to enforce this instead.'), ('theory_aware_branching', BOOL, False, 'Allow the context to use extra information from theory solvers regarding literal branching prioritization.'), - ('str.finite_overlap_models', BOOL, False, 'attempt a finite model search for overlapping variables instead of completely giving up on the arrangement') + ('str.finite_overlap_models', BOOL, False, 'attempt a finite model search for overlapping variables instead of completely giving up on the arrangement'), + ('str.overlap_priority', DOUBLE, -0.1, 'theory-aware priority for overlapping variable cases; use smt.theory_aware_branching=true') )) diff --git a/src/smt/params/theory_str_params.cpp b/src/smt/params/theory_str_params.cpp index 46302cf82..f86cd9379 100644 --- a/src/smt/params/theory_str_params.cpp +++ b/src/smt/params/theory_str_params.cpp @@ -30,4 +30,5 @@ void theory_str_params::updt_params(params_ref const & _p) { m_FiniteOverlapModels = p.str_finite_overlap_models(); m_UseBinarySearch = p.str_use_binary_search(); m_BinarySearchInitialUpperBound = p.str_binary_search_start(); + m_OverlapTheoryAwarePriority = p.str_overlap_priority(); } diff --git a/src/smt/params/theory_str_params.h b/src/smt/params/theory_str_params.h index 4effb0897..de0945395 100644 --- a/src/smt/params/theory_str_params.h +++ b/src/smt/params/theory_str_params.h @@ -78,6 +78,8 @@ struct theory_str_params { bool m_UseBinarySearch; unsigned m_BinarySearchInitialUpperBound; + double m_OverlapTheoryAwarePriority; + theory_str_params(params_ref const & p = params_ref()): m_AssertStrongerArrangements(true), m_AggressiveLengthTesting(false), @@ -88,7 +90,8 @@ struct theory_str_params { m_StringConstantCache(true), m_FiniteOverlapModels(false), m_UseBinarySearch(false), - m_BinarySearchInitialUpperBound(64) + m_BinarySearchInitialUpperBound(64), + m_OverlapTheoryAwarePriority(-0.1) { updt_params(p); } diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 706f2cd73..138b7db9f 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -3249,7 +3249,7 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { if (m_params.m_FiniteOverlapModels) { expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2); assert_implication(ax_l, tester); - add_theory_aware_branching_info(tester, -0.1, l_true); + add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true); } else { TRACE("t_str", tout << "AVOID LOOP: SKIPPED" << std::endl;); // TODO printCutVar(m, y); @@ -3307,7 +3307,7 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { if (m_params.m_FiniteOverlapModels) { expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2); assert_implication(ax_l, tester); - add_theory_aware_branching_info(tester, -0.1, l_true); + add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true); } else { TRACE("t_str", tout << "AVOID LOOP: SKIPPED" << std::endl;); // TODO printCutVar(m, y); @@ -3361,7 +3361,7 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { if (m_params.m_FiniteOverlapModels) { expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2); arrangement_disjunction.push_back(tester); - add_theory_aware_branching_info(tester, -0.1, l_true); + add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true); } else { TRACE("t_str", tout << "AVOID LOOP: SKIPPED" << std::endl;); TRACE("t_str_detail", {print_cut_var(m, tout); print_cut_var(y, tout);}); @@ -3406,7 +3406,7 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { // TODO this might repeat the case above, we may wish to avoid doing this twice expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2); arrangement_disjunction.push_back(tester); - add_theory_aware_branching_info(tester, -0.1, l_true); + add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true); } else { TRACE("t_str", tout << "AVOID LOOP: SKIPPED" << std::endl;); // TODO printCutVar(x, n); @@ -3645,7 +3645,7 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { if (m_params.m_FiniteOverlapModels) { expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2); assert_implication(ax_l, tester); - add_theory_aware_branching_info(tester, -0.1, l_true); + add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true); } else { TRACE("t_str", tout << "AVOID LOOP: SKIP" << std::endl;); // TODO printCutVar(m, y); @@ -3753,7 +3753,7 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { if (m_params.m_FiniteOverlapModels) { expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2); arrangement_disjunction.push_back(tester); - add_theory_aware_branching_info(tester, -0.1, l_true); + add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true); } else { TRACE("t_str", tout << "AVOID LOOP: SKIPPED" << std::endl;); // TODO printCutVar(m, y) @@ -4046,7 +4046,7 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { if (m_params.m_FiniteOverlapModels) { expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2); assert_implication(ax_l, tester); - add_theory_aware_branching_info(tester, -0.1, l_true); + add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true); } else { TRACE("t_str", tout << "AVOID LOOP: SKIPPED" << std::endl;); // TODO printCutVar(x, n); @@ -4126,7 +4126,7 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { if (m_params.m_FiniteOverlapModels) { expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2); arrangement_disjunction.push_back(tester); - add_theory_aware_branching_info(tester, -0.1, l_true); + add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true); } else { TRACE("t_str", tout << "AVOID LOOP: SKIPPED." << std::endl;); // TODO printCutVAR(x, n) @@ -4532,7 +4532,7 @@ void theory_str::process_concat_eq_type6(expr * concatAst1, expr * concatAst2) { if (m_params.m_FiniteOverlapModels) { expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2); arrangement_disjunction.push_back(tester); - add_theory_aware_branching_info(tester, -0.1, l_true); + add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true); } else { TRACE("t_str", tout << "AVOID LOOP: SKIPPED." << std::endl;); TRACE("t_str", print_cut_var(m, tout); print_cut_var(y, tout);); From a879b240114e75938ac2668ccd8f178a87c94ad4 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Fri, 27 Jan 2017 16:26:30 -0500 Subject: [PATCH 322/410] add str.prefixof, str.suffixof in theory_str --- src/ast/rewriter/str_rewriter.cpp | 18 ++++++++++++++++++ src/ast/rewriter/str_rewriter.h | 2 ++ src/ast/str_decl_plugin.cpp | 14 ++++++++++++++ src/ast/str_decl_plugin.h | 15 +++++++++++++++ 4 files changed, 49 insertions(+) diff --git a/src/ast/rewriter/str_rewriter.cpp b/src/ast/rewriter/str_rewriter.cpp index 2e3c82613..3926e66e1 100644 --- a/src/ast/rewriter/str_rewriter.cpp +++ b/src/ast/rewriter/str_rewriter.cpp @@ -375,6 +375,18 @@ br_status str_rewriter::mk_str_Replace(expr * base, expr * source, expr * target } } +br_status str_rewriter::mk_str_prefixof(expr * pre, expr * full, expr_ref & result) { + TRACE("t_str_rw", tout << "rewrite (str.prefixof " << mk_pp(pre, m()) << " " << mk_pp(full, m()) << ")" << std::endl;); + result = m_strutil.mk_str_StartsWith(full, pre); + return BR_REWRITE_FULL; +} + +br_status str_rewriter::mk_str_suffixof(expr * post, expr * full, expr_ref & result) { + TRACE("t_str_rw", tout << "rewrite (str.suffixof" << mk_pp(post, m()) << " " << mk_pp(full, m()) << ")" << std::endl;); + result = m_strutil.mk_str_EndsWith(full, post); + return BR_REWRITE_FULL; +} + br_status str_rewriter::mk_str_to_int(expr * arg0, expr_ref & result) { TRACE("t_str_rw", tout << "rewrite (str.to-int " << mk_pp(arg0, m()) << ")" << std::endl;); @@ -623,6 +635,12 @@ br_status str_rewriter::mk_app_core(func_decl * f, unsigned num_args, expr * con case OP_STR_REPLACE: SASSERT(num_args == 3); return mk_str_Replace(args[0], args[1], args[2], result); + case OP_STR_PREFIXOF: + SASSERT(num_args == 2); + return mk_str_prefixof(args[0], args[1], result); + case OP_STR_SUFFIXOF: + SASSERT(num_args == 2); + return mk_str_suffixof(args[0], args[1], result); case OP_STR_STR2INT: SASSERT(num_args == 1); return mk_str_to_int(args[0], result); diff --git a/src/ast/rewriter/str_rewriter.h b/src/ast/rewriter/str_rewriter.h index 145c0193e..0494d4d1b 100644 --- a/src/ast/rewriter/str_rewriter.h +++ b/src/ast/rewriter/str_rewriter.h @@ -53,6 +53,8 @@ public: br_status mk_str_LastIndexof(expr * haystack, expr * needle, expr_ref & result); br_status mk_str_Replace(expr * base, expr * source, expr * target, expr_ref & result); br_status mk_str_Substr(expr * base, expr * start, expr * len, expr_ref & result); + br_status mk_str_prefixof(expr * pre, expr * full, expr_ref & result); + br_status mk_str_suffixof(expr * post, expr * full, expr_ref & result); br_status mk_str_to_int(expr * arg0, expr_ref & result); br_status mk_str_from_int(expr * arg0, expr_ref & result); diff --git a/src/ast/str_decl_plugin.cpp b/src/ast/str_decl_plugin.cpp index 8ac1f722f..60f50b5c4 100644 --- a/src/ast/str_decl_plugin.cpp +++ b/src/ast/str_decl_plugin.cpp @@ -38,6 +38,8 @@ str_decl_plugin::str_decl_plugin(): m_replace_decl(0), m_str2int_decl(0), m_int2str_decl(0), + m_prefixof_decl(0), + m_suffixof_decl(0), m_re_str2regex_decl(0), m_re_regexin_decl(0), m_re_regexconcat_decl(0), @@ -69,6 +71,8 @@ void str_decl_plugin::finalize(void) { DEC_REF(m_lastindexof_decl); DEC_REF(m_substr_decl); DEC_REF(m_replace_decl); + DEC_REF(m_prefixof_decl); + DEC_REF(m_suffixof_decl); DEC_REF(m_str2int_decl); DEC_REF(m_int2str_decl); DEC_REF(m_re_str2regex_decl); @@ -149,6 +153,12 @@ void str_decl_plugin::set_manager(ast_manager * m, family_id id) { m_manager->inc_ref(m_replace_decl); } + m_prefixof_decl = m->mk_func_decl(symbol("str.prefixof"), s, s, boolT, func_decl_info(id, OP_STR_PREFIXOF)); + m_manager->inc_ref(m_prefixof_decl); + + m_suffixof_decl = m->mk_func_decl(symbol("str.suffixof"), s, s, boolT, func_decl_info(id, OP_STR_SUFFIXOF)); + m_manager->inc_ref(m_suffixof_decl); + m_str2int_decl = m->mk_func_decl(symbol("str.to-int"), s, i, func_decl_info(id, OP_STR_STR2INT)); m_manager->inc_ref(m_str2int_decl); @@ -206,6 +216,8 @@ func_decl * str_decl_plugin::mk_func_decl(decl_kind k) { case OP_STR_LASTINDEXOF: return m_lastindexof_decl; case OP_STR_SUBSTR: return m_substr_decl; case OP_STR_REPLACE: return m_replace_decl; + case OP_STR_PREFIXOF: return m_prefixof_decl; + case OP_STR_SUFFIXOF: return m_suffixof_decl; case OP_STR_STR2INT: return m_str2int_decl; case OP_STR_INT2STR: return m_int2str_decl; case OP_RE_STR2REGEX: return m_re_str2regex_decl; @@ -281,6 +293,8 @@ void str_decl_plugin::get_op_names(svector & op_names, symbol cons op_names.push_back(builtin_name("LastIndexof", OP_STR_LASTINDEXOF)); op_names.push_back(builtin_name("Substring", OP_STR_SUBSTR)); op_names.push_back(builtin_name("Replace", OP_STR_REPLACE)); + op_names.push_back(builtin_name("str.prefixof", OP_STR_PREFIXOF)); + op_names.push_back(builtin_name("str.suffixof", OP_STR_SUFFIXOF)); op_names.push_back(builtin_name("str.to-int", OP_STR_STR2INT)); op_names.push_back(builtin_name("str.from-int", OP_STR_INT2STR)); op_names.push_back(builtin_name("Str2Reg", OP_RE_STR2REGEX)); diff --git a/src/ast/str_decl_plugin.h b/src/ast/str_decl_plugin.h index ff531e942..3ae034b45 100644 --- a/src/ast/str_decl_plugin.h +++ b/src/ast/str_decl_plugin.h @@ -41,6 +41,9 @@ enum str_op_kind { OP_STR_LASTINDEXOF, OP_STR_SUBSTR, OP_STR_REPLACE, + // SMT-LIB 2.5 standard operators -- these are rewritten to internal ones + OP_STR_PREFIXOF, + OP_STR_SUFFIXOF, // string-integer conversion OP_STR_STR2INT, OP_STR_INT2STR, OP_STR_PLACEHOLDER1, OP_STR_PLACEHOLDER2, @@ -78,6 +81,8 @@ protected: func_decl * m_replace_decl; func_decl * m_str2int_decl; func_decl * m_int2str_decl; + func_decl * m_prefixof_decl; + func_decl * m_suffixof_decl; func_decl * m_re_str2regex_decl; func_decl * m_re_regexin_decl; @@ -167,6 +172,16 @@ public: } app * mk_string_with_escape_characters(std::string & val); + app * mk_str_StartsWith(expr * haystack, expr * needle) { + expr * es[2] = {haystack, needle}; + return m_manager.mk_app(get_fid(), OP_STR_STARTSWITH, 2, es); + } + + app * mk_str_EndsWith(expr * haystack, expr * needle) { + expr * es[2] = {haystack, needle}; + return m_manager.mk_app(get_fid(), OP_STR_ENDSWITH, 2, es); + } + app * mk_re_Str2Reg(expr * s) { expr * es[1] = {s}; return m_manager.mk_app(get_fid(), OP_RE_STR2REGEX, 1, es); From fa1ec0b80f7c22ef1aef9a58b571d5dd9a18e5d7 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Fri, 27 Jan 2017 16:49:40 -0500 Subject: [PATCH 323/410] smtlib25 draft standard in theory_str --- src/ast/str_decl_plugin.cpp | 60 ++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/src/ast/str_decl_plugin.cpp b/src/ast/str_decl_plugin.cpp index 60f50b5c4..bd6d70ebe 100644 --- a/src/ast/str_decl_plugin.cpp +++ b/src/ast/str_decl_plugin.cpp @@ -112,12 +112,12 @@ void str_decl_plugin::set_manager(ast_manager * m, family_id id) { FIELD = m->mk_func_decl(symbol(NAME), SORT, SORT, SORT, func_decl_info(id, KIND)); \ m->inc_ref(FIELD) - MK_OP(m_concat_decl, "Concat", OP_STRCAT, s); + MK_OP(m_concat_decl, "str.++", OP_STRCAT, s); - m_length_decl = m->mk_func_decl(symbol("Length"), s, i, func_decl_info(id, OP_STRLEN)); + m_length_decl = m->mk_func_decl(symbol("str.len"), s, i, func_decl_info(id, OP_STRLEN)); m_manager->inc_ref(m_length_decl); - m_charat_decl = m->mk_func_decl(symbol("CharAt"), s, i, s, func_decl_info(id, OP_STR_CHARAT)); + m_charat_decl = m->mk_func_decl(symbol("str.at"), s, i, s, func_decl_info(id, OP_STR_CHARAT)); m_manager->inc_ref(m_charat_decl); m_startswith_decl = m->mk_func_decl(symbol("StartsWith"), s, s, boolT, func_decl_info(id, OP_STR_STARTSWITH)); @@ -126,10 +126,10 @@ void str_decl_plugin::set_manager(ast_manager * m, family_id id) { m_endswith_decl = m->mk_func_decl(symbol("EndsWith"), s, s, boolT, func_decl_info(id, OP_STR_ENDSWITH)); m_manager->inc_ref(m_endswith_decl); - m_contains_decl = m->mk_func_decl(symbol("Contains"), s, s, boolT, func_decl_info(id, OP_STR_CONTAINS)); + m_contains_decl = m->mk_func_decl(symbol("str.contains"), s, s, boolT, func_decl_info(id, OP_STR_CONTAINS)); m_manager->inc_ref(m_contains_decl); - m_indexof_decl = m->mk_func_decl(symbol("Indexof"), s, s, i, func_decl_info(id, OP_STR_INDEXOF)); + m_indexof_decl = m->mk_func_decl(symbol("str.indexof"), s, s, i, func_decl_info(id, OP_STR_INDEXOF)); m_manager->inc_ref(m_indexof_decl); { @@ -138,18 +138,18 @@ void str_decl_plugin::set_manager(ast_manager * m, family_id id) { m_manager->inc_ref(m_indexof2_decl); } - m_lastindexof_decl = m->mk_func_decl(symbol("LastIndexof"), s, s, i, func_decl_info(id, OP_STR_LASTINDEXOF)); + m_lastindexof_decl = m->mk_func_decl(symbol("str.lastindexof"), s, s, i, func_decl_info(id, OP_STR_LASTINDEXOF)); m_manager->inc_ref(m_lastindexof_decl); { sort * d[3] = {s, i, i }; - m_substr_decl = m->mk_func_decl(symbol("Substring"), 3, d, s, func_decl_info(id, OP_STR_SUBSTR)); + m_substr_decl = m->mk_func_decl(symbol("str.substr"), 3, d, s, func_decl_info(id, OP_STR_SUBSTR)); m_manager->inc_ref(m_substr_decl); } { sort * d[3] = {s, s, s}; - m_replace_decl = m->mk_func_decl(symbol("Replace"), 3, d, s, func_decl_info(id, OP_STR_REPLACE)); + m_replace_decl = m->mk_func_decl(symbol("str.replace"), 3, d, s, func_decl_info(id, OP_STR_REPLACE)); m_manager->inc_ref(m_replace_decl); } @@ -165,28 +165,28 @@ void str_decl_plugin::set_manager(ast_manager * m, family_id id) { m_int2str_decl = m->mk_func_decl(symbol("str.from-int"), i, s, func_decl_info(id, OP_STR_INT2STR)); m_manager->inc_ref(m_int2str_decl); - m_re_str2regex_decl = m->mk_func_decl(symbol("Str2Reg"), s, re, func_decl_info(id, OP_RE_STR2REGEX)); + m_re_str2regex_decl = m->mk_func_decl(symbol("str.to.re"), s, re, func_decl_info(id, OP_RE_STR2REGEX)); m_manager->inc_ref(m_re_str2regex_decl); - m_re_regexin_decl = m->mk_func_decl(symbol("RegexIn"), s, re, boolT, func_decl_info(id, OP_RE_REGEXIN)); + m_re_regexin_decl = m->mk_func_decl(symbol("str.in.re"), s, re, boolT, func_decl_info(id, OP_RE_REGEXIN)); m_manager->inc_ref(m_re_regexin_decl); - m_re_regexconcat_decl = m->mk_func_decl(symbol("RegexConcat"), re, re, re, func_decl_info(id, OP_RE_REGEXCONCAT)); + m_re_regexconcat_decl = m->mk_func_decl(symbol("re.++"), re, re, re, func_decl_info(id, OP_RE_REGEXCONCAT)); m_manager->inc_ref(m_re_regexconcat_decl); - m_re_regexstar_decl = m->mk_func_decl(symbol("RegexStar"), re, re, func_decl_info(id, OP_RE_REGEXSTAR)); + m_re_regexstar_decl = m->mk_func_decl(symbol("re.*"), re, re, func_decl_info(id, OP_RE_REGEXSTAR)); m_manager->inc_ref(m_re_regexstar_decl); - m_re_regexplus_decl = m->mk_func_decl(symbol("RegexPlus"), re, re, func_decl_info(id, OP_RE_REGEXPLUS)); + m_re_regexplus_decl = m->mk_func_decl(symbol("re.+"), re, re, func_decl_info(id, OP_RE_REGEXPLUS)); m_manager->inc_ref(m_re_regexplus_decl); - m_re_regexunion_decl = m->mk_func_decl(symbol("RegexUnion"), re, re, re, func_decl_info(id, OP_RE_REGEXUNION)); + m_re_regexunion_decl = m->mk_func_decl(symbol("re.union"), re, re, re, func_decl_info(id, OP_RE_REGEXUNION)); m_manager->inc_ref(m_re_regexunion_decl); m_re_unroll_decl = m->mk_func_decl(symbol("Unroll"), re, i, s, func_decl_info(id, OP_RE_UNROLL)); m_manager->inc_ref(m_re_unroll_decl); - m_re_regexcharrange_decl = m->mk_func_decl(symbol("RegexCharRange"), s, s, re, func_decl_info(id, OP_RE_REGEXCHARRANGE)); + m_re_regexcharrange_decl = m->mk_func_decl(symbol("re.range"), s, s, re, func_decl_info(id, OP_RE_REGEXCHARRANGE)); m_manager->inc_ref(m_re_regexcharrange_decl); } @@ -282,29 +282,29 @@ app * str_decl_plugin::mk_fresh_string() { } void str_decl_plugin::get_op_names(svector & op_names, symbol const & logic) { - op_names.push_back(builtin_name("Concat", OP_STRCAT)); - op_names.push_back(builtin_name("Length", OP_STRLEN)); - op_names.push_back(builtin_name("CharAt", OP_STR_CHARAT)); + op_names.push_back(builtin_name("str.++", OP_STRCAT)); + op_names.push_back(builtin_name("str.len", OP_STRLEN)); + op_names.push_back(builtin_name("str.at", OP_STR_CHARAT)); op_names.push_back(builtin_name("StartsWith", OP_STR_STARTSWITH)); op_names.push_back(builtin_name("EndsWith", OP_STR_ENDSWITH)); - op_names.push_back(builtin_name("Contains", OP_STR_CONTAINS)); - op_names.push_back(builtin_name("Indexof", OP_STR_INDEXOF)); + op_names.push_back(builtin_name("str.contains", OP_STR_CONTAINS)); + op_names.push_back(builtin_name("str.indexof", OP_STR_INDEXOF)); op_names.push_back(builtin_name("Indexof2", OP_STR_INDEXOF2)); - op_names.push_back(builtin_name("LastIndexof", OP_STR_LASTINDEXOF)); - op_names.push_back(builtin_name("Substring", OP_STR_SUBSTR)); - op_names.push_back(builtin_name("Replace", OP_STR_REPLACE)); + op_names.push_back(builtin_name("str.lastindexof", OP_STR_LASTINDEXOF)); + op_names.push_back(builtin_name("str.substr", OP_STR_SUBSTR)); + op_names.push_back(builtin_name("str.replace", OP_STR_REPLACE)); op_names.push_back(builtin_name("str.prefixof", OP_STR_PREFIXOF)); op_names.push_back(builtin_name("str.suffixof", OP_STR_SUFFIXOF)); op_names.push_back(builtin_name("str.to-int", OP_STR_STR2INT)); op_names.push_back(builtin_name("str.from-int", OP_STR_INT2STR)); - op_names.push_back(builtin_name("Str2Reg", OP_RE_STR2REGEX)); - op_names.push_back(builtin_name("RegexIn", OP_RE_REGEXIN)); - op_names.push_back(builtin_name("RegexConcat", OP_RE_REGEXCONCAT)); - op_names.push_back(builtin_name("RegexStar", OP_RE_REGEXSTAR)); - op_names.push_back(builtin_name("RegexUnion", OP_RE_REGEXUNION)); - op_names.push_back(builtin_name("RegexPlus", OP_RE_REGEXPLUS)); + op_names.push_back(builtin_name("str.to.reg", OP_RE_STR2REGEX)); + op_names.push_back(builtin_name("str.in.reg", OP_RE_REGEXIN)); + op_names.push_back(builtin_name("re.++", OP_RE_REGEXCONCAT)); + op_names.push_back(builtin_name("re.*", OP_RE_REGEXSTAR)); + op_names.push_back(builtin_name("re.union", OP_RE_REGEXUNION)); + op_names.push_back(builtin_name("re.+", OP_RE_REGEXPLUS)); op_names.push_back(builtin_name("Unroll", OP_RE_UNROLL)); - op_names.push_back(builtin_name("RegexCharRange", OP_RE_REGEXCHARRANGE)); + op_names.push_back(builtin_name("re.range", OP_RE_REGEXCHARRANGE)); } void str_decl_plugin::get_sort_names(svector & sort_names, symbol const & logic) { From ebcfa966c7e3c6e10371df9e2a4799bcc3421330 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Mon, 30 Jan 2017 16:07:32 -0500 Subject: [PATCH 324/410] data structure refactor in theory_str --- src/smt/theory_str.cpp | 23 ++--------------------- src/smt/theory_str.h | 6 +++--- 2 files changed, 5 insertions(+), 24 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 138b7db9f..a59bfb90b 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -7870,7 +7870,7 @@ int theory_str::ctx_dep_analysis(std::map & strVarMap, std::map::iterator it = variable_set.begin(); it != variable_set.end(); ++it) { + for(obj_hashtable::iterator it = variable_set.begin(); it != variable_set.end(); ++it) { expr* var = *it; if (internal_variable_set.find(var) == internal_variable_set.end()) { TRACE("t_str_detail", tout << "new variable: " << mk_pp(var, m) << std::endl;); @@ -8819,7 +8819,7 @@ final_check_status theory_str::final_check_eh() { bool needToAssignFreeVars = false; std::set free_variables; std::set unused_internal_variables; - if (true) { // Z3str2 free variables check + { // Z3str2 free variables check std::map::iterator itor = varAppearInAssign.begin(); for (; itor != varAppearInAssign.end(); ++itor) { /* @@ -8845,25 +8845,6 @@ final_check_status theory_str::final_check_eh() { TRACE("t_str_detail", tout << "variable " << mk_pp(itor->first, m) << " = " << mk_pp(eqcString, m) << std::endl;); } } - } else { // new, possibly incorrect free variables check - // Check every variable to see if it's eq. to some string constant. - // If not, mark it as free. - TRACE("t_str_detail", tout << variable_set.size() << " variables in variable_set" << std::endl;); - for (std::set::iterator it = variable_set.begin(); it != variable_set.end(); ++it) { - TRACE("t_str_detail", tout << "checking eqc of variable " << mk_ismt2_pp(*it, m) << std::endl;); - bool has_eqc_value = false; - get_eqc_value(*it, has_eqc_value); - if (!has_eqc_value) { - // if this is an internal variable, it can be ignored...I think - if (internal_variable_set.find(*it) != internal_variable_set.end() || regex_variable_set.find(*it) != regex_variable_set.end()) { - TRACE("t_str_detail", tout << "WARNING: free internal variable " << mk_ismt2_pp(*it, m) << std::endl;); - //unused_internal_variables.insert(*it); - } else { - needToAssignFreeVars = true; - free_variables.insert(*it); - } - } - } } if (!needToAssignFreeVars) { diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 3bb093dcd..97f2b9fa4 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -240,9 +240,9 @@ namespace smt { bool loopDetected; obj_map > cut_var_map; - std::set variable_set; - std::set internal_variable_set; - std::set regex_variable_set; + obj_hashtable variable_set; + obj_hashtable internal_variable_set; + obj_hashtable regex_variable_set; std::map > internal_variable_scope_levels; obj_hashtable internal_lenTest_vars; From 19779f1a9b0a567f498a1665805fbe7b3d68f14a Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 31 Jan 2017 11:49:10 -0500 Subject: [PATCH 325/410] fix string operators in theory_str, this breaks theory_seq temporarily --- src/ast/seq_decl_plugin.cpp | 4 ++-- src/ast/str_decl_plugin.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ast/seq_decl_plugin.cpp b/src/ast/seq_decl_plugin.cpp index 787648e19..779096038 100644 --- a/src/ast/seq_decl_plugin.cpp +++ b/src/ast/seq_decl_plugin.cpp @@ -477,8 +477,8 @@ void seq_decl_plugin::init() { m_sigs[_OP_STRING_CHARAT] = alloc(psig, m, "str.at", 0, 2, strTint2T, strT); m_sigs[_OP_STRING_PREFIX] = alloc(psig, m, "str.prefixof", 0, 2, str2T, boolT); m_sigs[_OP_STRING_SUFFIX] = alloc(psig, m, "str.suffixof", 0, 2, str2T, boolT); - m_sigs[_OP_STRING_IN_REGEXP] = alloc(psig, m, "str.in.re", 0, 2, strTreT, boolT); - m_sigs[_OP_STRING_TO_REGEXP] = alloc(psig, m, "str.to.re", 0, 1, &strT, reT); + m_sigs[_OP_STRING_IN_REGEXP] = alloc(psig, m, "seqstr.in.re", 0, 2, strTreT, boolT); + m_sigs[_OP_STRING_TO_REGEXP] = alloc(psig, m, "seqstr.to.re", 0, 1, &strT, reT); m_sigs[_OP_REGEXP_EMPTY] = alloc(psig, m, "re.nostr", 0, 0, 0, reT); m_sigs[_OP_REGEXP_FULL] = alloc(psig, m, "re.allchar", 0, 0, 0, reT); m_sigs[_OP_STRING_SUBSTR] = alloc(psig, m, "str.substr", 0, 3, strTint2T, strT); diff --git a/src/ast/str_decl_plugin.cpp b/src/ast/str_decl_plugin.cpp index bd6d70ebe..766fefdcf 100644 --- a/src/ast/str_decl_plugin.cpp +++ b/src/ast/str_decl_plugin.cpp @@ -297,8 +297,8 @@ void str_decl_plugin::get_op_names(svector & op_names, symbol cons op_names.push_back(builtin_name("str.suffixof", OP_STR_SUFFIXOF)); op_names.push_back(builtin_name("str.to-int", OP_STR_STR2INT)); op_names.push_back(builtin_name("str.from-int", OP_STR_INT2STR)); - op_names.push_back(builtin_name("str.to.reg", OP_RE_STR2REGEX)); - op_names.push_back(builtin_name("str.in.reg", OP_RE_REGEXIN)); + op_names.push_back(builtin_name("str.to.re", OP_RE_STR2REGEX)); + op_names.push_back(builtin_name("str.in.re", OP_RE_REGEXIN)); op_names.push_back(builtin_name("re.++", OP_RE_REGEXCONCAT)); op_names.push_back(builtin_name("re.*", OP_RE_REGEXSTAR)); op_names.push_back(builtin_name("re.union", OP_RE_REGEXUNION)); From 55cb440aae7c24ac1923a57b40e4aa3b5afc5ffe Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 7 Feb 2017 14:41:16 -0500 Subject: [PATCH 326/410] add cut var info for theory_str processtype2 --- src/smt/theory_str.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index a59bfb90b..097cfcb15 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -3648,7 +3648,7 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true); } else { TRACE("t_str", tout << "AVOID LOOP: SKIP" << std::endl;); - // TODO printCutVar(m, y); + TRACE("t_str_detail", {print_cut_var(m, tout); print_cut_var(y, tout);}); } } } @@ -3756,7 +3756,7 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true); } else { TRACE("t_str", tout << "AVOID LOOP: SKIPPED" << std::endl;); - // TODO printCutVar(m, y) + TRACE("t_str_detail", {print_cut_var(m, tout); print_cut_var(y, tout);}); } } } From c456795acdffcf5ada19c10303487fe0686cd2f1 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 7 Feb 2017 17:14:11 -0500 Subject: [PATCH 327/410] temporarily remove finite model finding from theory_str --- src/smt/theory_str.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 097cfcb15..b3f2bc478 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -6968,6 +6968,7 @@ void theory_str::handle_equality(expr * lhs, expr * rhs) { return; } + /* // temporarily disabled, we are borrowing these testers for something else if (m_params.m_FiniteOverlapModels && !finite_model_test_varlists.empty()) { // TODO NEXT if (finite_model_test_varlists.contains(lhs)) { @@ -6976,6 +6977,7 @@ void theory_str::handle_equality(expr * lhs, expr * rhs) { finite_model_test(rhs, lhs); return; } } + */ if (free_var_attempt(lhs, rhs) || free_var_attempt(rhs, lhs)) { return; From 3670fa64e69971727ec8ee7612e426e98a95ef7a Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sat, 11 Feb 2017 16:59:06 -0500 Subject: [PATCH 328/410] add hex escape support theory_str --- src/ast/str_decl_plugin.cpp | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/ast/str_decl_plugin.cpp b/src/ast/str_decl_plugin.cpp index 766fefdcf..ea539c0c6 100644 --- a/src/ast/str_decl_plugin.cpp +++ b/src/ast/str_decl_plugin.cpp @@ -375,8 +375,7 @@ app * str_util::mk_string_with_escape_characters(std::string & val) { // check escape sequence i++; if (i >= val.length()) { - // TODO illegal escape sequence - NOT_IMPLEMENTED_YET(); + get_manager().raise_exception("invalid escape sequence"); } char escapeChar1 = val.at(i); if (escapeChar1 == 'a') { @@ -398,8 +397,21 @@ app * str_util::mk_string_with_escape_characters(std::string & val) { } else if (escapeChar1 == '\\') { parsedStr.push_back('\\'); } else if (escapeChar1 == 'x') { - // TODO hex escape - NOT_IMPLEMENTED_YET(); + // hex escape: we expect 'x' to be followed by exactly two hex digits + // which means that i+2 must be a valid index + if (i+2 >= val.length()) { + get_manager().raise_exception("invalid hex escape: \\x must be followed by exactly two hex digits"); + } + char hexDigitHi = val.at(i+1); + char hexDigitLo = val.at(i+2); + i += 2; + if (!isxdigit((int)hexDigitHi) || !isxdigit((int)hexDigitLo)) { + get_manager().raise_exception("invalid hex escape: \\x must be followed by exactly two hex digits"); + } + char tmp[3] = {hexDigitHi, hexDigitLo, '\0'}; + long converted = strtol(tmp, NULL, 16); + unsigned char convChar = (unsigned char)converted; + parsedStr.push_back(convChar); } else if (escapeChar1 == '0' || escapeChar1 == '1' || escapeChar1 == '2' || escapeChar1 == '3' || escapeChar1 == '4' || escapeChar1 == '5' || escapeChar1 == '6' || escapeChar1 == '7') { // TODO octal escape From e699f25889c8ba6e0ac608b0e83c77a9f6f3399d Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Mon, 13 Feb 2017 16:24:32 -0500 Subject: [PATCH 329/410] theory_str cleanup --- src/smt/theory_str.cpp | 163 ++--------------------------------------- 1 file changed, 5 insertions(+), 158 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index b3f2bc478..158342cb1 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -273,68 +273,6 @@ bool theory_str::internalize_term(app * term) { TRACE("t_str_axiom_bug", tout << "add " << mk_pp(e->get_owner(), m) << " to m_basicstr_axiom_todo" << std::endl;); } return true; - - /* // what I had before - SASSERT(!ctx.e_internalized(term)); - - unsigned num_args = term->get_num_args(); - for (unsigned i = 0; i < num_args; i++) - ctx.internalize(term->get_arg(i), false); - - enode * e = (ctx.e_internalized(term)) ? ctx.get_enode(term) : - ctx.mk_enode(term, false, false, true); - - if (is_attached_to_var(e)) - return false; - - attach_new_th_var(e); - - //if (is_concat(term)) { - // instantiate_concat_axiom(e); - //} - */ - - // TODO do we still need to do instantiate_concat_axiom()? - - // partially from theory_seq::internalize_term() - /* - if (ctx.e_internalized(term)) { - enode* e = ctx.get_enode(term); - mk_var(e); - return true; - } - TRACE("t_str_detail", tout << "internalizing term: " << mk_ismt2_pp(term, get_manager()) << std::endl;); - unsigned num_args = term->get_num_args(); - expr* arg; - for (unsigned i = 0; i < num_args; i++) { - arg = term->get_arg(i); - mk_var(ensure_enode(arg)); - } - 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* e = 0; - if (ctx.e_internalized(term)) { - e = ctx.get_enode(term); - } - else { - e = ctx.mk_enode(term, false, m.is_bool(term), true); - } - - if (opt_EagerStringConstantLengthAssertions && m_strutil.is_string(term)) { - TRACE("t_str", tout << "eagerly asserting length of string term " << mk_pp(term, m) << std::endl;); - m_basicstr_axiom_todo.insert(e); - TRACE("t_str_axiom_bug", tout << "add " << mk_pp(e->get_owner(), m) << " to m_basicstr_axiom_todo" << std::endl;); - } - - theory_var v = mk_var(e); - TRACE("t_str_detail", tout << "term " << mk_ismt2_pp(term, get_manager()) << " = v#" << v << std::endl;); - - return true; - */ } enode* theory_str::ensure_enode(expr* e) { @@ -351,18 +289,11 @@ void theory_str::refresh_theory_var(expr * e) { enode * en = ensure_enode(e); theory_var v = mk_var(en); TRACE("t_str_detail", tout << "refresh " << mk_pp(e, get_manager()) << ": v#" << v << std::endl;); - // TODO this is probably sub-optimal m_basicstr_axiom_todo.push_back(en); } theory_var theory_str::mk_var(enode* n) { TRACE("t_str_detail", tout << "mk_var for " << mk_pp(n->get_owner(), get_manager()) << std::endl;); - /* - if (!m_strutil.is_string(n->get_owner())) { - return null_theory_var; - } - */ - // TODO this may require an overhaul of m_strutil.is_string() if things suddenly start working after the following change: ast_manager & m = get_manager(); if (!(is_sort_of(m.get_sort(n->get_owner()), m_strutil.get_fid(), STRING_SORT))) { return null_theory_var; @@ -503,9 +434,6 @@ app * theory_str::mk_int(rational & q) { return m_autil.mk_numeral(q, true); } - -// TODO refactor all of these so that they don't use variable counters, but use ast_manager::mk_fresh_const instead - expr * theory_str::mk_internal_lenTest_var(expr * node, int lTries) { ast_manager & m = get_manager(); @@ -539,23 +467,6 @@ void theory_str::track_variable_scope(expr * var) { } app * theory_str::mk_internal_xor_var() { - /* - ast_manager & m = get_manager(); - std::stringstream ss; - ss << tmpXorVarCount; - tmpXorVarCount++; - std::string name = "$$_xor_" + ss.str(); - // Z3_sort r = of_sort(mk_c(c)->m().mk_sort(mk_c(c)->get_arith_fid(), INT_SORT)); - sort * int_sort = m.mk_sort(m_autil.get_family_id(), INT_SORT); - - char * new_buffer = alloc_svect(char, name.length() + 1); - strcpy(new_buffer, name.c_str()); - symbol sym(new_buffer); - - app * a = m.mk_const(m.mk_const_decl(sym, int_sort)); - m_trail.push_back(a); - return a; - */ return mk_int_var("$$_xor"); } @@ -1069,7 +980,6 @@ void theory_str::instantiate_concat_axiom(enode * cat) { // build LHS expr_ref len_xy(m); - // TODO should we use str_util for these and other expressions? len_xy = mk_strlen(a_cat); SASSERT(len_xy); @@ -1106,15 +1016,12 @@ void theory_str::instantiate_concat_axiom(enode * cat) { * Length(x) == strlen(x) */ void theory_str::instantiate_basic_string_axioms(enode * str) { - // TODO keep track of which enodes we have added axioms for, so we don't add the same ones twice? - context & ctx = get_context(); ast_manager & m = get_manager(); TRACE("t_str_axiom_bug", tout << "set up basic string axioms on " << mk_pp(str->get_owner(), m) << std::endl;); // TESTING: attempt to avoid a crash here when a variable goes out of scope - // TODO this seems to work so we probably need to do this for other propagate checks, etc. if (str->get_iscope_lvl() > ctx.get_scope_level()) { TRACE("t_str_detail", tout << "WARNING: skipping axiom setup on out-of-scope string term" << std::endl;); return; @@ -2596,7 +2503,6 @@ void theory_str::generate_mutual_exclusion(expr_ref_vector & terms) { literal_vector ls; for (unsigned i = 0; i < terms.size(); ++i) { expr * e = terms.get(i); - // TODO make sure the terms are internalized, etc.? literal l = ctx.get_literal(e); ls.push_back(l); } @@ -2605,28 +2511,6 @@ void theory_str::generate_mutual_exclusion(expr_ref_vector & terms) { void theory_str::print_cut_var(expr * node, std::ofstream & xout) { ast_manager & m = get_manager(); - /* -#ifdef DEBUGLOG - __debugPrint(logFile, "\n>> CUT info of ["); - printZ3Node(t, node); - __debugPrint(logFile, "]\n"); - - if (cut_VARMap.find(node) != cut_VARMap.end()) { - if (!cut_VARMap[node].empty()) { - __debugPrint(logFile, "[%2d] {", cut_VARMap[node].top()->level); - std::map::iterator itor = cut_VARMap[node].top()->vars.begin(); - for (; itor != cut_VARMap[node].top()->vars.end(); itor++) { - printZ3Node(t, itor->first); - __debugPrint(logFile, ", "); - } - __debugPrint(logFile, "}\n"); - } else { - - } - } - __debugPrint(logFile, "------------------------\n\n"); -#endif -*/ xout << "Cut info of " << mk_pp(node, m) << std::endl; if (cut_var_map.contains(node)) { if (!cut_var_map[node].empty()) { @@ -2924,8 +2808,6 @@ bool theory_str::will_result_in_overlap(expr * lhs, expr * rhs) { expr * m = to_app(new_nn2)->get_arg(0); expr * n = to_app(new_nn2)->get_arg(1); - // TODO is it too slow to perform length checks here to avoid false positives? - if (has_self_cut(m, y)) { TRACE("t_str_detail", tout << "Possible overlap found" << std::endl; print_cut_var(m, tout); print_cut_var(y, tout);); return true; @@ -3193,7 +3075,6 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { t2 = varForBreakConcat[key2][1]; xorFlag = varForBreakConcat[key2][2]; } - // TODO do I need to refresh the xorFlag, which is an integer var, and if so, how? refresh_theory_var(t1); add_nonempty_constraint(t1); refresh_theory_var(t2); @@ -3252,7 +3133,7 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true); } else { TRACE("t_str", tout << "AVOID LOOP: SKIPPED" << std::endl;); - // TODO printCutVar(m, y); + TRACE("t_str_detail", {print_cut_var(m, tout); print_cut_var(y, tout);}); } } } else if (splitType == 1) { @@ -3310,7 +3191,7 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true); } else { TRACE("t_str", tout << "AVOID LOOP: SKIPPED" << std::endl;); - // TODO printCutVar(m, y); + TRACE("t_str_detail", {print_cut_var(m, tout); print_cut_var(y, tout);}); } } } else if (splitType == -1) { @@ -3409,7 +3290,7 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true); } else { TRACE("t_str", tout << "AVOID LOOP: SKIPPED" << std::endl;); - // TODO printCutVar(x, n); + TRACE("t_str_detail", {print_cut_var(x, tout); print_cut_var(n, tout);}); } } @@ -3572,7 +3453,6 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { temp1 = varForBreakConcat[key2][0]; xorFlag = varForBreakConcat[key2][1]; } - // TODO refresh xorFlag? refresh_theory_var(temp1); add_nonempty_constraint(temp1); } @@ -4049,7 +3929,7 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true); } else { TRACE("t_str", tout << "AVOID LOOP: SKIPPED" << std::endl;); - // TODO printCutVar(x, n); + TRACE("t_str_detail", {print_cut_var(x, tout); print_cut_var(n, tout);}); } } } @@ -4129,7 +4009,7 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true); } else { TRACE("t_str", tout << "AVOID LOOP: SKIPPED." << std::endl;); - // TODO printCutVAR(x, n) + TRACE("t_str_detail", {print_cut_var(x, tout); print_cut_var(n, tout);}); } } } @@ -4712,26 +4592,6 @@ void theory_str::unroll_str2reg_constStr(expr * unrollFunc, expr * eqConstStr) { * Return that constant if it is found, and set hasEqcValue to true. * Otherwise, return n, and set hasEqcValue to false. */ -/* -expr * theory_str::get_eqc_value(expr * n, bool & hasEqcValue) { - context & ctx = get_context(); - // I hope this works - ctx.internalize(n, false); - enode * nNode = ctx.get_enode(n); - enode * eqcNode = nNode; - do { - app * ast = eqcNode->get_owner(); - if (is_string(eqcNode)) { - hasEqcValue = true; - return ast; - } - eqcNode = eqcNode->get_next(); - } while (eqcNode != nNode); - // not found - hasEqcValue = false; - return n; -} -*/ expr * theory_str::get_eqc_value(expr * n, bool & hasEqcValue) { return z3str2_get_eqc_value(n, hasEqcValue); @@ -6596,8 +6456,6 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { // check the entries in this map to make sure they're still in scope // before we use them. - // TODO XOR variables will always show up as "not in scope" because of how we update internal_variable_set - std::map, std::map >::iterator entry1 = varForBreakConcat.find(key1); std::map, std::map >::iterator entry2 = varForBreakConcat.find(key2); @@ -7409,14 +7267,6 @@ void theory_str::push_scope_eh() { theory::push_scope_eh(); m_trail_stack.push_scope(); - // TODO out-of-scope term debugging, see comment in pop_scope_eh() - /* - context & ctx = get_context(); - ast_manager & m = get_manager(); - expr_ref_vector assignments(m); - ctx.get_assignments(assignments); - */ - sLevel += 1; TRACE("t_str", tout << "push to " << sLevel << std::endl;); TRACE_CODE(if (is_trace_enabled("t_str_dump_assign_on_scope_change")) { dump_assignments(); }); @@ -9846,7 +9696,6 @@ expr * theory_str::gen_len_test_options(expr * freeVar, expr * indicator, int tr andList.push_back(and_expr); } - // TODO cache mk_string("more") expr_ref more_option(ctx.mk_eq_atom(indicator, mk_string("more")), m); orList.push_back(more_option); // decrease priority of this option @@ -10558,8 +10407,6 @@ model_value_proc * theory_str::mk_value(enode * n, model_generator & mg) { return alloc(expr_wrapper_proc, val); } else { TRACE("t_str", tout << "WARNING: failed to find a concrete value, falling back" << std::endl;); - // TODO make absolutely sure the reason we can't find a concrete value is because of an unassigned temporary - // e.g. for an expression like (Concat X $$_str0) return alloc(expr_wrapper_proc, to_app(mk_string("**UNUSED**"))); } } From 5ca4f2a1c86604aafb805189406ead9695729663 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Mon, 13 Feb 2017 17:15:13 -0500 Subject: [PATCH 330/410] theory_str cleanup --- src/smt/theory_str.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 158342cb1..a36a75868 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -4576,7 +4576,8 @@ void theory_str::unroll_str2reg_constStr(expr * unrollFunc, expr * eqConstStr) { std::string regStrValue = m_strutil.get_string_constant_value(strInStr2RegFunc); int strLen = strValue.length(); int regStrLen = regStrValue.length(); - int cnt = strLen / regStrLen; // TODO prevent DIV/0 on regStrLen + SASSERT(regStrLen != 0); // this should never occur -- the case for empty string is handled elsewhere + int cnt = strLen / regStrLen; expr_ref implyL(ctx.mk_eq_atom(unrollFunc, eqConstStr), m); expr_ref implyR1(ctx.mk_eq_atom(oriCnt, mk_int(cnt)), m); @@ -7446,7 +7447,7 @@ void theory_str::classify_ast_by_type(expr * node, std::map & varMap varMap[node] = 1; } // check whether the node is a function that we want to inspect - else if (is_app(node)) { // TODO + else if (is_app(node)) { app * aNode = to_app(node); if (is_strlen(aNode)) { // Length From 52eaae9da0939c193e60bba43df17f47c26e6732 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 14 Feb 2017 15:19:03 -0500 Subject: [PATCH 331/410] theory_str refactor: check_contain_by_eqc_val uses contain_pair_idx_map --- src/smt/theory_str.cpp | 224 +++++++++++++++++++++-------------------- 1 file changed, 113 insertions(+), 111 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index a36a75868..504e0e2fe 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -4842,130 +4842,134 @@ void theory_str::check_contain_by_eqc_val(expr * varNode, expr * constNode) { expr_ref_vector litems(m); - // TODO refactor to use the new contain_pair_idx_map + if (contain_pair_idx_map.find(varNode) != contain_pair_idx_map.end()) { + std::set >::iterator itor1 = contain_pair_idx_map[varNode].begin(); + for (; itor1 != contain_pair_idx_map[varNode].end(); ++itor1) { + expr * strAst = itor1->first; + expr * substrAst = itor1->second; - expr_ref_vector::iterator itor1 = contains_map.begin(); - for (; itor1 != contains_map.end(); ++itor1) { - expr * boolVar = *itor1; - // boolVar is actually a Contains term - app * containsApp = to_app(boolVar); - expr * strAst = containsApp->get_arg(0); - expr * substrAst = containsApp->get_arg(1); - - // we only want to inspect the Contains terms where either of strAst or substrAst - // are equal to varNode. - - TRACE("t_str_detail", tout << "considering Contains with strAst = " << mk_pp(strAst, m) << ", substrAst = " << mk_pp(substrAst, m) << "..." << std::endl;); - - if (varNode != strAst && varNode != substrAst) { - TRACE("t_str_detail", tout << "varNode not equal to strAst or substrAst, skip" << std::endl;); - continue; - } - TRACE("t_str_detail", tout << "varNode matched one of strAst or substrAst. Continuing" << std::endl;); - - // varEqcNode is str - if (strAst == varNode) { - expr_ref implyR(m); - litems.reset(); - - if (strAst != constNode) { - litems.push_back(ctx.mk_eq_atom(strAst, constNode)); + expr * boolVar; + if (!contain_pair_bool_map.find(strAst, substrAst, boolVar)) { + TRACE("t_str_detail", tout << "warning: no entry for boolVar in contain_pair_bool_map" << std::endl;); } - std::string strConst = m_strutil.get_string_constant_value(constNode); - bool subStrHasEqcValue = false; - expr * substrValue = get_eqc_value(substrAst, subStrHasEqcValue); - if (substrValue != substrAst) { - litems.push_back(ctx.mk_eq_atom(substrAst, substrValue)); + // boolVar is actually a Contains term + app * containsApp = to_app(boolVar); + + // we only want to inspect the Contains terms where either of strAst or substrAst + // are equal to varNode. + + TRACE("t_str_detail", tout << "considering Contains with strAst = " << mk_pp(strAst, m) << ", substrAst = " << mk_pp(substrAst, m) << "..." << std::endl;); + + if (varNode != strAst && varNode != substrAst) { + TRACE("t_str_detail", tout << "varNode not equal to strAst or substrAst, skip" << std::endl;); + continue; } + TRACE("t_str_detail", tout << "varNode matched one of strAst or substrAst. Continuing" << std::endl;); - if (subStrHasEqcValue) { - // subStr has an eqc constant value - std::string subStrConst = m_strutil.get_string_constant_value(substrValue); + // varEqcNode is str + if (strAst == varNode) { + expr_ref implyR(m); + litems.reset(); - TRACE("t_str_detail", tout << "strConst = " << strConst << ", subStrConst = " << subStrConst << std::endl;); - - if (strConst.find(subStrConst) != std::string::npos) { - //implyR = ctx.mk_eq(ctx, boolVar, Z3_mk_true(ctx)); - implyR = boolVar; - } else { - //implyR = Z3_mk_eq(ctx, boolVar, Z3_mk_false(ctx)); - implyR = m.mk_not(boolVar); + if (strAst != constNode) { + litems.push_back(ctx.mk_eq_atom(strAst, constNode)); } - } else { - // ------------------------------------------------------------------------------------------------ - // subStr doesn't have an eqc contant value - // however, subStr equals to some concat(arg_1, arg_2, ..., arg_n) - // if arg_j is a constant and is not a part of the strConst, it's sure that the contains is false - // ** This check is needed here because the "strConst" and "strAst" may not be in a same eqc yet - // ------------------------------------------------------------------------------------------------ - // collect eqc concat - std::set eqcConcats; - get_concats_in_eqc(substrAst, eqcConcats); - for (std::set::iterator concatItor = eqcConcats.begin(); - concatItor != eqcConcats.end(); concatItor++) { - expr_ref_vector constList(m); - bool counterEgFound = false; - // get constant strings in concat - expr * aConcat = *concatItor; - get_const_str_asts_in_node(aConcat, constList); - for (expr_ref_vector::iterator cstItor = constList.begin(); - cstItor != constList.end(); cstItor++) { - std::string pieceStr = m_strutil.get_string_constant_value(*cstItor); - if (strConst.find(pieceStr) == std::string::npos) { - counterEgFound = true; - if (aConcat != substrAst) { - litems.push_back(ctx.mk_eq_atom(substrAst, aConcat)); + std::string strConst = m_strutil.get_string_constant_value(constNode); + bool subStrHasEqcValue = false; + expr * substrValue = get_eqc_value(substrAst, subStrHasEqcValue); + if (substrValue != substrAst) { + litems.push_back(ctx.mk_eq_atom(substrAst, substrValue)); + } + + if (subStrHasEqcValue) { + // subStr has an eqc constant value + std::string subStrConst = m_strutil.get_string_constant_value(substrValue); + + TRACE("t_str_detail", tout << "strConst = " << strConst << ", subStrConst = " << subStrConst << std::endl;); + + if (strConst.find(subStrConst) != std::string::npos) { + //implyR = ctx.mk_eq(ctx, boolVar, Z3_mk_true(ctx)); + implyR = boolVar; + } else { + //implyR = Z3_mk_eq(ctx, boolVar, Z3_mk_false(ctx)); + implyR = m.mk_not(boolVar); + } + } else { + // ------------------------------------------------------------------------------------------------ + // subStr doesn't have an eqc contant value + // however, subStr equals to some concat(arg_1, arg_2, ..., arg_n) + // if arg_j is a constant and is not a part of the strConst, it's sure that the contains is false + // ** This check is needed here because the "strConst" and "strAst" may not be in a same eqc yet + // ------------------------------------------------------------------------------------------------ + // collect eqc concat + std::set eqcConcats; + get_concats_in_eqc(substrAst, eqcConcats); + for (std::set::iterator concatItor = eqcConcats.begin(); + concatItor != eqcConcats.end(); concatItor++) { + expr_ref_vector constList(m); + bool counterEgFound = false; + // get constant strings in concat + expr * aConcat = *concatItor; + get_const_str_asts_in_node(aConcat, constList); + for (expr_ref_vector::iterator cstItor = constList.begin(); + cstItor != constList.end(); cstItor++) { + std::string pieceStr = m_strutil.get_string_constant_value(*cstItor); + if (strConst.find(pieceStr) == std::string::npos) { + counterEgFound = true; + if (aConcat != substrAst) { + litems.push_back(ctx.mk_eq_atom(substrAst, aConcat)); + } + //implyR = Z3_mk_eq(ctx, boolVar, Z3_mk_false(ctx)); + implyR = m.mk_not(boolVar); + break; } - //implyR = Z3_mk_eq(ctx, boolVar, Z3_mk_false(ctx)); - implyR = m.mk_not(boolVar); + } + if (counterEgFound) { + TRACE("t_str_detail", tout << "Inconsistency found!" << std::endl;); break; } } - if (counterEgFound) { - TRACE("t_str_detail", tout << "Inconsistency found!" << std::endl;); - break; + } + // add assertion + if (implyR) { + expr_ref implyLHS(mk_and(litems), m); + assert_implication(implyLHS, implyR); + } + } + // varEqcNode is subStr + else if (substrAst == varNode) { + expr_ref implyR(m); + litems.reset(); + + if (substrAst != constNode) { + litems.push_back(ctx.mk_eq_atom(substrAst, constNode)); + } + bool strHasEqcValue = false; + expr * strValue = get_eqc_value(strAst, strHasEqcValue); + if (strValue != strAst) { + litems.push_back(ctx.mk_eq_atom(strAst, strValue)); + } + + if (strHasEqcValue) { + std::string strConst = m_strutil.get_string_constant_value(strValue); + std::string subStrConst = m_strutil.get_string_constant_value(constNode); + if (strConst.find(subStrConst) != std::string::npos) { + //implyR = Z3_mk_eq(ctx, boolVar, Z3_mk_true(ctx)); + implyR = boolVar; + } else { + // implyR = Z3_mk_eq(ctx, boolVar, Z3_mk_false(ctx)); + implyR = m.mk_not(boolVar); } } - } - // add assertion - if (implyR) { - expr_ref implyLHS(mk_and(litems), m); - assert_implication(implyLHS, implyR); - } - } - // varEqcNode is subStr - else if (substrAst == varNode) { - expr_ref implyR(m); - litems.reset(); - if (substrAst != constNode) { - litems.push_back(ctx.mk_eq_atom(substrAst, constNode)); - } - bool strHasEqcValue = false; - expr * strValue = get_eqc_value(strAst, strHasEqcValue); - if (strValue != strAst) { - litems.push_back(ctx.mk_eq_atom(strAst, strValue)); - } - - if (strHasEqcValue) { - std::string strConst = m_strutil.get_string_constant_value(strValue); - std::string subStrConst = m_strutil.get_string_constant_value(constNode); - if (strConst.find(subStrConst) != std::string::npos) { - //implyR = Z3_mk_eq(ctx, boolVar, Z3_mk_true(ctx)); - implyR = boolVar; - } else { - // implyR = Z3_mk_eq(ctx, boolVar, Z3_mk_false(ctx)); - implyR = m.mk_not(boolVar); + // add assertion + if (implyR) { + expr_ref implyLHS(mk_and(litems), m); + assert_implication(implyLHS, implyR); } } - - // add assertion - if (implyR) { - expr_ref implyLHS(mk_and(litems), m); - assert_implication(implyLHS, implyR); - } - } - } // for (itor1 : contains_map) + } // for (itor1 : contains_map) + } // if varNode in contain_pair_idx_map } void theory_str::check_contain_by_substr(expr * varNode, expr_ref_vector & willEqClass) { @@ -5782,7 +5786,6 @@ void theory_str::compute_contains(std::map & varAliasMap, } bool theory_str::can_concat_eq_str(expr * concat, std::string str) { - // TODO this method could use some traces and debugging info int strLen = str.length(); if (is_concat(to_app(concat))) { ptr_vector args; @@ -5834,7 +5837,6 @@ bool theory_str::can_concat_eq_str(expr * concat, std::string str) { } bool theory_str::can_concat_eq_concat(expr * concat1, expr * concat2) { - // TODO this method could use some traces and debugging info if (is_concat(to_app(concat1)) && is_concat(to_app(concat2))) { { // Suppose concat1 = (Concat X Y) and concat2 = (Concat M N). From 3e714075c48588751a724df4d64176ede3d1d345 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 14 Feb 2017 16:09:45 -0500 Subject: [PATCH 332/410] theory_str refactor: check_contain_by_substr uses contain_pair_idx_map --- src/smt/theory_str.cpp | 102 +++++++++++++++++++++-------------------- 1 file changed, 53 insertions(+), 49 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 504e0e2fe..37ebc0c93 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -4977,67 +4977,71 @@ void theory_str::check_contain_by_substr(expr * varNode, expr_ref_vector & willE ast_manager & m = get_manager(); expr_ref_vector litems(m); - // TODO refactor to use the new contain_pair_idx_map + if (contain_pair_idx_map.find(varNode) != contain_pair_idx_map.end()) { + std::set >::iterator itor1 = contain_pair_idx_map[varNode].begin(); + for (; itor1 != contain_pair_idx_map[varNode].end(); ++itor1) { + expr * strAst = itor1->first; + expr * substrAst = itor1->second; - expr_ref_vector::iterator itor1 = contains_map.begin(); - for (; itor1 != contains_map.end(); ++itor1) { - expr * boolVar = *itor1; - // boolVar is actually a Contains term - app * containsApp = to_app(boolVar); - expr * strAst = containsApp->get_arg(0); - expr * substrAst = containsApp->get_arg(1); + expr * boolVar; + if (!contain_pair_bool_map.find(strAst, substrAst, boolVar)) { + TRACE("t_str_detail", tout << "warning: no entry for boolVar in contain_pair_bool_map" << std::endl;); + } + // boolVar is actually a Contains term + app * containsApp = to_app(boolVar); - // we only want to inspect the Contains terms where either of strAst or substrAst - // are equal to varNode. + // we only want to inspect the Contains terms where either of strAst or substrAst + // are equal to varNode. - TRACE("t_str_detail", tout << "considering Contains with strAst = " << mk_pp(strAst, m) << ", substrAst = " << mk_pp(substrAst, m) << "..." << std::endl;); + TRACE("t_str_detail", tout << "considering Contains with strAst = " << mk_pp(strAst, m) << ", substrAst = " << mk_pp(substrAst, m) << "..." << std::endl;); - if (varNode != strAst && varNode != substrAst) { - TRACE("t_str_detail", tout << "varNode not equal to strAst or substrAst, skip" << std::endl;); - continue; - } - TRACE("t_str_detail", tout << "varNode matched one of strAst or substrAst. Continuing" << std::endl;); + if (varNode != strAst && varNode != substrAst) { + TRACE("t_str_detail", tout << "varNode not equal to strAst or substrAst, skip" << std::endl;); + continue; + } + TRACE("t_str_detail", tout << "varNode matched one of strAst or substrAst. Continuing" << std::endl;); - if (substrAst == varNode) { - bool strAstHasVal = false; - expr * strValue = get_eqc_value(strAst, strAstHasVal); - if (strAstHasVal) { - TRACE("t_str_detail", tout << mk_pp(strAst, m) << " has constant eqc value " << mk_pp(strValue, m) << std::endl;); - if (strValue != strAst) { - litems.push_back(ctx.mk_eq_atom(strAst, strValue)); - } - std::string strConst = m_strutil.get_string_constant_value(strValue); - // iterate eqc (also eqc-to-be) of substr - for (expr_ref_vector::iterator itAst = willEqClass.begin(); itAst != willEqClass.end(); itAst++) { - bool counterEgFound = false; - if (is_concat(to_app(*itAst))) { - expr_ref_vector constList(m); - // get constant strings in concat - app * aConcat = to_app(*itAst); - get_const_str_asts_in_node(aConcat, constList); - for (expr_ref_vector::iterator cstItor = constList.begin(); - cstItor != constList.end(); cstItor++) { - std::string pieceStr = m_strutil.get_string_constant_value(*cstItor); - if (strConst.find(pieceStr) == std::string::npos) { - TRACE("t_str_detail", tout << "Inconsistency found!" << std::endl;); - counterEgFound = true; - if (aConcat != substrAst) { - litems.push_back(ctx.mk_eq_atom(substrAst, aConcat)); + if (substrAst == varNode) { + bool strAstHasVal = false; + expr * strValue = get_eqc_value(strAst, strAstHasVal); + if (strAstHasVal) { + TRACE("t_str_detail", tout << mk_pp(strAst, m) << " has constant eqc value " << mk_pp(strValue, m) << std::endl;); + if (strValue != strAst) { + litems.push_back(ctx.mk_eq_atom(strAst, strValue)); + } + std::string strConst = m_strutil.get_string_constant_value(strValue); + // iterate eqc (also eqc-to-be) of substr + for (expr_ref_vector::iterator itAst = willEqClass.begin(); itAst != willEqClass.end(); itAst++) { + bool counterEgFound = false; + if (is_concat(to_app(*itAst))) { + expr_ref_vector constList(m); + // get constant strings in concat + app * aConcat = to_app(*itAst); + get_const_str_asts_in_node(aConcat, constList); + for (expr_ref_vector::iterator cstItor = constList.begin(); + cstItor != constList.end(); cstItor++) { + std::string pieceStr = m_strutil.get_string_constant_value(*cstItor); + if (strConst.find(pieceStr) == std::string::npos) { + TRACE("t_str_detail", tout << "Inconsistency found!" << std::endl;); + counterEgFound = true; + if (aConcat != substrAst) { + litems.push_back(ctx.mk_eq_atom(substrAst, aConcat)); + } + expr_ref implyLHS(mk_and(litems), m); + expr_ref implyR(m.mk_not(boolVar), m); + assert_implication(implyLHS, implyR); + break; } - expr_ref implyLHS(mk_and(litems), m); - expr_ref implyR(m.mk_not(boolVar), m); - assert_implication(implyLHS, implyR); - break; } } - } - if (counterEgFound) { - break; + if (counterEgFound) { + break; + } } } } } - } + } // varNode in contain_pair_idx_map } bool theory_str::in_contain_idx_map(expr * n) { From d5b1e4b015772730f82e751f2212ce482b8bbf85 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 14 Feb 2017 18:44:40 -0500 Subject: [PATCH 333/410] refactor theory_str: all library-aware/high-level terms are in one worklist --- src/smt/theory_str.cpp | 104 ++++++++++++----------------------------- src/smt/theory_str.h | 4 +- 2 files changed, 31 insertions(+), 77 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 37ebc0c93..9fcf1f084 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -805,11 +805,9 @@ expr * theory_str::mk_concat(expr * n1, expr * n2) { } bool theory_str::can_propagate() { - return !m_basicstr_axiom_todo.empty() || !m_str_eq_todo.empty() || !m_concat_axiom_todo.empty() || !m_concat_eval_todo.empty() - || !m_axiom_CharAt_todo.empty() || !m_axiom_StartsWith_todo.empty() || !m_axiom_EndsWith_todo.empty() - || !m_axiom_Contains_todo.empty() || !m_axiom_Indexof_todo.empty() || !m_axiom_Indexof2_todo.empty() || !m_axiom_LastIndexof_todo.empty() - || !m_axiom_Substr_todo.empty() || !m_axiom_Replace_todo.empty() - || !m_axiom_RegexIn_todo.empty() || !m_library_aware_axiom_todo.empty() + return !m_basicstr_axiom_todo.empty() || !m_str_eq_todo.empty() + || !m_concat_axiom_todo.empty() || !m_concat_eval_todo.empty() + || !m_library_aware_axiom_todo.empty() || !m_delayed_axiom_setup_terms.empty(); ; } @@ -842,62 +840,32 @@ void theory_str::propagate() { } m_concat_eval_todo.reset(); - for (unsigned i = 0; i < m_axiom_CharAt_todo.size(); ++i) { - instantiate_axiom_CharAt(m_axiom_CharAt_todo[i]); - } - m_axiom_CharAt_todo.reset(); - - for (unsigned i = 0; i < m_axiom_StartsWith_todo.size(); ++i) { - instantiate_axiom_StartsWith(m_axiom_StartsWith_todo[i]); - } - m_axiom_StartsWith_todo.reset(); - - for (unsigned i = 0; i < m_axiom_EndsWith_todo.size(); ++i) { - instantiate_axiom_EndsWith(m_axiom_EndsWith_todo[i]); - } - m_axiom_EndsWith_todo.reset(); - - for (unsigned i = 0; i < m_axiom_Contains_todo.size(); ++i) { - instantiate_axiom_Contains(m_axiom_Contains_todo[i]); - } - m_axiom_Contains_todo.reset(); - - for (unsigned i = 0; i < m_axiom_Indexof_todo.size(); ++i) { - instantiate_axiom_Indexof(m_axiom_Indexof_todo[i]); - } - m_axiom_Indexof_todo.reset(); - - for (unsigned i = 0; i < m_axiom_Indexof2_todo.size(); ++i) { - instantiate_axiom_Indexof2(m_axiom_Indexof2_todo[i]); - } - m_axiom_Indexof2_todo.reset(); - - for (unsigned i = 0; i < m_axiom_LastIndexof_todo.size(); ++i) { - instantiate_axiom_LastIndexof(m_axiom_LastIndexof_todo[i]); - } - m_axiom_LastIndexof_todo.reset(); - - for (unsigned i = 0; i < m_axiom_Substr_todo.size(); ++i) { - instantiate_axiom_Substr(m_axiom_Substr_todo[i]); - } - m_axiom_Substr_todo.reset(); - - for (unsigned i = 0; i < m_axiom_Replace_todo.size(); ++i) { - instantiate_axiom_Replace(m_axiom_Replace_todo[i]); - } - m_axiom_Replace_todo.reset(); - - for (unsigned i = 0; i < m_axiom_RegexIn_todo.size(); ++i) { - instantiate_axiom_RegexIn(m_axiom_RegexIn_todo[i]); - } - m_axiom_RegexIn_todo.reset(); - for (unsigned i = 0; i < m_library_aware_axiom_todo.size(); ++i) { enode * e = m_library_aware_axiom_todo[i]; if (is_str_to_int(e)) { instantiate_axiom_str_to_int(e); } else if (is_int_to_str(e)) { instantiate_axiom_int_to_str(e); + } else if (is_CharAt(e)) { + instantiate_axiom_CharAt(e); + } else if (is_StartsWith(e)) { + instantiate_axiom_StartsWith(e); + } else if (is_EndsWith(e)) { + instantiate_axiom_EndsWith(e); + } else if (is_Contains(e)) { + instantiate_axiom_Contains(e); + } else if (is_Indexof(e)) { + instantiate_axiom_Indexof(e); + } else if (is_Indexof2(e)) { + instantiate_axiom_Indexof2(e); + } else if (is_LastIndexof(e)) { + instantiate_axiom_LastIndexof(e); + } else if (is_Substr(e)) { + instantiate_axiom_Substr(e); + } else if (is_Replace(e)) { + instantiate_axiom_Replace(e); + } else if (is_RegexIn(e)) { + instantiate_axiom_RegexIn(e); } else { TRACE("t_str", tout << "BUG: unhandled library-aware term " << mk_pp(e->get_owner(), get_manager()) << std::endl;); NOT_IMPLEMENTED_YET(); @@ -7099,12 +7067,8 @@ void theory_str::set_up_axioms(expr * ex) { if (aVar->get_num_args() == 0 && !is_string(aVar)) { input_var_in_len.insert(var); } - } else if (is_CharAt(ap)) { - m_axiom_CharAt_todo.push_back(n); - } else if (is_Substr(ap)) { - m_axiom_Substr_todo.push_back(n); - } else if (is_Replace(ap)) { - m_axiom_Replace_todo.push_back(n); + } else if (is_CharAt(ap) || is_Substr(ap) || is_Replace(ap)) { + m_library_aware_axiom_todo.push_back(n); } else if (ap->get_num_args() == 0 && !is_string(ap)) { // if ex is a variable, add it to our list of variables TRACE("t_str_detail", tout << "tracking variable " << mk_ismt2_pp(ap, get_manager()) << std::endl;); @@ -7127,14 +7091,8 @@ void theory_str::set_up_axioms(expr * ex) { if (is_app(ex)) { app * ap = to_app(ex); - if (is_StartsWith(ap)) { - m_axiom_StartsWith_todo.push_back(n); - } else if (is_EndsWith(ap)) { - m_axiom_EndsWith_todo.push_back(n); - } else if (is_Contains(ap)) { - m_axiom_Contains_todo.push_back(n); - } else if (is_RegexIn(ap)) { - m_axiom_RegexIn_todo.push_back(n); + if (is_StartsWith(ap) || is_EndsWith(ap) || is_Contains(ap) || is_RegexIn(ap)) { + m_library_aware_axiom_todo.push_back(n); } } } else { @@ -7152,12 +7110,8 @@ void theory_str::set_up_axioms(expr * ex) { if (is_app(ex)) { app * ap = to_app(ex); - if (is_Indexof(ap)) { - m_axiom_Indexof_todo.push_back(n); - } else if (is_Indexof2(ap)) { - m_axiom_Indexof2_todo.push_back(n); - } else if (is_LastIndexof(ap)) { - m_axiom_LastIndexof_todo.push_back(n); + if (is_Indexof(ap) || is_Indexof2(ap) || is_LastIndexof(ap)) { + m_library_aware_axiom_todo.push_back(n); } else if (is_str_to_int(ap) || is_int_to_str(ap)) { string_int_conversion_terms.push_back(ap); m_library_aware_axiom_todo.push_back(n); diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 97f2b9fa4..1915763f1 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -210,7 +210,7 @@ namespace smt { ptr_vector m_concat_eval_todo; // enode lists for term-specific axioms - // TODO maybe refactor this into a generic "library_aware_axiom_todo" list + /* ptr_vector m_axiom_CharAt_todo; ptr_vector m_axiom_StartsWith_todo; ptr_vector m_axiom_EndsWith_todo; @@ -221,8 +221,8 @@ namespace smt { ptr_vector m_axiom_Substr_todo; ptr_vector m_axiom_Replace_todo; ptr_vector m_axiom_RegexIn_todo; + */ - // TODO refactor everything to use this worklist ptr_vector m_library_aware_axiom_todo; // hashtable of all exprs for which we've already set up term-specific axioms -- From f9b3c47bf513672f0dcf76c202e8cd7d6b509aa1 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 14 Feb 2017 18:45:09 -0500 Subject: [PATCH 334/410] remove commented-out old worklists --- src/smt/theory_str.h | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 1915763f1..47a8e8d0b 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -209,20 +209,7 @@ namespace smt { ptr_vector m_string_constant_length_todo; ptr_vector m_concat_eval_todo; - // enode lists for term-specific axioms - /* - ptr_vector m_axiom_CharAt_todo; - ptr_vector m_axiom_StartsWith_todo; - ptr_vector m_axiom_EndsWith_todo; - ptr_vector m_axiom_Contains_todo; - ptr_vector m_axiom_Indexof_todo; - ptr_vector m_axiom_Indexof2_todo; - ptr_vector m_axiom_LastIndexof_todo; - ptr_vector m_axiom_Substr_todo; - ptr_vector m_axiom_Replace_todo; - ptr_vector m_axiom_RegexIn_todo; - */ - + // enode lists for library-aware/high-level string terms (e.g. substr, contains) ptr_vector m_library_aware_axiom_todo; // hashtable of all exprs for which we've already set up term-specific axioms -- From d67f732c7cf4a62f3b0d7a992d5096b69e7f6bf6 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 15 Feb 2017 13:39:55 -0500 Subject: [PATCH 335/410] theory_str data structure refactoring --- src/smt/theory_str.cpp | 6 +++--- src/smt/theory_str.h | 7 +++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 9fcf1f084..da6f94afe 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -6606,7 +6606,7 @@ void theory_str::finite_model_test(expr * testvar, expr * str) { } } else { bool map_effectively_empty = false; - if (fvar_len_count_map.find(v) == fvar_len_count_map.end()) { + if (!fvar_len_count_map.contains(v)) { map_effectively_empty = true; } @@ -6701,7 +6701,7 @@ void theory_str::finite_model_test(expr * testvar, expr * str) { void theory_str::more_len_tests(expr * lenTester, std::string lenTesterValue) { ast_manager & m = get_manager(); - if (lenTester_fvar_map.find(lenTester) != lenTester_fvar_map.end()) { + if (lenTester_fvar_map.contains(lenTester)) { expr * fVar = lenTester_fvar_map[lenTester]; expr * toAssert = gen_len_val_options_for_free_var(fVar, lenTester, lenTesterValue); TRACE("t_str_detail", tout << "asserting more length tests for free variable " << mk_ismt2_pp(fVar, m) << std::endl;); @@ -9952,7 +9952,7 @@ expr * theory_str::gen_len_val_options_for_free_var(expr * freeVar, expr * lenTe return binary_search_length_test(freeVar, lenTesterInCbEq, lenTesterValue); } else { bool map_effectively_empty = false; - if (fvar_len_count_map.find(freeVar) == fvar_len_count_map.end()) { + if (!fvar_len_count_map.contains(freeVar)) { TRACE("t_str_detail", tout << "fvar_len_count_map is empty" << std::endl;); map_effectively_empty = true; } diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 47a8e8d0b..f81b4ada7 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -195,7 +195,6 @@ namespace smt { bool finalCheckProgressIndicator; - // TODO make sure that all generated expressions are saved into the trail expr_ref_vector m_trail; // trail for generated terms str_value_factory * m_factory; @@ -236,11 +235,11 @@ namespace smt { obj_hashtable internal_valTest_vars; obj_hashtable internal_unrollTest_vars; - std::set input_var_in_len; + obj_hashtable input_var_in_len; - std::map fvar_len_count_map; + obj_map fvar_len_count_map; std::map > fvar_lenTester_map; - std::map lenTester_fvar_map; + obj_map lenTester_fvar_map; std::map > > > fvar_valueTester_map; std::map valueTester_fvar_map; From 2e27e1cd366746113ff1717fce8a19bdd9953bf4 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 15 Feb 2017 16:08:54 -0500 Subject: [PATCH 336/410] fix obj_map insertions theory_str --- src/smt/theory_str.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index da6f94afe..3673d3e79 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -9981,7 +9981,7 @@ expr * theory_str::gen_len_val_options_for_free_var(expr * freeVar, expr * lenTe // no length assertions for this free variable have ever been added. TRACE("t_str_detail", tout << "no length assertions yet" << std::endl;); - fvar_len_count_map[freeVar] = 1; + fvar_len_count_map.insert(freeVar, 1); unsigned int testNum = fvar_len_count_map[freeVar]; expr_ref indicator(mk_internal_lenTest_var(freeVar, testNum), m); @@ -9990,7 +9990,7 @@ expr * theory_str::gen_len_val_options_for_free_var(expr * freeVar, expr * lenTe // since the map is "effectively empty", we can remove those variables that have left scope... fvar_lenTester_map[freeVar].shrink(0); fvar_lenTester_map[freeVar].push_back(indicator); - lenTester_fvar_map[indicator] = freeVar; + lenTester_fvar_map.insert(indicator, freeVar); expr * lenTestAssert = gen_len_test_options(freeVar, indicator, testNum); SASSERT(lenTestAssert != NULL); @@ -10089,7 +10089,7 @@ expr * theory_str::gen_len_val_options_for_free_var(expr * freeVar, expr * lenTe testNum = fvar_len_count_map[freeVar]; indicator = mk_internal_lenTest_var(freeVar, testNum); fvar_lenTester_map[freeVar].push_back(indicator); - lenTester_fvar_map[indicator] = freeVar; + lenTester_fvar_map.insert(indicator, freeVar); } else { // TODO make absolutely sure this is safe to do if 'indicator' is technically out of scope indicator = fvar_lenTester_map[freeVar][i]; From 90705cfd5f3ebba397e029735d2979f40366909b Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Fri, 17 Feb 2017 13:28:52 -0500 Subject: [PATCH 337/410] remove todo from str api --- src/api/api_str.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/api/api_str.cpp b/src/api/api_str.cpp index 1a1debb5b..eb56a839b 100644 --- a/src/api/api_str.cpp +++ b/src/api/api_str.cpp @@ -81,7 +81,6 @@ extern "C" { MK_UNARY(Z3_mk_str_length, mk_c(c)->get_str_fid(), OP_STRLEN, SKIP); MK_BINARY(Z3_mk_str_at, mk_c(c)->get_str_fid(), OP_STR_CHARAT, SKIP); // translate prefixof/suffixof to StartsWith/EndsWith - // TODO string standardization might just remove StartsWith/EndsWith in future Z3_ast Z3_API Z3_mk_str_prefixof(Z3_context c, Z3_ast pre, Z3_ast full) { LOG_Z3_mk_str_prefixof(c, pre, full); Z3_TRY; From fe1a976c21778c088f1e59c77cad87497055d663 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sat, 18 Feb 2017 15:25:04 -0500 Subject: [PATCH 338/410] fix merge remnant --- src/cmd_context/cmd_context.cpp | 32 +------------------------------- 1 file changed, 1 insertion(+), 31 deletions(-) diff --git a/src/cmd_context/cmd_context.cpp b/src/cmd_context/cmd_context.cpp index 4f1458318..dc66f5da9 100644 --- a/src/cmd_context/cmd_context.cpp +++ b/src/cmd_context/cmd_context.cpp @@ -528,39 +528,13 @@ bool cmd_context::logic_has_pb() const { } bool cmd_context::logic_has_fpa() const { -<<<<<<< HEAD - return !has_logic() || logic_has_fpa_core(m_logic); + return !has_logic() || smt_logics::logic_has_fpa(m_logic); } bool cmd_context::logic_has_str() const { return !has_logic() || m_logic == "QF_S"; } -bool cmd_context::logic_has_array_core(symbol const & s) const { - return - s == "QF_AX" || - s == "QF_AUFLIA" || - s == "QF_ANIA" || - s == "QF_ALIA" || - s == "QF_AUFLIRA" || - s == "QF_AUFNIA" || - s == "QF_AUFNIRA" || - s == "ALIA" || - s == "AUFLIA" || - s == "AUFLIRA" || - s == "AUFNIA" || - s == "AUFNIRA" || - s == "AUFBV" || - s == "ABV" || - s == "QF_ABV" || - s == "QF_AUFBV" || - s == "HORN"; -======= - return !has_logic() || smt_logics::logic_has_fpa(m_logic); ->>>>>>> upstream-master -} - - bool cmd_context::logic_has_array() const { return !has_logic() || smt_logics::logic_has_array(m_logic); } @@ -601,12 +575,8 @@ void cmd_context::init_manager_core(bool new_manager) { load_plugin(symbol("datatype"), logic_has_datatype(), fids); load_plugin(symbol("seq"), logic_has_seq(), fids); load_plugin(symbol("fpa"), logic_has_fpa(), fids); -<<<<<<< HEAD load_plugin(symbol("str"), logic_has_str(), fids); -======= load_plugin(symbol("pb"), logic_has_pb(), fids); - ->>>>>>> upstream-master svector::iterator it = fids.begin(); svector::iterator end = fids.end(); for (; it != end; ++it) { From a081d819413449e73166941cb60758aa0ebd936c Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Mon, 20 Feb 2017 13:27:36 -0500 Subject: [PATCH 339/410] remove local dev files from gitignore --- .gitignore | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.gitignore b/.gitignore index a5c9c7e66..7cc289168 100644 --- a/.gitignore +++ b/.gitignore @@ -87,10 +87,3 @@ src/*/*/*/CMakeLists.txt src/api/dotnet/cmake_install_gac.cmake.in src/api/dotnet/cmake_uninstall_gac.cmake.in -# reference code for z3str2 -Z3-str -Z3-str/** -# test cases -tests -tests/** - From 15e3d3ec3ce8e3547b291b699963a26413b45a1b Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 21 Feb 2017 15:51:08 -0500 Subject: [PATCH 340/410] octal escape theory_str --- src/ast/str_decl_plugin.cpp | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/ast/str_decl_plugin.cpp b/src/ast/str_decl_plugin.cpp index ea539c0c6..80493f3cf 100644 --- a/src/ast/str_decl_plugin.cpp +++ b/src/ast/str_decl_plugin.cpp @@ -414,8 +414,29 @@ app * str_util::mk_string_with_escape_characters(std::string & val) { parsedStr.push_back(convChar); } else if (escapeChar1 == '0' || escapeChar1 == '1' || escapeChar1 == '2' || escapeChar1 == '3' || escapeChar1 == '4' || escapeChar1 == '5' || escapeChar1 == '6' || escapeChar1 == '7') { - // TODO octal escape - NOT_IMPLEMENTED_YET(); + // octal escape: we expect exactly three octal digits + // which means that val[i], val[i+1], val[i+2] must all be octal digits + // and that i+2 must be a valid index + if (i+2 >= val.length()) { + get_manager().raise_exception("invalid octal escape: exactly three octal digits required"); + } + char c2 = escapeChar1; + char c1 = val.at(i+1); + char c0 = val.at(i+2); + i += 2; + + if (!isdigit(c2) || !isdigit(c1) || !isdigit(c0)) { + get_manager().raise_exception("invalid octal escape: exactly three octal digits required"); + } + + if (c2 == '8' || c2 == '9' || c1 == '8' || c1 == '9' || c0 == '8' || c0 == '9') { + get_manager().raise_exception("invalid octal escape: exactly three octal digits required"); + } + + char tmp[4] = {c2, c1, c0, '\0'}; + long converted = strtol(tmp, NULL, 8); + unsigned char convChar = (unsigned char)converted; + parsedStr.push_back(convChar); } else { // unrecognized escape sequence -- just emit that character parsedStr.push_back(escapeChar1); From 179b0f763095201bda085456bb05d2fa209b298a Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 21 Feb 2017 19:52:27 -0500 Subject: [PATCH 341/410] clean up todos theory_str --- src/ast/rewriter/str_rewriter.cpp | 5 -- src/smt/theory_str.cpp | 109 +++++------------------------- src/smt/theory_str.h | 4 -- 3 files changed, 17 insertions(+), 101 deletions(-) diff --git a/src/ast/rewriter/str_rewriter.cpp b/src/ast/rewriter/str_rewriter.cpp index 3926e66e1..045d06b97 100644 --- a/src/ast/rewriter/str_rewriter.cpp +++ b/src/ast/rewriter/str_rewriter.cpp @@ -38,7 +38,6 @@ void nfa::convert_re(expr * e, unsigned & start, unsigned & end, str_util & m_st std::string str = m_strutil.get_string_constant_value(arg_str); TRACE("t_str_rw", tout << "build NFA for '" << str << "'" << std::endl;); - // TODO this assumes the string is not empty /* * For an n-character string, we make (n-1) intermediate states, * labelled i_(0) through i_(n-2). @@ -219,7 +218,6 @@ br_status str_rewriter::mk_str_CharAt(expr * arg0, expr * arg1, expr_ref & resul result = m_strutil.mk_string(resultStr); return BR_DONE; } else { - // TODO if we ever figure out how to assert axioms in here, add the axiom code from Z3str2's strAstReduce.cpp return BR_FAILED; } } @@ -399,7 +397,6 @@ br_status str_rewriter::mk_str_to_int(expr * arg0, expr_ref & result) { // interpret str as a natural number and rewrite to the corresponding integer. // if this is not valid, rewrite to -1 - // TODO leading zeroes? rational convertedRepresentation(0); rational ten(10); for (unsigned i = 0; i < str.length(); ++i) { @@ -692,13 +689,11 @@ br_status str_rewriter::mk_eq_core(expr * l, expr * r, expr_ref & result) { } bool str_rewriter::reduce_eq(expr * l, expr * r, expr_ref_vector & lhs, expr_ref_vector & rhs, bool & change) { - // TODO inspect seq_rewriter::reduce_eq() change = false; return true; } bool str_rewriter::reduce_eq(expr_ref_vector& ls, expr_ref_vector& rs, expr_ref_vector& lhs, expr_ref_vector& rhs, bool& change) { - // TODO inspect seq_rewriter::reduce_eq() change = false; return true; } diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 3673d3e79..84295940a 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -549,7 +549,6 @@ app * theory_str::mk_regex_rep_var() { TRACE("t_str_axiom_bug", tout << "add " << mk_pp(a, m) << " to m_basicstr_axiom_todo" << std::endl;); m_trail.push_back(a); - // TODO cross-check which variable sets we need variable_set.insert(a); //internal_variable_set.insert(a); regex_variable_set.insert(a); @@ -1215,7 +1214,6 @@ void theory_str::instantiate_axiom_Contains(enode * e) { // quick path, because this is necessary due to rewriter behaviour // (at minimum it should fix z3str/concat-006.smt2 - // TODO: see if it's necessary for other such terms if (m_strutil.is_string(ex->get_arg(0)) && m_strutil.is_string(ex->get_arg(1))) { TRACE("t_str_detail", tout << "eval constant Contains term " << mk_pp(ex, m) << std::endl;); std::string haystackStr = m_strutil.get_string_constant_value(ex->get_arg(0)); @@ -1541,7 +1539,6 @@ void theory_str::instantiate_axiom_Substr(enode * e) { expr_ref ts0_contains_ts1(mk_contains(expr->get_arg(0), ts1), m); expr_ref_vector and_item(m); - // TODO simulate this contains check; it causes problems with a few regressions but we might need it for performance //and_item.push_back(ts0_contains_ts1); and_item.push_back(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(ts0, mk_concat(ts1, ts2)))); and_item.push_back(ctx.mk_eq_atom(expr->get_arg(1), mk_strlen(ts0))); @@ -1782,7 +1779,6 @@ void theory_str::reset_eh() { m_basicstr_axiom_todo.reset(); m_str_eq_todo.reset(); m_concat_axiom_todo.reset(); - // TODO reset a loooooot more internal stuff pop_scope_eh(get_context().get_scope_level()); } @@ -2377,7 +2373,7 @@ void theory_str::infer_len_concat_arg(expr * n, rational len) { if (arg0Len.is_nonneg()) { axr = ctx.mk_eq_atom(mk_strlen(arg0), mk_int(arg0Len)); } else { - // TODO negate? + // could negate } } else if (arg0_len_exists && !arg1_len_exists) { //if (mk_length(t, arg0) != mk_int(ctx, arg0_len)) { @@ -2388,7 +2384,7 @@ void theory_str::infer_len_concat_arg(expr * n, rational len) { if (arg1Len.is_nonneg()) { axr = ctx.mk_eq_atom(mk_strlen(arg1), mk_int(arg1Len)); } else { - // TODO negate? + // could negate } } else { @@ -3252,7 +3248,6 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { } else { loopDetected = true; if (m_params.m_FiniteOverlapModels) { - // TODO this might repeat the case above, we may wish to avoid doing this twice expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2); arrangement_disjunction.push_back(tester); add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true); @@ -5556,7 +5551,7 @@ void theory_str::get_grounded_concats(expr* node, std::map & varAl else { std::vector concatNodes; concatNodes.push_back(node); - groundedMap[node][concatNodes]; // TODO ??? + groundedMap[node][concatNodes]; } } } @@ -6653,7 +6648,6 @@ void theory_str::finite_model_test(expr * testvar, expr * str) { v_lower_bound == rational::zero(); } else if (lower_bound_exists && !upper_bound_exists) { // check some finite portion of the search space - // TODO here and below, factor out the increment to a param v_upper_bound = v_lower_bound + rational(10); } else { // no bounds information @@ -6678,7 +6672,6 @@ void theory_str::finite_model_test(expr * testvar, expr * str) { expr_ref_vector andList(m); for (rational l = v_lower_bound; l <= v_upper_bound; l += rational::one()) { - // TODO integrate with the enhancements in gen_len_test_options() std::string lStr = l.to_string(); expr_ref str_indicator(m_strutil.mk_string(lStr), m); expr_ref or_expr(ctx.mk_eq_atom(indicator, str_indicator), m); @@ -6718,7 +6711,6 @@ void theory_str::more_value_tests(expr * valTester, std::string valTesterValue) if (m_params.m_UseBinarySearch) { if (!binary_search_len_tester_stack.contains(fVar) || binary_search_len_tester_stack[fVar].empty()) { TRACE("t_str_binary_search", tout << "WARNING: no active length testers for " << mk_pp(fVar, m) << std::endl;); - // TODO handle this? NOT_IMPLEMENTED_YET(); } expr * effectiveLenInd = binary_search_len_tester_stack[fVar].back(); @@ -6803,7 +6795,6 @@ void theory_str::handle_equality(expr * lhs, expr * rhs) { /* // temporarily disabled, we are borrowing these testers for something else if (m_params.m_FiniteOverlapModels && !finite_model_test_varlists.empty()) { - // TODO NEXT if (finite_model_test_varlists.contains(lhs)) { finite_model_test(lhs, rhs); return; } else if (finite_model_test_varlists.contains(rhs)) { @@ -6879,8 +6870,6 @@ void theory_str::handle_equality(expr * lhs, expr * rhs) { } } - // TODO some setup with haveEQLength() which I skip for now, not sure if necessary - instantiate_str_eq_length_axiom(ctx.get_enode(lhs), ctx.get_enode(rhs)); // group terms by equivalence class (groupNodeInEqc()) @@ -7291,7 +7280,6 @@ void theory_str::pop_scope_eh(unsigned num_scopes) { sLevel -= num_scopes; TRACE("t_str", tout << "pop " << num_scopes << " to " << sLevel << std::endl;); - // TODO: figure out what's going out of scope and why context & ctx = get_context(); ast_manager & m = get_manager(); @@ -7315,7 +7303,7 @@ void theory_str::pop_scope_eh(unsigned num_scopes) { TRACE("t_str_cut_var_map", tout << "remove cut info for " << mk_pp(e, m) << std::endl; print_cut_var(e, tout);); T_cut * aCut = val.top(); val.pop(); - // dealloc(aCut); // TODO find a safer way to do this, it is causing a crash + // dealloc(aCut); } if (val.size() == 0) { cutvarmap_removes.insert(varItor->m_key); @@ -7331,30 +7319,6 @@ void theory_str::pop_scope_eh(unsigned num_scopes) { } } - /* - // see if any internal variables went out of scope - for (int check_level = sLevel + num_scopes ; check_level > sLevel; --check_level) { - TRACE("t_str_detail", tout << "cleaning up internal variables at scope level " << check_level << std::endl;); - std::map >::iterator it = internal_variable_scope_levels.find(check_level); - if (it != internal_variable_scope_levels.end()) { - unsigned count = 0; - std::set vars = it->second; - for (std::set::iterator var_it = vars.begin(); var_it != vars.end(); ++var_it) { - TRACE("t_str_detail", tout << "clean up variable " << mk_pp(*var_it, get_manager()) << std::endl;); - variable_set.erase(*var_it); - internal_variable_set.erase(*var_it); - regex_variable_set.erase(*var_it); - internal_unrollTest_vars.erase(*var_it); - count += 1; - } - TRACE("t_str_detail", tout << "cleaned up " << count << " variables" << std::endl;); - vars.clear(); - } - } - */ - - // TODO use the trail stack to do this for us! requires lots of refactoring - // TODO if this works, possibly remove axioms from other vectors as well ptr_vector new_m_basicstr; for (ptr_vector::iterator it = m_basicstr_axiom_todo.begin(); it != m_basicstr_axiom_todo.end(); ++it) { enode * e = *it; @@ -7732,7 +7696,7 @@ int theory_str::ctx_dep_analysis(std::map & strVarMap, std::mapfirst; do { - if (variable_set.find(curr) != variable_set.end()) { // TODO internal_variable_set? + if (variable_set.find(curr) != variable_set.end()) { if (aRoot == NULL) { aRoot = curr; } else { @@ -8280,7 +8244,6 @@ bool theory_str::finalcheck_str2int(app * a) { TRACE("t_str_detail", tout << "integer theory has no assignment for " << mk_pp(a, m) << std::endl;); NOT_IMPLEMENTED_YET(); } - // TODO also check assignment in string theory return axiomAdd; } @@ -8303,7 +8266,6 @@ bool theory_str::finalcheck_int2str(app * a) { // ignore this. we should already assert the axiom for what happens when the string is "" } else { // nonempty string --> convert to correct integer value, or disallow it - // TODO think about whether we need to persist the axiom in this case? rational convertedRepresentation(0); rational ten(10); bool conversionOK = true; @@ -8341,7 +8303,6 @@ bool theory_str::finalcheck_int2str(app * a) { TRACE("t_str_detail", tout << "string theory has no assignment for " << mk_pp(a, m) << std::endl;); NOT_IMPLEMENTED_YET(); } - // TODO also check assignment in integer theory return axiomAdd; } @@ -8503,7 +8464,6 @@ final_check_status theory_str::final_check_eh() { context & ctx = get_context(); ast_manager & m = get_manager(); - // TODO out-of-scope term debugging, see comment in pop_scope_eh() expr_ref_vector assignments(m); ctx.get_assignments(assignments); @@ -8811,7 +8771,6 @@ final_check_status theory_str::final_check_eh() { expr * freeVar = freeVarItor1->first; rational lenValue; bool lenValue_exists = get_len_value(freeVar, lenValue); - // TODO get_bound_strlen() tout << mk_pp(freeVar, m) << " [depCnt = " << freeVarItor1->second << ", length = " << (lenValue_exists ? lenValue.to_string() : "?") << "]" << std::endl; @@ -8842,7 +8801,6 @@ final_check_status theory_str::final_check_eh() { continue; } */ - // TODO if this variable represents a regular expression, continue expr * toAssert = gen_len_val_options_for_free_var(freeVar, NULL, ""); if (toAssert != NULL) { assert_axiom(toAssert); @@ -9026,12 +8984,10 @@ expr * theory_str::gen_val_options(expr * freeVar, expr * len_indicator, expr * // ---------------------------------------------------------------------------------------- - // TODO refactor this and below to use expr_ref_vector instead of ptr_vector/svect ptr_vector orList; ptr_vector andList; for (long long i = l; i < h; i++) { - // TODO can we share the val_indicator constants with the length tester cache? orList.push_back(m.mk_eq(val_indicator, mk_string(longlong_to_string(i).c_str()) )); if (m_params.m_AggressiveValueTesting) { literal l = mk_eq(val_indicator, mk_string(longlong_to_string(i).c_str()), false); @@ -9448,7 +9404,6 @@ expr * theory_str::gen_unroll_conditional_options(expr * var, std::set & // handle out-of-scope entries in unroll_tries_map ptr_vector outOfScopeTesters; - // TODO refactor unroll_tries_map and internal_unrollTest_vars to use m_trail_stack for (ptr_vector::iterator it = unroll_tries_map[var][unrolls].begin(); it != unroll_tries_map[var][unrolls].end(); ++it) { @@ -9807,10 +9762,8 @@ expr * theory_str::binary_search_length_test(expr * freeVar, expr * previousLenT lastTesterConstant = previousLenTesterValue; TRACE("t_str_binary_search", tout << "invoked with previousLenTester info matching top of stack" << std::endl;); } else { - // this is a bit unexpected TRACE("t_str_binary_search", tout << "WARNING: unexpected reordering of length testers!" << std::endl;); - // TODO resolve this case - NOT_IMPLEMENTED_YET(); return NULL; + UNREACHABLE(); return NULL; } } else { lastTesterConstant = m_strutil.get_string_constant_value(lastTesterValue); @@ -9822,8 +9775,7 @@ expr * theory_str::binary_search_length_test(expr * freeVar, expr * previousLenT if (!binary_search_len_tester_info.find(lastTester, lastBounds)) { // unexpected TRACE("t_str_binary_search", tout << "WARNING: no bounds information available for last tester!" << std::endl;); - // TODO resolve this - NOT_IMPLEMENTED_YET(); + UNREACHABLE(); } TRACE("t_str_binary_search", tout << "last bounds are [" << lastBounds.lowerBound << " | " << lastBounds.midPoint << " | " << lastBounds.upperBound << "]!" << lastBounds.windowSize << std::endl;); binary_search_info newBounds; @@ -9833,13 +9785,12 @@ expr * theory_str::binary_search_length_test(expr * freeVar, expr * previousLenT // we double the window size and adjust the bounds if (lastBounds.midPoint == lastBounds.upperBound && lastBounds.upperBound == lastBounds.windowSize) { TRACE("t_str_binary_search", tout << "search hit window size; expanding" << std::endl;); - // TODO is this correct? newBounds.lowerBound = lastBounds.windowSize + rational::one(); newBounds.windowSize = lastBounds.windowSize * rational(2); newBounds.upperBound = newBounds.windowSize; newBounds.calculate_midpoint(); } else if (false) { - // TODO handle the case where the midpoint can't be increased further + // handle the case where the midpoint can't be increased further // (e.g. a window like [50 | 50 | 50]!64 and we don't answer "50") } else { // general case @@ -9855,7 +9806,7 @@ expr * theory_str::binary_search_length_test(expr * freeVar, expr * previousLenT refresh_theory_var(newTester); } else if (lastTesterConstant == "less") { if (false) { - // TODO handle the case where the midpoint can't be decreased further + // handle the case where the midpoint can't be decreased further // (e.g. a window like [0 | 0 | 0]!64 and we don't answer "0" } else { // general case @@ -9888,8 +9839,7 @@ expr * theory_str::binary_search_length_test(expr * freeVar, expr * previousLenT if (!binary_search_len_tester_info.find(lastTester, lastBounds)) { // unexpected TRACE("t_str_binary_search", tout << "WARNING: no bounds information available for last tester!" << std::endl;); - // TODO resolve this - NOT_IMPLEMENTED_YET(); + UNREACHABLE(); } if (lastBounds.midPoint.is_neg()) { TRACE("t_str_binary_search", tout << "WARNING: length search converged on a negative value. Negating this constraint." << std::endl;); @@ -9960,7 +9910,6 @@ expr * theory_str::gen_len_val_options_for_free_var(expr * freeVar, expr * lenTe if (!map_effectively_empty) { // check whether any entries correspond to variables that went out of scope; // if every entry is out of scope then the map counts as being empty - // TODO: maybe remove them from the map instead? either here or in pop_scope_eh() // assume empty and find a counterexample map_effectively_empty = true; @@ -10059,7 +10008,6 @@ expr * theory_str::gen_len_val_options_for_free_var(expr * freeVar, expr * lenTe if (effectiveHasEqcValue) { effectiveLenIndiStr = m_strutil.get_string_constant_value(effective_eqc_value); } else { - // TODO this should be unreachable, but can we really do anything here? NOT_IMPLEMENTED_YET(); } } @@ -10091,7 +10039,6 @@ expr * theory_str::gen_len_val_options_for_free_var(expr * freeVar, expr * lenTe fvar_lenTester_map[freeVar].push_back(indicator); lenTester_fvar_map.insert(indicator, freeVar); } else { - // TODO make absolutely sure this is safe to do if 'indicator' is technically out of scope indicator = fvar_lenTester_map[freeVar][i]; refresh_theory_var(indicator); testNum = i + 1; @@ -10211,35 +10158,13 @@ void theory_str::process_free_var(std::map & freeVar_map) { } } - // TODO here's a great place for debugging info - - // testing: iterate over leafVarSet deterministically - if (false) { - // *** TESTING CODE - std::vector sortedLeafVarSet; - for (std::set::iterator itor1 = leafVarSet.begin(); itor1 != leafVarSet.end(); ++itor1) { - sortedLeafVarSet.push_back(*itor1); - } - std::sort(sortedLeafVarSet.begin(), sortedLeafVarSet.end(), cmpvarnames); - for(std::vector::iterator itor1 = sortedLeafVarSet.begin(); - itor1 != sortedLeafVarSet.end(); ++itor1) { - expr * toAssert = gen_len_val_options_for_free_var(*itor1, NULL, ""); - // gen_len_val_options_for_free_var() can legally return NULL, - // as methods that it calls may assert their own axioms instead. - if (toAssert != NULL) { - assert_axiom(toAssert); - } - } - } else { - // *** CODE FROM BEFORE - for(std::set::iterator itor1 = leafVarSet.begin(); - itor1 != leafVarSet.end(); ++itor1) { - expr * toAssert = gen_len_val_options_for_free_var(*itor1, NULL, ""); - // gen_len_val_options_for_free_var() can legally return NULL, - // as methods that it calls may assert their own axioms instead. - if (toAssert != NULL) { - assert_axiom(toAssert); - } + for(std::set::iterator itor1 = leafVarSet.begin(); + itor1 != leafVarSet.end(); ++itor1) { + expr * toAssert = gen_len_val_options_for_free_var(*itor1, NULL, ""); + // gen_len_val_options_for_free_var() can legally return NULL, + // as methods that it calls may assert their own axioms instead. + if (toAssert != NULL) { + assert_axiom(toAssert); } } diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index f81b4ada7..6b1ce9023 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -30,8 +30,6 @@ Revision History: #include"str_rewriter.h" #include"union_find.h" -// TODO refactor: anything that returns an expr* instead returns an expr_ref - namespace smt { class str_value_factory : public value_factory { @@ -256,7 +254,6 @@ namespace smt { theory_str_contain_pair_bool_map_t contain_pair_bool_map; //obj_map > contain_pair_idx_map; - // TODO Find a better data structure, this is 100% a hack right now std::map > > contain_pair_idx_map; std::map, expr*> regex_in_bool_map; @@ -458,7 +455,6 @@ namespace smt { void check_contain_by_substr(expr * varNode, expr_ref_vector & willEqClass); void check_contain_by_eq_nodes(expr * n1, expr * n2); bool in_contain_idx_map(expr * n); - // TODO refactor these methods to use expr_ref_vector instead of std::vector void compute_contains(std::map & varAliasMap, std::map & concatAliasMap, std::map & varConstMap, std::map & concatConstMap, std::map > & varEqConcatMap); From cff7c450c381067402334cb8f10482f9f78cbaba Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Thu, 23 Feb 2017 14:57:48 -0500 Subject: [PATCH 342/410] refactor: uint_set --- src/smt/smt_context.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/smt/smt_context.h b/src/smt/smt_context.h index edc122bd6..e23ecaf43 100644 --- a/src/smt/smt_context.h +++ b/src/smt/smt_context.h @@ -230,8 +230,7 @@ namespace smt { // Theory case split // // ----------------------------------- - typedef int_hashtable > int_set; - int_set m_all_th_case_split_literals; + uint_set m_all_th_case_split_literals; vector m_th_case_split_sets; u_map< vector > m_literal2casesplitsets; // returns the case split literal sets that a literal participates in unsigned m_th_case_split_qhead; From 5107e5cafc2dd74d2627de7f9986ba1c88702532 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Thu, 23 Feb 2017 15:01:55 -0500 Subject: [PATCH 343/410] refactor: remove t_str_refcount_hack traces --- src/ast/ast.cpp | 1 - src/smt/smt_context.cpp | 8 -------- src/smt/theory_str.cpp | 15 --------------- 3 files changed, 24 deletions(-) diff --git a/src/ast/ast.cpp b/src/ast/ast.cpp index 37ea297c2..7271048b1 100644 --- a/src/ast/ast.cpp +++ b/src/ast/ast.cpp @@ -1829,7 +1829,6 @@ void ast_manager::delete_node(ast * n) { TRACE("ast", tout << "Deleting object " << n->m_id << " " << n << "\n";); CTRACE("del_quantifier", is_quantifier(n), tout << "deleting quantifier " << n->m_id << " " << n << "\n";); TRACE("mk_var_bug", tout << "del_ast: " << n->m_id << "\n";); - TRACE("t_str_refcount_hack", tout << "delete ast " << n->m_id << std::endl;); TRACE("ast_delete_node", tout << mk_bounded_pp(n, *this) << "\n";); SASSERT(m_ast_table.contains(n)); diff --git a/src/smt/smt_context.cpp b/src/smt/smt_context.cpp index 7532bf3f8..29321ecf7 100644 --- a/src/smt/smt_context.cpp +++ b/src/smt/smt_context.cpp @@ -2403,9 +2403,6 @@ namespace smt { */ unsigned context::pop_scope_core(unsigned num_scopes) { - TRACE("t_str_refcount_hack", tout << "begin pop_scope_core in smt_context" << std::endl;); - - if (m_manager.has_trace_stream()) m_manager.trace_stream() << "[pop] " << num_scopes << " " << m_scope_lvl << "\n"; @@ -2453,9 +2450,7 @@ namespace smt { ptr_vector::iterator it = m_theory_set.begin(); ptr_vector::iterator end = m_theory_set.end(); for (; it != end; ++it) { - TRACE("t_str_refcount_hack", tout << "begin theory pop_scope_eh" << std::endl;); (*it)->pop_scope_eh(num_scopes); - TRACE("t_str_refcount_hack", tout << "end theory pop_scope_eh" << std::endl;); } del_justifications(m_justifications, s.m_justifications_lim); @@ -2482,9 +2477,6 @@ namespace smt { reassert_units(units_to_reassert_lim); TRACE("pop_scope_detail", tout << "end of pop_scope: \n"; display(tout);); CASSERT("context", check_invariant()); - - TRACE("t_str_refcount_hack", tout << "end pop_scope_core in smt_context" << std::endl;); - return num_bool_vars; } diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 84295940a..b78cbbe59 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -1793,7 +1793,6 @@ void theory_str::reset_eh() { * Then add an assertion: (y2 == (Concat ce m2)) AND ("str3" == (Concat abc x2)) -> (y2 != "str3") */ bool theory_str::new_eq_check(expr * lhs, expr * rhs) { - TRACE("t_str_refcount_hack", tout << "begin new_eq_check in theory_str" << std::endl;); context & ctx = get_context(); ast_manager & m = get_manager(); @@ -1819,7 +1818,6 @@ bool theory_str::new_eq_check(expr * lhs, expr * rhs) { expr_ref to_assert(m.mk_not(ctx.mk_eq_atom(eqc_nn1, eqc_nn2)), m); assert_axiom(to_assert); // this shouldn't use the integer theory at all, so we don't allow the option of quick-return - TRACE("t_str_refcount_hack", tout << "end new_eq_check in theory_str" << std::endl;); return false; } if (!check_length_consistency(eqc_nn1, eqc_nn2)) { @@ -1827,7 +1825,6 @@ bool theory_str::new_eq_check(expr * lhs, expr * rhs) { if (opt_NoQuickReturn_IntegerTheory){ TRACE("t_str_detail", tout << "continuing in new_eq_check() due to opt_NoQuickReturn_IntegerTheory" << std::endl;); } else { - TRACE("t_str_refcount_hack", tout << "end new_eq_check in theory_str" << std::endl;); return false; } } @@ -1846,7 +1843,6 @@ bool theory_str::new_eq_check(expr * lhs, expr * rhs) { } // okay, all checks here passed - TRACE("t_str_refcount_hack", tout << "end new_eq_check in theory_str" << std::endl;); return true; } @@ -7276,20 +7272,11 @@ void theory_str::check_variable_scope() { } void theory_str::pop_scope_eh(unsigned num_scopes) { - TRACE("t_str_refcount_hack", tout << "begin pop_scope_eh in theory_str" << std::endl;); - sLevel -= num_scopes; TRACE("t_str", tout << "pop " << num_scopes << " to " << sLevel << std::endl;); context & ctx = get_context(); ast_manager & m = get_manager(); - // { - // expr_ref_vector assignments(m); - // ctx.get_assignments(assignments); - // TRACE("t_str_refcount_hack", tout << "assignment vector about to go out of scope" << std::endl;); - // } - // TRACE("t_str_refcount_hack", tout << "assignment vector has gone out of scope" << std::endl;); - TRACE_CODE(if (is_trace_enabled("t_str_dump_assign_on_scope_change")) { dump_assignments(); }); // list of expr* to remove from cut_var_map @@ -7337,8 +7324,6 @@ void theory_str::pop_scope_eh(unsigned num_scopes) { theory::pop_scope_eh(num_scopes); //check_variable_scope(); - - TRACE("t_str_refcount_hack", tout << "end pop_scope_eh in theory_str" << std::endl;); } void theory_str::dump_assignments() { From 858c754b15ab57b4050e328173193492bcf8a0f7 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Thu, 23 Feb 2017 15:05:43 -0500 Subject: [PATCH 344/410] refactor: remove unused variable in smt_case_split_queue --- src/smt/smt_case_split_queue.cpp | 46 +++++++++++++------------------- 1 file changed, 18 insertions(+), 28 deletions(-) diff --git a/src/smt/smt_case_split_queue.cpp b/src/smt/smt_case_split_queue.cpp index c7ef655f2..67f370da0 100644 --- a/src/smt/smt_case_split_queue.cpp +++ b/src/smt/smt_case_split_queue.cpp @@ -1351,35 +1351,25 @@ namespace smt { p.m_case_split_strategy = CS_ACTIVITY; } - case_split_queue * baseQueue; - - if (p.m_theory_aware_branching) { - // override - baseQueue = alloc(theory_aware_branching_queue, ctx, p); - } else { - switch (p.m_case_split_strategy) { - case CS_ACTIVITY_DELAY_NEW: - baseQueue = alloc(dact_case_split_queue, ctx, p); - break; - case CS_ACTIVITY_WITH_CACHE: - baseQueue = alloc(cact_case_split_queue, ctx, p); - break; - case CS_RELEVANCY: - baseQueue = alloc(rel_case_split_queue, ctx, p); - break; - case CS_RELEVANCY_ACTIVITY: - baseQueue = alloc(rel_act_case_split_queue, ctx, p); - break; - case CS_RELEVANCY_GOAL: - baseQueue = alloc(rel_goal_case_split_queue, ctx, p); - break; - default: - baseQueue = alloc(act_case_split_queue, ctx, p); - break; + if (p.m_theory_aware_branching) { + // override + return alloc(theory_aware_branching_queue, ctx, p); + } else { + switch (p.m_case_split_strategy) { + case CS_ACTIVITY_DELAY_NEW: + return alloc(dact_case_split_queue, ctx, p); + case CS_ACTIVITY_WITH_CACHE: + return alloc(cact_case_split_queue, ctx, p); + case CS_RELEVANCY: + return alloc(rel_case_split_queue, ctx, p); + case CS_RELEVANCY_ACTIVITY: + return alloc(rel_act_case_split_queue, ctx, p); + case CS_RELEVANCY_GOAL: + return alloc(rel_goal_case_split_queue, ctx, p); + default: + return alloc(act_case_split_queue, ctx, p); + } } - } - - return baseQueue; } }; From 6387d59f5c37bb7cbc981dd9513457b33fc7a37f Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Thu, 23 Feb 2017 15:08:05 -0500 Subject: [PATCH 345/410] refactor: remove commented-out code --- src/smt/smt_case_split_queue.cpp | 113 ------------------------------- 1 file changed, 113 deletions(-) diff --git a/src/smt/smt_case_split_queue.cpp b/src/smt/smt_case_split_queue.cpp index 67f370da0..6cdfed7ea 100644 --- a/src/smt/smt_case_split_queue.cpp +++ b/src/smt/smt_case_split_queue.cpp @@ -1112,119 +1112,6 @@ namespace smt { } }; - /* - class theory_aware_branching_queue : public case_split_queue { - protected: - context & m_context; - smt_params & m_params; - - theory_var_priority_map m_theory_var_priority; - theory_aware_act_queue m_theory_queue; - case_split_queue * m_base_queue; - int_hashtable > m_theory_vars; - map > m_theory_var_phase; - public: - theory_aware_branching_queue(context & ctx, smt_params & p, case_split_queue * base_queue) : - m_context(ctx), - m_params(p), - m_theory_var_priority(), - m_theory_queue(1024, theory_aware_act_lt(m_theory_var_priority)), - m_base_queue(base_queue) { - } - - virtual void activity_increased_eh(bool_var v) { - m_base_queue->activity_increased_eh(v); - } - - virtual void mk_var_eh(bool_var v) { - // do nothing. we only "react" if/when we learn this is an important theory literal - m_base_queue->mk_var_eh(v); - } - - virtual void del_var_eh(bool_var v) { - if (m_theory_queue.contains(v)) { - m_theory_queue.erase(v); - } - m_base_queue->del_var_eh(v); - } - - virtual void assign_lit_eh(literal l) { - m_base_queue->assign_lit_eh(l); - } - - virtual void unassign_var_eh(bool_var v) { - if (m_theory_vars.contains(v) && !m_theory_queue.contains(v)) { - m_theory_queue.insert(v); - } - m_base_queue->unassign_var_eh(v); - } - - virtual void relevant_eh(expr * n) { - m_base_queue->relevant_eh(n); - } - - virtual void init_search_eh() { - m_base_queue->init_search_eh(); - } - - virtual void end_search_eh() { - m_base_queue->end_search_eh(); - } - - virtual void internalize_instance_eh(expr * e, unsigned gen) { - m_base_queue->internalize_instance_eh(e, gen); - } - - virtual void reset() { - m_theory_queue.reset(); - m_theory_vars.reset(); - m_theory_var_phase.reset(); - m_theory_var_priority.reset(); - m_base_queue->reset(); - } - - virtual void push_scope() { - m_base_queue->push_scope(); - } - - virtual void pop_scope(unsigned num_scopes) { - m_base_queue->pop_scope(num_scopes); - } - - virtual void next_case_split(bool_var & next, lbool & phase) { - while (!m_theory_queue.empty()) { - next = m_theory_queue.erase_min(); - // if this literal is unassigned, it is the theory literal with the highest priority, - // so case split on this - if (m_context.get_assignment(next) == l_undef) { - TRACE("theory_aware_branching", tout << "Theory-aware branch on l#" << next << std::endl;); - if (!m_theory_var_phase.find(next, phase)) { - phase = l_undef; - } - return; - } - } - // if we reach this point, the theory literal queue is empty, - // so fall back to the base queue - m_base_queue->next_case_split(next, phase); - } - - virtual void add_theory_aware_branching_info(bool_var v, double priority, lbool phase) { - TRACE("theory_aware_branching", tout << "Add theory-aware branching information for l#" << v << ": priority=" << priority << std::endl;); - m_theory_vars.insert(v); - m_theory_var_phase.insert(v, phase); - m_theory_var_priority.insert(v, priority); - m_theory_queue.reserve(v+1); - m_theory_queue.insert(v); - } - - virtual void display(std::ostream & out) { - // TODO - m_base_queue->display(out); - } - }; - */ - class theory_aware_branching_queue : public case_split_queue { protected: context & m_context; From 3816779ba12c24fdfd05d9a244101aca1f712b35 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Thu, 23 Feb 2017 15:25:20 -0500 Subject: [PATCH 346/410] fix indent --- src/smt/smt_case_split_queue.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/smt/smt_case_split_queue.cpp b/src/smt/smt_case_split_queue.cpp index 6cdfed7ea..35cdcb6fe 100644 --- a/src/smt/smt_case_split_queue.cpp +++ b/src/smt/smt_case_split_queue.cpp @@ -46,8 +46,8 @@ namespace smt { bool operator()(bool_var v1, bool_var v2) const { double p_v1, p_v2; if (!m_theory_var_priority.find(v1, p_v1)) { - p_v1 = 0.0; - } + p_v1 = 0.0; + } if (!m_theory_var_priority.find(v2, p_v2)) { p_v2 = 0.0; } From a7b21dc5d51c3256ecadccc499b8b310af2ab31e Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Thu, 23 Feb 2017 16:00:05 -0500 Subject: [PATCH 347/410] refactor: aligned external/internal names for str.strong_arrangements option --- src/smt/params/theory_str_params.cpp | 2 +- src/smt/params/theory_str_params.h | 4 ++-- src/smt/theory_str.cpp | 36 ++++++++++++++-------------- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/smt/params/theory_str_params.cpp b/src/smt/params/theory_str_params.cpp index f86cd9379..6090086b8 100644 --- a/src/smt/params/theory_str_params.cpp +++ b/src/smt/params/theory_str_params.cpp @@ -20,7 +20,7 @@ Revision History: void theory_str_params::updt_params(params_ref const & _p) { smt_params_helper p(_p); - m_AssertStrongerArrangements = p.str_strong_arrangements(); + m_StrongArrangements = p.str_strong_arrangements(); m_AggressiveLengthTesting = p.str_aggressive_length_testing(); m_AggressiveValueTesting = p.str_aggressive_value_testing(); m_AggressiveUnrollTesting = p.str_aggressive_unroll_testing(); diff --git a/src/smt/params/theory_str_params.h b/src/smt/params/theory_str_params.h index de0945395..207b635d7 100644 --- a/src/smt/params/theory_str_params.h +++ b/src/smt/params/theory_str_params.h @@ -28,7 +28,7 @@ struct theory_str_params { * This is a stronger version of the standard axiom. * The Z3str2 axioms can be simulated by setting this to false. */ - bool m_AssertStrongerArrangements; + bool m_StrongArrangements; /* * If AggressiveLengthTesting is true, we manipulate the phase of length tester equalities @@ -81,7 +81,7 @@ struct theory_str_params { double m_OverlapTheoryAwarePriority; theory_str_params(params_ref const & p = params_ref()): - m_AssertStrongerArrangements(true), + m_StrongArrangements(true), m_AggressiveLengthTesting(false), m_AggressiveValueTesting(false), m_AggressiveUnrollTesting(true), diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index b78cbbe59..80781d6aa 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -3079,7 +3079,7 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { add_cut_info_merge(t1, sLevel, m); add_cut_info_merge(t1, sLevel, y); - if (m_params.m_AssertStrongerArrangements) { + if (m_params.m_StrongArrangements) { expr_ref ax_strong(ctx.mk_eq_atom(ax_l, ax_r), mgr); assert_axiom(ax_strong); } else { @@ -3137,7 +3137,7 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { add_cut_info_merge(t2, sLevel, x); add_cut_info_merge(t2, sLevel, n); - if (m_params.m_AssertStrongerArrangements) { + if (m_params.m_StrongArrangements) { expr_ref ax_strong(ctx.mk_eq_atom(ax_l, ax_r), mgr); assert_axiom(ax_strong); } else { @@ -3272,7 +3272,7 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { if (!arrangement_disjunction.empty()) { expr_ref premise(ctx.mk_eq_atom(concatAst1, concatAst2), mgr); expr_ref conclusion(mk_or(arrangement_disjunction), mgr); - if (m_params.m_AssertStrongerArrangements) { + if (m_params.m_StrongArrangements) { expr_ref ax_strong(ctx.mk_eq_atom(premise, conclusion), mgr); assert_axiom(ax_strong); } else { @@ -3472,7 +3472,7 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { add_cut_info_merge(temp1, sLevel, y); add_cut_info_merge(temp1, sLevel, m); - if (m_params.m_AssertStrongerArrangements) { + if (m_params.m_StrongArrangements) { expr_ref ax_strong(ctx.mk_eq_atom(ax_l, ax_r), mgr); assert_axiom(ax_strong); } else { @@ -3547,7 +3547,7 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { expr_ref ax_l(mk_and(l_items), mgr); expr_ref ax_r(mk_and(r_items), mgr); - if (m_params.m_AssertStrongerArrangements) { + if (m_params.m_StrongArrangements) { expr_ref ax_strong(ctx.mk_eq_atom(ax_l, ax_r), mgr); assert_axiom(ax_strong); } else { @@ -3628,7 +3628,7 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { if (!arrangement_disjunction.empty()) { expr_ref implyR(mk_or(arrangement_disjunction), mgr); - if (m_params.m_AssertStrongerArrangements) { + if (m_params.m_StrongArrangements) { expr_ref implyLHS(ctx.mk_eq_atom(concatAst1, concatAst2), mgr); expr_ref ax_strong(ctx.mk_eq_atom(implyLHS, implyR), mgr); assert_axiom(ax_strong); @@ -3816,7 +3816,7 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { r_items.push_back(ctx.mk_eq_atom(x, prefixAst)); r_items.push_back(ctx.mk_eq_atom(y, suf_n_concat)); - if (m_params.m_AssertStrongerArrangements) { + if (m_params.m_StrongArrangements) { expr_ref ax_strong(ctx.mk_eq_atom(ax_l, mk_and(r_items)), mgr); assert_axiom(ax_strong); } else { @@ -3836,7 +3836,7 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { expr_ref ax_l(mgr.mk_and(ax_l1, ax_l2), mgr); expr_ref ax_r(mgr.mk_and(ctx.mk_eq_atom(x, strAst), ctx.mk_eq_atom(y, n)), mgr); - if (m_params.m_AssertStrongerArrangements) { + if (m_params.m_StrongArrangements) { expr_ref ax_strong(ctx.mk_eq_atom(ax_l, ax_r), mgr); assert_axiom(ax_strong); } else { @@ -3874,7 +3874,7 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { add_cut_info_merge(temp1, sLevel, x); add_cut_info_merge(temp1, sLevel, n); - if (m_params.m_AssertStrongerArrangements) { + if (m_params.m_StrongArrangements) { expr_ref ax_strong(ctx.mk_eq_atom(ax_l, ax_r), mgr); assert_axiom(ax_strong); } else { @@ -3977,7 +3977,7 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { if (!arrangement_disjunction.empty()) { expr_ref implyR(mk_or(arrangement_disjunction), mgr); - if (m_params.m_AssertStrongerArrangements) { + if (m_params.m_StrongArrangements) { expr_ref ax_lhs(ctx.mk_eq_atom(concatAst1, concatAst2), mgr); expr_ref ax_strong(ctx.mk_eq_atom(ax_lhs, implyR), mgr); assert_axiom(ax_strong); @@ -4059,7 +4059,7 @@ void theory_str::process_concat_eq_type4(expr * concatAst1, expr * concatAst2) { if (!in_same_eqc(tmpAst, n)) { // break down option 4-1 expr_ref implyR(ctx.mk_eq_atom(n, tmpAst), mgr); - if (m_params.m_AssertStrongerArrangements) { + if (m_params.m_StrongArrangements) { expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr); assert_axiom(ax_strong); } else { @@ -4071,7 +4071,7 @@ void theory_str::process_concat_eq_type4(expr * concatAst1, expr * concatAst2) { //break down option 4-2 expr_ref implyR(ctx.mk_eq_atom(n, y), mgr); - if (m_params.m_AssertStrongerArrangements) { + if (m_params.m_StrongArrangements) { expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr); assert_axiom(ax_strong); } else { @@ -4084,7 +4084,7 @@ void theory_str::process_concat_eq_type4(expr * concatAst1, expr * concatAst2) { if (!in_same_eqc(y, tmpAst)) { //break down option 4-3 expr_ref implyR(ctx.mk_eq_atom(y, tmpAst), mgr); - if (m_params.m_AssertStrongerArrangements) { + if (m_params.m_StrongArrangements) { expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr); assert_axiom(ax_strong); } else { @@ -4161,7 +4161,7 @@ void theory_str::process_concat_eq_type5(expr * concatAst1, expr * concatAst2) { expr_ref x_deltaStr(mk_concat(x, mk_string(deltaStr)), mgr); if (!in_same_eqc(m, x_deltaStr)) { expr_ref implyR(ctx.mk_eq_atom(m, x_deltaStr), mgr); - if (m_params.m_AssertStrongerArrangements) { + if (m_params.m_StrongArrangements) { expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr); assert_axiom(ax_strong); } else { @@ -4172,7 +4172,7 @@ void theory_str::process_concat_eq_type5(expr * concatAst1, expr * concatAst2) { // test if (!in_same_eqc(x, m)) { expr_ref implyR(ctx.mk_eq_atom(x, m), mgr); - if (m_params.m_AssertStrongerArrangements) { + if (m_params.m_StrongArrangements) { expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr); assert_axiom(ax_strong); } else { @@ -4184,7 +4184,7 @@ void theory_str::process_concat_eq_type5(expr * concatAst1, expr * concatAst2) { expr_ref m_deltaStr(mk_concat(m, mk_string(deltaStr)), mgr); if (!in_same_eqc(x, m_deltaStr)) { expr_ref implyR(ctx.mk_eq_atom(x, m_deltaStr), mgr); - if (m_params.m_AssertStrongerArrangements) { + if (m_params.m_StrongArrangements) { expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr); assert_axiom(ax_strong); } else { @@ -4420,7 +4420,7 @@ void theory_str::process_concat_eq_type6(expr * concatAst1, expr * concatAst2) { expr_ref implyR(mk_or(arrangement_disjunction), mgr); - if (m_params.m_AssertStrongerArrangements) { + if (m_params.m_StrongArrangements) { expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr); assert_axiom(ax_strong); } else { @@ -6515,7 +6515,7 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { assert_axiom(negate_ast); } else { implyR1 = mk_or(arrangement_disjunction); - if (m_params.m_AssertStrongerArrangements) { + if (m_params.m_StrongArrangements) { expr_ref ax_strong(ctx.mk_eq_atom(implyL, implyR1), m); assert_axiom(ax_strong); } else { From 725352234d8f28e66845304aef8eaeb1ad353621 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Mon, 27 Feb 2017 13:22:56 -0500 Subject: [PATCH 348/410] refactoring theory_str --- src/api/api_ast.cpp | 16 --- src/api/api_context.cpp | 2 - src/api/api_context.h | 4 - src/api/api_str.cpp | 160 ------------------------------ src/api/z3_api.h | 2 - src/ast/ast_smt2_pp.h | 5 +- src/ast/ast_smt_pp.cpp | 5 - src/ast/reg_decl_plugins.cpp | 4 - src/ast/rewriter/str_rewriter.cpp | 3 + src/ast/rewriter/str_rewriter.h | 4 + src/ast/str_decl_plugin.cpp | 5 + src/ast/str_decl_plugin.h | 5 + src/cmd_context/check_logic.cpp | 7 +- src/cmd_context/cmd_context.cpp | 8 +- src/parsers/smt2/smt2parser.cpp | 27 ----- src/smt/theory_str.cpp | 137 ++++++++++--------------- src/smt/theory_str.h | 83 ++-------------- 17 files changed, 87 insertions(+), 390 deletions(-) delete mode 100644 src/api/api_str.cpp diff --git a/src/api/api_ast.cpp b/src/api/api_ast.cpp index f3b3b7edf..c9cdc6ab3 100644 --- a/src/api/api_ast.cpp +++ b/src/api/api_ast.cpp @@ -647,12 +647,6 @@ extern "C" { else if (fid == mk_c(c)->get_seq_fid() && k == RE_SORT) { return Z3_RE_SORT; } - else if (fid == mk_c(c)->get_str_fid() && k == STRING_SORT) { - return Z3_STRING_SORT; - } - else if (fid == mk_c(c)->get_str_fid() && k == REGEX_SORT) { - return Z3_REGEX_SORT; - } else { return Z3_UNKNOWN_SORT; } @@ -1147,16 +1141,6 @@ extern "C" { } } - if (mk_c(c)->get_str_fid() == _d->get_family_id()) { - switch (_d->get_decl_kind()) { - // TODO(z3str2) add others - case OP_STRCAT: return Z3_OP_STR_CONCAT; - case OP_STRLEN: return Z3_OP_STR_LENGTH; - default: - return Z3_OP_UNINTERPRETED; - } - } - if (mk_c(c)->get_fpa_fid() == _d->get_family_id()) { switch (_d->get_decl_kind()) { case OP_FPA_RM_NEAREST_TIES_TO_EVEN: return Z3_OP_FPA_RM_NEAREST_TIES_TO_EVEN; diff --git a/src/api/api_context.cpp b/src/api/api_context.cpp index fd3d16bd0..bcd3c60f2 100644 --- a/src/api/api_context.cpp +++ b/src/api/api_context.cpp @@ -81,7 +81,6 @@ namespace api { m_fpa_util(m()), m_dtutil(m()), m_sutil(m()), - m_strutil(m()), m_last_result(m()), m_ast_trail(m()), m_pmanager(m_limit) { @@ -105,7 +104,6 @@ namespace api { m_datalog_fid = m().mk_family_id("datalog_relation"); m_fpa_fid = m().mk_family_id("fpa"); m_seq_fid = m().mk_family_id("seq"); - m_str_fid = m().mk_family_id("str"); m_dt_plugin = static_cast(m().get_plugin(m_dt_fid)); install_tactics(*this); diff --git a/src/api/api_context.h b/src/api/api_context.h index 7459bd102..6e34f6d6e 100644 --- a/src/api/api_context.h +++ b/src/api/api_context.h @@ -26,7 +26,6 @@ Revision History: #include"arith_decl_plugin.h" #include"bv_decl_plugin.h" #include"seq_decl_plugin.h" -#include"str_decl_plugin.h" #include"datatype_decl_plugin.h" #include"dl_decl_plugin.h" #include"fpa_decl_plugin.h" @@ -63,8 +62,6 @@ namespace api { datatype_util m_dtutil; seq_util m_sutil; - str_util m_strutil; - // Support for old solver API smt_params m_fparams; // ------------------------------- @@ -130,7 +127,6 @@ namespace api { fpa_util & fpautil() { return m_fpa_util; } datatype_util& dtutil() { return m_dtutil; } seq_util& sutil() { return m_sutil; } - str_util& strutil() { return m_strutil; } family_id get_basic_fid() const { return m_basic_fid; } family_id get_array_fid() const { return m_array_fid; } family_id get_arith_fid() const { return m_arith_fid; } diff --git a/src/api/api_str.cpp b/src/api/api_str.cpp deleted file mode 100644 index eb56a839b..000000000 --- a/src/api/api_str.cpp +++ /dev/null @@ -1,160 +0,0 @@ -/*++ -Copyright (c) 2016 Microsoft Corporation - -Module Name: - - api_str.cpp - -Abstract: - - API for strings and regular expressions (Z3str2 implementation). - -Author: - - Murphy Berzish (mtrberzi) 2016-10-03. - -Revision History: - ---*/ -#include -#include"z3.h" -#include"api_log_macros.h" -#include"api_context.h" -#include"api_util.h" -#include"ast_pp.h" - -extern "C" { - - Z3_sort Z3_API Z3_mk_str_sort(Z3_context c) { - Z3_TRY; - LOG_Z3_mk_str_sort(c); - RESET_ERROR_CODE(); - sort * ty = mk_c(c)->strutil().mk_string_sort(); - mk_c(c)->save_ast_trail(ty); - RETURN_Z3(of_sort(ty)); - Z3_CATCH_RETURN(0); - } - - Z3_bool Z3_API Z3_is_str_sort(Z3_context c, Z3_sort s) { - Z3_TRY; - LOG_Z3_is_str_sort(c, s); - RESET_ERROR_CODE(); - bool result = mk_c(c)->strutil().is_str_sort(to_sort(s)); - return result?Z3_TRUE:Z3_FALSE; - Z3_CATCH_RETURN(Z3_FALSE); - } - - Z3_bool Z3_API Z3_is_str(Z3_context c, Z3_ast s) { - Z3_TRY; - LOG_Z3_is_str(c, s); - RESET_ERROR_CODE(); - bool result = mk_c(c)->strutil().is_string(to_expr(s)); - return result ? Z3_TRUE : Z3_FALSE; - Z3_CATCH_RETURN(Z3_FALSE); - } - - Z3_string Z3_API Z3_get_str(Z3_context c, Z3_ast s) { - Z3_TRY; - LOG_Z3_get_str(c, s); - RESET_ERROR_CODE(); - if (!mk_c(c)->strutil().is_string(to_expr(s))) { - SET_ERROR_CODE(Z3_INVALID_ARG); - return ""; - } - std::string result = mk_c(c)->strutil().get_string_constant_value(to_expr(s)); - return mk_c(c)->mk_external_string(result); - Z3_CATCH_RETURN(""); - } - - Z3_ast Z3_API Z3_mk_str(Z3_context c, Z3_string str) { - Z3_TRY; - LOG_Z3_mk_str(c, str); - RESET_ERROR_CODE(); - std::string s(str); - app * a = mk_c(c)->strutil().mk_string(str); - mk_c(c)->save_ast_trail(a); - RETURN_Z3(of_ast(a)); - Z3_CATCH_RETURN(0); - } - - MK_BINARY(Z3_mk_str_concat, mk_c(c)->get_str_fid(), OP_STRCAT, SKIP); - MK_UNARY(Z3_mk_str_length, mk_c(c)->get_str_fid(), OP_STRLEN, SKIP); - MK_BINARY(Z3_mk_str_at, mk_c(c)->get_str_fid(), OP_STR_CHARAT, SKIP); - // translate prefixof/suffixof to StartsWith/EndsWith - Z3_ast Z3_API Z3_mk_str_prefixof(Z3_context c, Z3_ast pre, Z3_ast full) { - LOG_Z3_mk_str_prefixof(c, pre, full); - Z3_TRY; - RESET_ERROR_CODE(); - expr * args[2] = { to_expr(full), to_expr(pre) }; // reverse args - ast * a = mk_c(c)->m().mk_app(mk_c(c)->get_str_fid(), OP_STR_STARTSWITH, 0, 0, 2, args); - mk_c(c)->save_ast_trail(a); - check_sorts(c, a); - RETURN_Z3(of_ast(a)); - Z3_CATCH_RETURN(0); - } - Z3_ast Z3_API Z3_mk_str_suffixof(Z3_context c, Z3_ast suf, Z3_ast full) { - LOG_Z3_mk_str_suffixof(c, suf, full); - Z3_TRY; - RESET_ERROR_CODE(); - expr * args[2] = { to_expr(full), to_expr(suf) }; // reverse args - ast * a = mk_c(c)->m().mk_app(mk_c(c)->get_str_fid(), OP_STR_ENDSWITH, 0, 0, 2, args); - mk_c(c)->save_ast_trail(a); - check_sorts(c, a); - RETURN_Z3(of_ast(a)); - Z3_CATCH_RETURN(0); - } - - MK_BINARY(Z3_mk_str_contains, mk_c(c)->get_str_fid(), OP_STR_CONTAINS, SKIP); - MK_TERNARY(Z3_mk_str_indexof, mk_c(c)->get_str_fid(), OP_STR_INDEXOF, SKIP); - MK_TERNARY(Z3_mk_str_substr, mk_c(c)->get_str_fid(), OP_STR_SUBSTR, SKIP); - MK_TERNARY(Z3_mk_str_replace, mk_c(c)->get_str_fid(), OP_STR_REPLACE, SKIP); - - Z3_ast Z3_API Z3_mk_str_to_regex(Z3_context c, Z3_string str) { - LOG_Z3_mk_str_to_regex(c, str); - Z3_TRY; - RESET_ERROR_CODE(); - std::string s(str); - app * a = mk_c(c)->strutil().mk_string(str); - mk_c(c)->save_ast_trail(a); - - expr * args[1] = { to_expr(a) }; - ast * re = mk_c(c)->m().mk_app(mk_c(c)->get_str_fid(), OP_RE_STR2REGEX, 0, 0, 1, args); - mk_c(c)->save_ast_trail(re); - check_sorts(c, re); - RETURN_Z3(of_ast(re)); - Z3_CATCH_RETURN(0); - } - - MK_BINARY(Z3_mk_str_in_regex, mk_c(c)->get_str_fid(), OP_RE_REGEXIN, SKIP); - MK_BINARY(Z3_mk_regex_concat, mk_c(c)->get_str_fid(), OP_RE_REGEXCONCAT, SKIP); - MK_BINARY(Z3_mk_regex_union, mk_c(c)->get_str_fid(), OP_RE_REGEXUNION, SKIP); - MK_UNARY(Z3_mk_regex_star, mk_c(c)->get_str_fid(), OP_RE_REGEXSTAR, SKIP); - MK_UNARY(Z3_mk_regex_plus, mk_c(c)->get_str_fid(), OP_RE_REGEXPLUS, SKIP); - - Z3_ast Z3_API Z3_mk_regex_range(Z3_context c, Z3_string start, Z3_string end) { - LOG_Z3_mk_regex_range(c, start, end); - Z3_TRY; - RESET_ERROR_CODE(); - - std::string cStart(start); - std::string cEnd(end); - if(cStart.length() != 1 || cEnd.length() != 1) { - SET_ERROR_CODE(Z3_INVALID_ARG); - return 0; - } - - app * a1 = mk_c(c)->strutil().mk_string(cStart); - mk_c(c)->save_ast_trail(a1); - app * a2 = mk_c(c)->strutil().mk_string(cEnd); - mk_c(c)->save_ast_trail(a2); - - expr * args[2] = { to_expr(a1), to_expr(a2) }; - ast * range = mk_c(c)->m().mk_app(mk_c(c)->get_str_fid(), OP_RE_REGEXCHARRANGE, 0, 0, 2, args); - mk_c(c)->save_ast_trail(range); - check_sorts(c, range); - RETURN_Z3(of_ast(range)); - - Z3_CATCH_RETURN(0); - } - -}; diff --git a/src/api/z3_api.h b/src/api/z3_api.h index aa4701f13..0b8351190 100644 --- a/src/api/z3_api.h +++ b/src/api/z3_api.h @@ -165,8 +165,6 @@ typedef enum Z3_ROUNDING_MODE_SORT, Z3_SEQ_SORT, Z3_RE_SORT, - Z3_STRING_SORT, - Z3_REGEX_SORT, Z3_UNKNOWN_SORT = 1000 } Z3_sort_kind; diff --git a/src/ast/ast_smt2_pp.h b/src/ast/ast_smt2_pp.h index 0bff579bc..b1bdf52bd 100644 --- a/src/ast/ast_smt2_pp.h +++ b/src/ast/ast_smt2_pp.h @@ -50,7 +50,6 @@ public: virtual array_util & get_arutil() = 0; virtual fpa_util & get_futil() = 0; virtual seq_util & get_sutil() = 0; - virtual str_util & get_strutil() = 0; virtual datalog::dl_decl_util& get_dlutil() = 0; virtual bool uses(symbol const & s) const = 0; virtual format_ns::format * pp_fdecl(func_decl * f, unsigned & len); @@ -77,17 +76,15 @@ class smt2_pp_environment_dbg : public smt2_pp_environment { array_util m_arutil; fpa_util m_futil; seq_util m_sutil; - str_util m_strutil; datalog::dl_decl_util m_dlutil; public: - smt2_pp_environment_dbg(ast_manager & m):m_manager(m), m_autil(m), m_bvutil(m), m_arutil(m), m_futil(m), m_sutil(m), m_strutil(m), m_dlutil(m) {} + smt2_pp_environment_dbg(ast_manager & m):m_manager(m), m_autil(m), m_bvutil(m), m_arutil(m), m_futil(m), m_sutil(m), m_dlutil(m) {} virtual ast_manager & get_manager() const { return m_manager; } virtual arith_util & get_autil() { return m_autil; } virtual bv_util & get_bvutil() { return m_bvutil; } virtual seq_util & get_sutil() { return m_sutil; } virtual array_util & get_arutil() { return m_arutil; } virtual fpa_util & get_futil() { return m_futil; } - virtual str_util & get_strutil() { return m_strutil; } virtual datalog::dl_decl_util& get_dlutil() { return m_dlutil; } virtual bool uses(symbol const & s) const { return false; } }; diff --git a/src/ast/ast_smt_pp.cpp b/src/ast/ast_smt_pp.cpp index 75e6a46f1..de6ae6cc3 100644 --- a/src/ast/ast_smt_pp.cpp +++ b/src/ast/ast_smt_pp.cpp @@ -165,7 +165,6 @@ class smt_printer { bv_util m_bvutil; seq_util m_sutil; fpa_util m_futil; - str_util m_strutil; family_id m_basic_fid; family_id m_bv_fid; family_id m_str_fid; @@ -473,9 +472,6 @@ class smt_printer { m_out << ") bv1[1])"; } } - else if (m_strutil.is_string(n, &str)) { - m_out << "\"" << str << "\""; - } else if (m_manager.is_label(n, pos, names) && names.size() >= 1) { if (m_is_smt2) { m_out << "(! "; @@ -839,7 +835,6 @@ public: m_bvutil(m), m_sutil(m), m_futil(m), - m_strutil(m), m_logic(logic), m_AUFLIRA("AUFLIRA"), // It's much easier to read those testcases with that. diff --git a/src/ast/reg_decl_plugins.cpp b/src/ast/reg_decl_plugins.cpp index 886e3f495..b4ff63ede 100644 --- a/src/ast/reg_decl_plugins.cpp +++ b/src/ast/reg_decl_plugins.cpp @@ -26,7 +26,6 @@ Revision History: #include"seq_decl_plugin.h" #include"pb_decl_plugin.h" #include"fpa_decl_plugin.h" -#include"str_decl_plugin.h" void reg_decl_plugins(ast_manager & m) { if (!m.get_plugin(m.mk_family_id(symbol("arith")))) { @@ -53,7 +52,4 @@ void reg_decl_plugins(ast_manager & m) { if (!m.get_plugin(m.mk_family_id(symbol("pb")))) { m.register_plugin(symbol("pb"), alloc(pb_decl_plugin)); } - if (!m.get_plugin(m.mk_family_id(symbol("str")))) { - m.register_plugin(symbol("str"), alloc(str_decl_plugin)); - } } diff --git a/src/ast/rewriter/str_rewriter.cpp b/src/ast/rewriter/str_rewriter.cpp index 045d06b97..3933e7fdb 100644 --- a/src/ast/rewriter/str_rewriter.cpp +++ b/src/ast/rewriter/str_rewriter.cpp @@ -17,6 +17,8 @@ Notes: --*/ +#if 0 + #include"str_rewriter.h" #include"arith_decl_plugin.h" #include"ast_pp.h" @@ -698,3 +700,4 @@ bool str_rewriter::reduce_eq(expr_ref_vector& ls, expr_ref_vector& rs, expr_ref_ return true; } +#endif /* disable */ diff --git a/src/ast/rewriter/str_rewriter.h b/src/ast/rewriter/str_rewriter.h index 0494d4d1b..8d6041a51 100644 --- a/src/ast/rewriter/str_rewriter.h +++ b/src/ast/rewriter/str_rewriter.h @@ -17,6 +17,8 @@ Notes: --*/ +#if 0 + #include"str_decl_plugin.h" #include"arith_decl_plugin.h" #include"rewriter_types.h" @@ -114,3 +116,5 @@ public: bool matches(std::string input); }; + +#endif /* disable */ diff --git a/src/ast/str_decl_plugin.cpp b/src/ast/str_decl_plugin.cpp index 80493f3cf..067420f04 100644 --- a/src/ast/str_decl_plugin.cpp +++ b/src/ast/str_decl_plugin.cpp @@ -14,6 +14,9 @@ Author: Revision History: --*/ + +#if 0 + #include #include"str_decl_plugin.h" #include"string_buffer.h" @@ -494,3 +497,5 @@ std::string str_util::get_std_regex_str(expr * regex) { UNREACHABLE(); return ""; } } + +#endif /* disable */ diff --git a/src/ast/str_decl_plugin.h b/src/ast/str_decl_plugin.h index 3ae034b45..28ecd1e43 100644 --- a/src/ast/str_decl_plugin.h +++ b/src/ast/str_decl_plugin.h @@ -14,6 +14,9 @@ Author: Revision History: --*/ + +#if 0 + #ifndef _STR_DECL_PLUGIN_H_ #define _STR_DECL_PLUGIN_H_ @@ -211,3 +214,5 @@ public: }; #endif /* _STR_DECL_PLUGIN_H_ */ + +#endif /* disable */ diff --git a/src/cmd_context/check_logic.cpp b/src/cmd_context/check_logic.cpp index 02f66fc4d..c75c12689 100644 --- a/src/cmd_context/check_logic.cpp +++ b/src/cmd_context/check_logic.cpp @@ -21,7 +21,6 @@ Revision History: #include"array_decl_plugin.h" #include"bv_decl_plugin.h" #include"seq_decl_plugin.h" -#include"str_decl_plugin.h" #include"pb_decl_plugin.h" #include"datatype_decl_plugin.h" #include"ast_pp.h" @@ -35,7 +34,6 @@ struct check_logic::imp { bv_util m_bv_util; array_util m_ar_util; seq_util m_seq_util; - str_util m_str_util; datatype_util m_dt_util; pb_util m_pb_util; bool m_uf; // true if the logic supports uninterpreted functions @@ -49,7 +47,7 @@ struct check_logic::imp { bool m_quantifiers; // true if the logic supports quantifiers bool m_unknown_logic; - imp(ast_manager & _m):m(_m), m_a_util(m), m_bv_util(m), m_ar_util(m), m_seq_util(m), m_str_util(m), m_dt_util(m), m_pb_util(m) { + imp(ast_manager & _m):m(_m), m_a_util(m), m_bv_util(m), m_ar_util(m), m_seq_util(m), m_dt_util(m), m_pb_util(m) { reset(); } @@ -444,9 +442,6 @@ struct check_logic::imp { else if (fid == m_seq_util.get_family_id()) { // nothing to check } - else if (fid == m_str_util.get_family_id()) { - // nothing to check - } else if (fid == m_dt_util.get_family_id() && m_logic == "QF_FD") { // nothing to check } diff --git a/src/cmd_context/cmd_context.cpp b/src/cmd_context/cmd_context.cpp index dc66f5da9..b387e8810 100644 --- a/src/cmd_context/cmd_context.cpp +++ b/src/cmd_context/cmd_context.cpp @@ -26,7 +26,6 @@ Notes: #include"seq_decl_plugin.h" #include"pb_decl_plugin.h" #include"fpa_decl_plugin.h" -#include"str_decl_plugin.h" #include"ast_pp.h" #include"var_subst.h" #include"pp.h" @@ -250,7 +249,6 @@ protected: array_util m_arutil; fpa_util m_futil; seq_util m_sutil; - str_util m_strutil; datalog::dl_decl_util m_dlutil; @@ -272,7 +270,7 @@ protected: } public: - pp_env(cmd_context & o):m_owner(o), m_autil(o.m()), m_bvutil(o.m()), m_arutil(o.m()), m_futil(o.m()), m_sutil(o.m()), m_strutil(o.m()), m_dlutil(o.m()) {} + pp_env(cmd_context & o):m_owner(o), m_autil(o.m()), m_bvutil(o.m()), m_arutil(o.m()), m_futil(o.m()), m_sutil(o.m()), m_dlutil(o.m()) {} virtual ~pp_env() {} virtual ast_manager & get_manager() const { return m_owner.m(); } virtual arith_util & get_autil() { return m_autil; } @@ -280,7 +278,7 @@ public: virtual array_util & get_arutil() { return m_arutil; } virtual fpa_util & get_futil() { return m_futil; } virtual seq_util & get_sutil() { return m_sutil; } - virtual str_util & get_strutil() { return m_strutil; } + virtual datalog::dl_decl_util& get_dlutil() { return m_dlutil; } virtual bool uses(symbol const & s) const { return @@ -561,7 +559,6 @@ void cmd_context::init_manager_core(bool new_manager) { register_plugin(symbol("pb"), alloc(pb_decl_plugin), logic_has_pb()); register_plugin(symbol("fpa"), alloc(fpa_decl_plugin), logic_has_fpa()); register_plugin(symbol("datalog_relation"), alloc(datalog::dl_decl_plugin), !has_logic()); - register_plugin(symbol("str"), alloc(str_decl_plugin), logic_has_str()); } else { // the manager was created by an external module @@ -575,7 +572,6 @@ void cmd_context::init_manager_core(bool new_manager) { load_plugin(symbol("datatype"), logic_has_datatype(), fids); load_plugin(symbol("seq"), logic_has_seq(), fids); load_plugin(symbol("fpa"), logic_has_fpa(), fids); - load_plugin(symbol("str"), logic_has_str(), fids); load_plugin(symbol("pb"), logic_has_pb(), fids); svector::iterator it = fids.begin(); svector::iterator end = fids.end(); diff --git a/src/parsers/smt2/smt2parser.cpp b/src/parsers/smt2/smt2parser.cpp index fc28fa6e7..cbfcbf1fe 100644 --- a/src/parsers/smt2/smt2parser.cpp +++ b/src/parsers/smt2/smt2parser.cpp @@ -23,7 +23,6 @@ Revision History: #include"bv_decl_plugin.h" #include"arith_decl_plugin.h" #include"seq_decl_plugin.h" -#include"str_decl_plugin.h" #include"ast_pp.h" #include"well_sorted.h" #include"pattern_validation.h" @@ -68,7 +67,6 @@ namespace smt2 { scoped_ptr m_bv_util; scoped_ptr m_arith_util; scoped_ptr m_seq_util; - scoped_ptr m_str_util; scoped_ptr m_pattern_validator; scoped_ptr m_var_shifter; @@ -286,12 +284,6 @@ namespace smt2 { return *(m_bv_util.get()); } - str_util & strutil() { - if (m_str_util.get() == 0) - m_str_util = alloc(str_util, m()); - return *(m_str_util.get()); - } - pattern_validator & pat_validator() { if (m_pattern_validator.get() == 0) { m_pattern_validator = alloc(pattern_validator, m()); @@ -1086,29 +1078,10 @@ namespace smt2 { next(); } - // sorry, breaking theory_seq for a bit - /* void parse_string_const() { SASSERT(curr() == scanner::STRING_TOKEN); expr_stack().push_back(sutil().str.mk_string(symbol(m_scanner.get_string()))); TRACE("smt2parser", tout << "new string: " << mk_pp(expr_stack().back(), m()) << "\n";); - next(); - } - */ - - void parse_string_const() { - parse_string(); - } - - void parse_string() { - SASSERT(curr() == scanner::STRING_TOKEN); - char const *original_token = m_scanner.get_string(); - size_t bufsize = strlen(original_token); - char * buf = alloc_svect(char, bufsize + 1); - strncpy(buf, original_token, bufsize); - buf[bufsize] = '\0'; - TRACE("parse_string", tout << "new string constant: " << buf << " length=" << bufsize << "\n";); - expr_stack().push_back(strutil().mk_string_with_escape_characters(buf)); next(); } diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 80781d6aa..1d56c43a4 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -44,7 +44,7 @@ theory_str::theory_str(ast_manager & m, theory_str_params const & params): /* Internal setup */ search_started(false), m_autil(m), - m_strutil(m), + u(m), sLevel(0), finalCheckProgressIndicator(false), m_trail(m), @@ -70,36 +70,26 @@ theory_str::~theory_str() { m_trail_stack.reset(); } -expr * theory_str::mk_string(std::string str) { +expr * theory_str::mk_string(zstring const& str) { if (m_params.m_StringConstantCache) { ++totalCacheAccessCount; expr * val; if (stringConstantCache.find(str, val)) { - // cache hit - ++cacheHitCount; - TRACE("t_str_cache", tout << "cache hit: \"" << str << "\" (" - << cacheHitCount << " hits, " << cacheMissCount << " misses out of " - << totalCacheAccessCount << " accesses)" << std::endl;); return val; } else { - // cache miss - ++cacheMissCount; - TRACE("t_str_cache", tout << "cache miss: \"" << str << "\" (" - << cacheHitCount << " hits, " << cacheMissCount << " misses out of " - << totalCacheAccessCount << " accesses)" << std::endl;); - val = m_strutil.mk_string(str); + val = u.str.mk_string(str); m_trail.push_back(val); stringConstantCache.insert(str, val); return val; } } else { - return m_strutil.mk_string(str); + return u.str.mk_string(str); } } expr * theory_str::mk_string(const char * str) { - std::string valStr(str); - return mk_string(valStr); + symbol sym(str); + return u.str.mk_string(sym); } void theory_str::initialize_charset() { @@ -210,25 +200,6 @@ void theory_str::assert_implication(expr * premise, expr * conclusion) { } bool theory_str::internalize_atom(app * atom, bool gate_ctx) { - /* - TRACE("t_str", tout << "internalizing atom: " << mk_ismt2_pp(atom, get_manager()) << std::endl;); - SASSERT(atom->get_family_id() == get_family_id()); - - context & ctx = get_context(); - - if (ctx.b_internalized(atom)) - return true; - - unsigned num_args = atom->get_num_args(); - for (unsigned i = 0; i < num_args; i++) - ctx.internalize(atom->get_arg(i), false); - - literal l(ctx.mk_bool_var(atom)); - - ctx.set_var_theory(l.var(), get_id()); - - return true; - */ return internalize_term(atom); } @@ -267,10 +238,9 @@ bool theory_str::internalize_term(app * term) { theory_var v = mk_var(e); TRACE("t_str_detail", tout << "term has theory var #" << v << std::endl;); - if (opt_EagerStringConstantLengthAssertions && m_strutil.is_string(term)) { + if (opt_EagerStringConstantLengthAssertions && u.str.is_string(term)) { TRACE("t_str", tout << "eagerly asserting length of string term " << mk_pp(term, m) << std::endl;); m_basicstr_axiom_todo.insert(e); - TRACE("t_str_axiom_bug", tout << "add " << mk_pp(e->get_owner(), m) << " to m_basicstr_axiom_todo" << std::endl;); } return true; } @@ -295,7 +265,7 @@ void theory_str::refresh_theory_var(expr * e) { theory_var theory_str::mk_var(enode* n) { TRACE("t_str_detail", tout << "mk_var for " << mk_pp(n->get_owner(), get_manager()) << std::endl;); ast_manager & m = get_manager(); - if (!(is_sort_of(m.get_sort(n->get_owner()), m_strutil.get_fid(), STRING_SORT))) { + if (!(is_sort_of(m.get_sort(n->get_owner()), u.get_family_id(), _STRING_SORT))) { return null_theory_var; } if (is_attached_to_var(n)) { @@ -413,7 +383,7 @@ void theory_str::add_cut_info_merge(expr * destNode, int slevel, expr * srcNode) void theory_str::check_and_init_cut_var(expr * node) { if (cut_var_map.contains(node)) { return; - } else if (!m_strutil.is_string(node)) { + } else if (!u.str.is_string(node)) { add_cut_info_one_node(node, -1, node); } } @@ -511,7 +481,7 @@ app * theory_str::mk_str_var(std::string name) { TRACE("t_str_detail", tout << "creating string variable " << name << " at scope level " << sLevel << std::endl;); - sort * string_sort = m.mk_sort(get_family_id(), STRING_SORT); + sort * string_sort = u.str.mk_string_sort(); app * a = m.mk_fresh_const(name.c_str(), string_sort); TRACE("t_str_detail", tout << "a->get_family_id() = " << a->get_family_id() << std::endl @@ -538,7 +508,7 @@ app * theory_str::mk_regex_rep_var() { context & ctx = get_context(); ast_manager & m = get_manager(); - sort * string_sort = m.mk_sort(get_family_id(), STRING_SORT); + sort * string_sort = u.str.mk_string_sort(); app * a = m.mk_fresh_const("regex", string_sort); ctx.internalize(a, false); @@ -590,7 +560,7 @@ app * theory_str::mk_nonempty_str_var() { TRACE("t_str_detail", tout << "creating nonempty string variable " << name << " at scope level " << sLevel << std::endl;); - sort * string_sort = m.mk_sort(get_family_id(), STRING_SORT); + sort * string_sort = u.str.mk_string_sort(); app * a = m.mk_fresh_const(name.c_str(), string_sort); ctx.internalize(a, false); @@ -642,8 +612,7 @@ app * theory_str::mk_unroll(expr * n, expr * bound) { } app * theory_str::mk_contains(expr * haystack, expr * needle) { - expr * args[2] = {haystack, needle}; - app * contains = get_manager().mk_app(get_id(), OP_STR_CONTAINS, 0, 0, 2, args); + app * contains = u.str.mk_contains(haystack, needle); // TODO double-check semantics/argument order m_trail.push_back(contains); // immediately force internalization so that axiom setup does not fail get_context().internalize(contains, false); @@ -652,8 +621,8 @@ app * theory_str::mk_contains(expr * haystack, expr * needle) { } app * theory_str::mk_indexof(expr * haystack, expr * needle) { - expr * args[2] = {haystack, needle}; - app * indexof = get_manager().mk_app(get_id(), OP_STR_INDEXOF, 0, 0, 2, args); + // TODO check meaning of the third argument here + app * indexof = u.str.mk_index(haystack, needle, mk_int(0)); m_trail.push_back(indexof); // immediately force internalization so that axiom setup does not fail get_context().internalize(indexof, false); @@ -663,25 +632,23 @@ app * theory_str::mk_indexof(expr * haystack, expr * needle) { app * theory_str::mk_strlen(expr * e) { /*if (m_strutil.is_string(e)) {*/ if (false) { - const char * strval = 0; - m_strutil.is_string(e, &strval); - int len = strlen(strval); + zstring strval; + u.str.is_string(e, strval); + unsigned int len = strval.length(); return m_autil.mk_numeral(rational(len), true); } else { if (false) { // use cache app * lenTerm = NULL; if (!length_ast_map.find(e, lenTerm)) { - expr * args[1] = {e}; - lenTerm = get_manager().mk_app(get_id(), OP_STRLEN, 0, 0, 1, args); + lenTerm = u.str.mk_length(e); length_ast_map.insert(e, lenTerm); m_trail.push_back(lenTerm); } return lenTerm; } else { // always regen - expr * args[1] = {e}; - return get_manager().mk_app(get_id(), OP_STRLEN, 0, 0, 1, args); + return u.str.mk_length(e); } } } @@ -699,24 +666,22 @@ expr * theory_str::mk_concat_const_str(expr * n1, expr * n2) { expr * v1 = get_eqc_value(n1, n1HasEqcValue); expr * v2 = get_eqc_value(n2, n2HasEqcValue); if (n1HasEqcValue && n2HasEqcValue) { - const char * n1_str_tmp; - m_strutil.is_string(v1, & n1_str_tmp); - std::string n1_str(n1_str_tmp); - const char * n2_str_tmp; - m_strutil.is_string(v2, & n2_str_tmp); - std::string n2_str(n2_str_tmp); - std::string result = n1_str + n2_str; + zstring n1_str; + u.str.is_string(v1, n1_str); + zstring n2_str; + u.str.is_string(v2, n2_str); + zstring result = n1_str + n2_str; return mk_string(result); } else if (n1HasEqcValue && !n2HasEqcValue) { - const char * n1_str_tmp; - m_strutil.is_string(v1, & n1_str_tmp); - if (strcmp(n1_str_tmp, "") == 0) { + zstring n1_str; + u.str.is_string(v1, n1_str); + if (n1_str.empty()) { return n2; } } else if (!n1HasEqcValue && n2HasEqcValue) { - const char * n2_str_tmp; - m_strutil.is_string(v2, & n2_str_tmp); - if (strcmp(n2_str_tmp, "") == 0) { + zstring n2_str; + u.str.is_string(v2, n2_str); + if (n2_str.empty()) { return n1; } } @@ -735,38 +700,42 @@ expr * theory_str::mk_concat(expr * n1, expr * n2) { if (n1HasEqcValue && n2HasEqcValue) { return mk_concat_const_str(n1, n2); } else if (n1HasEqcValue && !n2HasEqcValue) { - bool n2_isConcatFunc = is_concat(to_app(n2)); - if (m_strutil.get_string_constant_value(n1) == "") { + bool n2_isConcatFunc = u.str.is_concat(to_app(n2)); + zstring n1_str; + u.str.is_string(n1, n1_str); + if (n1_str.empty()) { return n2; } if (n2_isConcatFunc) { expr * n2_arg0 = to_app(n2)->get_arg(0); expr * n2_arg1 = to_app(n2)->get_arg(1); - if (m_strutil.is_string(n2_arg0)) { + if (u.str.is_string(n2_arg0)) { n1 = mk_concat_const_str(n1, n2_arg0); // n1 will be a constant n2 = n2_arg1; } } } else if (!n1HasEqcValue && n2HasEqcValue) { - if (m_strutil.get_string_constant_value(n2) == "") { + zstring n2_str; + u.str.is_string(n2, n2_str); + if (n2_str.empty()) { return n1; } - if (is_concat(to_app(n1))) { + if (u.str.is_concat(to_app(n1))) { expr * n1_arg0 = to_app(n1)->get_arg(0); expr * n1_arg1 = to_app(n1)->get_arg(1); - if (m_strutil.is_string(n1_arg1)) { + if (u.str.is_string(n1_arg1)) { n1 = n1_arg0; n2 = mk_concat_const_str(n1_arg1, n2); // n2 will be a constant } } } else { - if (is_concat(to_app(n1)) && is_concat(to_app(n2))) { + if (u.str.is_concat(to_app(n1)) && u.str.is_concat(to_app(n2))) { expr * n1_arg0 = to_app(n1)->get_arg(0); expr * n1_arg1 = to_app(n1)->get_arg(1); expr * n2_arg0 = to_app(n2)->get_arg(0); expr * n2_arg1 = to_app(n2)->get_arg(1); - if (m_strutil.is_string(n1_arg1) && m_strutil.is_string(n2_arg0)) { + if (u.str.is_string(n1_arg1) && u.str.is_string(n2_arg0)) { expr * tmpN1 = n1_arg0; expr * tmpN2 = mk_concat_const_str(n1_arg1, n2_arg0); n1 = mk_concat(tmpN1, tmpN2); @@ -784,8 +753,7 @@ expr * theory_str::mk_concat(expr * n1, expr * n2) { expr * concatAst = NULL; if (!concat_astNode_map.find(n1, n2, concatAst)) { - expr * args[2] = {n1, n2}; - concatAst = m.mk_app(get_id(), OP_STRCAT, 0, 0, 2, args); + concatAst = u.str.mk_concat(n1, n2); m_trail.push_back(concatAst); concat_astNode_map.insert(n1, n2, concatAst); @@ -841,25 +809,30 @@ void theory_str::propagate() { for (unsigned i = 0; i < m_library_aware_axiom_todo.size(); ++i) { enode * e = m_library_aware_axiom_todo[i]; - if (is_str_to_int(e)) { + app * a = e->get_owner(); + if (u.str.is_stoi(a)) { instantiate_axiom_str_to_int(e); - } else if (is_int_to_str(e)) { + } else if (u.str.is_itos(a)) { instantiate_axiom_int_to_str(e); - } else if (is_CharAt(e)) { + } else if (u.str.is_at(a)) { instantiate_axiom_CharAt(e); + /* TODO NEXT: StartsWith/EndsWith -> prefixof/suffixof } else if (is_StartsWith(e)) { instantiate_axiom_StartsWith(e); } else if (is_EndsWith(e)) { instantiate_axiom_EndsWith(e); - } else if (is_Contains(e)) { + */ + } else if (u.str.is_contains(a)) { instantiate_axiom_Contains(e); - } else if (is_Indexof(e)) { + } else if (u.str.is_index(a)) { instantiate_axiom_Indexof(e); + /* TODO NEXT: Indexof2/Lastindexof rewrite? } else if (is_Indexof2(e)) { instantiate_axiom_Indexof2(e); } else if (is_LastIndexof(e)) { instantiate_axiom_LastIndexof(e); - } else if (is_Substr(e)) { + */ + } else if (u.str.is_substr(a)) { instantiate_axiom_Substr(e); } else if (is_Replace(e)) { instantiate_axiom_Replace(e); diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 6b1ce9023..63f5d3cfc 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -27,32 +27,13 @@ Revision History: #include #include #include -#include"str_rewriter.h" +#include +#include"seq_decl_plugin.h" #include"union_find.h" +#include"theory_seq_empty.h" namespace smt { - class str_value_factory : public value_factory { - str_util m_util; - public: - str_value_factory(ast_manager & m, family_id fid) : - value_factory(m, fid), - m_util(m) {} - virtual ~str_value_factory() {} - virtual expr * get_some_value(sort * s) { - return m_util.mk_string("some value"); - } - virtual bool get_some_values(sort * s, expr_ref & v1, expr_ref & v2) { - v1 = m_util.mk_string("value 1"); - v2 = m_util.mk_string("value 2"); - return true; - } - virtual expr * get_fresh_value(sort * s) { - return m_util.mk_fresh_string(); - } - virtual void register_value(expr * n) { /* Ignore */ } - }; - // rather than modify obj_pair_map I inherit from it and add my own helper methods class theory_str_contain_pair_bool_map_t : public obj_pair_map { public: @@ -110,12 +91,12 @@ namespace smt { typedef union_find th_union_find; typedef map, default_eq > rational_map; - struct str_hash_proc { - unsigned operator()(std::string const & s) const { - return string_hash(s.c_str(), static_cast(s.length()), 17); + struct zstring_hash_proc { + unsigned operator()(zstring const & s) const { + return string_hash(s.encode().c_str(), static_cast(s.length()), 17); } }; - typedef map > string_map; + typedef map > string_map; protected: theory_str_params const & m_params; @@ -188,14 +169,14 @@ namespace smt { bool search_started; arith_util m_autil; - str_util m_strutil; + seq_util u; int sLevel; bool finalCheckProgressIndicator; expr_ref_vector m_trail; // trail for generated terms - str_value_factory * m_factory; + seq_factory * m_factory; // terms we couldn't go through set_up_axioms() with because they weren't internalized expr_ref_vector m_delayed_axiom_setup_terms; @@ -259,7 +240,7 @@ namespace smt { std::map, expr*> regex_in_bool_map; std::map > regex_in_var_reg_str_map; - std::map regex_nfa_cache; // Regex term --> NFA + // std::map regex_nfa_cache; // Regex term --> NFA char * char_set; std::map charSetLookupTable; @@ -327,7 +308,7 @@ namespace smt { void assert_implication(expr * premise, expr * conclusion); expr * rewrite_implication(expr * premise, expr * conclusion); - expr * mk_string(std::string str); + expr * mk_string(zstring const& str); expr * mk_string(const char * str); app * mk_strlen(expr * e); @@ -359,48 +340,6 @@ namespace smt { app * mk_unroll_test_var(); void add_nonempty_constraint(expr * s); - bool is_concat(app const * a) const { return a->is_app_of(get_id(), OP_STRCAT); } - bool is_concat(enode const * n) const { return is_concat(n->get_owner()); } - bool is_string(app const * a) const { return a->is_app_of(get_id(), OP_STR); } - bool is_string(enode const * n) const { return is_string(n->get_owner()); } - bool is_strlen(app const * a) const { return a->is_app_of(get_id(), OP_STRLEN); } - bool is_strlen(enode const * n) const { return is_strlen(n->get_owner()); } - bool is_CharAt(app const * a) const { return a->is_app_of(get_id(), OP_STR_CHARAT); } - bool is_CharAt(enode const * n) const { return is_CharAt(n->get_owner()); } - bool is_StartsWith(app const * a) const { return a->is_app_of(get_id(), OP_STR_STARTSWITH); } - bool is_StartsWith(enode const * n) const { return is_StartsWith(n->get_owner()); } - bool is_EndsWith(app const * a) const { return a->is_app_of(get_id(), OP_STR_ENDSWITH); } - bool is_EndsWith(enode const * n) const { return is_EndsWith(n->get_owner()); } - bool is_Contains(app const * a) const { return a->is_app_of(get_id(), OP_STR_CONTAINS); } - bool is_Contains(enode const * n) const { return is_Contains(n->get_owner()); } - bool is_Indexof(app const * a) const { return a->is_app_of(get_id(), OP_STR_INDEXOF); } - bool is_Indexof(enode const * n) const { return is_Indexof(n->get_owner()); } - bool is_Indexof2(app const * a) const { return a->is_app_of(get_id(), OP_STR_INDEXOF2); } - bool is_Indexof2(enode const * n) const { return is_Indexof2(n->get_owner()); } - bool is_LastIndexof(app const * a) const { return a->is_app_of(get_id(), OP_STR_LASTINDEXOF); } - bool is_LastIndexof(enode const * n) const { return is_LastIndexof(n->get_owner()); } - bool is_Substr(app const * a) const { return a->is_app_of(get_id(), OP_STR_SUBSTR); } - bool is_Substr(enode const * n) const { return is_Substr(n->get_owner()); } - bool is_Replace(app const * a) const { return a->is_app_of(get_id(), OP_STR_REPLACE); } - bool is_Replace(enode const * n) const { return is_Replace(n->get_owner()); } - bool is_str_to_int(app const * a) const { return a->is_app_of(get_id(), OP_STR_STR2INT); } - bool is_str_to_int(enode const * n) const { return is_str_to_int(n->get_owner()); } - bool is_int_to_str(app const * a) const { return a->is_app_of(get_id(), OP_STR_INT2STR); } - bool is_int_to_str(enode const * n) const { return is_int_to_str(n->get_owner()); } - - bool is_RegexIn(app const * a) const { return a->is_app_of(get_id(), OP_RE_REGEXIN); } - bool is_RegexIn(enode const * n) const { return is_RegexIn(n->get_owner()); } - bool is_RegexConcat(app const * a) const { return a->is_app_of(get_id(), OP_RE_REGEXCONCAT); } - bool is_RegexConcat(enode const * n) const { return is_RegexConcat(n->get_owner()); } - bool is_RegexStar(app const * a) const { return a->is_app_of(get_id(), OP_RE_REGEXSTAR); } - bool is_RegexStar(enode const * n) const { return is_RegexStar(n->get_owner()); } - bool is_RegexUnion(app const * a) const { return a->is_app_of(get_id(), OP_RE_REGEXUNION); } - bool is_RegexUnion(enode const * n) const { return is_RegexUnion(n->get_owner()); } - bool is_Str2Reg(app const * a) const { return a->is_app_of(get_id(), OP_RE_STR2REGEX); } - bool is_Str2Reg(enode const * n) const { return is_Str2Reg(n->get_owner()); } - bool is_Unroll(app const * a) const { return a->is_app_of(get_id(), OP_RE_UNROLL); } - bool is_Unroll(enode const * n) const { return is_Unroll(n->get_owner()); } - void instantiate_concat_axiom(enode * cat); void try_eval_concat(enode * cat); void instantiate_basic_string_axioms(enode * str); From 3f1ceedcb14b0ecef4f9f73bb24fec296857c9c8 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Mon, 27 Feb 2017 20:48:55 -0500 Subject: [PATCH 349/410] theory_str refactor pass 2 --- src/smt/theory_str.cpp | 932 +++++++++++++++++++++-------------------- src/smt/theory_str.h | 57 ++- 2 files changed, 529 insertions(+), 460 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 1d56c43a4..6585bd7f2 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -23,6 +23,7 @@ Revision History: #include #include #include +#include"theory_seq_empty.h" #include "../ast/ast.h" #include"theory_arith.h" @@ -832,11 +833,12 @@ void theory_str::propagate() { } else if (is_LastIndexof(e)) { instantiate_axiom_LastIndexof(e); */ - } else if (u.str.is_substr(a)) { + } else if (u.str.is_extract(a)) { + // TODO check semantics of substr vs. extract instantiate_axiom_Substr(e); - } else if (is_Replace(e)) { + } else if (u.str.is_replace(a)) { instantiate_axiom_Replace(e); - } else if (is_RegexIn(e)) { + } else if (u.str.is_in_re(a)) { instantiate_axiom_RegexIn(e); } else { TRACE("t_str", tout << "BUG: unhandled library-aware term " << mk_pp(e->get_owner(), get_manager()) << std::endl;); @@ -861,8 +863,8 @@ void theory_str::propagate() { */ void theory_str::try_eval_concat(enode * cat) { - SASSERT(is_concat(cat)); app * a_cat = cat->get_owner(); + SASSERT(u.str.is_concat(a_cat)); context & ctx = get_context(); ast_manager & m = get_manager(); @@ -870,7 +872,7 @@ void theory_str::try_eval_concat(enode * cat) { TRACE("t_str_detail", tout << "attempting to flatten " << mk_pp(a_cat, m) << std::endl;); std::stack worklist; - std::string flattenedString(""); + zstring flattenedString(""); bool constOK = true; { @@ -883,10 +885,10 @@ void theory_str::try_eval_concat(enode * cat) { while (constOK && !worklist.empty()) { app * evalArg = worklist.top(); worklist.pop(); - if (m_strutil.is_string(evalArg)) { - std::string nextStr = m_strutil.get_string_constant_value(evalArg); - flattenedString.append(nextStr); - } else if (is_concat(evalArg)) { + zstring nextStr; + if (u.str.is_string(evalArg, nextStr)) { + flattenedString += nextStr; + } else if (u.str.is_concat(evalArg)) { app * arg0 = to_app(evalArg->get_arg(0)); app * arg1 = to_app(evalArg->get_arg(1)); @@ -899,7 +901,7 @@ void theory_str::try_eval_concat(enode * cat) { } } if (constOK) { - TRACE("t_str_detail", tout << "flattened to \"" << flattenedString << "\"" << std::endl;); + TRACE("t_str_detail", tout << "flattened to \"" << flattenedString.encode().c_str() << "\"" << std::endl;); expr_ref constStr(mk_string(flattenedString), m); expr_ref axiom(ctx.mk_eq_atom(a_cat, constStr), m); assert_axiom(axiom); @@ -911,8 +913,8 @@ void theory_str::try_eval_concat(enode * cat) { * Length(Concat(x, y)) = Length(x) + Length(y) */ void theory_str::instantiate_concat_axiom(enode * cat) { - SASSERT(is_concat(cat)); app * a_cat = cat->get_owner(); + SASSERT(u.str.is_concat(a_cat)); ast_manager & m = get_manager(); @@ -969,15 +971,15 @@ void theory_str::instantiate_basic_string_axioms(enode * str) { // generate a stronger axiom for constant strings app * a_str = str->get_owner(); - if (m_strutil.is_string(str->get_owner())) { + if (u.str.is_string(a_str)) { expr_ref len_str(m); len_str = mk_strlen(a_str); SASSERT(len_str); - const char * strconst = 0; - m_strutil.is_string(str->get_owner(), & strconst); - TRACE("t_str_detail", tout << "instantiating constant string axioms for \"" << strconst << "\"" << std::endl;); - int l = strlen(strconst); + zstring strconst; + u.str.is_string(str->get_owner(), strconst); + TRACE("t_str_detail", tout << "instantiating constant string axioms for \"" << strconst.encode().c_str() << "\"" << std::endl;); + unsigned int l = strconst.length(); expr_ref len(m_autil.mk_numeral(rational(l), true), m); literal lit(mk_eq(len_str, len, false)); @@ -1186,12 +1188,11 @@ void theory_str::instantiate_axiom_Contains(enode * e) { axiomatized_terms.insert(ex); // quick path, because this is necessary due to rewriter behaviour - // (at minimum it should fix z3str/concat-006.smt2 - if (m_strutil.is_string(ex->get_arg(0)) && m_strutil.is_string(ex->get_arg(1))) { + // at minimum it should fix z3str/concat-006.smt2 + zstring haystackStr, needleStr; + if (u.str.is_string(ex->get_arg(0), haystackStr) && u.str.is_string(ex->get_arg(1), needleStr)) { TRACE("t_str_detail", tout << "eval constant Contains term " << mk_pp(ex, m) << std::endl;); - std::string haystackStr = m_strutil.get_string_constant_value(ex->get_arg(0)); - std::string needleStr = m_strutil.get_string_constant_value(ex->get_arg(1)); - if (haystackStr.find(needleStr) != std::string::npos) { + if (haystackStr.contains(needleStr)) { assert_axiom(ex); } else { assert_axiom(m.mk_not(ex)); @@ -1378,8 +1379,8 @@ void theory_str::instantiate_axiom_LastIndexof(enode * e) { thenItems.push_back(ctx.mk_eq_atom(indexAst, mk_strlen(x1))); bool canSkip = false; - if (m_strutil.is_string(expr->get_arg(1))) { - std::string arg1Str = m_strutil.get_string_constant_value(expr->get_arg(1)); + zstring arg1Str; + if (u.str.is_string(expr->get_arg(1), arg1Str)) { if (arg1Str.length() == 1) { canSkip = true; } @@ -1503,30 +1504,6 @@ void theory_str::instantiate_axiom_Substr(enode * e) { expr_ref finalAxiom(m.mk_and(case1, case2, case3), m); SASSERT(finalAxiom); assert_axiom(finalAxiom); - - /* - expr_ref ts0(mk_str_var("ts0"), m); - expr_ref ts1(mk_str_var("ts1"), m); - expr_ref ts2(mk_str_var("ts2"), m); - - expr_ref ts0_contains_ts1(mk_contains(expr->get_arg(0), ts1), m); - - expr_ref_vector and_item(m); - //and_item.push_back(ts0_contains_ts1); - and_item.push_back(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(ts0, mk_concat(ts1, ts2)))); - and_item.push_back(ctx.mk_eq_atom(expr->get_arg(1), mk_strlen(ts0))); - and_item.push_back(ctx.mk_eq_atom(expr->get_arg(2), mk_strlen(ts1))); - - expr_ref breakdownAssert(m.mk_and(and_item.size(), and_item.c_ptr()), m); - SASSERT(breakdownAssert); - - expr_ref reduceToVar(ctx.mk_eq_atom(expr, ts1), m); - SASSERT(reduceToVar); - - expr_ref finalAxiom(m.mk_and(breakdownAssert, reduceToVar), m); - SASSERT(finalAxiom); - assert_axiom(finalAxiom); - */ } void theory_str::instantiate_axiom_Replace(enode * e) { @@ -1651,14 +1628,58 @@ void theory_str::instantiate_axiom_int_to_str(enode * e) { } expr * theory_str::mk_RegexIn(expr * str, expr * regexp) { - expr * args[2] = {str, regexp}; - app * regexIn = get_manager().mk_app(get_id(), OP_RE_REGEXIN, 0, 0, 2, args); + app * regexIn = u.re.mk_in_re(str, regexp); // immediately force internalization so that axiom setup does not fail get_context().internalize(regexIn, false); set_up_axioms(regexIn); return regexIn; } +static zstring str2RegexStr(zstring str) { + zstring res(""); + int len = str.length(); + for (int i = 0; i < len; i++) { + char nc = str[i]; + // 12 special chars + if (nc == '\\' || nc == '^' || nc == '$' || nc == '.' || nc == '|' || nc == '?' + || nc == '*' || nc == '+' || nc == '(' || nc == ')' || nc == '[' || nc == '{') { + res += zstring("\\"); + } + res += zstring(1, (unsigned)str[i]); + } + return res; +} + +zstring theory_str::get_std_regex_str(expr * regex) { + app * a_regex = to_app(regex); + if (u.re.is_to_re(a_regex)) { + expr * regAst = a_regex->get_arg(0); + zstring regAstVal; + u.str.is_string(regAst, regAstVal); + zstring regStr = str2RegexStr(regAstVal); + return regStr; + } else if (u.re.is_concat(a_regex)) { + expr * reg1Ast = a_regex->get_arg(0); + expr * reg2Ast = a_regex->get_arg(1); + zstring reg1Str = get_std_regex_str(reg1Ast); + zstring reg2Str = get_std_regex_str(reg2Ast); + return zstring("(") + reg1Str + zstring(")(") + reg2Str + zstring(")"); + } else if (u.re.is_union(a_regex)) { + expr * reg1Ast = a_regex->get_arg(0); + expr * reg2Ast = a_regex->get_arg(1); + zstring reg1Str = get_std_regex_str(reg1Ast); + zstring reg2Str = get_std_regex_str(reg2Ast); + return zstring("(") + reg1Str + zstring(")|(") + reg2Str + zstring(")"); + } else if (u.re.is_star(a_regex)) { + expr * reg1Ast = a_regex->get_arg(0); + zstring reg1Str = get_std_regex_str(reg1Ast); + return zstring("(") + reg1Str + zstring(")*"); + } else { + TRACE("t_str", tout << "BUG: unrecognized regex term " << mk_pp(regex, get_manager()) << std::endl;); + UNREACHABLE(); return zstring(""); + } +} + void theory_str::instantiate_axiom_RegexIn(enode * e) { context & ctx = get_context(); ast_manager & m = get_manager(); @@ -1673,8 +1694,8 @@ void theory_str::instantiate_axiom_RegexIn(enode * e) { TRACE("t_str_detail", tout << "instantiate RegexIn axiom for " << mk_pp(ex, m) << std::endl;); { - std::string regexStr = m_strutil.get_std_regex_str(ex->get_arg(1)); - std::pair key1(ex->get_arg(0), regexStr); + zstring regexStr = get_std_regex_str(ex->get_arg(1)); + std::pair key1(ex->get_arg(0), regexStr); // skip Z3str's map check, because we already check if we set up axioms on this term regex_in_bool_map[key1] = ex; regex_in_var_reg_str_map[ex->get_arg(0)].insert(regexStr); @@ -1683,7 +1704,7 @@ void theory_str::instantiate_axiom_RegexIn(enode * e) { expr_ref str(ex->get_arg(0), m); app * regex = to_app(ex->get_arg(1)); - if (is_Str2Reg(regex)) { + if (u.re.is_to_re(regex)) { expr_ref rxStr(regex->get_arg(0), m); // want to assert 'expr IFF (str == rxStr)' expr_ref rhs(ctx.mk_eq_atom(str, rxStr), m); @@ -1691,7 +1712,7 @@ void theory_str::instantiate_axiom_RegexIn(enode * e) { SASSERT(finalAxiom); assert_axiom(finalAxiom); TRACE("t_str", tout << "set up Str2Reg: (RegexIn " << mk_pp(str, m) << " " << mk_pp(regex, m) << ")" << std::endl;); - } else if (is_RegexConcat(regex)) { + } else if (u.re.is_concat(regex)) { expr_ref var1(mk_regex_rep_var(), m); expr_ref var2(mk_regex_rep_var(), m); expr_ref rhs(mk_concat(var1, var2), m); @@ -1708,7 +1729,7 @@ void theory_str::instantiate_axiom_RegexIn(enode * e) { expr_ref finalAxiom(mk_and(items), m); SASSERT(finalAxiom); assert_axiom(finalAxiom); - } else if (is_RegexUnion(regex)) { + } else if (u.re.is_union(regex)) { expr_ref var1(mk_regex_rep_var(), m); expr_ref var2(mk_regex_rep_var(), m); expr_ref orVar(m.mk_or(ctx.mk_eq_atom(str, var1), ctx.mk_eq_atom(str, var2)), m); @@ -1721,7 +1742,7 @@ void theory_str::instantiate_axiom_RegexIn(enode * e) { items.push_back(var2InRegex2); items.push_back(ctx.mk_eq_atom(ex, orVar)); assert_axiom(mk_and(items)); - } else if (is_RegexStar(regex)) { + } else if (u.re.is_star(regex)) { // slightly more complex due to the unrolling step. expr_ref regex1(regex->get_arg(0), m); expr_ref unrollCount(mk_unroll_bound_var(), m); @@ -1852,13 +1873,13 @@ void theory_str::group_terms_by_eqc(expr * n, std::set & concats, std::se expr * eqcNode = n; do { app * ast = to_app(eqcNode); - if (is_concat(ast)) { + if (u.str.is_concat(ast)) { expr * simConcat = simplify_concat(ast); if (simConcat != ast) { - if (is_concat(to_app(simConcat))) { + if (u.str.is_concat(to_app(simConcat))) { concats.insert(simConcat); } else { - if (m_strutil.is_string(simConcat)) { + if (u.str.is_string(simConcat)) { consts.insert(simConcat); } else { vars.insert(simConcat); @@ -1867,7 +1888,7 @@ void theory_str::group_terms_by_eqc(expr * n, std::set & concats, std::se } else { concats.insert(simConcat); } - } else if (is_string(ast)) { + } else if (u.str.is_string(ast)) { consts.insert(ast); } else { vars.insert(ast); @@ -1878,7 +1899,7 @@ void theory_str::group_terms_by_eqc(expr * n, std::set & concats, std::se void theory_str::get_nodes_in_concat(expr * node, ptr_vector & nodeList) { app * a_node = to_app(node); - if (!is_concat(a_node)) { + if (!u.str.is_concat(a_node)) { nodeList.push_back(node); return; } else { @@ -1901,16 +1922,21 @@ expr * theory_str::eval_concat(expr * n1, expr * n2) { expr * v1 = get_eqc_value(n1, n1HasEqcValue); expr * v2 = get_eqc_value(n2, n2HasEqcValue); if (n1HasEqcValue && n2HasEqcValue) { - std::string n1_str = m_strutil.get_string_constant_value(v1); - std::string n2_str = m_strutil.get_string_constant_value(v2); - std::string result = n1_str + n2_str; + zstring n1_str, n2_str; + u.str.is_string(v1, n1_str); + u.str.is_string(v2, n2_str); + zstring result = n1_str + n2_str; return mk_string(result); } else if (n1HasEqcValue && !n2HasEqcValue) { - if (m_strutil.get_string_constant_value(v1) == "") { + zstring v1_str; + u.str.is_string(v1, v1_str); + if (v1_str.empty()) { return n2; } } else if (n2HasEqcValue && !n1HasEqcValue) { - if (m_strutil.get_string_constant_value(v2) == "") { + zstring v2_str; + u.str.is_string(v2, v2_str); + if (v2_str.empty()) { return n1; } } @@ -1943,7 +1969,8 @@ void theory_str::simplify_parent(expr * nn, expr * eq_str) { ctx.internalize(nn, false); - std::string eq_strValue = m_strutil.get_string_constant_value(eq_str); + zstring eq_strValue; + u.str.is_string(eq_str, eq_strValue); expr * n_eqNode = nn; do { enode * n_eq_enode = ctx.get_enode(n_eqNode); @@ -1966,7 +1993,7 @@ void theory_str::simplify_parent(expr * nn, expr * eq_str) { app * a_parent = e_parent->get_owner(); TRACE("t_str_detail", tout << "considering parent " << mk_ismt2_pp(a_parent, m) << std::endl;); - if (is_concat(a_parent)) { + if (u.str.is_concat(a_parent)) { expr * arg0 = a_parent->get_arg(0); expr * arg1 = a_parent->get_arg(1); @@ -2028,7 +2055,7 @@ void theory_str::simplify_parent(expr * nn, expr * eq_str) { assert_implication(implyL, implyR); } - } else if (is_concat(to_app(n_eqNode))) { + } else if (u.str.is_concat(to_app(n_eqNode))) { expr_ref simpleConcat(m); simpleConcat = mk_concat(eq_str, arg1); if (!in_same_eqc(a_parent, simpleConcat)) { @@ -2097,7 +2124,7 @@ void theory_str::simplify_parent(expr * nn, expr * eq_str) { assert_implication(implyL, implyR); } - } else if (is_concat(to_app(n_eqNode))) { + } else if (u.str.is_concat(to_app(n_eqNode))) { expr_ref simpleConcat(m); simpleConcat = mk_concat(arg0, eq_str); if (!in_same_eqc(a_parent, simpleConcat)) { @@ -2116,11 +2143,11 @@ void theory_str::simplify_parent(expr * nn, expr * eq_str) { //--------------------------------------------------------- // Case (2-1) begin: (Concat n_eqNode (Concat str var)) - if (arg0 == n_eqNode && is_concat(to_app(arg1))) { + if (arg0 == n_eqNode && u.str.is_concat(to_app(arg1))) { app * a_arg1 = to_app(arg1); TRACE("t_str_detail", tout << "simplify_parent #3" << std::endl;); expr * r_concat_arg0 = a_arg1->get_arg(0); - if (m_strutil.is_string(r_concat_arg0)) { + if (u.str.is_string(r_concat_arg0)) { expr * combined_str = eval_concat(eq_str, r_concat_arg0); SASSERT(combined_str); expr * r_concat_arg1 = a_arg1->get_arg(1); @@ -2140,11 +2167,11 @@ void theory_str::simplify_parent(expr * nn, expr * eq_str) { //--------------------------------------------------------- // Case (2-2) begin: (Concat (Concat var str) n_eqNode) - if (is_concat(to_app(arg0)) && arg1 == n_eqNode) { + if (u.str.is_concat(to_app(arg0)) && arg1 == n_eqNode) { app * a_arg0 = to_app(arg0); TRACE("t_str_detail", tout << "simplify_parent #4" << std::endl;); expr * l_concat_arg1 = a_arg0->get_arg(1); - if (m_strutil.is_string(l_concat_arg1)) { + if (u.str.is_string(l_concat_arg1)) { expr * combined_str = eval_concat(l_concat_arg1, eq_str); SASSERT(combined_str); expr * l_concat_arg0 = a_arg0->get_arg(0); @@ -2169,10 +2196,10 @@ void theory_str::simplify_parent(expr * nn, expr * eq_str) { concat_parent_it != e_parent->end_parents(); concat_parent_it++) { enode * e_concat_parent = *concat_parent_it; app * concat_parent = e_concat_parent->get_owner(); - if (is_concat(concat_parent)) { + if (u.str.is_concat(concat_parent)) { expr * concat_parent_arg0 = concat_parent->get_arg(0); expr * concat_parent_arg1 = concat_parent->get_arg(1); - if (concat_parent_arg0 == a_parent && m_strutil.is_string(concat_parent_arg1)) { + if (concat_parent_arg0 == a_parent && u.str.is_string(concat_parent_arg1)) { TRACE("t_str_detail", tout << "simplify_parent #5" << std::endl;); expr * combinedStr = eval_concat(eq_str, concat_parent_arg1); SASSERT(combinedStr); @@ -2195,10 +2222,10 @@ void theory_str::simplify_parent(expr * nn, expr * eq_str) { concat_parent_it != e_parent->end_parents(); concat_parent_it++) { enode * e_concat_parent = *concat_parent_it; app * concat_parent = e_concat_parent->get_owner(); - if (is_concat(concat_parent)) { + if (u.str.is_concat(concat_parent)) { expr * concat_parent_arg0 = concat_parent->get_arg(0); expr * concat_parent_arg1 = concat_parent->get_arg(1); - if (concat_parent_arg1 == a_parent && m_strutil.is_string(concat_parent_arg0)) { + if (concat_parent_arg1 == a_parent && u.str.is_string(concat_parent_arg0)) { TRACE("t_str_detail", tout << "simplify_parent #6" << std::endl;); expr * combinedStr = eval_concat(concat_parent_arg0, eq_str); SASSERT(combinedStr); @@ -2376,7 +2403,7 @@ void theory_str::infer_len_concat_equality(expr * nn1, expr * nn2) { // Known: a1_arg0 and a1_arg1 // Unknown: nn1 - if (is_concat(to_app(nn1))) { + if (u.str.is_concat(to_app(nn1))) { rational nn1ConcatLen; bool nn1ConcatLen_exists = infer_len_concat(nn1, nn1ConcatLen); if (nnLen_exists && nn1ConcatLen_exists) { @@ -2388,7 +2415,7 @@ void theory_str::infer_len_concat_equality(expr * nn1, expr * nn2) { // Known: a1_arg0 and a1_arg1 // Unknown: nn1 - if (is_concat(to_app(nn2))) { + if (u.str.is_concat(to_app(nn2))) { rational nn2ConcatLen; bool nn2ConcatLen_exists = infer_len_concat(nn2, nn2ConcatLen); if (nnLen_exists && nn2ConcatLen_exists) { @@ -2397,10 +2424,10 @@ void theory_str::infer_len_concat_equality(expr * nn1, expr * nn2) { } if (nnLen_exists) { - if (is_concat(to_app(nn1))) { + if (u.str.is_concat(to_app(nn1))) { infer_len_concat_arg(nn1, nnLen); } - if (is_concat(to_app(nn2))) { + if (u.str.is_concat(to_app(nn2))) { infer_len_concat_arg(nn2, nnLen); } } @@ -2604,17 +2631,17 @@ void theory_str::simplify_concat_equality(expr * nn1, expr * nn2) { // check whether new_nn1 and new_nn2 are still concats - bool n1IsConcat = is_concat(a_new_nn1); - bool n2IsConcat = is_concat(a_new_nn2); + bool n1IsConcat = u.str.is_concat(a_new_nn1); + bool n2IsConcat = u.str.is_concat(a_new_nn2); if (!n1IsConcat && n2IsConcat) { TRACE("t_str_detail", tout << "nn1_new is not a concat" << std::endl;); - if (is_string(a_new_nn1)) { + if (u.str.is_string(a_new_nn1)) { simplify_parent(new_nn2, new_nn1); } return; } else if (n1IsConcat && !n2IsConcat) { TRACE("t_str_detail", tout << "nn2_new is not a concat" << std::endl;); - if (is_string(a_new_nn2)) { + if (u.str.is_string(a_new_nn2)) { simplify_parent(new_nn1, new_nn2); } return; @@ -2712,8 +2739,8 @@ bool theory_str::will_result_in_overlap(expr * lhs, expr * rhs) { app * a_new_nn1 = to_app(new_nn1); app * a_new_nn2 = to_app(new_nn2); - bool n1IsConcat = is_concat(a_new_nn1); - bool n2IsConcat = is_concat(a_new_nn2); + bool n1IsConcat = u.str.is_concat(a_new_nn1); + bool n2IsConcat = u.str.is_concat(a_new_nn2); if (!n1IsConcat && !n2IsConcat) { // we simplified both sides to non-concat expressions... return false; @@ -2766,7 +2793,7 @@ bool theory_str::will_result_in_overlap(expr * lhs, expr * rhs) { expr * v2_arg0 = to_app(new_nn2)->get_arg(0); expr * v2_arg1 = to_app(new_nn2)->get_arg(1); - if (m_strutil.is_string(v1_arg1) && !m_strutil.is_string(v2_arg1)) { + if (u.str.is_string(v1_arg1) && !u.str.is_string(v2_arg1)) { m = v1_arg0; strAst = v1_arg1; x = v2_arg0; @@ -2800,7 +2827,7 @@ bool theory_str::will_result_in_overlap(expr * lhs, expr * rhs) { expr * strAst = NULL; expr * n = NULL; - if (m_strutil.is_string(v1_arg0) && !m_strutil.is_string(v2_arg0)) { + if (u.str.is_string(v1_arg0) && !u.str.is_string(v2_arg0)) { strAst = v1_arg0; n = v1_arg1; x = v2_arg0; @@ -2848,7 +2875,7 @@ bool theory_str::will_result_in_overlap(expr * lhs, expr * rhs) { expr * m = NULL; expr * str2Ast = NULL; - if (m_strutil.is_string(v1_arg0)) { + if (u.str.is_string(v1_arg0)) { str1Ast = v1_arg0; y = v1_arg1; m = v2_arg0; @@ -2881,7 +2908,7 @@ bool theory_str::is_concat_eq_type1(expr * concatAst1, expr * concatAst2) { expr * m = to_app(concatAst2)->get_arg(0); expr * n = to_app(concatAst2)->get_arg(1); - if (!m_strutil.is_string(x) && !m_strutil.is_string(y) && !m_strutil.is_string(m) && !m_strutil.is_string(n)) { + if (!u.str.is_string(x) && !u.str.is_string(y) && !u.str.is_string(m) && !u.str.is_string(n)) { return true; } else { return false; @@ -2896,11 +2923,11 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { << "concatAst2 = " << mk_ismt2_pp(concatAst2, mgr) << std::endl; ); - if (!is_concat(to_app(concatAst1))) { + if (!u.str.is_concat(to_app(concatAst1))) { TRACE("t_str_detail", tout << "concatAst1 is not a concat function" << std::endl;); return; } - if (!is_concat(to_app(concatAst2))) { + if (!u.str.is_concat(to_app(concatAst2))) { TRACE("t_str_detail", tout << "concatAst2 is not a concat function" << std::endl;); return; } @@ -3268,11 +3295,11 @@ bool theory_str::is_concat_eq_type2(expr * concatAst1, expr * concatAst2) { expr * v2_arg0 = to_app(concatAst2)->get_arg(0); expr * v2_arg1 = to_app(concatAst2)->get_arg(1); - if ((!m_strutil.is_string(v1_arg0)) && m_strutil.is_string(v1_arg1) - && (!m_strutil.is_string(v2_arg0)) && (!m_strutil.is_string(v2_arg1))) { + if ((!u.str.is_string(v1_arg0)) && u.str.is_string(v1_arg1) + && (!u.str.is_string(v2_arg0)) && (!u.str.is_string(v2_arg1))) { return true; - } else if ((!m_strutil.is_string(v2_arg0)) && m_strutil.is_string(v2_arg1) - && (!m_strutil.is_string(v1_arg0)) && (!m_strutil.is_string(v1_arg1))) { + } else if ((!u.str.is_string(v2_arg0)) && u.str.is_string(v2_arg1) + && (!u.str.is_string(v1_arg0)) && (!u.str.is_string(v1_arg1))) { return true; } else { return false; @@ -3287,11 +3314,11 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { << "concatAst2 = " << mk_ismt2_pp(concatAst2, mgr) << std::endl; ); - if (!is_concat(to_app(concatAst1))) { + if (!u.str.is_concat(to_app(concatAst1))) { TRACE("t_str_detail", tout << "concatAst1 is not a concat function" << std::endl;); return; } - if (!is_concat(to_app(concatAst2))) { + if (!u.str.is_concat(to_app(concatAst2))) { TRACE("t_str_detail", tout << "concatAst2 is not a concat function" << std::endl;); return; } @@ -3306,7 +3333,7 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { expr * v2_arg0 = to_app(concatAst2)->get_arg(0); expr * v2_arg1 = to_app(concatAst2)->get_arg(1); - if (m_strutil.is_string(v1_arg1) && !m_strutil.is_string(v2_arg1)) { + if (u.str.is_string(v1_arg1) && !u.str.is_string(v2_arg1)) { m = v1_arg0; strAst = v1_arg1; x = v2_arg0; @@ -3318,14 +3345,15 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { y = v1_arg1; } - std::string strValue = m_strutil.get_string_constant_value(strAst); + zstring strValue; + u.str.is_string(strAst, strValue); rational x_len, y_len, m_len, str_len; bool x_len_exists = get_len_value(x, x_len); bool y_len_exists = get_len_value(y, y_len); bool m_len_exists = get_len_value(m, m_len); bool str_len_exists = true; - str_len = rational((unsigned)(strValue.length())); + str_len = rational(strValue.length()); // setup @@ -3502,12 +3530,12 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { << "mLen = " << m_len.to_string() << std::endl << "strLen = " << str_len.to_string() << std::endl << "lenDelta = " << lenDelta.to_string() << std::endl - << "strValue = \"" << strValue << "\" (len=" << strValue.length() << ")" << std::endl + << "strValue = \"" << strValue << "\" (len=" << strValue.length() << ")" << "\n" ; ); - std::string part1Str = strValue.substr(0, lenDelta.get_unsigned()); - std::string part2Str = strValue.substr(lenDelta.get_unsigned(), strValue.length() - lenDelta.get_unsigned()); + zstring part1Str = strValue.extract(0, lenDelta.get_unsigned()); + zstring part2Str = strValue.extract(lenDelta.get_unsigned(), strValue.length() - lenDelta.get_unsigned()); expr_ref prefixStr(mk_string(part1Str), mgr); expr_ref x_concat(mk_concat(m, prefixStr), mgr); @@ -3573,9 +3601,9 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { } } - for (int i = 0; i <= (int)strValue.size(); ++i) { - std::string part1Str = strValue.substr(0, i); - std::string part2Str = strValue.substr(i, strValue.size() - i); + for (unsigned int i = 0; i <= strValue.length(); ++i) { + zstring part1Str = strValue.extract(0, i); + zstring part2Str = strValue.extract(i, strValue.length() - i); expr_ref prefixStr(mk_string(part1Str), mgr); expr_ref x_concat(mk_concat(m, prefixStr), mgr); expr_ref cropStr(mk_string(part2Str), mgr); @@ -3624,11 +3652,11 @@ bool theory_str::is_concat_eq_type3(expr * concatAst1, expr * concatAst2) { expr * v2_arg0 = to_app(concatAst2)->get_arg(0); expr * v2_arg1 = to_app(concatAst2)->get_arg(1); - if (m_strutil.is_string(v1_arg0) && (!m_strutil.is_string(v1_arg1)) - && (!m_strutil.is_string(v2_arg0)) && (!m_strutil.is_string(v2_arg1))) { + if (u.str.is_string(v1_arg0) && (!u.str.is_string(v1_arg1)) + && (!u.str.is_string(v2_arg0)) && (!u.str.is_string(v2_arg1))) { return true; - } else if (m_strutil.is_string(v2_arg0) && (!m_strutil.is_string(v2_arg1)) - && (!m_strutil.is_string(v1_arg0)) && (!m_strutil.is_string(v1_arg1))) { + } else if (u.str.is_string(v2_arg0) && (!u.str.is_string(v2_arg1)) + && (!u.str.is_string(v1_arg0)) && (!u.str.is_string(v1_arg1))) { return true; } else { return false; @@ -3643,11 +3671,11 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { << "concatAst2 = " << mk_ismt2_pp(concatAst2, mgr) << std::endl; ); - if (!is_concat(to_app(concatAst1))) { + if (!u.str.is_concat(to_app(concatAst1))) { TRACE("t_str_detail", tout << "concatAst1 is not a concat function" << std::endl;); return; } - if (!is_concat(to_app(concatAst2))) { + if (!u.str.is_concat(to_app(concatAst2))) { TRACE("t_str_detail", tout << "concatAst2 is not a concat function" << std::endl;); return; } @@ -3662,7 +3690,7 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { expr * strAst = NULL; expr * n = NULL; - if (m_strutil.is_string(v1_arg0) && !m_strutil.is_string(v2_arg0)) { + if (u.str.is_string(v1_arg0) && !u.str.is_string(v2_arg0)) { strAst = v1_arg0; n = v1_arg1; x = v2_arg0; @@ -3674,7 +3702,8 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { y = v1_arg1; } - std::string strValue = m_strutil.get_string_constant_value(strAst); + zstring strValue; + u.str.is_string(strAst, strValue); rational x_len, y_len, str_len, n_len; bool x_len_exists = get_len_value(x, x_len); @@ -3776,9 +3805,9 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { prefixLen = x_len; litems.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_int(x_len))); } - std::string prefixStr = strValue.substr(0, prefixLen.get_unsigned()); + zstring prefixStr = strValue.extract(0, prefixLen.get_unsigned()); rational str_sub_prefix = str_len - prefixLen; - std::string suffixStr = strValue.substr(prefixLen.get_unsigned(), str_sub_prefix.get_unsigned()); + zstring suffixStr = strValue.extract(prefixLen.get_unsigned(), str_sub_prefix.get_unsigned()); expr_ref prefixAst(mk_string(prefixStr), mgr); expr_ref suffixAst(mk_string(suffixStr), mgr); expr_ref ax_l(mgr.mk_and(litems.size(), litems.c_ptr()), mgr); @@ -3878,9 +3907,9 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { unsigned option = 0; int pos = 1; - for (int i = 0; i <= (int) strValue.size(); i++) { - std::string part1Str = strValue.substr(0, i); - std::string part2Str = strValue.substr(i, strValue.size() - i); + for (unsigned int i = 0; i <= strValue.length(); i++) { + zstring part1Str = strValue.extract(0, i); + zstring part2Str = strValue.extract(i, strValue.length() - i); expr_ref cropStr(mk_string(part1Str), mgr); expr_ref suffixStr(mk_string(part2Str), mgr); expr_ref y_concat(mk_concat(suffixStr, n), mgr); @@ -3900,7 +3929,7 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { expr_ref option1(mk_and(and_item), mgr); arrangement_disjunction.push_back(option1); double priority; - if (i == (int)strValue.size()) { + if (i == strValue.length()) { priority = 0.5; } else { priority = 0.1; @@ -3974,8 +4003,8 @@ bool theory_str::is_concat_eq_type4(expr * concatAst1, expr * concatAst2) { expr * v2_arg0 = to_app(concatAst2)->get_arg(0); expr * v2_arg1 = to_app(concatAst2)->get_arg(1); - if (m_strutil.is_string(v1_arg0) && (!m_strutil.is_string(v1_arg1)) - && m_strutil.is_string(v2_arg0) && (!m_strutil.is_string(v2_arg1))) { + if (u.str.is_string(v1_arg0) && (!u.str.is_string(v1_arg1)) + && u.str.is_string(v2_arg0) && (!u.str.is_string(v2_arg1))) { return true; } else { return false; @@ -3990,11 +4019,11 @@ void theory_str::process_concat_eq_type4(expr * concatAst1, expr * concatAst2) { << "concatAst2 = " << mk_ismt2_pp(concatAst2, mgr) << std::endl; ); - if (!is_concat(to_app(concatAst1))) { + if (!u.str.is_concat(to_app(concatAst1))) { TRACE("t_str_detail", tout << "concatAst1 is not a concat function" << std::endl;); return; } - if (!is_concat(to_app(concatAst2))) { + if (!u.str.is_concat(to_app(concatAst2))) { TRACE("t_str_detail", tout << "concatAst2 is not a concat function" << std::endl;); return; } @@ -4009,17 +4038,15 @@ void theory_str::process_concat_eq_type4(expr * concatAst1, expr * concatAst2) { expr * str2Ast = v2_arg0; expr * n = v2_arg1; - const char *tmp = 0; - m_strutil.is_string(str1Ast, &tmp); - std::string str1Value(tmp); - m_strutil.is_string(str2Ast, &tmp); - std::string str2Value(tmp); + zstring str1Value, str2Value; + u.str.is_string(str1Ast, str1Value); + u.str.is_string(str2Ast, str2Value); - int str1Len = str1Value.length(); - int str2Len = str2Value.length(); + unsigned int str1Len = str1Value.length(); + unsigned int str2Len = str2Value.length(); int commonLen = (str1Len > str2Len) ? str2Len : str1Len; - if (str1Value.substr(0, commonLen) != str2Value.substr(0, commonLen)) { + if (str1Value.extract(0, commonLen) != str2Value.extract(0, commonLen)) { TRACE("t_str_detail", tout << "Conflict: " << mk_ismt2_pp(concatAst1, mgr) << " has no common prefix with " << mk_ismt2_pp(concatAst2, mgr) << std::endl;); expr_ref toNegate(mgr.mk_not(ctx.mk_eq_atom(concatAst1, concatAst2)), mgr); @@ -4027,7 +4054,7 @@ void theory_str::process_concat_eq_type4(expr * concatAst1, expr * concatAst2) { return; } else { if (str1Len > str2Len) { - std::string deltaStr = str1Value.substr(str2Len, str1Len - str2Len); + zstring deltaStr = str1Value.extract(str2Len, str1Len - str2Len); expr_ref tmpAst(mk_concat(mk_string(deltaStr), y), mgr); if (!in_same_eqc(tmpAst, n)) { // break down option 4-1 @@ -4052,7 +4079,7 @@ void theory_str::process_concat_eq_type4(expr * concatAst1, expr * concatAst2) { } } } else { - std::string deltaStr = str2Value.substr(str1Len, str2Len - str1Len); + zstring deltaStr = str2Value.extract(str1Len, str2Len - str1Len); expr_ref tmpAst(mk_concat(mk_string(deltaStr), n), mgr); if (!in_same_eqc(y, tmpAst)) { //break down option 4-3 @@ -4077,8 +4104,8 @@ bool theory_str::is_concat_eq_type5(expr * concatAst1, expr * concatAst2) { expr * v2_arg0 = to_app(concatAst2)->get_arg(0); expr * v2_arg1 = to_app(concatAst2)->get_arg(1); - if ((!m_strutil.is_string(v1_arg0)) && m_strutil.is_string(v1_arg1) - && (!m_strutil.is_string(v2_arg0)) && m_strutil.is_string(v2_arg1)) { + if ((!u.str.is_string(v1_arg0)) && u.str.is_string(v1_arg1) + && (!u.str.is_string(v2_arg0)) && u.str.is_string(v2_arg1)) { return true; } else { return false; @@ -4093,11 +4120,11 @@ void theory_str::process_concat_eq_type5(expr * concatAst1, expr * concatAst2) { << "concatAst2 = " << mk_ismt2_pp(concatAst2, mgr) << std::endl; ); - if (!is_concat(to_app(concatAst1))) { + if (!u.str.is_concat(to_app(concatAst1))) { TRACE("t_str_detail", tout << "concatAst1 is not a concat function" << std::endl;); return; } - if (!is_concat(to_app(concatAst2))) { + if (!u.str.is_concat(to_app(concatAst2))) { TRACE("t_str_detail", tout << "concatAst2 is not a concat function" << std::endl;); return; } @@ -4112,17 +4139,15 @@ void theory_str::process_concat_eq_type5(expr * concatAst1, expr * concatAst2) { expr * m = v2_arg0; expr * str2Ast = v2_arg1; - const char *tmp = 0; - m_strutil.is_string(str1Ast, &tmp); - std::string str1Value(tmp); - m_strutil.is_string(str2Ast, &tmp); - std::string str2Value(tmp); + zstring str1Value, str2Value; + u.str.is_string(str1Ast, str1Value); + u.str.is_string(str2Ast, str2Value); - int str1Len = str1Value.length(); - int str2Len = str2Value.length(); + unsigned int str1Len = str1Value.length(); + unsigned int str2Len = str2Value.length(); int cLen = (str1Len > str2Len) ? str2Len : str1Len; - if (str1Value.substr(str1Len - cLen, cLen) != str2Value.substr(str2Len - cLen, cLen)) { + if (str1Value.extract(str1Len - cLen, cLen) != str2Value.extract(str2Len - cLen, cLen)) { TRACE("t_str_detail", tout << "Conflict: " << mk_ismt2_pp(concatAst1, mgr) << " has no common suffix with " << mk_ismt2_pp(concatAst2, mgr) << std::endl;); expr_ref toNegate(mgr.mk_not(ctx.mk_eq_atom(concatAst1, concatAst2)), mgr); @@ -4130,7 +4155,7 @@ void theory_str::process_concat_eq_type5(expr * concatAst1, expr * concatAst2) { return; } else { if (str1Len > str2Len) { - std::string deltaStr = str1Value.substr(0, str1Len - str2Len); + zstring deltaStr = str1Value.extract(0, str1Len - str2Len); expr_ref x_deltaStr(mk_concat(x, mk_string(deltaStr)), mgr); if (!in_same_eqc(m, x_deltaStr)) { expr_ref implyR(ctx.mk_eq_atom(m, x_deltaStr), mgr); @@ -4153,7 +4178,7 @@ void theory_str::process_concat_eq_type5(expr * concatAst1, expr * concatAst2) { } } } else { - std::string deltaStr = str2Value.substr(0, str2Len - str1Len); + zstring deltaStr = str2Value.extract(0, str2Len - str1Len); expr_ref m_deltaStr(mk_concat(m, mk_string(deltaStr)), mgr); if (!in_same_eqc(x, m_deltaStr)) { expr_ref implyR(ctx.mk_eq_atom(x, m_deltaStr), mgr); @@ -4177,11 +4202,11 @@ bool theory_str::is_concat_eq_type6(expr * concatAst1, expr * concatAst2) { expr * v2_arg0 = to_app(concatAst2)->get_arg(0); expr * v2_arg1 = to_app(concatAst2)->get_arg(1); - if (m_strutil.is_string(v1_arg0) && (!m_strutil.is_string(v1_arg1)) - && (!m_strutil.is_string(v2_arg0)) && m_strutil.is_string(v2_arg1)) { + if (u.str.is_string(v1_arg0) && (!u.str.is_string(v1_arg1)) + && (!u.str.is_string(v2_arg0)) && u.str.is_string(v2_arg1)) { return true; - } else if (m_strutil.is_string(v2_arg0) && (!m_strutil.is_string(v2_arg1)) - && (!m_strutil.is_string(v1_arg0)) && m_strutil.is_string(v1_arg1)) { + } else if (u.str.is_string(v2_arg0) && (!u.str.is_string(v2_arg1)) + && (!u.str.is_string(v1_arg0)) && u.str.is_string(v1_arg1)) { return true; } else { return false; @@ -4196,11 +4221,11 @@ void theory_str::process_concat_eq_type6(expr * concatAst1, expr * concatAst2) { << "concatAst2 = " << mk_ismt2_pp(concatAst2, mgr) << std::endl; ); - if (!is_concat(to_app(concatAst1))) { + if (!u.str.is_concat(to_app(concatAst1))) { TRACE("t_str_detail", tout << "concatAst1 is not a concat function" << std::endl;); return; } - if (!is_concat(to_app(concatAst2))) { + if (!u.str.is_concat(to_app(concatAst2))) { TRACE("t_str_detail", tout << "concatAst2 is not a concat function" << std::endl;); return; } @@ -4216,7 +4241,7 @@ void theory_str::process_concat_eq_type6(expr * concatAst1, expr * concatAst2) { expr * m = NULL; expr * str2Ast = NULL; - if (m_strutil.is_string(v1_arg0)) { + if (u.str.is_string(v1_arg0)) { str1Ast = v1_arg0; y = v1_arg1; m = v2_arg0; @@ -4228,14 +4253,12 @@ void theory_str::process_concat_eq_type6(expr * concatAst1, expr * concatAst2) { str2Ast = v1_arg1; } - const char *tmp = 0; - m_strutil.is_string(str1Ast, &tmp); - std::string str1Value(tmp); - m_strutil.is_string(str2Ast, &tmp); - std::string str2Value(tmp); + zstring str1Value, str2Value; + u.str.is_string(str1Ast, str1Value); + u.str.is_string(str2Ast, str2Value); - int str1Len = str1Value.length(); - int str2Len = str2Value.length(); + unsigned int str1Len = str1Value.length(); + unsigned int str2Len = str2Value.length(); //---------------------------------------- //(a) |---str1---|----y----| @@ -4248,11 +4271,11 @@ void theory_str::process_concat_eq_type6(expr * concatAst1, expr * concatAst2) { // |------m------|-str2-| //---------------------------------------- - std::list overlapLen; + std::list overlapLen; overlapLen.push_back(0); - for (int i = 1; i <= str1Len && i <= str2Len; i++) { - if (str1Value.substr(str1Len - i, i) == str2Value.substr(0, i)) + for (unsigned int i = 1; i <= str1Len && i <= str2Len; i++) { + if (str1Value.extract(str1Len - i, i) == str2Value.extract(0, i)) overlapLen.push_back(i); } @@ -4351,10 +4374,10 @@ void theory_str::process_concat_eq_type6(expr * concatAst1, expr * concatAst2) { } } - for (std::list::iterator itor = overlapLen.begin(); itor != overlapLen.end(); itor++) { - int overLen = *itor; - std::string prefix = str1Value.substr(0, str1Len - overLen); - std::string suffix = str2Value.substr(overLen, str2Len - overLen); + for (std::list::iterator itor = overlapLen.begin(); itor != overlapLen.end(); itor++) { + unsigned int overLen = *itor; + zstring prefix = str1Value.extract(0, str1Len - overLen); + zstring suffix = str2Value.extract(overLen, str2Len - overLen); expr_ref_vector and_item(mgr); @@ -4408,12 +4431,13 @@ void theory_str::process_unroll_eq_const_str(expr * unrollFunc, expr * constStr) if (!is_Unroll(to_app(unrollFunc))) { return; } - if (!m_strutil.is_string(constStr)) { + if (!u.str.is_string(constStr)) { return; } expr * funcInUnroll = to_app(unrollFunc)->get_arg(0); - std::string strValue = m_strutil.get_string_constant_value(constStr); + zstring strValue; + u.str.is_string(constStr, strValue); TRACE("t_str_detail", tout << "unrollFunc: " << mk_pp(unrollFunc, m) << std::endl << "constStr: " << mk_pp(constStr, m) << std::endl;); @@ -4422,7 +4446,7 @@ void theory_str::process_unroll_eq_const_str(expr * unrollFunc, expr * constStr) return; } - if (is_Str2Reg(to_app(funcInUnroll))) { + if (u.re.is_to_re(to_app(funcInUnroll))) { unroll_str2reg_constStr(unrollFunc, constStr); return; } @@ -4504,12 +4528,14 @@ void theory_str::unroll_str2reg_constStr(expr * unrollFunc, expr * eqConstStr) { expr * strInStr2RegFunc = to_app(str2RegFunc)->get_arg(0); expr * oriCnt = to_app(unrollFunc)->get_arg(1); - std::string strValue = m_strutil.get_string_constant_value(eqConstStr); - std::string regStrValue = m_strutil.get_string_constant_value(strInStr2RegFunc); - int strLen = strValue.length(); - int regStrLen = regStrValue.length(); + zstring strValue; + u.str.is_string(eqConstStr, strValue); + zstring regStrValue; + u.str.is_string(strInStr2RegFunc, regStrValue); + unsigned int strLen = strValue.length(); + unsigned int regStrLen = regStrValue.length(); SASSERT(regStrLen != 0); // this should never occur -- the case for empty string is handled elsewhere - int cnt = strLen / regStrLen; + unsigned int cnt = strLen / regStrLen; expr_ref implyL(ctx.mk_eq_atom(unrollFunc, eqConstStr), m); expr_ref implyR1(ctx.mk_eq_atom(oriCnt, mk_int(cnt)), m); @@ -4537,7 +4563,7 @@ expr * theory_str::get_eqc_value(expr * n, bool & hasEqcValue) { expr * theory_str::z3str2_get_eqc_value(expr * n , bool & hasEqcValue) { expr * curr = n; do { - if (m_strutil.is_string(curr)) { + if (u.str.is_string(curr)) { hasEqcValue = true; return curr; } @@ -4649,14 +4675,16 @@ bool theory_str::get_len_value(expr* e, rational& val) { while (!todo.empty()) { expr* c = todo.back(); todo.pop_back(); - if (is_concat(to_app(c))) { + if (u.str.is_concat(to_app(c))) { e1 = to_app(c)->get_arg(0); e2 = to_app(c)->get_arg(1); todo.push_back(e1); todo.push_back(e2); } - else if (is_string(to_app(c))) { - int sl = m_strutil.get_string_constant_value(c).length(); + else if (u.str.is_string(to_app(c))) { + zstring tmp; + u.str.is_string(to_app(c), tmp); + unsigned int sl = tmp.length(); val += rational(sl); } else { @@ -4738,7 +4766,7 @@ expr * theory_str::collect_eq_nodes(expr * n, expr_ref_vector & eqcSet) { expr * ex = n; do { - if (m_strutil.is_string(to_app(ex))) { + if (u.str.is_string(to_app(ex))) { constStrNode = ex; } eqcSet.push_back(ex); @@ -4753,7 +4781,7 @@ expr * theory_str::collect_eq_nodes(expr * n, expr_ref_vector & eqcSet) { */ void theory_str::get_const_str_asts_in_node(expr * node, expr_ref_vector & astList) { ast_manager & m = get_manager(); - if (m_strutil.is_string(node)) { + if (u.str.is_string(node)) { astList.push_back(node); //} else if (getNodeType(t, node) == my_Z3_Func) { } else if (is_app(node)) { @@ -4806,7 +4834,8 @@ void theory_str::check_contain_by_eqc_val(expr * varNode, expr * constNode) { if (strAst != constNode) { litems.push_back(ctx.mk_eq_atom(strAst, constNode)); } - std::string strConst = m_strutil.get_string_constant_value(constNode); + zstring strConst; + u.str.is_string(constNode, strConst); bool subStrHasEqcValue = false; expr * substrValue = get_eqc_value(substrAst, subStrHasEqcValue); if (substrValue != substrAst) { @@ -4815,11 +4844,12 @@ void theory_str::check_contain_by_eqc_val(expr * varNode, expr * constNode) { if (subStrHasEqcValue) { // subStr has an eqc constant value - std::string subStrConst = m_strutil.get_string_constant_value(substrValue); + zstring subStrConst; + u.str.is_string(substrValue, subStrConst); - TRACE("t_str_detail", tout << "strConst = " << strConst << ", subStrConst = " << subStrConst << std::endl;); + TRACE("t_str_detail", tout << "strConst = " << strConst << ", subStrConst = " << subStrConst << "\n";); - if (strConst.find(subStrConst) != std::string::npos) { + if (strConst.contains(subStrConst)) { //implyR = ctx.mk_eq(ctx, boolVar, Z3_mk_true(ctx)); implyR = boolVar; } else { @@ -4845,8 +4875,9 @@ void theory_str::check_contain_by_eqc_val(expr * varNode, expr * constNode) { get_const_str_asts_in_node(aConcat, constList); for (expr_ref_vector::iterator cstItor = constList.begin(); cstItor != constList.end(); cstItor++) { - std::string pieceStr = m_strutil.get_string_constant_value(*cstItor); - if (strConst.find(pieceStr) == std::string::npos) { + zstring pieceStr; + u.str.is_string(*cstItor, pieceStr); + if (strConst.contains(pieceStr)) { counterEgFound = true; if (aConcat != substrAst) { litems.push_back(ctx.mk_eq_atom(substrAst, aConcat)); @@ -4883,9 +4914,10 @@ void theory_str::check_contain_by_eqc_val(expr * varNode, expr * constNode) { } if (strHasEqcValue) { - std::string strConst = m_strutil.get_string_constant_value(strValue); - std::string subStrConst = m_strutil.get_string_constant_value(constNode); - if (strConst.find(subStrConst) != std::string::npos) { + zstring strConst, subStrConst; + u.str.is_string(strValue, strConst); + u.str.is_string(constNode, subStrConst); + if (strConst.contains(subStrConst)) { //implyR = Z3_mk_eq(ctx, boolVar, Z3_mk_true(ctx)); implyR = boolVar; } else { @@ -4941,19 +4973,21 @@ void theory_str::check_contain_by_substr(expr * varNode, expr_ref_vector & willE if (strValue != strAst) { litems.push_back(ctx.mk_eq_atom(strAst, strValue)); } - std::string strConst = m_strutil.get_string_constant_value(strValue); + zstring strConst; + u.str.is_string(strValue, strConst); // iterate eqc (also eqc-to-be) of substr for (expr_ref_vector::iterator itAst = willEqClass.begin(); itAst != willEqClass.end(); itAst++) { bool counterEgFound = false; - if (is_concat(to_app(*itAst))) { + if (u.str.is_concat(to_app(*itAst))) { expr_ref_vector constList(m); // get constant strings in concat app * aConcat = to_app(*itAst); get_const_str_asts_in_node(aConcat, constList); for (expr_ref_vector::iterator cstItor = constList.begin(); cstItor != constList.end(); cstItor++) { - std::string pieceStr = m_strutil.get_string_constant_value(*cstItor); - if (strConst.find(pieceStr) == std::string::npos) { + zstring pieceStr; + u.str.is_string(*cstItor, pieceStr); + if (!strConst.contains(pieceStr)) { TRACE("t_str_detail", tout << "Inconsistency found!" << std::endl;); counterEgFound = true; if (aConcat != substrAst) { @@ -5045,18 +5079,19 @@ void theory_str::check_contain_by_eq_nodes(expr * n1, expr * n2) { litems1.push_back(ctx.mk_eq_atom(subAst2, subValue2)); } - std::string subConst1 = m_strutil.get_string_constant_value(subValue1); - std::string subConst2 = m_strutil.get_string_constant_value(subValue2); + zstring subConst1, subConst2; + u.str.is_string(subValue1, subConst1); + u.str.is_string(subValue2, subConst2); expr_ref implyR(m); if (subConst1 == subConst2) { // key1.first = key2.first /\ key1.second = key2.second // ==> (containPairBoolMap[key1] = containPairBoolMap[key2]) implyR = ctx.mk_eq_atom(contain_pair_bool_map[key1], contain_pair_bool_map[key2]); - } else if (subConst1.find(subConst2) != std::string::npos) { + } else if (subConst1.contains(subConst2)) { // key1.first = key2.first /\ Contains(key1.second, key2.second) // ==> (containPairBoolMap[key1] --> containPairBoolMap[key2]) implyR = rewrite_implication(contain_pair_bool_map[key1], contain_pair_bool_map[key2]); - } else if (subConst2.find(subConst1) != std::string::npos) { + } else if (subConst2.contains(subConst1)) { // key1.first = key2.first /\ Contains(key2.second, key1.second) // ==> (containPairBoolMap[key2] --> containPairBoolMap[key1]) implyR = rewrite_implication(contain_pair_bool_map[key2], contain_pair_bool_map[key1]); @@ -5191,19 +5226,20 @@ void theory_str::check_contain_by_eq_nodes(expr * n1, expr * n2) { litems1.push_back(ctx.mk_eq_atom(str2, strVal2)); } - std::string const1 = m_strutil.get_string_constant_value(strVal1); - std::string const2 = m_strutil.get_string_constant_value(strVal2); + zstring const1, const2; + u.str.is_string(strVal1, const1); + u.str.is_string(strVal2, const2); expr_ref implyR(m); if (const1 == const2) { // key1.second = key2.second /\ key1.first = key2.first // ==> (containPairBoolMap[key1] = containPairBoolMap[key2]) implyR = ctx.mk_eq_atom(contain_pair_bool_map[key1], contain_pair_bool_map[key2]); - } else if (const1.find(const2) != std::string::npos) { + } else if (const1.contains(const2)) { // key1.second = key2.second /\ Contains(key1.first, key2.first) // ==> (containPairBoolMap[key2] --> containPairBoolMap[key1]) implyR = rewrite_implication(contain_pair_bool_map[key2], contain_pair_bool_map[key1]); - } else if (const2.find(const1) != std::string::npos) { + } else if (const2.contains(const1)) { // key1.first = key2.first /\ Contains(key2.first, key1.first) // ==> (containPairBoolMap[key1] --> containPairBoolMap[key2]) implyR = rewrite_implication(contain_pair_bool_map[key1], contain_pair_bool_map[key2]); @@ -5398,7 +5434,7 @@ void theory_str::check_contain_in_new_eq(expr * n1, expr * n2) { expr * theory_str::dealias_node(expr * node, std::map & varAliasMap, std::map & concatAliasMap) { if (variable_set.find(node) != variable_set.end()) { return get_alias_index_ast(varAliasMap, node); - } else if (is_concat(to_app(node))) { + } else if (u.str.is_concat(to_app(node))) { return get_alias_index_ast(concatAliasMap, node); } return node; @@ -5427,13 +5463,13 @@ void theory_str::get_grounded_concats(expr* node, std::map & varAl ast_manager & m = get_manager(); // const strings: node is de-aliased - if (m_strutil.is_string(node)) { + if (u.str.is_string(node)) { std::vector concatNodes; concatNodes.push_back(node); groundedMap[node][concatNodes].clear(); // no condition } // Concat functions - else if (is_concat(to_app(node))) { + else if (u.str.is_concat(to_app(node))) { // if "node" equals to a constant string, thenjust push the constant into the concat vector // Again "node" has been de-aliased at the very beginning if (concatConstMap.find(node) != concatConstMap.end()) { @@ -5461,7 +5497,7 @@ void theory_str::get_grounded_concats(expr* node, std::map & varAl ndVec.insert(ndVec.end(), arg0_grdItor->first.begin(), arg0_grdItor->first.end()); int arg0VecSize = arg0_grdItor->first.size(); int arg1VecSize = arg1_grdItor->first.size(); - if (arg0VecSize > 0 && arg1VecSize > 0 && m_strutil.is_string(arg0_grdItor->first[arg0VecSize - 1]) && m_strutil.is_string(arg1_grdItor->first[0])) { + if (arg0VecSize > 0 && arg1VecSize > 0 && u.str.is_string(arg0_grdItor->first[arg0VecSize - 1]) && u.str.is_string(arg1_grdItor->first[0])) { ndVec.pop_back(); ndVec.push_back(mk_concat(arg0_grdItor->first[arg0VecSize - 1], arg1_grdItor->first[0])); for (int i = 1; i < arg1VecSize; i++) { @@ -5565,11 +5601,11 @@ bool theory_str::is_partial_in_grounded_concat(const std::vector & strVec } if (subStrCnt == 1) { - if (m_strutil.is_string(subStrVec[0])) { - std::string subStrVal = m_strutil.get_string_constant_value(subStrVec[0]); + zstring subStrVal; + if (u.str.is_string(subStrVec[0]), subStrVal) { for (int i = 0; i < strCnt; i++) { - if (m_strutil.is_string(strVec[i])) { - std::string strVal = m_strutil.get_string_constant_value(strVec[i]); + zstring strVal; + if (u.str.is_string(strVec[i], strVal)) { if (strVal.find(subStrVal) != std::string::npos) { return true; } @@ -5589,12 +5625,12 @@ bool theory_str::is_partial_in_grounded_concat(const std::vector & strVec // * constant: a suffix of a note in strVec[i] // * variable: bool firstNodesOK = true; - if (m_strutil.is_string(subStrVec[0])) { - std::string subStrHeadVal = m_strutil.get_string_constant_value(subStrVec[0]); - if (m_strutil.is_string(strVec[i])) { - std::string strHeadVal = m_strutil.get_string_constant_value(strVec[i]); - if (strHeadVal.size() >= subStrHeadVal.size()) { - std::string suffix = strHeadVal.substr(strHeadVal.size() - subStrHeadVal.size(), subStrHeadVal.size()); + zstring subStrHeadVal; + if (u.str.is_string(subStrVec[0], subStrHeadVal)) { + zstring strHeadVal; + if (u.str.is_string(strVec[i], strHeadVal)) { + if (strHeadVal.length() >= subStrHeadVal.length()) { + std::string suffix = strHeadVal.extract(strHeadVal.length() - subStrHeadVal.length(), subStrHeadVal.length()); if (suffix != subStrHeadVal) { firstNodesOK = false; } @@ -5625,12 +5661,12 @@ bool theory_str::is_partial_in_grounded_concat(const std::vector & strVec // tail nodes int tailIdx = i + subStrCnt - 1; - if (m_strutil.is_string(subStrVec[subStrCnt - 1])) { - std::string subStrTailVal = m_strutil.get_string_constant_value(subStrVec[subStrCnt - 1]); - if (m_strutil.is_string(strVec[tailIdx])) { - std::string strTailVal = m_strutil.get_string_constant_value(strVec[tailIdx]); - if (strTailVal.size() >= subStrTailVal.size()) { - std::string prefix = strTailVal.substr(0, subStrTailVal.size()); + zstring subStrTailVal; + if (u.str.is_string(subStrVec[subStrCnt - 1], subStrTailVal)) { + zstring strTailVal; + if (u.str.is_string(strVec[tailIdx], strTailVal)) { + if (strTailVal.length() >= subStrTailVal.length()) { + zstring prefix = strTailVal.extract(0, subStrTailVal.length()); if (prefix == subStrTailVal) { return true; } else { @@ -5721,44 +5757,44 @@ void theory_str::compute_contains(std::map & varAliasMap, } } -bool theory_str::can_concat_eq_str(expr * concat, std::string str) { +bool theory_str::can_concat_eq_str(expr * concat, zstring& str) { int strLen = str.length(); - if (is_concat(to_app(concat))) { + if (u.str.is_concat(to_app(concat))) { ptr_vector args; get_nodes_in_concat(concat, args); expr * ml_node = args[0]; expr * mr_node = args[args.size() - 1]; - if (m_strutil.is_string(ml_node)) { - std::string ml_str = m_strutil.get_string_constant_value(ml_node); - int ml_len = ml_str.length(); + zstring ml_str; + if (u.str.is_string(ml_node, ml_str)) { + unsigned int ml_len = ml_str.length(); if (ml_len > strLen) { return false; } - int cLen = ml_len; - if (ml_str != str.substr(0, cLen)) { + unsigned int cLen = ml_len; + if (ml_str != str.extract(0, cLen)) { return false; } } - if (m_strutil.is_string(mr_node)) { - std::string mr_str = m_strutil.get_string_constant_value(mr_node); - int mr_len = mr_str.length(); + zstring mr_str; + if (u.str.is_string(mr_node, mr_str)) { + unsigned int mr_len = mr_str.length(); if (mr_len > strLen) { return false; } - int cLen = mr_len; - if (mr_str != str.substr(strLen - cLen, cLen)) { + unsigned int cLen = mr_len; + if (mr_str != str.extract(strLen - cLen, cLen)) { return false; } } - int sumLen = 0; + unsigned int sumLen = 0; for (unsigned int i = 0 ; i < args.size() ; i++) { expr * oneArg = args[i]; - if (m_strutil.is_string(oneArg)) { - std::string arg_str = m_strutil.get_string_constant_value(oneArg); - if (str.find(arg_str) == std::string::npos) { + zstring arg_str; + if (u.str.is_string(oneArg, arg_str)) { + if (str.contains(arg_str)) { return false; } sumLen += arg_str.length(); @@ -5773,17 +5809,16 @@ bool theory_str::can_concat_eq_str(expr * concat, std::string str) { } bool theory_str::can_concat_eq_concat(expr * concat1, expr * concat2) { - if (is_concat(to_app(concat1)) && is_concat(to_app(concat2))) { + if (u.str.is_concat(to_app(concat1)) && u.str.is_concat(to_app(concat2))) { { // Suppose concat1 = (Concat X Y) and concat2 = (Concat M N). expr * concat1_mostL = getMostLeftNodeInConcat(concat1); expr * concat2_mostL = getMostLeftNodeInConcat(concat2); // if both X and M are constant strings, check whether they have the same prefix - if (m_strutil.is_string(concat1_mostL) && m_strutil.is_string(concat2_mostL)) { - std::string concat1_mostL_str = m_strutil.get_string_constant_value(concat1_mostL); - std::string concat2_mostL_str = m_strutil.get_string_constant_value(concat2_mostL); - int cLen = std::min(concat1_mostL_str.length(), concat2_mostL_str.length()); - if (concat1_mostL_str.substr(0, cLen) != concat2_mostL_str.substr(0, cLen)) { + zstring concat1_mostL_str, concat2_mostL_str; + if (u.str.is_string(concat1_mostL, concat1_mostL_str) && u.str.is_string(concat2_mostL, concat2_mostL_str)) { + unsigned int cLen = std::min(concat1_mostL_str.length(), concat2_mostL_str.length()); + if (concat1_mostL_str.extract(0, cLen) != concat2_mostL_str.extract(0, cLen)) { return false; } } @@ -5793,12 +5828,11 @@ bool theory_str::can_concat_eq_concat(expr * concat1, expr * concat2) { // Similarly, if both Y and N are constant strings, check whether they have the same suffix expr * concat1_mostR = getMostRightNodeInConcat(concat1); expr * concat2_mostR = getMostRightNodeInConcat(concat2); - if (m_strutil.is_string(concat1_mostR) && m_strutil.is_string(concat2_mostR)) { - std::string concat1_mostR_str = m_strutil.get_string_constant_value(concat1_mostR); - std::string concat2_mostR_str = m_strutil.get_string_constant_value(concat2_mostR); - int cLen = std::min(concat1_mostR_str.length(), concat2_mostR_str.length()); - if (concat1_mostR_str.substr(concat1_mostR_str.length() - cLen, cLen) != - concat2_mostR_str.substr(concat2_mostR_str.length() - cLen, cLen)) { + zstring concat1_mostR_str, concat2_mostR_str; + if (u.str.is_string(concat1_mostR, concat1_mostR_str) && u.str.is_string(concat2_mostR, concat2_mostR_str)) { + unsigned int cLen = std::min(concat1_mostR_str.length(), concat2_mostR_str.length()); + if (concat1_mostR_str.extract(concat1_mostR_str.length() - cLen, cLen) != + concat2_mostR_str.extract(concat2_mostR_str.length() - cLen, cLen)) { return false; } } @@ -5817,31 +5851,29 @@ bool theory_str::can_two_nodes_eq(expr * n1, expr * n2) { app * n2_curr = to_app(n2); // case 0: n1_curr is const string, n2_curr is const string - if (is_string(n1_curr) && is_string(n2_curr)) { + if (u.str.is_string(n1_curr) && u.str.is_string(n2_curr)) { if (n1_curr != n2_curr) { return false; } } // case 1: n1_curr is concat, n2_curr is const string - else if (is_concat(n1_curr) && is_string(n2_curr)) { - const char * tmp = 0; - m_strutil.is_string(n2_curr, & tmp); - std::string n2_curr_str(tmp); + else if (u.str.is_concat(n1_curr) && u.str.is_string(n2_curr)) { + zstring n2_curr_str; + u.str.is_string(n2_curr, n2_curr_str); if (!can_concat_eq_str(n1_curr, n2_curr_str)) { return false; } } // case 2: n2_curr is concat, n1_curr is const string - else if (is_concat(n2_curr) && is_string(n1_curr)) { - const char * tmp = 0; - m_strutil.is_string(n1_curr, & tmp); - std::string n1_curr_str(tmp); + else if (u.str.is_concat(n2_curr) && u.str.is_string(n1_curr)) { + zstring n1_curr_str; + u.str.is_string(n1_curr, n1_curr_str); if (!can_concat_eq_str(n2_curr, n1_curr_str)) { return false; } } // case 3: both are concats - else if (is_concat(n1_curr) && is_concat(n2_curr)) { + else if (u.str.is_concat(n1_curr) && u.str.is_concat(n2_curr)) { if (!can_concat_eq_concat(n1_curr, n2_curr)) { return false; } @@ -5857,9 +5889,11 @@ bool theory_str::check_length_const_string(expr * n1, expr * constStr) { ast_manager & mgr = get_manager(); context & ctx = get_context(); - rational strLen((unsigned) (m_strutil.get_string_constant_value(constStr).length())); + zstring tmp; + u.str.is_string(constStr, tmp); + rational strLen(tmp.length()); - if (is_concat(to_app(n1))) { + if (u.str.is_concat(to_app(n1))) { ptr_vector args; expr_ref_vector items(mgr); @@ -5870,7 +5904,7 @@ bool theory_str::check_length_const_string(expr * n1, expr * constStr) { rational argLen; bool argLen_exists = get_len_value(args[i], argLen); if (argLen_exists) { - if (!m_strutil.is_string(args[i])) { + if (!u.str.is_string(args[i])) { items.push_back(ctx.mk_eq_atom(mk_strlen(args[i]), mk_int(argLen))); } TRACE("t_str_detail", tout << "concat arg: " << mk_pp(args[i], mgr) << " has len = " << argLen.to_string() << std::endl;); @@ -5926,7 +5960,7 @@ bool theory_str::check_length_concat_concat(expr * n1, expr * n2) { bool argLen_exists = get_len_value(oneArg, argLen); if (argLen_exists) { sum1 += argLen; - if (!m_strutil.is_string(oneArg)) { + if (!u.str.is_string(oneArg)) { items.push_back(ctx.mk_eq_atom(mk_strlen(oneArg), mk_int(argLen))); } } else { @@ -5940,7 +5974,7 @@ bool theory_str::check_length_concat_concat(expr * n1, expr * n2) { bool argLen_exists = get_len_value(oneArg, argLen); if (argLen_exists) { sum2 += argLen; - if (!m_strutil.is_string(oneArg)) { + if (!u.str.is_string(oneArg)) { items.push_back(ctx.mk_eq_atom(mk_strlen(oneArg), mk_int(argLen))); } } else { @@ -5993,7 +6027,7 @@ bool theory_str::check_length_concat_var(expr * concat, expr * var) { rational argLen; bool argLen_exists = get_len_value(oneArg, argLen); if (argLen_exists) { - if (!m_strutil.is_string(oneArg) && !argLen.is_zero()) { + if (!u.str.is_string(oneArg) && !argLen.is_zero()) { items.push_back(ctx.mk_eq_atom(mk_strlen(oneArg), mk_int(argLen))); } sumLen += argLen; @@ -6036,8 +6070,8 @@ bool theory_str::check_length_var_var(expr * var1, expr * var2) { // - note that these are different from the semantics in Z3str2 bool theory_str::check_length_eq_var_concat(expr * n1, expr * n2) { // n1 and n2 are not const string: either variable or concat - bool n1Concat = is_concat(to_app(n1)); - bool n2Concat = is_concat(to_app(n2)); + bool n1Concat = u.str.is_concat(to_app(n1)); + bool n2Concat = u.str.is_concat(to_app(n2)); if (n1Concat && n2Concat) { return check_length_concat_concat(n1, n2); } @@ -6059,12 +6093,12 @@ bool theory_str::check_length_eq_var_concat(expr * n1, expr * n2) { // returns false if an inconsistency is detected, or true if no inconsistencies were found // - note that these are different from the semantics of checkLengConsistency() in Z3str2 bool theory_str::check_length_consistency(expr * n1, expr * n2) { - if (m_strutil.is_string(n1) && m_strutil.is_string(n2)) { + if (u.str.is_string(n1) && u.str.is_string(n2)) { // consistency has already been checked in can_two_nodes_eq(). return true; - } else if (m_strutil.is_string(n1) && (!m_strutil.is_string(n2))) { + } else if (u.str.is_string(n1) && (!u.str.is_string(n2))) { return check_length_const_string(n2, n1); - } else if (m_strutil.is_string(n2) && (!m_strutil.is_string(n1))) { + } else if (u.str.is_string(n2) && (!u.str.is_string(n1))) { return check_length_const_string(n1, n2); } else { // n1 and n2 are vars or concats @@ -6082,7 +6116,7 @@ bool theory_str::check_concat_len_in_eqc(expr * concat) { expr * eqc_n = concat; do { - if (is_concat(to_app(eqc_n))) { + if (u.str.is_concat(to_app(eqc_n))) { rational unused; bool status = infer_len_concat(eqc_n, unused); if (status) { @@ -6114,13 +6148,15 @@ void theory_str::check_regex_in(expr * nn1, expr * nn2) { std::set::iterator strItor = regex_in_var_reg_str_map[*itor].begin(); for (; strItor != regex_in_var_reg_str_map[*itor].end(); strItor++) { std::string regStr = *strItor; - std::string constStrValue = m_strutil.get_string_constant_value(constStr); + zstring constStrValue; + u.str.is_string(constStr, constStrValue); std::pair key1 = std::make_pair(*itor, regStr); if (regex_in_bool_map.find(key1) != regex_in_bool_map.end()) { expr * boolVar = regex_in_bool_map[key1]; // actually the RegexIn term app * a_regexIn = to_app(boolVar); expr * regexTerm = a_regexIn->get_arg(1); + // TODO figure out regex NFA stuff if (regex_nfa_cache.find(regexTerm) == regex_nfa_cache.end()) { TRACE("t_str_detail", tout << "regex_nfa_cache: cache miss" << std::endl;); regex_nfa_cache[regexTerm] = nfa(m_strutil, regexTerm); @@ -6159,16 +6195,14 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { TRACE("t_str_detail", tout << mk_ismt2_pp(concat, m) << " == " << mk_ismt2_pp(str, m) << std::endl;); - if (is_concat(to_app(concat)) && is_string(to_app(str))) { - const char * tmp = 0; - m_strutil.is_string(str, & tmp); - std::string const_str(tmp); + zstring const_str; + if (u.str.is_concat(to_app(concat)) && u.str.is_string(to_app(str), const_str)) { app * a_concat = to_app(concat); SASSERT(a_concat->get_num_args() == 2); expr * a1 = a_concat->get_arg(0); expr * a2 = a_concat->get_arg(1); - if (const_str == "") { + if (const_str.empty()) { TRACE("t_str", tout << "quick path: concat == \"\"" << std::endl;); // assert the following axiom: // ( (Concat a1 a2) == "" ) -> ( (a1 == "") AND (a2 == "") ) @@ -6211,26 +6245,22 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { if (newConcat == str) { return; } - if (!is_concat(to_app(newConcat))) { + if (!u.str.is_concat(to_app(newConcat))) { return; } if (arg1_has_eqc_value && arg2_has_eqc_value) { // Case 1: Concat(const, const) == const TRACE("t_str", tout << "Case 1: Concat(const, const) == const" << std::endl;); - const char * str1; - m_strutil.is_string(arg1, & str1); - std::string arg1_str(str1); + zstring arg1_str, arg2_str; + u.str.is_string(arg1, arg1_str); + u.str.is_string(arg2, arg2_str); - const char * str2; - m_strutil.is_string(arg2, & str2); - std::string arg2_str(str2); - - std::string result_str = arg1_str + arg2_str; + zstring result_str = arg1_str + arg2_str; if (result_str != const_str) { // Inconsistency TRACE("t_str", tout << "inconsistency detected: \"" << arg1_str << "\" + \"" << arg2_str << - "\" != \"" << const_str << "\"" << std::endl;); + "\" != \"" << const_str << "\"\n"); expr_ref equality(ctx.mk_eq_atom(concat, str), m); expr_ref diseq(m.mk_not(equality), m); assert_axiom(diseq); @@ -6239,31 +6269,30 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { } else if (!arg1_has_eqc_value && arg2_has_eqc_value) { // Case 2: Concat(var, const) == const TRACE("t_str", tout << "Case 2: Concat(var, const) == const" << std::endl;); - const char * str2; - m_strutil.is_string(arg2, & str2); - std::string arg2_str(str2); - int resultStrLen = const_str.length(); - int arg2StrLen = arg2_str.length(); + zstring arg2_str; + u.str.is_string(arg2, arg2_str); + unsigned int resultStrLen = const_str.length(); + unsigned int arg2StrLen = arg2_str.length(); if (resultStrLen < arg2StrLen) { // Inconsistency TRACE("t_str", tout << "inconsistency detected: \"" << arg2_str << "\" is longer than \"" << const_str << "\"," - << " so cannot be concatenated with anything to form it" << std::endl;); + << " so cannot be concatenated with anything to form it\n"); expr_ref equality(ctx.mk_eq_atom(newConcat, str), m); expr_ref diseq(m.mk_not(equality), m); assert_axiom(diseq); return; } else { int varStrLen = resultStrLen - arg2StrLen; - std::string firstPart = const_str.substr(0, varStrLen); - std::string secondPart = const_str.substr(varStrLen, arg2StrLen); + zstring firstPart = const_str.extract(0, varStrLen); + zstring secondPart = const_str.extract(varStrLen, arg2StrLen); if (arg2_str != secondPart) { // Inconsistency TRACE("t_str", tout << "inconsistency detected: " << "suffix of concatenation result expected \"" << secondPart << "\", " << "actually \"" << arg2_str << "\"" - << std::endl;); + << "\n"); expr_ref equality(ctx.mk_eq_atom(newConcat, str), m); expr_ref diseq(m.mk_not(equality), m); assert_axiom(diseq); @@ -6279,31 +6308,30 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { } else if (arg1_has_eqc_value && !arg2_has_eqc_value) { // Case 3: Concat(const, var) == const TRACE("t_str", tout << "Case 3: Concat(const, var) == const" << std::endl;); - const char * str1; - m_strutil.is_string(arg1, & str1); - std::string arg1_str(str1); - int resultStrLen = const_str.length(); - int arg1StrLen = arg1_str.length(); + zstring arg1_str; + u.str.is_string(arg1, arg1_str); + unsigned int resultStrLen = const_str.length(); + unsigned int arg1StrLen = arg1_str.length(); if (resultStrLen < arg1StrLen) { // Inconsistency TRACE("t_str", tout << "inconsistency detected: \"" << arg1_str << "\" is longer than \"" << const_str << "\"," - << " so cannot be concatenated with anything to form it" << std::endl;); + << " so cannot be concatenated with anything to form it" << "\n";); expr_ref equality(ctx.mk_eq_atom(newConcat, str), m); expr_ref diseq(m.mk_not(equality), m); assert_axiom(diseq); return; } else { int varStrLen = resultStrLen - arg1StrLen; - std::string firstPart = const_str.substr(0, arg1StrLen); - std::string secondPart = const_str.substr(arg1StrLen, varStrLen); + zstring firstPart = const_str.extract(0, arg1StrLen); + zstring secondPart = const_str.extract(arg1StrLen, varStrLen); if (arg1_str != firstPart) { // Inconsistency TRACE("t_str", tout << "inconsistency detected: " << "prefix of concatenation result expected \"" << secondPart << "\", " << "actually \"" << arg1_str << "\"" - << std::endl;); + << "\n";); expr_ref equality(ctx.mk_eq_atom(newConcat, str), m); expr_ref diseq(m.mk_not(equality), m); assert_axiom(diseq); @@ -6327,7 +6355,7 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { if (arg1Len_exists || arg2Len_exists) { expr_ref ax_l1(ctx.mk_eq_atom(concat, str), m); expr_ref ax_l2(m); - std::string prefixStr, suffixStr; + zstring prefixStr, suffixStr; if (arg1Len_exists) { if (arg1Len.is_neg()) { TRACE("t_str_detail", tout << "length conflict: arg1Len = " << arg1Len << ", concatStrLen = " << concatStrLen << std::endl;); @@ -6341,9 +6369,9 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { return; } - prefixStr = const_str.substr(0, arg1Len.get_unsigned()); + prefixStr = const_str.extract(0, arg1Len.get_unsigned()); rational concat_minus_arg1 = concatStrLen - arg1Len; - suffixStr = const_str.substr(arg1Len.get_unsigned(), concat_minus_arg1.get_unsigned()); + suffixStr = const_str.extract(arg1Len.get_unsigned(), concat_minus_arg1.get_unsigned()); ax_l2 = ctx.mk_eq_atom(mk_strlen(arg1), mk_int(arg1Len)); } else { // arg2's length is available @@ -6360,17 +6388,17 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { } rational concat_minus_arg2 = concatStrLen - arg2Len; - prefixStr = const_str.substr(0, concat_minus_arg2.get_unsigned()); - suffixStr = const_str.substr(concat_minus_arg2.get_unsigned(), arg2Len.get_unsigned()); + prefixStr = const_str.extract(0, concat_minus_arg2.get_unsigned()); + suffixStr = const_str.extract(concat_minus_arg2.get_unsigned(), arg2Len.get_unsigned()); ax_l2 = ctx.mk_eq_atom(mk_strlen(arg2), mk_int(arg2Len)); } // consistency check - if (is_concat(to_app(arg1)) && !can_concat_eq_str(arg1, prefixStr)) { + if (u.str.is_concat(to_app(arg1)) && !can_concat_eq_str(arg1, prefixStr)) { expr_ref ax_r(m.mk_not(ax_l2), m); assert_implication(ax_l1, ax_r); return; } - if (is_concat(to_app(arg2)) && !can_concat_eq_str(arg2, suffixStr)) { + if (u.str.is_concat(to_app(arg2)) && !can_concat_eq_str(arg2, suffixStr)) { expr_ref ax_r(m.mk_not(ax_l2), m); assert_implication(ax_l1, ax_r); return; @@ -6379,10 +6407,10 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { r_items.push_back(ctx.mk_eq_atom(arg1, mk_string(prefixStr))); r_items.push_back(ctx.mk_eq_atom(arg2, mk_string(suffixStr))); if (!arg1Len_exists) { - r_items.push_back(ctx.mk_eq_atom(mk_strlen(arg1), mk_int(prefixStr.size()))); + r_items.push_back(ctx.mk_eq_atom(mk_strlen(arg1), mk_int(prefixStr.length()))); } if (!arg2Len_exists) { - r_items.push_back(ctx.mk_eq_atom(mk_strlen(arg2), mk_int(suffixStr.size()))); + r_items.push_back(ctx.mk_eq_atom(mk_strlen(arg2), mk_int(suffixStr.length()))); } expr_ref lhs(m.mk_and(ax_l1, ax_l2), m); expr_ref rhs(mk_and(r_items), m); @@ -6456,13 +6484,13 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { for (int i = 0; i < concatStrLen + 1; ++i) { expr_ref_vector and_items(m); - std::string prefixStr = const_str.substr(0, i); - std::string suffixStr = const_str.substr(i, concatStrLen - i); + zstring prefixStr = const_str.extract(0, i); + zstring suffixStr = const_str.extract(i, concatStrLen - i); // skip invalid options - if (is_concat(to_app(arg1)) && !can_concat_eq_str(arg1, prefixStr)) { + if (u.str.is_concat(to_app(arg1)) && !can_concat_eq_str(arg1, prefixStr)) { continue; } - if (is_concat(to_app(arg2)) && !can_concat_eq_str(arg2, suffixStr)) { + if (u.str.is_concat(to_app(arg2)) && !can_concat_eq_str(arg2, suffixStr)) { continue; } @@ -6530,8 +6558,8 @@ expr_ref theory_str::set_up_finite_model_test(expr * lhs, expr * rhs) { } // make things easy for the core wrt. testvar - expr_ref t1(ctx.mk_eq_atom(testvar, m_strutil.mk_string("")), m); - expr_ref t_yes(ctx.mk_eq_atom(testvar, m_strutil.mk_string("yes")), m); + expr_ref t1(ctx.mk_eq_atom(testvar, u.str.mk_string("")), m); + expr_ref t_yes(ctx.mk_eq_atom(testvar, u.str.mk_string("yes")), m); expr_ref testvaraxiom(m.mk_or(t1, t_yes), m); assert_axiom(testvaraxiom); @@ -6544,8 +6572,8 @@ void theory_str::finite_model_test(expr * testvar, expr * str) { context & ctx = get_context(); ast_manager & m = get_manager(); - if (!m_strutil.is_string(str)) return; - std::string s = m_strutil.get_string_constant_value(str); + zstring s; + if (!u.str.is_string(str, s)) return; if (s == "yes") { TRACE("t_str", tout << "start finite model test for " << mk_pp(testvar, m) << std::endl;); ptr_vector & vars = finite_model_test_varlists[testvar]; @@ -6642,7 +6670,7 @@ void theory_str::finite_model_test(expr * testvar, expr * str) { for (rational l = v_lower_bound; l <= v_upper_bound; l += rational::one()) { std::string lStr = l.to_string(); - expr_ref str_indicator(m_strutil.mk_string(lStr), m); + expr_ref str_indicator(u.str.mk_string(lStr), m); expr_ref or_expr(ctx.mk_eq_atom(indicator, str_indicator), m); orList.push_back(or_expr); expr_ref and_expr(ctx.mk_eq_atom(or_expr, ctx.mk_eq_atom(vLengthExpr, m_autil.mk_numeral(l, true))), m); @@ -6661,7 +6689,7 @@ void theory_str::finite_model_test(expr * testvar, expr * str) { } // (s == "yes") } -void theory_str::more_len_tests(expr * lenTester, std::string lenTesterValue) { +void theory_str::more_len_tests(expr * lenTester, zstring lenTesterValue) { ast_manager & m = get_manager(); if (lenTester_fvar_map.contains(lenTester)) { expr * fVar = lenTester_fvar_map[lenTester]; @@ -6673,7 +6701,7 @@ void theory_str::more_len_tests(expr * lenTester, std::string lenTesterValue) { } } -void theory_str::more_value_tests(expr * valTester, std::string valTesterValue) { +void theory_str::more_value_tests(expr * valTester, zstring valTesterValue) { ast_manager & m = get_manager(); expr * fVar = valueTester_fvar_map[valTester]; @@ -6689,7 +6717,8 @@ void theory_str::more_value_tests(expr * valTester, std::string valTesterValue) TRACE("t_str_binary_search", tout << "WARNING: length tester " << mk_pp(effectiveLenInd, m) << " at top of stack for " << mk_pp(fVar, m) << " has no EQC value" << std::endl;); } else { // safety check - std::string effectiveLenIndiStr = m_strutil.get_string_constant_value(len_indicator_value); + zstring effectiveLenIndiStr; + u.str.is_string(len_indicator_value, effectiveLenIndiStr); if (effectiveLenIndiStr == "more" || effectiveLenIndiStr == "less") { TRACE("t_str_binary_search", tout << "ERROR: illegal state -- requesting 'more value tests' but a length tester is not yet concrete!" << std::endl;); UNREACHABLE(); @@ -6704,13 +6733,14 @@ void theory_str::more_value_tests(expr * valTester, std::string valTesterValue) int lenTesterCount = fvar_lenTester_map[fVar].size(); expr * effectiveLenInd = NULL; - std::string effectiveLenIndiStr = ""; + zstring effectiveLenIndiStr = ""; for (int i = 0; i < lenTesterCount; ++i) { expr * len_indicator_pre = fvar_lenTester_map[fVar][i]; bool indicatorHasEqcValue = false; expr * len_indicator_value = get_eqc_value(len_indicator_pre, indicatorHasEqcValue); if (indicatorHasEqcValue) { - std::string len_pIndiStr = m_strutil.get_string_constant_value(len_indicator_value); + zstring len_pIndiStr; + u.str.is_string(len_indicator_value, len_pIndiStr); if (len_pIndiStr != "more") { effectiveLenInd = len_indicator_pre; effectiveLenIndiStr = len_pIndiStr; @@ -6728,14 +6758,13 @@ void theory_str::more_value_tests(expr * valTester, std::string valTesterValue) bool theory_str::free_var_attempt(expr * nn1, expr * nn2) { ast_manager & m = get_manager(); - - if (internal_lenTest_vars.contains(nn1) && m_strutil.is_string(nn2)) { + zstring nn2_str; + if (internal_lenTest_vars.contains(nn1) && u.str.is_string(nn2, nn2_str)) { TRACE("t_str", tout << "acting on equivalence between length tester var " << mk_ismt2_pp(nn1, m) << " and constant " << mk_ismt2_pp(nn2, m) << std::endl;); - more_len_tests(nn1, m_strutil.get_string_constant_value(nn2)); + more_len_tests(nn1, nn2_str); return true; - } else if (internal_valTest_vars.contains(nn1) && m_strutil.is_string(nn2)) { - std::string nn2_str = m_strutil.get_string_constant_value(nn2); + } else if (internal_valTest_vars.contains(nn1) && u.str.is_string(nn2, nn2_str)) { if (nn2_str == "more") { TRACE("t_str", tout << "acting on equivalence between value var " << mk_ismt2_pp(nn1, m) << " and constant " << mk_ismt2_pp(nn2, m) << std::endl;); @@ -6755,7 +6784,7 @@ void theory_str::handle_equality(expr * lhs, expr * rhs) { // both terms must be of sort String sort * lhs_sort = m.get_sort(lhs); sort * rhs_sort = m.get_sort(rhs); - sort * str_sort = m.mk_sort(get_family_id(), STRING_SORT); + sort * str_sort = u.str.mk_string_sort(); if (lhs_sort != str_sort || rhs_sort != str_sort) { TRACE("t_str_detail", tout << "skip equality: not String sort" << std::endl;); @@ -6776,7 +6805,7 @@ void theory_str::handle_equality(expr * lhs, expr * rhs) { return; } - if (is_concat(to_app(lhs)) && is_concat(to_app(rhs))) { + if (u.str.is_concat(to_app(lhs)) && u.str.is_concat(to_app(rhs))) { bool nn1HasEqcValue = false; bool nn2HasEqcValue = false; expr * nn1_value = get_eqc_value(lhs, nn1HasEqcValue); @@ -6993,7 +7022,7 @@ void theory_str::set_up_axioms(expr * ex) { context & ctx = get_context(); sort * ex_sort = m.get_sort(ex); - sort * str_sort = m.mk_sort(get_family_id(), STRING_SORT); + sort * str_sort = u.str.mk_string_sort(); sort * bool_sort = m.mk_bool_sort(); family_id m_arith_fid = m.mk_family_id("arith"); @@ -7011,23 +7040,23 @@ void theory_str::set_up_axioms(expr * ex) { if (is_app(ex)) { app * ap = to_app(ex); - if (is_concat(ap)) { + if (u.str.is_concat(ap)) { // if ex is a concat, set up concat axioms later m_concat_axiom_todo.push_back(n); // we also want to check whether we can eval this concat, // in case the rewriter did not totally finish with this term m_concat_eval_todo.push_back(n); - } else if (is_strlen(ap)) { + } else if (u.str.is_length(ap)) { // if the argument is a variable, // keep track of this for later, we'll need it during model gen expr * var = ap->get_arg(0); app * aVar = to_app(var); - if (aVar->get_num_args() == 0 && !is_string(aVar)) { + if (aVar->get_num_args() == 0 && !u.str.is_string(aVar)) { input_var_in_len.insert(var); } - } else if (is_CharAt(ap) || is_Substr(ap) || is_Replace(ap)) { + } else if (u.str.is_at(ap) || u.str.is_extract(ap) || u.str.is_replace(ap)) { m_library_aware_axiom_todo.push_back(n); - } else if (ap->get_num_args() == 0 && !is_string(ap)) { + } else if (ap->get_num_args() == 0 && !u.str.is_string(ap)) { // if ex is a variable, add it to our list of variables TRACE("t_str_detail", tout << "tracking variable " << mk_ismt2_pp(ap, get_manager()) << std::endl;); variable_set.insert(ex); @@ -7049,7 +7078,7 @@ void theory_str::set_up_axioms(expr * ex) { if (is_app(ex)) { app * ap = to_app(ex); - if (is_StartsWith(ap) || is_EndsWith(ap) || is_Contains(ap) || is_RegexIn(ap)) { + if (u.str.is_prefix(ap) || u.str.is_suffix(ap) || u.str.is_contains(ap) || u.str.is_in_re(ap)) { m_library_aware_axiom_todo.push_back(n); } } @@ -7068,9 +7097,10 @@ void theory_str::set_up_axioms(expr * ex) { if (is_app(ex)) { app * ap = to_app(ex); - if (is_Indexof(ap) || is_Indexof2(ap) || is_LastIndexof(ap)) { + // TODO indexof2/lastindexof + if (u.str.is_index(ap) /* || is_Indexof2(ap) || is_LastIndexof(ap) */) { m_library_aware_axiom_todo.push_back(n); - } else if (is_str_to_int(ap) || is_int_to_str(ap)) { + } else if (u.str.is_stoi(ap) || u.str.is_itos(ap)) { string_int_conversion_terms.push_back(ap); m_library_aware_axiom_todo.push_back(n); } @@ -7200,12 +7230,12 @@ void theory_str::recursive_check_variable_scope(expr * ex) { if (a->get_num_args() == 0) { // we only care about string variables sort * s = m.get_sort(ex); - sort * string_sort = m.mk_sort(get_family_id(), STRING_SORT); + sort * string_sort = u.str.mk_string_sort(); if (s != string_sort) { return; } // base case: string constant / var - if (m_strutil.is_string(a)) { + if (u.str.is_string(a)) { return; } else { // assume var @@ -7331,10 +7361,10 @@ void theory_str::classify_ast_by_type(expr * node, std::map & varMap // check whether the node is a function that we want to inspect else if (is_app(node)) { app * aNode = to_app(node); - if (is_strlen(aNode)) { + if (u.str.is_length(aNode)) { // Length return; - } else if (is_concat(aNode)) { + } else if (u.str.is_concat(aNode)) { expr * arg0 = aNode->get_arg(0); expr * arg1 = aNode->get_arg(1); bool arg0HasEq = false; @@ -7343,10 +7373,13 @@ void theory_str::classify_ast_by_type(expr * node, std::map & varMap expr * arg1Val = get_eqc_value(arg1, arg1HasEq); int canskip = 0; - if (arg0HasEq && m_strutil.get_string_constant_value(arg0Val).empty()) { + zstring tmp; + u.str.is_string(arg0Val, tmp); + if (arg0HasEq && tmp.empty()) { canskip = 1; } - if (canskip == 0 && arg1HasEq && m_strutil.get_string_constant_value(arg1Val).empty()) { + u.str.is_string(arg1Val, tmp); + if (canskip == 0 && arg1HasEq && tmp.empty()) { canskip = 1; } if (canskip == 0 && concatMap.find(node) == concatMap.end()) { @@ -7402,7 +7435,7 @@ inline expr * theory_str::get_alias_index_ast(std::map & aliasInde inline expr * theory_str::getMostLeftNodeInConcat(expr * node) { app * aNode = to_app(node); - if (!is_concat(aNode)) { + if (!u.str.is_concat(aNode)) { return node; } else { expr * concatArgL = aNode->get_arg(0); @@ -7412,7 +7445,7 @@ inline expr * theory_str::getMostLeftNodeInConcat(expr * node) { inline expr * theory_str::getMostRightNodeInConcat(expr * node) { app * aNode = to_app(node); - if (!is_concat(aNode)) { + if (!u.str.is_concat(aNode)) { return node; } else { expr * concatArgR = aNode->get_arg(1); @@ -7556,7 +7589,7 @@ void theory_str::trace_ctx_dep(std::ofstream & tout, enode * e_curr_end = e_curr; do { app * curr = e_curr->get_owner(); - if (is_concat(curr)) { + if (u.str.is_concat(curr)) { tout << " >>> " << mk_pp(curr, mgr) << std::endl; } e_curr = e_curr->get_next(); @@ -7691,7 +7724,7 @@ int theory_str::ctx_dep_analysis(std::map & strVarMap, std::mapget_arg(0); expr * arg1 = aCurr->get_arg(1); bool arg0HasEqcValue = false; @@ -7701,18 +7734,18 @@ int theory_str::ctx_dep_analysis(std::map & strVarMap, std::map & strVarMap, std::mapfirst; do { - if (is_concat(to_app(curr))) { + if (u.str.is_concat(to_app(curr))) { if (aRoot == NULL) { aRoot = curr; } else { @@ -7780,7 +7813,7 @@ int theory_str::ctx_dep_analysis(std::map & strVarMap, std::map & strVarMap, std::map::iterator itor1 = itor->second.begin(); itor1 != itor->second.end(); itor1++) { expr * concatNode = itor1->first; expr * mLNode = getMostLeftNodeInConcat(concatNode); - const char * strval; - if (m_strutil.is_string(to_app(mLNode), & strval)) { - if (mLConst == NULL && strcmp(strval, "") != 0) { + zstring strval; + if (u.str.is_string(to_app(mLNode), strval)) { + if (mLConst == NULL && strval.empty()) { mLConst = mLNode; } } else { @@ -7913,8 +7946,8 @@ int theory_str::ctx_dep_analysis(std::map & strVarMap, std::map integer value < 0 if (Sval.empty()) { // ignore this. we should already assert the axiom for what happens when the string is "" @@ -8228,7 +8262,7 @@ bool theory_str::finalcheck_int2str(app * a) { rational ten(10); bool conversionOK = true; for (unsigned i = 0; i < Sval.length(); ++i) { - char digit = Sval.at(i); + char digit = (int)Sval[i]; if (isdigit((int)digit)) { std::string sDigit(1, digit); int val = atoi(sDigit.c_str()); @@ -8272,11 +8306,11 @@ void theory_str::collect_var_concat(expr * node, std::set & varSet, std:: } else if (is_app(node)) { app * aNode = to_app(node); - if (is_strlen(aNode)) { + if (u.str.is_length(aNode)) { // Length return; } - if (is_concat(aNode)) { + if (u.str.is_concat(aNode)) { expr * arg0 = aNode->get_arg(0); expr * arg1 = aNode->get_arg(1); if (concatSet.find(node) == concatSet.end()) { @@ -8406,7 +8440,7 @@ bool theory_str::propagate_length(std::set & varSet, std::set & co void theory_str::get_unique_non_concat_nodes(expr * node, std::set & argSet) { app * a_node = to_app(node); - if (!is_concat(a_node)) { + if (!u.str.is_concat(a_node)) { argSet.insert(node); return; } else { @@ -8447,7 +8481,7 @@ final_check_status theory_str::final_check_eh() { for (std::set::iterator it = eqc_roots.begin(); it != eqc_roots.end(); ++it) { enode * e = *it; app * a = e->get_owner(); - if (!(is_sort_of(m.get_sort(a), m_strutil.get_fid(), STRING_SORT))) { + if (!(m.get_sort(a) == u.str.mk_string_sort())) { TRACE("t_str_detail", tout << "EQC root " << mk_pp(a, m) << " not a string term; skipping" << std::endl;); } else { TRACE("t_str_detail", tout << "EQC root " << mk_pp(a, m) << " is a string term. Checking this EQC" << std::endl;); @@ -8516,9 +8550,10 @@ final_check_status theory_str::final_check_eh() { if (concat_lhs_haseqc && concat_rhs_haseqc && !var_haseqc) { TRACE("t_str_detail", tout << "backpropagate into " << mk_pp(var, m) << " = " << mk_pp(concat, m) << std::endl << "LHS ~= " << mk_pp(concat_lhs_str, m) << " RHS ~= " << mk_pp(concat_rhs_str, m) << std::endl;); - std::string lhsString = m_strutil.get_string_constant_value(concat_lhs_str); - std::string rhsString = m_strutil.get_string_constant_value(concat_rhs_str); - std::string concatString = lhsString + rhsString; + zstring lhsString, rhsString; + u.str.is_string(concat_lhs_str, lhsString); + u.str.is_string(concat_rhs_str, rhsString); + zstring concatString = lhsString + rhsString; expr_ref lhs1(ctx.mk_eq_atom(concat_lhs, concat_lhs_str), m); expr_ref lhs2(ctx.mk_eq_atom(concat_rhs, concat_rhs_str), m); expr_ref lhs(m.mk_and(lhs1, lhs2), m); @@ -8584,12 +8619,12 @@ final_check_status theory_str::final_check_eh() { bool addedStrIntAxioms = false; for (unsigned i = 0; i < string_int_conversion_terms.size(); ++i) { app * ex = to_app(string_int_conversion_terms[i].get()); - if (is_str_to_int(ex)) { + if (u.str.is_stoi(ex)) { bool axiomAdd = finalcheck_str2int(ex); if (axiomAdd) { addedStrIntAxioms = true; } - } else if (is_int_to_str(ex)) { + } else if (u.str.is_itos(ex)) { bool axiomAdd = finalcheck_int2str(ex); if (axiomAdd) { addedStrIntAxioms = true; @@ -8664,7 +8699,7 @@ final_check_status theory_str::final_check_eh() { expr * unroll = urItor->first; expr * curr = unroll; do { - if (is_concat(to_app(curr))) { + if (u.str.is_concat(to_app(curr))) { concatEqUnrollsMap[curr].insert(unroll); concatEqUnrollsMap[curr].insert(unrollGroup_map[unroll].begin(), unrollGroup_map[unroll].end()); } @@ -8690,7 +8725,7 @@ final_check_status theory_str::final_check_eh() { } else { fvUnrollSet.insert(concatArg1); } - } else if (is_concat(to_app(concatArg1))) { + } else if (u.str.is_concat(to_app(concatArg1))) { if (concatEqUnrollsMap.find(concatArg1) == concatEqUnrollsMap.end()) { arg1Bounded = true; } @@ -8702,7 +8737,7 @@ final_check_status theory_str::final_check_eh() { } else { fvUnrollSet.insert(concatArg2); } - } else if (is_concat(to_app(concatArg2))) { + } else if (u.str.is_concat(to_app(concatArg2))) { if (concatEqUnrollsMap.find(concatArg2) == concatEqUnrollsMap.end()) { arg2Bounded = true; } @@ -8794,10 +8829,11 @@ final_check_status theory_str::final_check_eh() { return FC_CONTINUE; // since by this point we've added axioms } -inline std::string int_to_string(int i) { +inline zstring int_to_string(int i) { std::stringstream ss; ss << i; - return ss.str(); + std::string str = ss.str(); + return zstring(str.c_str()); } inline std::string longlong_to_string(long long i) { @@ -8823,11 +8859,11 @@ void theory_str::print_value_tester_list(svector > & teste ); } -std::string theory_str::gen_val_string(int len, int_vector & encoding) { +zstring theory_str::gen_val_string(int len, int_vector & encoding) { SASSERT(charSetSize > 0); SASSERT(char_set != NULL); - std::string re = std::string(len, char_set[0]); + zstring re(len, (int) char_set[0]); for (int i = 0; i < (int) encoding.size() - 1; i++) { int idx = encoding[i]; re[len - 1 - i] = char_set[idx]; @@ -8876,7 +8912,7 @@ bool theory_str::get_next_val_encode(int_vector & base, int_vector & next) { } expr * theory_str::gen_val_options(expr * freeVar, expr * len_indicator, expr * val_indicator, - std::string lenStr, int tries) { + zstring lenStr, int tries) { ast_manager & m = get_manager(); context & ctx = get_context(); @@ -8894,7 +8930,7 @@ expr * theory_str::gen_val_options(expr * freeVar, expr * len_indicator, expr * // {0, 0, 1} // the last item "1" shows this is not a valid encoding, and we have covered all space // ---------------------------------------------------------------------------------------- - int len = atoi(lenStr.c_str()); + int len = atoi(lenStr.encode().c_str()); bool coverAll = false; svector options; int_vector base; @@ -8903,8 +8939,8 @@ expr * theory_str::gen_val_options(expr * freeVar, expr * len_indicator, expr * << "freeVar = " << mk_ismt2_pp(freeVar, m) << std::endl << "len_indicator = " << mk_ismt2_pp(len_indicator, m) << std::endl << "val_indicator = " << mk_ismt2_pp(val_indicator, m) << std::endl - << "lenstr = " << lenStr << std::endl - << "tries = " << tries << std::endl; + << "lenstr = " << lenStr << "\n" + << "tries = " << tries << "\n"; if (m_params.m_AggressiveValueTesting) { tout << "note: aggressive value testing is enabled" << std::endl; } @@ -8953,7 +8989,7 @@ expr * theory_str::gen_val_options(expr * freeVar, expr * len_indicator, expr * ctx.force_phase(l); } - std::string aStr = gen_val_string(len, options[i - l]); + zstring aStr = gen_val_string(len, options[i - l]); expr * strAst; if (m_params.m_UseFastValueTesterCache) { if (!valueTesterCache.find(aStr, strAst)) { @@ -8996,7 +9032,7 @@ expr * theory_str::gen_val_options(expr * freeVar, expr * len_indicator, expr * // Should add ($$_len_x_j = 16) /\ ($$_val_x_16_i = "more") // --------------------------------------- andList.reset(); - andList.push_back(m.mk_eq(len_indicator, mk_string(lenStr.c_str()))); + andList.push_back(m.mk_eq(len_indicator, mk_string(lenStr))); for (int i = 0; i < tries; i++) { expr * vTester = fvar_valueTester_map[freeVar][len][i].second; if (vTester != val_indicator) @@ -9019,10 +9055,10 @@ expr * theory_str::gen_val_options(expr * freeVar, expr * len_indicator, expr * } expr * theory_str::gen_free_var_options(expr * freeVar, expr * len_indicator, - std::string len_valueStr, expr * valTesterInCbEq, std::string valTesterValueStr) { + zstring len_valueStr, expr * valTesterInCbEq, zstring valTesterValueStr) { ast_manager & m = get_manager(); - int len = atoi(len_valueStr.c_str()); + int len = atoi(len_valueStr.encode().c_str()); // check whether any value tester is actually in scope TRACE("t_str_detail", tout << "checking scope of previous value testers" << std::endl;); @@ -9117,7 +9153,7 @@ void theory_str::reduce_virtual_regex_in(expr * var, expr * regex, expr_ref_vect TRACE("t_str_detail", tout << "reduce regex " << mk_pp(regex, mgr) << " with respect to variable " << mk_pp(var, mgr) << std::endl;); app * regexFuncDecl = to_app(regex); - if (is_Str2Reg(regexFuncDecl)) { + if (u.re.is_to_re(regexFuncDecl)) { // --------------------------------------------------------- // var \in Str2Reg(s1) // ==> @@ -9129,7 +9165,7 @@ void theory_str::reduce_virtual_regex_in(expr * var, expr * regex, expr_ref_vect return; } // RegexUnion - else if (is_RegexUnion(regexFuncDecl)) { + else if (u.re.is_union(regexFuncDecl)) { // --------------------------------------------------------- // var \in RegexUnion(r1, r2) // ==> @@ -9156,7 +9192,7 @@ void theory_str::reduce_virtual_regex_in(expr * var, expr * regex, expr_ref_vect return; } // RegexConcat - else if (is_RegexConcat(regexFuncDecl)) { + else if (u.re.is_concat(regexFuncDecl)) { // --------------------------------------------------------- // var \in RegexConcat(r1, r2) // ==> @@ -9177,7 +9213,7 @@ void theory_str::reduce_virtual_regex_in(expr * var, expr * regex, expr_ref_vect return; } // Unroll - else if (is_RegexStar(regexFuncDecl)) { + else if (u.re.is_star(regexFuncDecl)) { // --------------------------------------------------------- // var \in Star(r1) // ==> @@ -9190,6 +9226,7 @@ void theory_str::reduce_virtual_regex_in(expr * var, expr * regex, expr_ref_vect items.push_back(ctx.mk_eq_atom(mk_strlen(var), mk_strlen(unrollFunc))); return; } else { + get_manager().raise_exception("unrecognized regex operator"); UNREACHABLE(); } } @@ -9291,8 +9328,8 @@ static int computeLCM(int a, int b) { return temp ? (a / temp * b) : 0; } -static std::string get_unrolled_string(std::string core, int count) { - std::string res = ""; +static zstring get_unrolled_string(zstring core, int count) { + zstring res(""); for (int i = 0; i < count; i++) { res += core; } @@ -9306,11 +9343,12 @@ expr * theory_str::gen_assign_unroll_Str2Reg(expr * n, std::set & unrolls int lcm = 1; int coreValueCount = 0; expr * oneUnroll = NULL; - std::string oneCoreStr = ""; + zstring oneCoreStr(""); for (std::set::iterator itor = unrolls.begin(); itor != unrolls.end(); itor++) { expr * str2RegFunc = to_app(*itor)->get_arg(0); expr * coreVal = to_app(str2RegFunc)->get_arg(0); - std::string coreStr = m_strutil.get_string_constant_value(coreVal); + zstring coreStr; + u.str.is_string(coreVal, coreStr); if (oneUnroll == NULL) { oneUnroll = *itor; oneCoreStr = coreStr; @@ -9322,13 +9360,14 @@ expr * theory_str::gen_assign_unroll_Str2Reg(expr * n, std::set & unrolls // bool canHaveNonEmptyAssign = true; expr_ref_vector litems(mgr); - std::string lcmStr = get_unrolled_string(oneCoreStr, (lcm / oneCoreStr.length())); + zstring lcmStr = get_unrolled_string(oneCoreStr, (lcm / oneCoreStr.length())); for (std::set::iterator itor = unrolls.begin(); itor != unrolls.end(); itor++) { expr * str2RegFunc = to_app(*itor)->get_arg(0); expr * coreVal = to_app(str2RegFunc)->get_arg(0); - std::string coreStr = m_strutil.get_string_constant_value(coreVal); - int core1Len = coreStr.length(); - std::string uStr = get_unrolled_string(coreStr, (lcm / core1Len)); + zstring coreStr; + u.str.is_string(coreVal, coreStr); + unsigned int core1Len = coreStr.length(); + zstring uStr = get_unrolled_string(coreStr, (lcm / core1Len)); if (uStr != lcmStr) { canHaveNonEmptyAssign = false; } @@ -9346,7 +9385,7 @@ expr * theory_str::gen_assign_unroll_Str2Reg(expr * n, std::set & unrolls } } -expr * theory_str::gen_unroll_conditional_options(expr * var, std::set & unrolls, std::string lcmStr) { +expr * theory_str::gen_unroll_conditional_options(expr * var, std::set & unrolls, zstring lcmStr) { context & ctx = get_context(); ast_manager & mgr = get_manager(); @@ -9416,8 +9455,9 @@ expr * theory_str::gen_unroll_conditional_options(expr * var, std::set & // insert [tester = "more"] to litems so that the implyL for next tester is correct litems.push_back(ctx.mk_eq_atom(tester, moreAst)); } else { - std::string testerStr = m_strutil.get_string_constant_value(testerVal); - TRACE("t_str_detail", tout << "Tester [" << mk_pp(tester, mgr) << "] = " << testerStr << std::endl;); + zstring testerStr; + u.str.is_string(testerVal, testerStr); + TRACE("t_str_detail", tout << "Tester [" << mk_pp(tester, mgr) << "] = " << testerStr << "\n";); if (testerStr == "more") { litems.push_back(ctx.mk_eq_atom(tester, moreAst)); } @@ -9438,12 +9478,12 @@ expr * theory_str::gen_unroll_conditional_options(expr * var, std::set & return toAssert; } -expr * theory_str::gen_unroll_assign(expr * var, std::string lcmStr, expr * testerVar, int l, int h) { +expr * theory_str::gen_unroll_assign(expr * var, zstring lcmStr, expr * testerVar, int l, int h) { context & ctx = get_context(); ast_manager & mgr = get_manager(); TRACE("t_str_detail", tout << "entry: var = " << mk_pp(var, mgr) << ", lcmStr = " << lcmStr - << ", l = " << l << ", h = " << h << std::endl;); + << ", l = " << l << ", h = " << h << "\n";); if (m_params.m_AggressiveUnrollTesting) { TRACE("t_str_detail", tout << "note: aggressive unroll testing is active" << std::endl;); @@ -9453,7 +9493,7 @@ expr * theory_str::gen_unroll_assign(expr * var, std::string lcmStr, expr * test expr_ref_vector andItems(mgr); for (int i = l; i < h; i++) { - std::string iStr = int_to_string(i); + zstring iStr = int_to_string(i); expr_ref testerEqAst(ctx.mk_eq_atom(testerVar, mk_string(iStr)), mgr); TRACE("t_str_detail", tout << "testerEqAst = " << mk_pp(testerEqAst, mgr) << std::endl;); if (m_params.m_AggressiveUnrollTesting) { @@ -9463,7 +9503,7 @@ expr * theory_str::gen_unroll_assign(expr * var, std::string lcmStr, expr * test } orItems.push_back(testerEqAst); - std::string unrollStrInstance = get_unrolled_string(lcmStr, i); + zstring unrollStrInstance = get_unrolled_string(lcmStr, i); expr_ref x1(ctx.mk_eq_atom(testerEqAst, ctx.mk_eq_atom(var, mk_string(unrollStrInstance))), mgr); TRACE("t_str_detail", tout << "x1 = " << mk_pp(x1, mgr) << std::endl;); @@ -9535,14 +9575,14 @@ expr * theory_str::gen_len_test_options(expr * freeVar, expr * indicator, int tr str_indicator = expr_ref(lookup_val, m); } else { // no match; create and insert - std::string i_str = int_to_string(i); + zstring i_str = int_to_string(i); expr_ref new_val(mk_string(i_str), m); lengthTesterCache.insert(ri, new_val); m_trail.push_back(new_val); str_indicator = expr_ref(new_val, m); } } else { - std::string i_str = int_to_string(i); + zstring i_str = int_to_string(i); str_indicator = expr_ref(mk_string(i_str), m); } expr_ref or_expr(ctx.mk_eq_atom(indicator, str_indicator), m); @@ -9661,7 +9701,7 @@ expr_ref theory_str::binary_search_case_split(expr * freeVar, expr * tester, bin testerCases.push_back(caseMore); combinedCaseSplit.push_back(ctx.mk_eq_atom(caseMore, m_autil.mk_ge(lenFreeVar, m_autil.mk_numeral(N_plus_one, true) ))); - expr_ref caseEq(ctx.mk_eq_atom(tester, mk_string(N.to_string())), m); + expr_ref caseEq(ctx.mk_eq_atom(tester, mk_string(N.to_string().c_str())), m); testerCases.push_back(caseEq); combinedCaseSplit.push_back(ctx.mk_eq_atom(caseEq, ctx.mk_eq_atom(lenFreeVar, m_autil.mk_numeral(N, true)))); @@ -9712,7 +9752,7 @@ expr * theory_str::binary_search_length_test(expr * freeVar, expr * previousLenT expr * lastTester = binary_search_len_tester_stack[freeVar].back(); bool lastTesterHasEqcValue; expr * lastTesterValue = get_eqc_value(lastTester, lastTesterHasEqcValue); - std::string lastTesterConstant; + zstring lastTesterConstant; if (!lastTesterHasEqcValue) { TRACE("t_str_binary_search", tout << "length tester " << mk_pp(lastTester, m) << " at top of stack doesn't have an EQC value yet" << std::endl;); // check previousLenTester @@ -9724,9 +9764,9 @@ expr * theory_str::binary_search_length_test(expr * freeVar, expr * previousLenT UNREACHABLE(); return NULL; } } else { - lastTesterConstant = m_strutil.get_string_constant_value(lastTesterValue); + u.str.is_string(lastTesterValue, lastTesterConstant); } - TRACE("t_str_binary_search", tout << "last length tester is assigned \"" << lastTesterConstant << "\"" << std::endl;); + TRACE("t_str_binary_search", tout << "last length tester is assigned \"" << lastTesterConstant << "\"" << "\n";); if (lastTesterConstant == "more" || lastTesterConstant == "less") { // use the previous bounds info to generate a new midpoint binary_search_info lastBounds; @@ -9805,7 +9845,7 @@ expr * theory_str::binary_search_length_test(expr * freeVar, expr * previousLenT return axiom; } // length is fixed - expr * valueAssert = gen_free_var_options(freeVar, lastTester, lastTesterConstant, NULL, ""); + expr * valueAssert = gen_free_var_options(freeVar, lastTester, lastTesterConstant, NULL, zstring("")); return valueAssert; } } else { @@ -9906,7 +9946,7 @@ expr * theory_str::gen_len_val_options_for_free_var(expr * freeVar, expr * lenTe TRACE("t_str_detail", tout << "found previous in-scope length assertions" << std::endl;); expr * effectiveLenInd = NULL; - std::string effectiveLenIndiStr = ""; + zstring effectiveLenIndiStr(""); int lenTesterCount = (int) fvar_lenTester_map[freeVar].size(); TRACE("t_str_detail", @@ -9934,9 +9974,8 @@ expr * theory_str::gen_len_val_options_for_free_var(expr * freeVar, expr * lenTe TRACE("t_str_detail", tout << "length indicator " << mk_ismt2_pp(len_indicator_pre, m) << " = " << mk_ismt2_pp(len_indicator_value, m) << std::endl;); if (indicatorHasEqcValue) { - const char * val = 0; - m_strutil.is_string(len_indicator_value, & val); - std::string len_pIndiStr(val); + zstring len_pIndiStr; + u.str.is_string(len_indicator_value, len_pIndiStr); if (len_pIndiStr != "more") { effectiveLenInd = len_indicator_pre; effectiveLenIndiStr = len_pIndiStr; @@ -9964,7 +10003,7 @@ expr * theory_str::gen_len_val_options_for_free_var(expr * freeVar, expr * lenTe effectiveLenIndiStr = lenTesterValue; } else { if (effectiveHasEqcValue) { - effectiveLenIndiStr = m_strutil.get_string_constant_value(effective_eqc_value); + u.str.is_string(effective_eqc_value, effectiveLenIndiStr); } else { NOT_IMPLEMENTED_YET(); } @@ -9988,7 +10027,7 @@ expr * theory_str::gen_len_val_options_for_free_var(expr * freeVar, expr * lenTe unsigned int testNum = 0; TRACE("t_str", tout << "effectiveLenIndiStr = " << effectiveLenIndiStr - << ", i = " << i << ", lenTesterCount = " << lenTesterCount << std::endl;); + << ", i = " << i << ", lenTesterCount = " << lenTesterCount << "\n";); if (i == lenTesterCount) { fvar_len_count_map[freeVar] = fvar_len_count_map[freeVar] + 1; @@ -10007,7 +10046,7 @@ expr * theory_str::gen_len_val_options_for_free_var(expr * freeVar, expr * lenTe } else { TRACE("t_str", tout << "length is fixed; generating models for free var" << std::endl;); // length is fixed - expr * valueAssert = gen_free_var_options(freeVar, effectiveLenInd, effectiveLenIndiStr, NULL, ""); + expr * valueAssert = gen_free_var_options(freeVar, effectiveLenInd, effectiveLenIndiStr, NULL, zstring("")); return valueAssert; } } // fVarLenCountMap.find(...) @@ -10020,7 +10059,7 @@ void theory_str::get_concats_in_eqc(expr * n, std::set & concats) { expr * eqcNode = n; do { - if (is_concat(to_app(eqcNode))) { + if (u.str.is_concat(to_app(eqcNode))) { concats.insert(eqcNode); } eqcNode = get_eqc_next(eqcNode); @@ -10096,7 +10135,7 @@ void theory_str::process_free_var(std::map & freeVar_map) { enode_vector::iterator it = e_freeVar->begin_parents(); for (; it != e_freeVar->end_parents(); ++it) { expr * parentAst = (*it)->get_owner(); - if (is_concat(to_app(parentAst))) { + if (u.str.is_concat(to_app(parentAst))) { standAlone = false; break; } @@ -10150,7 +10189,7 @@ void theory_str::get_eqc_allUnroll(expr * n, expr * &constStr, std::set & expr * curr = n; do { - if (is_string(to_app(curr))) { + if (u.str.is_string(to_app(curr))) { constStr = curr; } else if (is_Unroll(to_app(curr))) { if (unrollFuncSet.find(curr) == unrollFuncSet.end()) { @@ -10169,11 +10208,11 @@ void theory_str::get_eqc_simpleUnroll(expr * n, expr * &constStr, std::setget_arg(0); - if (is_Str2Reg(to_app(core))) { + if (u.re.is_to_re(to_app(core))) { if (unrollFuncSet.find(curr) == unrollFuncSet.end()) { unrollFuncSet.insert(curr); } @@ -10200,9 +10239,9 @@ void theory_str::init_model(model_generator & mg) { * or else returns NULL if no concrete value was derived. */ app * theory_str::mk_value_helper(app * n) { - if (m_strutil.is_string(n)) { + if (u.str.is_string(n)) { return n; - } else if (is_concat(n)) { + } else if (u.str.is_concat(n)) { // recursively call this function on each argument SASSERT(n->get_num_args() == 2); expr * a0 = n->get_arg(0); @@ -10212,15 +10251,10 @@ app * theory_str::mk_value_helper(app * n) { app * a1_conststr = mk_value_helper(to_app(a1)); if (a0_conststr != NULL && a1_conststr != NULL) { - const char * a0_str = 0; - m_strutil.is_string(a0_conststr, &a0_str); - - const char * a1_str = 0; - m_strutil.is_string(a1_conststr, &a1_str); - - std::string a0_s(a0_str); - std::string a1_s(a1_str); - std::string result = a0_s + a1_s; + zstring a0_s, a1_s; + u.str.is_string(a0_conststr, a0_s); + u.str.is_string(a1_conststr, a1_s); + zstring result = a0_s + a1_s; return to_app(mk_string(result)); } } diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 63f5d3cfc..3be852cf4 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -30,10 +30,44 @@ Revision History: #include #include"seq_decl_plugin.h" #include"union_find.h" -#include"theory_seq_empty.h" namespace smt { + class str_value_factory : public value_factory { + seq_util u; + symbol_set m_strings; + std::string delim; + unsigned m_next; + public: + str_value_factory(ast_manager & m, family_id fid) : + value_factory(m, fid), + u(m), delim("!"), m_next(0) {} + virtual ~str_value_factory() {} + virtual expr * get_some_value(sort * s) { + return u.str.mk_string("some value"); + } + virtual bool get_some_values(sort * s, expr_ref & v1, expr_ref & v2) { + v1 = u.str.mk_string("value 1"); + v2 = u.str.mk_string("value 2"); + return true; + } + virtual expr * get_fresh_value(sort * s) { + if (u.is_string(s)) { + while (true) { + std::ostringstream strm; + strm << delim << std::hex << (m_next++) << std::dec << delim; + symbol sym(strm.str().c_str()); + if (m_strings.contains(sym)) continue; + m_strings.insert(sym); + return u.str.mk_string(sym); + } + } else { + UNREACHABLE(); return NULL; + } + } + virtual void register_value(expr * n) { /* Ignore */ } + }; + // rather than modify obj_pair_map I inherit from it and add my own helper methods class theory_str_contain_pair_bool_map_t : public obj_pair_map { public: @@ -237,8 +271,8 @@ namespace smt { //obj_map > contain_pair_idx_map; std::map > > contain_pair_idx_map; - std::map, expr*> regex_in_bool_map; - std::map > regex_in_var_reg_str_map; + std::map, expr*> regex_in_bool_map; + std::map > regex_in_var_reg_str_map; // std::map regex_nfa_cache; // Regex term --> NFA @@ -380,7 +414,7 @@ namespace smt { bool upper_bound(expr* _e, rational& hi); bool can_two_nodes_eq(expr * n1, expr * n2); - bool can_concat_eq_str(expr * concat, std::string str); + bool can_concat_eq_str(expr * concat, zstring& str); bool can_concat_eq_concat(expr * concat1, expr * concat2); bool check_concat_len_in_eqc(expr * concat); bool check_length_consistency(expr * n1, expr * n2); @@ -462,20 +496,20 @@ namespace smt { void process_free_var(std::map & freeVar_map); expr * gen_len_test_options(expr * freeVar, expr * indicator, int tries); expr * gen_free_var_options(expr * freeVar, expr * len_indicator, - std::string len_valueStr, expr * valTesterInCbEq, std::string valTesterValueStr); + zstring len_valueStr, expr * valTesterInCbEq, zstring valTesterValueStr); expr * gen_val_options(expr * freeVar, expr * len_indicator, expr * val_indicator, - std::string lenStr, int tries); + zstring lenStr, int tries); void print_value_tester_list(svector > & testerList); bool get_next_val_encode(int_vector & base, int_vector & next); - std::string gen_val_string(int len, int_vector & encoding); + zstring gen_val_string(int len, int_vector & encoding); // binary search heuristic expr * binary_search_length_test(expr * freeVar, expr * previousLenTester, std::string previousLenTesterValue); expr_ref binary_search_case_split(expr * freeVar, expr * tester, binary_search_info & bounds, literal_vector & case_split_lits); bool free_var_attempt(expr * nn1, expr * nn2); - void more_len_tests(expr * lenTester, std::string lenTesterValue); - void more_value_tests(expr * valTester, std::string valTesterValue); + void more_len_tests(expr * lenTester, zstring lenTesterValue); + void more_value_tests(expr * valTester, zstring valTesterValue); expr * get_alias_index_ast(std::map & aliasIndexMap, expr * node); expr * getMostLeftNodeInConcat(expr * node); @@ -494,10 +528,11 @@ namespace smt { void get_eqc_simpleUnroll(expr * n, expr * &constStr, std::set & unrollFuncSet); void gen_assign_unroll_reg(std::set & unrolls); expr * gen_assign_unroll_Str2Reg(expr * n, std::set & unrolls); - expr * gen_unroll_conditional_options(expr * var, std::set & unrolls, std::string lcmStr); - expr * gen_unroll_assign(expr * var, std::string lcmStr, expr * testerVar, int l, int h); + expr * gen_unroll_conditional_options(expr * var, std::set & unrolls, zstring lcmStr); + expr * gen_unroll_assign(expr * var, zstring lcmStr, expr * testerVar, int l, int h); void reduce_virtual_regex_in(expr * var, expr * regex, expr_ref_vector & items); void check_regex_in(expr * nn1, expr * nn2); + zstring get_std_regex_str(expr * r); void dump_assignments(); void initialize_charset(); From c62b55f9b1edeeb1ffa7497a76577b99cb27aa9e Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Mon, 27 Feb 2017 20:51:30 -0500 Subject: [PATCH 350/410] fix npos semantics --- src/smt/theory_str.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 6585bd7f2..21564c327 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -4877,7 +4877,7 @@ void theory_str::check_contain_by_eqc_val(expr * varNode, expr * constNode) { cstItor != constList.end(); cstItor++) { zstring pieceStr; u.str.is_string(*cstItor, pieceStr); - if (strConst.contains(pieceStr)) { + if (!strConst.contains(pieceStr)) { counterEgFound = true; if (aConcat != substrAst) { litems.push_back(ctx.mk_eq_atom(substrAst, aConcat)); @@ -5606,7 +5606,7 @@ bool theory_str::is_partial_in_grounded_concat(const std::vector & strVec for (int i = 0; i < strCnt; i++) { zstring strVal; if (u.str.is_string(strVec[i], strVal)) { - if (strVal.find(subStrVal) != std::string::npos) { + if (strVal.contains(subStrVal)) { return true; } } @@ -5794,7 +5794,7 @@ bool theory_str::can_concat_eq_str(expr * concat, zstring& str) { expr * oneArg = args[i]; zstring arg_str; if (u.str.is_string(oneArg, arg_str)) { - if (str.contains(arg_str)) { + if (!str.contains(arg_str)) { return false; } sumLen += arg_str.length(); From 11000efbfeb8470cdac95f2da497d03290c22ca8 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Mon, 27 Feb 2017 21:16:15 -0500 Subject: [PATCH 351/410] fix zstring --- src/smt/theory_str.cpp | 4 ++-- src/smt/theory_str.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 21564c327..4440d6462 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -9723,7 +9723,7 @@ expr_ref theory_str::binary_search_case_split(expr * freeVar, expr * tester, bin return final_term; } -expr * theory_str::binary_search_length_test(expr * freeVar, expr * previousLenTester, std::string previousLenTesterValue) { +expr * theory_str::binary_search_length_test(expr * freeVar, expr * previousLenTester, zstring previousLenTesterValue) { ast_manager & m = get_manager(); context & ctx = get_context(); @@ -9889,7 +9889,7 @@ expr * theory_str::binary_search_length_test(expr * freeVar, expr * previousLenT // lenTesterInCbEq != NULL, and its value will be passed by lenTesterValue // The difference is that in new_eq_eh(), lenTesterInCbEq and its value have NOT been put into a same eqc // ----------------------------------------------------------------------------------------------------- -expr * theory_str::gen_len_val_options_for_free_var(expr * freeVar, expr * lenTesterInCbEq, std::string lenTesterValue) { +expr * theory_str::gen_len_val_options_for_free_var(expr * freeVar, expr * lenTesterInCbEq, zstring lenTesterValue) { ast_manager & m = get_manager(); diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 3be852cf4..233b3b7f5 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -492,7 +492,7 @@ namespace smt { std::map & concatMap, std::map & unrollMap); expr * mk_internal_lenTest_var(expr * node, int lTries); - expr * gen_len_val_options_for_free_var(expr * freeVar, expr * lenTesterInCbEq, std::string lenTesterValue); + expr * gen_len_val_options_for_free_var(expr * freeVar, expr * lenTesterInCbEq, zstring lenTesterValue); void process_free_var(std::map & freeVar_map); expr * gen_len_test_options(expr * freeVar, expr * indicator, int tries); expr * gen_free_var_options(expr * freeVar, expr * len_indicator, @@ -504,7 +504,7 @@ namespace smt { zstring gen_val_string(int len, int_vector & encoding); // binary search heuristic - expr * binary_search_length_test(expr * freeVar, expr * previousLenTester, std::string previousLenTesterValue); + expr * binary_search_length_test(expr * freeVar, expr * previousLenTester, zstring previousLenTesterValue); expr_ref binary_search_case_split(expr * freeVar, expr * tester, binary_search_info & bounds, literal_vector & case_split_lits); bool free_var_attempt(expr * nn1, expr * nn2); From 8b077ebbe799f0be78679f537167103e34904c92 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 28 Feb 2017 14:06:13 -0500 Subject: [PATCH 352/410] re-add regex NFA --- src/smt/theory_str.cpp | 161 ++++++++++++++++++++++++++++++++++++++--- src/smt/theory_str.h | 48 +++++++++++- 2 files changed, 199 insertions(+), 10 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 4440d6462..d81bb9471 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -598,7 +598,7 @@ app * theory_str::mk_unroll(expr * n, expr * bound) { ast_manager & m = get_manager(); expr * args[2] = {n, bound}; - app * unrollFunc = get_manager().mk_app(get_id(), OP_RE_UNROLL, 0, 0, 2, args); + app * unrollFunc = get_manager().mk_app(get_id(), _OP_RE_UNROLL, 0, 0, 2, args); m_trail.push_back(unrollFunc); expr_ref_vector items(m); @@ -4428,7 +4428,7 @@ void theory_str::process_concat_eq_type6(expr * concatAst1, expr * concatAst2) { void theory_str::process_unroll_eq_const_str(expr * unrollFunc, expr * constStr) { ast_manager & m = get_manager(); - if (!is_Unroll(to_app(unrollFunc))) { + if (!u.re.is_unroll(to_app(unrollFunc))) { return; } if (!u.str.is_string(constStr)) { @@ -5444,7 +5444,7 @@ void theory_str::get_grounded_concats(expr* node, std::map & varAl std::map & concatAliasMap, std::map & varConstMap, std::map & concatConstMap, std::map > & varEqConcatMap, std::map, std::set > > & groundedMap) { - if (is_Unroll(to_app(node))) { + if (u.re.is_unroll(to_app(node))) { return; } // ************************************************** @@ -6129,6 +6129,149 @@ bool theory_str::check_concat_len_in_eqc(expr * concat) { return no_assertions; } +// Convert a regular expression to an e-NFA using Thompson's construction +void nfa::convert_re(expr * e, unsigned & start, unsigned & end, seq_util & u) { + start = next_id(); + end = next_id(); + if (u.re.is_to_re(e)) { + app * a = to_app(e); + expr * arg_str = a->get_arg(0); + zstring str; + if (u.str.is_string(arg_str, str)) { + TRACE("t_str_rw", tout << "build NFA for '" << str << "'" << "\n";); + + /* + * For an n-character string, we make (n-1) intermediate states, + * labelled i_(0) through i_(n-2). + * Then we construct the following transitions: + * start --str[0]--> i_(0) --str[1]--> i_(1) --...--> i_(n-2) --str[n-1]--> final + */ + unsigned last = start; + for (int i = 0; i <= ((int)str.length()) - 2; ++i) { + unsigned i_state = next_id(); + make_transition(last, str[i], i_state); + TRACE("t_str_rw", tout << "string transition " << last << "--" << str[i] << "--> " << i_state << "\n";); + last = i_state; + } + make_transition(last, str[(str.length() - 1)], end); + TRACE("t_str_rw", tout << "string transition " << last << "--" << str[(str.length() - 1)] << "--> " << end << "\n";); + TRACE("t_str_rw", tout << "string NFA: start = " << start << ", end = " << end << std::endl;); + } else { + TRACE("t_str_rw", tout << "invalid string constant in Str2Reg" << std::endl;); + m_valid = false; + return; + } + } else if (u.re.is_concat(e)){ + app * a = to_app(e); + expr * re1 = a->get_arg(0); + expr * re2 = a->get_arg(1); + unsigned start1, end1; + convert_re(re1, start1, end1, u); + unsigned start2, end2; + convert_re(re2, start2, end2, u); + // start --e--> start1 --...--> end1 --e--> start2 --...--> end2 --e--> end + make_epsilon_move(start, start1); + make_epsilon_move(end1, start2); + make_epsilon_move(end2, end); + TRACE("t_str_rw", tout << "concat NFA: start = " << start << ", end = " << end << std::endl;); + } else if (u.re.is_union(e)) { + app * a = to_app(e); + expr * re1 = a->get_arg(0); + expr * re2 = a->get_arg(1); + unsigned start1, end1; + convert_re(re1, start1, end1, u); + unsigned start2, end2; + convert_re(re2, start2, end2, u); + + // start --e--> start1 ; start --e--> start2 + // end1 --e--> end ; end2 --e--> end + make_epsilon_move(start, start1); + make_epsilon_move(start, start2); + make_epsilon_move(end1, end); + make_epsilon_move(end2, end); + TRACE("t_str_rw", tout << "union NFA: start = " << start << ", end = " << end << std::endl;); + } else if (u.re.is_star(e)) { + app * a = to_app(e); + expr * subex = a->get_arg(0); + unsigned start_subex, end_subex; + convert_re(subex, start_subex, end_subex, u); + // start --e--> start_subex, start --e--> end + // end_subex --e--> start_subex, end_subex --e--> end + make_epsilon_move(start, start_subex); + make_epsilon_move(start, end); + make_epsilon_move(end_subex, start_subex); + make_epsilon_move(end_subex, end); + TRACE("t_str_rw", tout << "star NFA: start = " << start << ", end = " << end << std::endl;); + } else { + TRACE("t_str_rw", tout << "invalid regular expression" << std::endl;); + m_valid = false; + return; + } +} + +void nfa::epsilon_closure(unsigned start, std::set & closure) { + std::deque worklist; + closure.insert(start); + worklist.push_back(start); + + while(!worklist.empty()) { + unsigned state = worklist.front(); + worklist.pop_front(); + if (epsilon_map.find(state) != epsilon_map.end()) { + for (std::set::iterator it = epsilon_map[state].begin(); + it != epsilon_map[state].end(); ++it) { + unsigned new_state = *it; + if (closure.find(new_state) == closure.end()) { + closure.insert(new_state); + worklist.push_back(new_state); + } + } + } + } +} + +bool nfa::matches(zstring input) { + /* + * Keep a set of all states the NFA can currently be in. + * Initially this is the e-closure of m_start_state + * For each character A in the input string, + * the set of next states contains + * all states in transition_map[S][A] for each S in current_states, + * and all states in epsilon_map[S] for each S in current_states. + * After consuming the entire input string, + * the match is successful iff current_states contains m_end_state. + */ + std::set current_states; + epsilon_closure(m_start_state, current_states); + for (unsigned i = 0; i < input.length(); ++i) { + char A = input.at(i); + std::set next_states; + for (std::set::iterator it = current_states.begin(); + it != current_states.end(); ++it) { + unsigned S = *it; + // check transition_map + if (transition_map[S].find(A) != transition_map[S].end()) { + next_states.insert(transition_map[S][A]); + } + } + + // take e-closure over next_states to compute the actual next_states + std::set epsilon_next_states; + for (std::set::iterator it = next_states.begin(); it != next_states.end(); ++it) { + unsigned S = *it; + std::set closure; + epsilon_closure(S, closure); + epsilon_next_states.insert(closure.begin(), closure.end()); + } + current_states = epsilon_next_states; + } + if (current_states.find(m_end_state) != current_states.end()) { + return true; + } else { + return false; + } +} + void theory_str::check_regex_in(expr * nn1, expr * nn2) { context & ctx = get_context(); ast_manager & m = get_manager(); @@ -6159,7 +6302,7 @@ void theory_str::check_regex_in(expr * nn1, expr * nn2) { // TODO figure out regex NFA stuff if (regex_nfa_cache.find(regexTerm) == regex_nfa_cache.end()) { TRACE("t_str_detail", tout << "regex_nfa_cache: cache miss" << std::endl;); - regex_nfa_cache[regexTerm] = nfa(m_strutil, regexTerm); + regex_nfa_cache[regexTerm] = nfa(u, regexTerm); } else { TRACE("t_str_detail", tout << "regex_nfa_cache: cache hit" << std::endl;); } @@ -7385,7 +7528,7 @@ void theory_str::classify_ast_by_type(expr * node, std::map & varMap if (canskip == 0 && concatMap.find(node) == concatMap.end()) { concatMap[node] = 1; } - } else if (is_Unroll(aNode)) { + } else if (u.re.is_unroll(aNode)) { // Unroll if (unrollMap.find(node) == unrollMap.end()) { unrollMap[node] = 1; @@ -7658,7 +7801,7 @@ int theory_str::ctx_dep_analysis(std::map & strVarMap, std::mapget_owner(); - if (is_Unroll(curr)) { + if (u.re.is_unroll(curr)) { if (aRoot == NULL) { aRoot = curr; } @@ -7753,7 +7896,7 @@ int theory_str::ctx_dep_analysis(std::map & strVarMap, std::map & do { if (u.str.is_string(to_app(curr))) { constStr = curr; - } else if (is_Unroll(to_app(curr))) { + } else if (u.re.is_unroll(to_app(curr))) { if (unrollFuncSet.find(curr) == unrollFuncSet.end()) { unrollFuncSet.insert(curr); } @@ -10210,7 +10353,7 @@ void theory_str::get_eqc_simpleUnroll(expr * n, expr * &constStr, std::setget_arg(0); if (u.re.is_to_re(to_app(core))) { if (unrollFuncSet.find(curr) == unrollFuncSet.end()) { diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 233b3b7f5..499bb23f8 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -110,6 +110,52 @@ namespace smt { } }; + + class nfa { + protected: + bool m_valid; + unsigned m_next_id; + + unsigned next_id() { + unsigned retval = m_next_id; + ++m_next_id; + return retval; + } + + unsigned m_start_state; + unsigned m_end_state; + + std::map > transition_map; + std::map > epsilon_map; + + void make_transition(unsigned start, char symbol, unsigned end) { + transition_map[start][symbol] = end; + } + + void make_epsilon_move(unsigned start, unsigned end) { + epsilon_map[start].insert(end); + } + + // Convert a regular expression to an e-NFA using Thompson's construction + void convert_re(expr * e, unsigned & start, unsigned & end, seq_util & u); + + public: + nfa(seq_util & u, expr * e) + : m_valid(true), m_next_id(0), m_start_state(0), m_end_state(0) { + convert_re(e, m_start_state, m_end_state, u); + } + + nfa() : m_valid(false), m_next_id(0), m_start_state(0), m_end_state(0) {} + + bool is_valid() const { + return m_valid; + } + + void epsilon_closure(unsigned start, std::set & closure); + + bool matches(zstring input); + }; + class theory_str : public theory { struct T_cut { @@ -274,7 +320,7 @@ namespace smt { std::map, expr*> regex_in_bool_map; std::map > regex_in_var_reg_str_map; - // std::map regex_nfa_cache; // Regex term --> NFA + std::map regex_nfa_cache; // Regex term --> NFA char * char_set; std::map charSetLookupTable; From ab71dea82d574aba85811d858a674f97820c5f0c Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 28 Feb 2017 17:47:55 -0500 Subject: [PATCH 353/410] theory_str refactoring --- src/api/z3_api.h | 148 ------------------------------- src/ast/ast_smt2_pp.cpp | 55 ------------ src/ast/ast_smt2_pp.h | 3 +- src/ast/rewriter/th_rewriter.cpp | 8 -- src/ast/seq_decl_plugin.cpp | 50 ++++++++++- src/ast/seq_decl_plugin.h | 6 +- src/model/model_evaluator.cpp | 7 -- src/smt/theory_str.cpp | 76 +++++----------- src/smt/theory_str.h | 10 ++- 9 files changed, 83 insertions(+), 280 deletions(-) diff --git a/src/api/z3_api.h b/src/api/z3_api.h index 0b8351190..87c48f3d2 100644 --- a/src/api/z3_api.h +++ b/src/api/z3_api.h @@ -3173,154 +3173,6 @@ extern "C" { /*@}*/ - /** @name Strings and regular expressions (Z3str2 implementation) */ - /*@{*/ - - /** - \brief Create a string sort for 8-bit ASCII strings. - - This function creates a sort for ASCII strings. - Each character is 8 bits. - - def_API('Z3_mk_str_sort', SORT, (_in(CONTEXT), )) - */ - Z3_sort Z3_API Z3_mk_str_sort(Z3_context c); - - /** - \brief Check if \c s is a string sort. - - def_API('Z3_is_str_sort', BOOL, (_in(CONTEXT), _in(SORT))) - */ - - Z3_bool Z3_API Z3_is_str_sort(Z3_context c, Z3_sort s); - - /** - \brief Determine if \c s is a string constant. - - def_API('Z3_is_str', BOOL, (_in(CONTEXT), _in(AST))) - */ - - Z3_bool Z3_API Z3_is_str(Z3_context c, Z3_ast s); - - /** - \brief Retrieve the string constant stored in \c s. - - \pre Z3_is_str(c, s) - - def_API('Z3_get_str', STRING, (_in(CONTEXT), _in(AST))) - */ - - Z3_string Z3_API Z3_get_str(Z3_context c, Z3_ast s); - - /** - \brief Create a string constant. - - \param c logical context. - \param str The ASCII representation of the string constant. - - def_API('Z3_mk_str', AST, (_in(CONTEXT), _in(STRING))) - */ - Z3_ast Z3_API Z3_mk_str(Z3_context c, Z3_string str); - - /** - \brief Create a string concatenation term. - def_API('Z3_mk_str_concat', AST, (_in(CONTEXT), _in(AST), _in(AST))) - */ - Z3_ast Z3_API Z3_mk_str_concat(Z3_context c, Z3_ast s1, Z3_ast s2); - - /** - \brief Create a string length term. (Integer representation) - def_API('Z3_mk_str_length', AST, (_in(CONTEXT), _in(AST))) - */ - Z3_ast Z3_API Z3_mk_str_length(Z3_context c, Z3_ast s); - - /** - \brief Create 'character at index' term. - def_API('Z3_mk_str_at', AST, (_in(CONTEXT), _in(AST), _in(AST))) - */ - Z3_ast Z3_API Z3_mk_str_at(Z3_context c, Z3_ast s, Z3_ast idx); - - /** - \brief Create 'str.prefixof' term. - def_API('Z3_mk_str_prefixof', AST, (_in(CONTEXT), _in(AST), _in(AST))) - */ - Z3_ast Z3_API Z3_mk_str_prefixof(Z3_context c, Z3_ast pre, Z3_ast full); - - /** - \brief Create 'str.suffixof' term. - def_API('Z3_mk_str_suffixof', AST, (_in(CONTEXT), _in(AST), _in(AST))) - */ - Z3_ast Z3_API Z3_mk_str_suffixof(Z3_context c, Z3_ast suf, Z3_ast full); - - /** - \brief Create 'str.contains' term. - def_API('Z3_mk_str_contains', AST, (_in(CONTEXT), _in(AST), _in(AST))) - */ - Z3_ast Z3_API Z3_mk_str_contains(Z3_context c, Z3_ast needle, Z3_ast haystack); - - /** - \brief Create 'str.indexof' term. - def_API('Z3_mk_str_indexof', AST, (_in(CONTEXT), _in(AST), _in(AST), _in(AST))) - */ - Z3_ast Z3_API Z3_mk_str_indexof(Z3_context c, Z3_ast haystack, Z3_ast needle, Z3_ast start); - - /** - \brief Create 'str.substr' term. - def_API('Z3_mk_str_substr', AST, (_in(CONTEXT), _in(AST), _in(AST), _in(AST))) - */ - Z3_ast Z3_API Z3_mk_str_substr(Z3_context c, Z3_ast s, Z3_ast start, Z3_ast count); - - /** - \brief Create 'str.replace' term. - def_API('Z3_mk_str_replace', AST, (_in(CONTEXT), _in(AST), _in(AST), _in(AST))) - */ - Z3_ast Z3_API Z3_mk_str_replace(Z3_context c, Z3_ast base, Z3_ast target, Z3_ast replacement); - - - /** - \brief Create a regular expression that matches the given string constant. - def_API('Z3_mk_str_to_regex', AST, (_in(CONTEXT), _in(STRING))) - */ - Z3_ast Z3_API Z3_mk_str_to_regex(Z3_context c, Z3_string str); - - /** - \brief Create a regular expression membership predicate. - def_API('Z3_mk_str_in_regex', AST, (_in(CONTEXT), _in(AST), _in(AST))) - */ - Z3_ast Z3_API Z3_mk_str_in_regex(Z3_context c, Z3_ast str, Z3_ast regex); - - /** - \brief Create a regex concatenation term. - def_API('Z3_mk_regex_concat', AST, (_in(CONTEXT), _in(AST), _in(AST))) - */ - Z3_ast Z3_API Z3_mk_regex_concat(Z3_context c, Z3_ast r1, Z3_ast r2); - - /** - \brief Create a regex union term. - def_API('Z3_mk_regex_union', AST, (_in(CONTEXT), _in(AST), _in(AST))) - */ - Z3_ast Z3_API Z3_mk_regex_union(Z3_context c, Z3_ast r1, Z3_ast r2); - - /** - \brief Create a regex Kleene star term. - def_API('Z3_mk_regex_star', AST, (_in(CONTEXT), _in(AST))) - */ - Z3_ast Z3_API Z3_mk_regex_star(Z3_context c, Z3_ast r); - - /** - \brief Create a regex plus term. - def_API('Z3_mk_regex_plus', AST, (_in(CONTEXT), _in(AST))) - */ - Z3_ast Z3_API Z3_mk_regex_plus(Z3_context c, Z3_ast r); - - /** - \brief Create a regex character range term. - def_API('Z3_mk_regex_range', AST, (_in(CONTEXT), _in(STRING), _in(STRING))) - */ - Z3_ast Z3_API Z3_mk_regex_range(Z3_context c, Z3_string start, Z3_string end); - - /*@}*/ - /** @name Sequences and regular expressions */ /*@{*/ diff --git a/src/ast/ast_smt2_pp.cpp b/src/ast/ast_smt2_pp.cpp index 023773f62..98c3b7962 100644 --- a/src/ast/ast_smt2_pp.cpp +++ b/src/ast/ast_smt2_pp.cpp @@ -304,58 +304,6 @@ format * smt2_pp_environment::mk_float(rational const & val) const { return mk_string(get_manager(), s.c_str()); } -format * smt2_pp_environment::pp_str_literal(app * t) { - ast_manager & m = get_manager(); - str_util & u = get_strutil(); - TRACE("parse_string", tout << "pp_str_literal\n";); - - SASSERT(u.is_string(t)); - std::string strVal = u.get_string_constant_value(t); - string_buffer<> buf; - buf << "\""; - - // we want to scan strVal and escape every non-printable character - for (unsigned int i = 0; i < strVal.length(); ++i) { - char c = strVal.at(i); - if (c == '"') { - // SMT-LIB 2.5 string escape - buf << "\"\""; - } else if (isprint(c)) { - buf << c; - } else if (c == '\a') { - buf << "\\a"; - } else if (c == '\b') { - buf << "\\b"; - } else if (c == '\e') { - buf << "\\e"; - } else if (c == '\f') { - buf << "\\f"; - } else if (c == '\n') { - buf << "\\n"; - } else if (c == '\r') { - buf << "\\r"; - } else if (c == '\t') { - buf << "\\t"; - } else if (c == '\v') { - buf << "\\v"; - } else if (c == '\\') { - buf << "\\" << "\\"; - } else { - // general hex escape - buf << "\\x"; - unsigned int cVal = ((unsigned int)c) & 0x000000FF; - const char convtable[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; - unsigned int highPart = cVal / 16; - unsigned int lowPart = cVal % 16; - SASSERT(highPart < 16); SASSERT(lowPart < 16); - buf << convtable[highPart] << convtable[lowPart]; - } - } - - buf << "\""; - return mk_string(m, buf.c_str()); -} - format * smt2_pp_environment::pp_arith_literal(app * t, bool decimal, unsigned decimal_prec) { arith_util & u = get_autil(); SASSERT(u.is_numeral(t) || u.is_irrational_algebraic_numeral(t)); @@ -666,9 +614,6 @@ class smt2_printer { else if (m_env.get_dlutil().is_numeral(c)) { f = m_env.pp_datalog_literal(c); } - else if (m_env.get_strutil().is_string(c)) { - f = m_env.pp_str_literal(c); - } else { buffer names; if (m().is_label_lit(c, names)) { diff --git a/src/ast/ast_smt2_pp.h b/src/ast/ast_smt2_pp.h index b1bdf52bd..2f79ebaec 100644 --- a/src/ast/ast_smt2_pp.h +++ b/src/ast/ast_smt2_pp.h @@ -56,9 +56,8 @@ public: virtual format_ns::format * pp_bv_literal(app * t, bool use_bv_lits, bool bv_neg); virtual format_ns::format * pp_arith_literal(app * t, bool decimal, unsigned prec); virtual format_ns::format * pp_float_literal(app * t, bool use_bv_lits, bool use_float_real_lits); - virtual format_ns::format * pp_str_literal(app * t); - virtual format_ns::format * pp_datalog_literal(app * t); virtual format_ns::format * pp_string_literal(app * t); + virtual format_ns::format * pp_datalog_literal(app * t); virtual format_ns::format * pp_sort(sort * s); virtual format_ns::format * pp_fdecl_ref(func_decl * f); format_ns::format * pp_fdecl_name(symbol const & fname, unsigned & len) const; diff --git a/src/ast/rewriter/th_rewriter.cpp b/src/ast/rewriter/th_rewriter.cpp index 86772cdb4..0c57ea609 100644 --- a/src/ast/rewriter/th_rewriter.cpp +++ b/src/ast/rewriter/th_rewriter.cpp @@ -27,7 +27,6 @@ Notes: #include"dl_rewriter.h" #include"pb_rewriter.h" #include"seq_rewriter.h" -#include"str_rewriter.h" #include"rewriter_def.h" #include"expr_substitution.h" #include"ast_smt2_pp.h" @@ -46,7 +45,6 @@ struct th_rewriter_cfg : public default_rewriter_cfg { dl_rewriter m_dl_rw; pb_rewriter m_pb_rw; seq_rewriter m_seq_rw; - str_rewriter m_str_rw; arith_util m_a_util; bv_util m_bv_util; unsigned long long m_max_memory; // in bytes @@ -81,7 +79,6 @@ struct th_rewriter_cfg : public default_rewriter_cfg { m_ar_rw.updt_params(p); m_f_rw.updt_params(p); m_seq_rw.updt_params(p); - m_str_rw.updt_params(p); updt_local_params(p); } @@ -182,8 +179,6 @@ struct th_rewriter_cfg : public default_rewriter_cfg { st = m_ar_rw.mk_eq_core(args[0], args[1], result); else if (s_fid == m_seq_rw.get_fid()) st = m_seq_rw.mk_eq_core(args[0], args[1], result); - else if (s_fid == m_str_rw.get_fid()) - st = m_str_rw.mk_eq_core(args[0], args[1], result); if (st != BR_FAILED) return st; @@ -220,8 +215,6 @@ struct th_rewriter_cfg : public default_rewriter_cfg { return m_pb_rw.mk_app_core(f, num, args, result); if (fid == m_seq_rw.get_fid()) return m_seq_rw.mk_app_core(f, num, args, result); - if (fid == m_str_rw.get_fid()) - return m_str_rw.mk_app_core(f, num, args, result); return BR_FAILED; } @@ -680,7 +673,6 @@ struct th_rewriter_cfg : public default_rewriter_cfg { m_dl_rw(m), m_pb_rw(m), m_seq_rw(m), - m_str_rw(m), m_a_util(m), m_bv_util(m), m_used_dependencies(m), diff --git a/src/ast/seq_decl_plugin.cpp b/src/ast/seq_decl_plugin.cpp index 5022c57a6..059fe9674 100644 --- a/src/ast/seq_decl_plugin.cpp +++ b/src/ast/seq_decl_plugin.cpp @@ -284,8 +284,54 @@ zstring zstring::operator+(zstring const& other) const { return result; } -std::ostream& zstring::operator<<(std::ostream& out) const { - return out << encode(); +bool zstring::operator==(const zstring& other) const { + // two strings are equal iff they have the same length and characters + if (length() != other.length()) { + return false; + } + for (unsigned i = 0; i < length(); ++i) { + unsigned Xi = m_buffer[i]; + unsigned Yi = other[i]; + if (Xi != Yi) { + return false; + } + } + + return true; +} + +bool zstring::operator!=(const zstring& other) const { + return !(*this == other); +} + +std::ostream& operator<<(std::ostream &os, const zstring &str) { + return os << str.encode(); +} + +bool operator<(const zstring& lhs, const zstring& rhs) { + // This has the same semantics as strcmp() + unsigned len = lhs.length(); + if (rhs.length() < len) { + len = rhs.length(); + } + for (unsigned i = 0; i < len; ++i) { + unsigned Li = lhs[i]; + unsigned Ri = rhs[i]; + if (Li < Ri) { + return true; + } else if (Li > Ri) { + return false; + } else { + continue; + } + } + // at this point, all compared characters are equal, + // so decide based on the relative lengths + if (lhs.length() < rhs.length()) { + return true; + } else { + return false; + } } diff --git a/src/ast/seq_decl_plugin.h b/src/ast/seq_decl_plugin.h index b07e4d307..a7e534bbb 100644 --- a/src/ast/seq_decl_plugin.h +++ b/src/ast/seq_decl_plugin.h @@ -114,7 +114,11 @@ public: int indexof(zstring const& other, int offset) const; zstring extract(int lo, int hi) const; zstring operator+(zstring const& other) const; - std::ostream& operator<<(std::ostream& out) const; + bool operator==(const zstring& other) const; + bool operator!=(const zstring& other) const; + + friend std::ostream& operator<<(std::ostream &os, const zstring &str); + friend bool operator<(const zstring& lhs, const zstring& rhs); }; class seq_decl_plugin : public decl_plugin { diff --git a/src/model/model_evaluator.cpp b/src/model/model_evaluator.cpp index 06bbceb43..af2253801 100644 --- a/src/model/model_evaluator.cpp +++ b/src/model/model_evaluator.cpp @@ -28,7 +28,6 @@ Revision History: #include"datatype_rewriter.h" #include"array_rewriter.h" #include"fpa_rewriter.h" -#include"str_rewriter.h" #include"rewriter_def.h" #include"cooperate.h" #include"ast_pp.h" @@ -46,7 +45,6 @@ struct evaluator_cfg : public default_rewriter_cfg { pb_rewriter m_pb_rw; fpa_rewriter m_f_rw; seq_rewriter m_seq_rw; - str_rewriter m_str_rw; array_util m_ar; unsigned long long m_max_memory; unsigned m_max_steps; @@ -66,7 +64,6 @@ struct evaluator_cfg : public default_rewriter_cfg { m_pb_rw(m), m_f_rw(m), m_seq_rw(m), - m_str_rw(m), m_ar(m) { bool flat = true; m_b_rw.set_flat(flat); @@ -158,8 +155,6 @@ struct evaluator_cfg : public default_rewriter_cfg { st = m_f_rw.mk_eq_core(args[0], args[1], result); else if (s_fid == m_seq_rw.get_fid()) st = m_seq_rw.mk_eq_core(args[0], args[1], result); - else if (s_fid == m_str_rw.get_fid()) - st = m_str_rw.mk_eq_core(args[0], args[1], result); else if (s_fid == m_ar_rw.get_fid()) st = mk_array_eq(args[0], args[1], result); if (st != BR_FAILED) @@ -182,8 +177,6 @@ struct evaluator_cfg : public default_rewriter_cfg { st = m_f_rw.mk_app_core(f, num, args, result); else if (fid == m_seq_rw.get_fid()) st = m_seq_rw.mk_app_core(f, num, args, result); - else if (fid == m_str_rw.get_fid()) - st = m_str_rw.mk_app_core(f, num, args, result); else if (fid == m().get_label_family_id() && num == 1) { result = args[0]; st = BR_DONE; diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index d81bb9471..79b6efb8b 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -887,7 +887,7 @@ void theory_str::try_eval_concat(enode * cat) { app * evalArg = worklist.top(); worklist.pop(); zstring nextStr; if (u.str.is_string(evalArg, nextStr)) { - flattenedString += nextStr; + flattenedString = flattenedString + nextStr; } else if (u.str.is_concat(evalArg)) { app * arg0 = to_app(evalArg->get_arg(0)); app * arg1 = to_app(evalArg->get_arg(1)); @@ -1643,9 +1643,10 @@ static zstring str2RegexStr(zstring str) { // 12 special chars if (nc == '\\' || nc == '^' || nc == '$' || nc == '.' || nc == '|' || nc == '?' || nc == '*' || nc == '+' || nc == '(' || nc == ')' || nc == '[' || nc == '{') { - res += zstring("\\"); + res = res + zstring("\\"); } - res += zstring(1, (unsigned)str[i]); + char tmp[1] = {(char)str[i]}; + res = res + zstring(tmp); } return res; } @@ -2783,11 +2784,9 @@ bool theory_str::will_result_in_overlap(expr * lhs, expr * rhs) { // case 2: concat(x, y) = concat(m, "str") //************************************************************* if (is_concat_eq_type2(new_nn1, new_nn2)) { - expr * x = NULL; - expr * y = NULL; - expr * strAst = NULL; - expr * m = NULL; + expr * y = NULL; + expr * m = NULL; expr * v1_arg0 = to_app(new_nn1)->get_arg(0); expr * v1_arg1 = to_app(new_nn1)->get_arg(1); expr * v2_arg0 = to_app(new_nn2)->get_arg(0); @@ -2795,13 +2794,9 @@ bool theory_str::will_result_in_overlap(expr * lhs, expr * rhs) { if (u.str.is_string(v1_arg1) && !u.str.is_string(v2_arg1)) { m = v1_arg0; - strAst = v1_arg1; - x = v2_arg0; y = v2_arg1; } else { m = v2_arg0; - strAst = v2_arg1; - x = v1_arg0; y = v1_arg1; } @@ -2823,20 +2818,14 @@ bool theory_str::will_result_in_overlap(expr * lhs, expr * rhs) { expr * v2_arg1 = to_app(new_nn2)->get_arg(1); expr * x = NULL; - expr * y = NULL; - expr * strAst = NULL; expr * n = NULL; if (u.str.is_string(v1_arg0) && !u.str.is_string(v2_arg0)) { - strAst = v1_arg0; n = v1_arg1; x = v2_arg0; - y = v2_arg1; } else { - strAst = v2_arg0; n = v2_arg1; x = v1_arg0; - y = v1_arg1; } if (has_self_cut(x, n)) { TRACE("t_str_detail", tout << "Possible overlap found" << std::endl; print_cut_var(x, tout); print_cut_var(n, tout);); @@ -2870,21 +2859,15 @@ bool theory_str::will_result_in_overlap(expr * lhs, expr * rhs) { expr * v2_arg0 = to_app(new_nn2)->get_arg(0); expr * v2_arg1 = to_app(new_nn2)->get_arg(1); - expr * str1Ast = NULL; expr * y = NULL; expr * m = NULL; - expr * str2Ast = NULL; if (u.str.is_string(v1_arg0)) { - str1Ast = v1_arg0; y = v1_arg1; m = v2_arg0; - str2Ast = v2_arg1; } else { - str1Ast = v2_arg0; y = v2_arg1; m = v1_arg0; - str2Ast = v1_arg1; } if (has_self_cut(m, y)) { TRACE("t_str_detail", tout << "Possible overlap found" << std::endl; print_cut_var(m, tout); print_cut_var(y, tout);); @@ -3160,9 +3143,6 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { // This vector will eventually contain one term for each possible arrangement we explore. expr_ref_vector arrangement_disjunction(mgr); - int option = 0; - int pos = 1; - // break option 1: m cuts y // len(x) < len(m) || len(y) > len(n) if (!avoidLoopCut || !has_self_cut(m, y)) { @@ -3508,16 +3488,13 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { // | m | str | rational lenDelta; expr_ref_vector l_items(mgr); - int l_count = 0; l_items.push_back(ctx.mk_eq_atom(concatAst1, concatAst2)); if (x_len_exists && m_len_exists) { l_items.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_int(x_len))); l_items.push_back(ctx.mk_eq_atom(mk_strlen(m), mk_int(m_len))); - l_count = 3; lenDelta = x_len - m_len; } else { l_items.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_int(y_len))); - l_count = 2; lenDelta = str_len - y_len; } TRACE("t_str", @@ -3562,12 +3539,8 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { } } else { // Split type -1: no idea about the length... - int optionTotal = 2 + strValue.length(); expr_ref_vector arrangement_disjunction(mgr); - int option = 0; - int pos = 1; - expr_ref temp1_strAst(mk_concat(temp1, strAst), mgr); // m cuts y @@ -3904,7 +3877,6 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { // Split type -1. We know nothing about the length... expr_ref_vector arrangement_disjunction(mgr); - unsigned option = 0; int pos = 1; for (unsigned int i = 0; i <= strValue.length(); i++) { @@ -4336,7 +4308,6 @@ void theory_str::process_concat_eq_type6(expr * concatAst1, expr * concatAst2) { } expr_ref_vector arrangement_disjunction(mgr); - int option = 0; int pos = 1; if (!avoidLoopCut || !has_self_cut(m, y)) { @@ -5602,7 +5573,7 @@ bool theory_str::is_partial_in_grounded_concat(const std::vector & strVec if (subStrCnt == 1) { zstring subStrVal; - if (u.str.is_string(subStrVec[0]), subStrVal) { + if (u.str.is_string(subStrVec[0], subStrVal)) { for (int i = 0; i < strCnt; i++) { zstring strVal; if (u.str.is_string(strVec[i], strVal)) { @@ -5630,7 +5601,7 @@ bool theory_str::is_partial_in_grounded_concat(const std::vector & strVec zstring strHeadVal; if (u.str.is_string(strVec[i], strHeadVal)) { if (strHeadVal.length() >= subStrHeadVal.length()) { - std::string suffix = strHeadVal.extract(strHeadVal.length() - subStrHeadVal.length(), subStrHeadVal.length()); + zstring suffix = strHeadVal.extract(strHeadVal.length() - subStrHeadVal.length(), subStrHeadVal.length()); if (suffix != subStrHeadVal) { firstNodesOK = false; } @@ -5758,7 +5729,7 @@ void theory_str::compute_contains(std::map & varAliasMap, } bool theory_str::can_concat_eq_str(expr * concat, zstring& str) { - int strLen = str.length(); + unsigned int strLen = str.length(); if (u.str.is_concat(to_app(concat))) { ptr_vector args; get_nodes_in_concat(concat, args); @@ -6244,7 +6215,7 @@ bool nfa::matches(zstring input) { std::set current_states; epsilon_closure(m_start_state, current_states); for (unsigned i = 0; i < input.length(); ++i) { - char A = input.at(i); + char A = (char)input[i]; std::set next_states; for (std::set::iterator it = current_states.begin(); it != current_states.end(); ++it) { @@ -6288,12 +6259,12 @@ void theory_str::check_regex_in(expr * nn1, expr * nn2) { expr_ref_vector::iterator itor = eqNodeSet.begin(); for (; itor != eqNodeSet.end(); itor++) { if (regex_in_var_reg_str_map.find(*itor) != regex_in_var_reg_str_map.end()) { - std::set::iterator strItor = regex_in_var_reg_str_map[*itor].begin(); + std::set::iterator strItor = regex_in_var_reg_str_map[*itor].begin(); for (; strItor != regex_in_var_reg_str_map[*itor].end(); strItor++) { - std::string regStr = *strItor; + zstring regStr = *strItor; zstring constStrValue; u.str.is_string(constStr, constStrValue); - std::pair key1 = std::make_pair(*itor, regStr); + std::pair key1 = std::make_pair(*itor, regStr); if (regex_in_bool_map.find(key1) != regex_in_bool_map.end()) { expr * boolVar = regex_in_bool_map[key1]; // actually the RegexIn term app * a_regexIn = to_app(boolVar); @@ -6403,7 +6374,7 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { // Inconsistency TRACE("t_str", tout << "inconsistency detected: \"" << arg1_str << "\" + \"" << arg2_str << - "\" != \"" << const_str << "\"\n"); + "\" != \"" << const_str << "\"" << "\n";); expr_ref equality(ctx.mk_eq_atom(concat, str), m); expr_ref diseq(m.mk_not(equality), m); assert_axiom(diseq); @@ -6421,7 +6392,7 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { TRACE("t_str", tout << "inconsistency detected: \"" << arg2_str << "\" is longer than \"" << const_str << "\"," - << " so cannot be concatenated with anything to form it\n"); + << " so cannot be concatenated with anything to form it" << "\n";); expr_ref equality(ctx.mk_eq_atom(newConcat, str), m); expr_ref diseq(m.mk_not(equality), m); assert_axiom(diseq); @@ -6435,7 +6406,7 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { TRACE("t_str", tout << "inconsistency detected: " << "suffix of concatenation result expected \"" << secondPart << "\", " << "actually \"" << arg2_str << "\"" - << "\n"); + << "\n";); expr_ref equality(ctx.mk_eq_atom(newConcat, str), m); expr_ref diseq(m.mk_not(equality), m); assert_axiom(diseq); @@ -6620,7 +6591,6 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { } int concatStrLen = const_str.length(); - int xor_pos = 0; int and_count = 1; expr_ref_vector arrangement_disjunction(m); @@ -6701,8 +6671,8 @@ expr_ref theory_str::set_up_finite_model_test(expr * lhs, expr * rhs) { } // make things easy for the core wrt. testvar - expr_ref t1(ctx.mk_eq_atom(testvar, u.str.mk_string("")), m); - expr_ref t_yes(ctx.mk_eq_atom(testvar, u.str.mk_string("yes")), m); + expr_ref t1(ctx.mk_eq_atom(testvar, mk_string("")), m); + expr_ref t_yes(ctx.mk_eq_atom(testvar, mk_string("yes")), m); expr_ref testvaraxiom(m.mk_or(t1, t_yes), m); assert_axiom(testvaraxiom); @@ -6812,8 +6782,8 @@ void theory_str::finite_model_test(expr * testvar, expr * str) { expr_ref_vector andList(m); for (rational l = v_lower_bound; l <= v_upper_bound; l += rational::one()) { - std::string lStr = l.to_string(); - expr_ref str_indicator(u.str.mk_string(lStr), m); + zstring lStr = zstring(l.to_string().c_str()); + expr_ref str_indicator(mk_string(lStr), m); expr_ref or_expr(ctx.mk_eq_atom(indicator, str_indicator), m); orList.push_back(or_expr); expr_ref and_expr(ctx.mk_eq_atom(or_expr, ctx.mk_eq_atom(vLengthExpr, m_autil.mk_numeral(l, true))), m); @@ -9006,12 +8976,12 @@ zstring theory_str::gen_val_string(int len, int_vector & encoding) { SASSERT(charSetSize > 0); SASSERT(char_set != NULL); - zstring re(len, (int) char_set[0]); + std::string re(len, char_set[0]); for (int i = 0; i < (int) encoding.size() - 1; i++) { int idx = encoding[i]; re[len - 1 - i] = char_set[idx]; } - return re; + return zstring(re.c_str()); } /* @@ -9474,7 +9444,7 @@ static int computeLCM(int a, int b) { static zstring get_unrolled_string(zstring core, int count) { zstring res(""); for (int i = 0; i < count; i++) { - res += core; + res = res + core; } return res; } diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 499bb23f8..fc238acbd 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -33,6 +33,8 @@ Revision History: namespace smt { + typedef hashtable symbol_set; + class str_value_factory : public value_factory { seq_util u; symbol_set m_strings; @@ -44,11 +46,11 @@ namespace smt { u(m), delim("!"), m_next(0) {} virtual ~str_value_factory() {} virtual expr * get_some_value(sort * s) { - return u.str.mk_string("some value"); + return u.str.mk_string(symbol("some value")); } virtual bool get_some_values(sort * s, expr_ref & v1, expr_ref & v2) { - v1 = u.str.mk_string("value 1"); - v2 = u.str.mk_string("value 2"); + v1 = u.str.mk_string(symbol("value 1")); + v2 = u.str.mk_string(symbol("value 2")); return true; } virtual expr * get_fresh_value(sort * s) { @@ -256,7 +258,7 @@ namespace smt { expr_ref_vector m_trail; // trail for generated terms - seq_factory * m_factory; + str_value_factory * m_factory; // terms we couldn't go through set_up_axioms() with because they weren't internalized expr_ref_vector m_delayed_axiom_setup_terms; From d00723e7ea4252c0a2d7e6cf66668f54f41d830d Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 1 Mar 2017 18:23:48 -0500 Subject: [PATCH 354/410] add String name for string sort, SMTLIB2.5 compat --- src/ast/seq_decl_plugin.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ast/seq_decl_plugin.cpp b/src/ast/seq_decl_plugin.cpp index 059fe9674..90fd3fb22 100644 --- a/src/ast/seq_decl_plugin.cpp +++ b/src/ast/seq_decl_plugin.cpp @@ -830,6 +830,8 @@ void seq_decl_plugin::get_sort_names(svector & sort_names, symbol init(); sort_names.push_back(builtin_name("Seq", SEQ_SORT)); sort_names.push_back(builtin_name("RegEx", RE_SORT)); + // SMT-LIB 2.5 compatibility + sort_names.push_back(builtin_name("String", _STRING_SORT)); sort_names.push_back(builtin_name("StringSequence", _STRING_SORT)); } From 9f79015ee6b2e61fcec3d363b96610948242739d Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 1 Mar 2017 22:18:18 -0500 Subject: [PATCH 355/410] patches to theory_str for theory_seq compat --- src/ast/seq_decl_plugin.cpp | 2 +- src/smt/smt_setup.cpp | 4 +++- src/smt/theory_str.cpp | 4 ++-- src/smt/theory_str.h | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/ast/seq_decl_plugin.cpp b/src/ast/seq_decl_plugin.cpp index 90fd3fb22..2483d2370 100644 --- a/src/ast/seq_decl_plugin.cpp +++ b/src/ast/seq_decl_plugin.cpp @@ -336,7 +336,7 @@ bool operator<(const zstring& lhs, const zstring& rhs) { seq_decl_plugin::seq_decl_plugin(): m_init(false), - m_stringc_sym("StringSequence"), + m_stringc_sym("String"), m_charc_sym("Char"), m_string(0), m_char(0), diff --git a/src/smt/smt_setup.cpp b/src/smt/smt_setup.cpp index 3a4f7f981..78a295e27 100644 --- a/src/smt/smt_setup.cpp +++ b/src/smt/smt_setup.cpp @@ -825,7 +825,9 @@ namespace smt { } void setup::setup_seq() { - m_context.register_plugin(alloc(theory_seq, m_manager)); + // TODO proper negotiation of theory_str vs. theory_seq + //m_context.register_plugin(alloc(theory_seq, m_manager)); + setup_str(); } void setup::setup_card() { diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 79b6efb8b..1f276125c 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -31,7 +31,7 @@ Revision History: namespace smt { theory_str::theory_str(ast_manager & m, theory_str_params const & params): - theory(m.mk_family_id("str")), + theory(m.mk_family_id("seq")), m_params(params), /* Options */ opt_EagerStringConstantLengthAssertions(true), @@ -266,7 +266,7 @@ void theory_str::refresh_theory_var(expr * e) { theory_var theory_str::mk_var(enode* n) { TRACE("t_str_detail", tout << "mk_var for " << mk_pp(n->get_owner(), get_manager()) << std::endl;); ast_manager & m = get_manager(); - if (!(is_sort_of(m.get_sort(n->get_owner()), u.get_family_id(), _STRING_SORT))) { + if (!(m.get_sort(n->get_owner()) == u.str.mk_string_sort())) { return null_theory_var; } if (is_attached_to_var(n)) { diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index fc238acbd..5a67f72f1 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -603,7 +603,7 @@ namespace smt { theory_str(ast_manager & m, theory_str_params const & params); virtual ~theory_str(); - virtual char const * get_name() const { return "strings"; } + virtual char const * get_name() const { return "seq"; } virtual void display(std::ostream & out) const; bool overlapping_variables_detected() const { return loopDetected; } From 82b1a61b250810c5bd904856f4651bbafcabb730 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sat, 4 Mar 2017 16:30:36 -0500 Subject: [PATCH 356/410] fix string operator names --- src/ast/seq_decl_plugin.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ast/seq_decl_plugin.cpp b/src/ast/seq_decl_plugin.cpp index 2483d2370..465c6f675 100644 --- a/src/ast/seq_decl_plugin.cpp +++ b/src/ast/seq_decl_plugin.cpp @@ -558,8 +558,8 @@ void seq_decl_plugin::init() { m_sigs[_OP_STRING_CHARAT] = alloc(psig, m, "str.at", 0, 2, strTint2T, strT); m_sigs[_OP_STRING_PREFIX] = alloc(psig, m, "str.prefixof", 0, 2, str2T, boolT); m_sigs[_OP_STRING_SUFFIX] = alloc(psig, m, "str.suffixof", 0, 2, str2T, boolT); - m_sigs[_OP_STRING_IN_REGEXP] = alloc(psig, m, "seqstr.in.re", 0, 2, strTreT, boolT); - m_sigs[_OP_STRING_TO_REGEXP] = alloc(psig, m, "seqstr.to.re", 0, 1, &strT, reT); + m_sigs[_OP_STRING_IN_REGEXP] = alloc(psig, m, "str.in.re", 0, 2, strTreT, boolT); + m_sigs[_OP_STRING_TO_REGEXP] = alloc(psig, m, "str.to.re", 0, 1, &strT, reT); m_sigs[_OP_REGEXP_EMPTY] = alloc(psig, m, "re.nostr", 0, 0, 0, reT); m_sigs[_OP_REGEXP_FULL] = alloc(psig, m, "re.allchar", 0, 0, 0, reT); m_sigs[_OP_STRING_SUBSTR] = alloc(psig, m, "str.substr", 0, 3, strTint2T, strT); From 577cb19745f8be7f29988d4dd9fec44e24fd210a Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Mon, 6 Mar 2017 13:58:03 -0500 Subject: [PATCH 357/410] experimental rewrite of bitvector unit sequences to string constants --- src/ast/rewriter/seq_rewriter.cpp | 35 ++++++++++++++++++++++++++++++- src/ast/rewriter/seq_rewriter.h | 1 + 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/ast/rewriter/seq_rewriter.cpp b/src/ast/rewriter/seq_rewriter.cpp index 26c3e23e4..0c77dfcf2 100644 --- a/src/ast/rewriter/seq_rewriter.cpp +++ b/src/ast/rewriter/seq_rewriter.cpp @@ -322,7 +322,13 @@ br_status seq_rewriter::mk_app_core(func_decl * f, unsigned num_args, expr * con switch(f->get_decl_kind()) { case OP_SEQ_UNIT: - return BR_FAILED; + // TODO configuration param + if (true) { + SASSERT(num_args == 1); + return mk_seq_unit(args[0], result); + } else { + return BR_FAILED; + } case OP_SEQ_EMPTY: return BR_FAILED; case OP_RE_PLUS: @@ -427,6 +433,33 @@ br_status seq_rewriter::mk_app_core(func_decl * f, unsigned num_args, expr * con return BR_FAILED; } +/* + * (seq.unit (_ BitVector 8)) ==> String constant + */ +br_status seq_rewriter::mk_seq_unit(expr* e, expr_ref& result) { + sort * s = m().get_sort(e); + bv_util bvu(m()); + + if (is_sort_of(s, bvu.get_family_id(), BV_SORT)) { + // specifically we want (_ BitVector 8) + rational n_val; + unsigned int n_size; + if (bvu.is_numeral(e, n_val, n_size)) { + if (n_size == 8) { + // convert to string constant + char ch = (char)n_val.get_int32(); + TRACE("seq", tout << "rewrite seq.unit of 8-bit value " << n_val.to_string() << " to string constant \"" << ch << "\"" << std::endl;); + char s_tmp[2] = {ch, '\0'}; + symbol s(s_tmp); + result = m_util.str.mk_string(s); + return BR_DONE; + } + } + } + + return BR_FAILED; +} + /* string + string = string a + (b + c) = (a + b) + c diff --git a/src/ast/rewriter/seq_rewriter.h b/src/ast/rewriter/seq_rewriter.h index 2b434f475..eed08e376 100644 --- a/src/ast/rewriter/seq_rewriter.h +++ b/src/ast/rewriter/seq_rewriter.h @@ -98,6 +98,7 @@ class seq_rewriter { re2automaton m_re2aut; expr_ref_vector m_es, m_lhs, m_rhs; + br_status mk_seq_unit(expr* e, expr_ref& result); br_status mk_seq_concat(expr* a, expr* b, expr_ref& result); br_status mk_seq_length(expr* a, expr_ref& result); br_status mk_seq_extract(expr* a, expr* b, expr* c, expr_ref& result); From 4d5c1dcfb696a687c0934259b810305669cb4971 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Mon, 6 Mar 2017 17:04:07 -0500 Subject: [PATCH 358/410] fix model gen for regex terms in theory_str --- src/smt/theory_str.h | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 5a67f72f1..54fdc6538 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -63,9 +63,14 @@ namespace smt { m_strings.insert(sym); return u.str.mk_string(sym); } - } else { - UNREACHABLE(); return NULL; } + sort* seq = 0; + if (u.is_re(s, seq)) { + expr* v0 = get_fresh_value(seq); + return u.re.mk_to_re(v0); + } + TRACE("t_str", tout << "unexpected sort in get_fresh_value(): " << mk_pp(s, m_manager) << std::endl;); + UNREACHABLE(); return NULL; } virtual void register_value(expr * n) { /* Ignore */ } }; From c198bc58638bda1da1621a4a1f373ae4c0c47714 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Fri, 10 Mar 2017 13:13:45 -0500 Subject: [PATCH 359/410] fix re.range rewrite for theory_str --- src/ast/rewriter/seq_rewriter.cpp | 42 ++++++++++++++++++++++++++++--- src/ast/rewriter/seq_rewriter.h | 1 + 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/src/ast/rewriter/seq_rewriter.cpp b/src/ast/rewriter/seq_rewriter.cpp index 0c77dfcf2..4efb4b9d7 100644 --- a/src/ast/rewriter/seq_rewriter.cpp +++ b/src/ast/rewriter/seq_rewriter.cpp @@ -350,7 +350,8 @@ br_status seq_rewriter::mk_app_core(func_decl * f, unsigned num_args, expr * con SASSERT(num_args == 2); return mk_re_union(args[0], args[1], result); case OP_RE_RANGE: - return BR_FAILED; + SASSERT(num_args == 2); + return mk_re_range(args[0], args[1], result); case OP_RE_INTERSECT: SASSERT(num_args == 2); return mk_re_inter(args[0], args[1], result); @@ -1313,6 +1314,39 @@ br_status seq_rewriter::mk_re_star(expr* a, expr_ref& result) { return BR_FAILED; } +/* + * (re.range c_1 c_n) = (re.union (str.to.re c1) (str.to.re c2) ... (str.to.re cn)) + */ +br_status seq_rewriter::mk_re_range(expr* lo, expr* hi, expr_ref& result) { + TRACE("seq", tout << "rewrite re.range [" << mk_pp(lo, m()) << " " << mk_pp(hi, m()) << "]\n";); + zstring str_lo, str_hi; + if (m_util.str.is_string(lo, str_lo) && m_util.str.is_string(hi, str_hi)) { + if (str_lo.length() == 1 && str_hi.length() == 1) { + unsigned int c1 = str_lo[0]; + unsigned int c2 = str_hi[0]; + if (c1 > c2) { + // exchange c1 and c2 + unsigned int tmp = c1; + c2 = c1; + c1 = tmp; + } + zstring s(c1); + expr_ref acc(m_util.re.mk_to_re(m_util.str.mk_string(s)), m()); + for (unsigned int ch = c1 + 1; ch <= c2; ++ch) { + zstring s_ch(ch); + expr_ref acc2(m_util.re.mk_to_re(m_util.str.mk_string(s_ch)), m()); + acc = m_util.re.mk_union(acc, acc2); + } + result = acc; + return BR_REWRITE2; + } else { + m().raise_exception("string constants in re.range must have length 1"); + } + } + + return BR_FAILED; +} + /* emp+ = emp all+ = all @@ -1342,9 +1376,9 @@ br_status seq_rewriter::mk_re_plus(expr* a, expr_ref& result) { return BR_DONE; } - return BR_FAILED; -// result = m_util.re.mk_concat(a, m_util.re.mk_star(a)); -// return BR_REWRITE2; + //return BR_FAILED; + result = m_util.re.mk_concat(a, m_util.re.mk_star(a)); + return BR_REWRITE2; } br_status seq_rewriter::mk_re_opt(expr* a, expr_ref& result) { diff --git a/src/ast/rewriter/seq_rewriter.h b/src/ast/rewriter/seq_rewriter.h index eed08e376..210b2d72c 100644 --- a/src/ast/rewriter/seq_rewriter.h +++ b/src/ast/rewriter/seq_rewriter.h @@ -120,6 +120,7 @@ class seq_rewriter { br_status mk_re_plus(expr* a, expr_ref& result); br_status mk_re_opt(expr* a, expr_ref& result); br_status mk_re_loop(unsigned num_args, expr* const* args, expr_ref& result); + br_status mk_re_range(expr* lo, expr* hi, expr_ref& result); bool set_empty(unsigned sz, expr* const* es, bool all, expr_ref_vector& lhs, expr_ref_vector& rhs); bool is_subsequence(unsigned n, expr* const* l, unsigned m, expr* const* r, From b459d17624c13fdb40d0b802ebc7a4981570ce87 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Fri, 10 Mar 2017 13:53:55 -0500 Subject: [PATCH 360/410] fix int-to-str terms in theory_str not being picked up --- src/smt/theory_str.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 1f276125c..ccfdaf8aa 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -7169,6 +7169,10 @@ void theory_str::set_up_axioms(expr * ex) { } } else if (u.str.is_at(ap) || u.str.is_extract(ap) || u.str.is_replace(ap)) { m_library_aware_axiom_todo.push_back(n); + } else if (u.str.is_itos(ap)) { + TRACE("t_str_detail", tout << "found string-integer conversion term: " << mk_pp(ex, get_manager()) << std::endl;); + string_int_conversion_terms.push_back(ap); + m_library_aware_axiom_todo.push_back(n); } else if (ap->get_num_args() == 0 && !u.str.is_string(ap)) { // if ex is a variable, add it to our list of variables TRACE("t_str_detail", tout << "tracking variable " << mk_ismt2_pp(ap, get_manager()) << std::endl;); @@ -7213,7 +7217,8 @@ void theory_str::set_up_axioms(expr * ex) { // TODO indexof2/lastindexof if (u.str.is_index(ap) /* || is_Indexof2(ap) || is_LastIndexof(ap) */) { m_library_aware_axiom_todo.push_back(n); - } else if (u.str.is_stoi(ap) || u.str.is_itos(ap)) { + } else if (u.str.is_stoi(ap)) { + TRACE("t_str_detail", tout << "found string-integer conversion term: " << mk_pp(ex, get_manager()) << std::endl;); string_int_conversion_terms.push_back(ap); m_library_aware_axiom_todo.push_back(n); } From 24df976f956fc728af51d0a7f84e050576ff99e8 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Mon, 13 Mar 2017 17:03:36 -0400 Subject: [PATCH 361/410] fixup startswith/endswith to prefixof/suffixof --- src/smt/theory_str.cpp | 103 ++++++++++++++++++++--------------------- src/smt/theory_str.h | 4 +- 2 files changed, 52 insertions(+), 55 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index ccfdaf8aa..0554ae2c2 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -817,12 +817,10 @@ void theory_str::propagate() { instantiate_axiom_int_to_str(e); } else if (u.str.is_at(a)) { instantiate_axiom_CharAt(e); - /* TODO NEXT: StartsWith/EndsWith -> prefixof/suffixof - } else if (is_StartsWith(e)) { - instantiate_axiom_StartsWith(e); - } else if (is_EndsWith(e)) { - instantiate_axiom_EndsWith(e); - */ + } else if (u.str.is_prefix(a)) { + instantiate_axiom_prefixof(e); + } else if (u.str.is_suffix(a)) { + instantiate_axiom_suffixof(e); } else if (u.str.is_contains(a)) { instantiate_axiom_Contains(e); } else if (u.str.is_index(a)) { @@ -1101,64 +1099,26 @@ void theory_str::instantiate_axiom_CharAt(enode * e) { assert_axiom(finalAxiom); } -void theory_str::instantiate_axiom_StartsWith(enode * e) { +void theory_str::instantiate_axiom_prefixof(enode * e) { context & ctx = get_context(); ast_manager & m = get_manager(); app * expr = e->get_owner(); if (axiomatized_terms.contains(expr)) { - TRACE("t_str_detail", tout << "already set up StartsWith axiom for " << mk_pp(expr, m) << std::endl;); + TRACE("t_str_detail", tout << "already set up prefixof axiom for " << mk_pp(expr, m) << std::endl;); return; } axiomatized_terms.insert(expr); - TRACE("t_str_detail", tout << "instantiate StartsWith axiom for " << mk_pp(expr, m) << std::endl;); + TRACE("t_str_detail", tout << "instantiate prefixof axiom for " << mk_pp(expr, m) << std::endl;); expr_ref ts0(mk_str_var("ts0"), m); expr_ref ts1(mk_str_var("ts1"), m); expr_ref_vector innerItems(m); - innerItems.push_back(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(ts0, ts1))); - innerItems.push_back(ctx.mk_eq_atom(mk_strlen(ts0), mk_strlen(expr->get_arg(1)))); - innerItems.push_back(m.mk_ite(ctx.mk_eq_atom(ts0, expr->get_arg(1)), expr, m.mk_not(expr))); - expr_ref then1(m.mk_and(innerItems.size(), innerItems.c_ptr()), m); - SASSERT(then1); - - // the top-level condition is Length(arg0) >= Length(arg1). - // of course, the integer theory is not so accommodating - expr_ref topLevelCond( - m_autil.mk_ge( - m_autil.mk_add( - mk_strlen(expr->get_arg(0)), m_autil.mk_mul(mk_int(-1), mk_strlen(expr->get_arg(1)))), - mk_int(0)) - , m); - SASSERT(topLevelCond); - - expr_ref finalAxiom(m.mk_ite(topLevelCond, then1, m.mk_not(expr)), m); - SASSERT(finalAxiom); - assert_axiom(finalAxiom); -} - -void theory_str::instantiate_axiom_EndsWith(enode * e) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - - app * expr = e->get_owner(); - if (axiomatized_terms.contains(expr)) { - TRACE("t_str_detail", tout << "already set up EndsWith axiom for " << mk_pp(expr, m) << std::endl;); - return; - } - axiomatized_terms.insert(expr); - - TRACE("t_str_detail", tout << "instantiate EndsWith axiom for " << mk_pp(expr, m) << std::endl;); - - expr_ref ts0(mk_str_var("ts0"), m); - expr_ref ts1(mk_str_var("ts1"), m); - - expr_ref_vector innerItems(m); - innerItems.push_back(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(ts0, ts1))); - innerItems.push_back(ctx.mk_eq_atom(mk_strlen(ts1), mk_strlen(expr->get_arg(1)))); - innerItems.push_back(m.mk_ite(ctx.mk_eq_atom(ts1, expr->get_arg(1)), expr, m.mk_not(expr))); + innerItems.push_back(ctx.mk_eq_atom(expr->get_arg(1), mk_concat(ts0, ts1))); + innerItems.push_back(ctx.mk_eq_atom(mk_strlen(ts0), mk_strlen(expr->get_arg(0)))); + innerItems.push_back(m.mk_ite(ctx.mk_eq_atom(ts0, expr->get_arg(0)), expr, m.mk_not(expr))); expr_ref then1(m.mk_and(innerItems.size(), innerItems.c_ptr()), m); SASSERT(then1); @@ -1166,9 +1126,46 @@ void theory_str::instantiate_axiom_EndsWith(enode * e) { expr_ref topLevelCond( m_autil.mk_ge( m_autil.mk_add( - mk_strlen(expr->get_arg(0)), m_autil.mk_mul(mk_int(-1), mk_strlen(expr->get_arg(1)))), - mk_int(0)) - , m); + mk_strlen(expr->get_arg(1)), m_autil.mk_mul(mk_int(-1), mk_strlen(expr->get_arg(0)))), + mk_int(0)) + , m); + SASSERT(topLevelCond); + + expr_ref finalAxiom(m.mk_ite(topLevelCond, then1, m.mk_not(expr)), m); + SASSERT(finalAxiom); + assert_axiom(finalAxiom); +} + +void theory_str::instantiate_axiom_suffixof(enode * e) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + app * expr = e->get_owner(); + if (axiomatized_terms.contains(expr)) { + TRACE("t_str_detail", tout << "already set up suffixof axiom for " << mk_pp(expr, m) << std::endl;); + return; + } + axiomatized_terms.insert(expr); + + TRACE("t_str_detail", tout << "instantiate suffixof axiom for " << mk_pp(expr, m) << std::endl;); + + expr_ref ts0(mk_str_var("ts0"), m); + expr_ref ts1(mk_str_var("ts1"), m); + + expr_ref_vector innerItems(m); + innerItems.push_back(ctx.mk_eq_atom(expr->get_arg(1), mk_concat(ts0, ts1))); + innerItems.push_back(ctx.mk_eq_atom(mk_strlen(ts1), mk_strlen(expr->get_arg(0)))); + innerItems.push_back(m.mk_ite(ctx.mk_eq_atom(ts1, expr->get_arg(0)), expr, m.mk_not(expr))); + expr_ref then1(m.mk_and(innerItems.size(), innerItems.c_ptr()), m); + SASSERT(then1); + + // the top-level condition is Length(arg0) >= Length(arg1) + expr_ref topLevelCond( + m_autil.mk_ge( + m_autil.mk_add( + mk_strlen(expr->get_arg(1)), m_autil.mk_mul(mk_int(-1), mk_strlen(expr->get_arg(0)))), + mk_int(0)) + , m); SASSERT(topLevelCond); expr_ref finalAxiom(m.mk_ite(topLevelCond, then1, m.mk_not(expr)), m); diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 54fdc6538..3ea4db7d4 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -433,8 +433,8 @@ namespace smt { void instantiate_str_eq_length_axiom(enode * lhs, enode * rhs); void instantiate_axiom_CharAt(enode * e); - void instantiate_axiom_StartsWith(enode * e); - void instantiate_axiom_EndsWith(enode * e); + void instantiate_axiom_prefixof(enode * e); + void instantiate_axiom_suffixof(enode * e); void instantiate_axiom_Contains(enode * e); void instantiate_axiom_Indexof(enode * e); void instantiate_axiom_Indexof2(enode * e); From 8021d63539b697476f510dbd79abeaeca06b9a7e Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 15 Mar 2017 15:25:48 -0400 Subject: [PATCH 362/410] remove legacy str_decl_plugin and str_rewriter classes; these have been unified with sequence-compatible equivalents --- src/ast/ast_smt2_pp.h | 1 - src/ast/ast_smt_pp.cpp | 1 - src/ast/rewriter/str_rewriter.cpp | 703 ------------------------------ src/ast/rewriter/str_rewriter.h | 120 ----- src/ast/str_decl_plugin.cpp | 501 --------------------- src/ast/str_decl_plugin.h | 218 --------- 6 files changed, 1544 deletions(-) delete mode 100644 src/ast/rewriter/str_rewriter.cpp delete mode 100644 src/ast/rewriter/str_rewriter.h delete mode 100644 src/ast/str_decl_plugin.cpp delete mode 100644 src/ast/str_decl_plugin.h diff --git a/src/ast/ast_smt2_pp.h b/src/ast/ast_smt2_pp.h index 2f79ebaec..244594461 100644 --- a/src/ast/ast_smt2_pp.h +++ b/src/ast/ast_smt2_pp.h @@ -30,7 +30,6 @@ Revision History: #include"fpa_decl_plugin.h" #include"dl_decl_plugin.h" #include"seq_decl_plugin.h" -#include"str_decl_plugin.h" #include"smt2_util.h" class smt2_pp_environment { diff --git a/src/ast/ast_smt_pp.cpp b/src/ast/ast_smt_pp.cpp index de6ae6cc3..c3f1523b1 100644 --- a/src/ast/ast_smt_pp.cpp +++ b/src/ast/ast_smt_pp.cpp @@ -24,7 +24,6 @@ Revision History: #include"ast_smt_pp.h" #include"arith_decl_plugin.h" #include"bv_decl_plugin.h" -#include"str_decl_plugin.h" #include"array_decl_plugin.h" #include"datatype_decl_plugin.h" #include"fpa_decl_plugin.h" diff --git a/src/ast/rewriter/str_rewriter.cpp b/src/ast/rewriter/str_rewriter.cpp deleted file mode 100644 index 3933e7fdb..000000000 --- a/src/ast/rewriter/str_rewriter.cpp +++ /dev/null @@ -1,703 +0,0 @@ -/*++ -Copyright (c) 2016 Microsoft Corporation - -Module Name: - - str_rewriter.cpp - -Abstract: - - AST rewriting rules for string terms. - -Author: - - Murphy Berzish - -Notes: - ---*/ - -#if 0 - -#include"str_rewriter.h" -#include"arith_decl_plugin.h" -#include"ast_pp.h" -#include"ast_util.h" -#include"well_sorted.h" -#include -#include -#include -#include - -// Convert a regular expression to an e-NFA using Thompson's construction -void nfa::convert_re(expr * e, unsigned & start, unsigned & end, str_util & m_strutil) { - start = next_id(); - end = next_id(); - if (m_strutil.is_re_Str2Reg(e)) { - app * a = to_app(e); - expr * arg_str = a->get_arg(0); - if (m_strutil.is_string(arg_str)) { - std::string str = m_strutil.get_string_constant_value(arg_str); - TRACE("t_str_rw", tout << "build NFA for '" << str << "'" << std::endl;); - - /* - * For an n-character string, we make (n-1) intermediate states, - * labelled i_(0) through i_(n-2). - * Then we construct the following transitions: - * start --str[0]--> i_(0) --str[1]--> i_(1) --...--> i_(n-2) --str[n-1]--> final - */ - unsigned last = start; - for (int i = 0; i <= ((int)str.length()) - 2; ++i) { - unsigned i_state = next_id(); - make_transition(last, str.at(i), i_state); - TRACE("t_str_rw", tout << "string transition " << last << "--" << str.at(i) << "--> " << i_state << std::endl;); - last = i_state; - } - make_transition(last, str.at(str.length() - 1), end); - TRACE("t_str_rw", tout << "string transition " << last << "--" << str.at(str.length() - 1) << "--> " << end << std::endl;); - TRACE("t_str_rw", tout << "string NFA: start = " << start << ", end = " << end << std::endl;); - } else { - TRACE("t_str_rw", tout << "invalid string constant in Str2Reg" << std::endl;); - m_valid = false; - return; - } - } else if (m_strutil.is_re_RegexConcat(e)){ - app * a = to_app(e); - expr * re1 = a->get_arg(0); - expr * re2 = a->get_arg(1); - unsigned start1, end1; - convert_re(re1, start1, end1, m_strutil); - unsigned start2, end2; - convert_re(re2, start2, end2, m_strutil); - // start --e--> start1 --...--> end1 --e--> start2 --...--> end2 --e--> end - make_epsilon_move(start, start1); - make_epsilon_move(end1, start2); - make_epsilon_move(end2, end); - TRACE("t_str_rw", tout << "concat NFA: start = " << start << ", end = " << end << std::endl;); - } else if (m_strutil.is_re_RegexUnion(e)) { - app * a = to_app(e); - expr * re1 = a->get_arg(0); - expr * re2 = a->get_arg(1); - unsigned start1, end1; - convert_re(re1, start1, end1, m_strutil); - unsigned start2, end2; - convert_re(re2, start2, end2, m_strutil); - - // start --e--> start1 ; start --e--> start2 - // end1 --e--> end ; end2 --e--> end - make_epsilon_move(start, start1); - make_epsilon_move(start, start2); - make_epsilon_move(end1, end); - make_epsilon_move(end2, end); - TRACE("t_str_rw", tout << "union NFA: start = " << start << ", end = " << end << std::endl;); - } else if (m_strutil.is_re_RegexStar(e)) { - app * a = to_app(e); - expr * subex = a->get_arg(0); - unsigned start_subex, end_subex; - convert_re(subex, start_subex, end_subex, m_strutil); - // start --e--> start_subex, start --e--> end - // end_subex --e--> start_subex, end_subex --e--> end - make_epsilon_move(start, start_subex); - make_epsilon_move(start, end); - make_epsilon_move(end_subex, start_subex); - make_epsilon_move(end_subex, end); - TRACE("t_str_rw", tout << "star NFA: start = " << start << ", end = " << end << std::endl;); - } else { - TRACE("t_str_rw", tout << "invalid regular expression" << std::endl;); - m_valid = false; - return; - } -} - -void nfa::epsilon_closure(unsigned start, std::set & closure) { - std::deque worklist; - closure.insert(start); - worklist.push_back(start); - - while(!worklist.empty()) { - unsigned state = worklist.front(); - worklist.pop_front(); - if (epsilon_map.find(state) != epsilon_map.end()) { - for (std::set::iterator it = epsilon_map[state].begin(); - it != epsilon_map[state].end(); ++it) { - unsigned new_state = *it; - if (closure.find(new_state) == closure.end()) { - closure.insert(new_state); - worklist.push_back(new_state); - } - } - } - } -} - -bool nfa::matches(std::string input) { - /* - * Keep a set of all states the NFA can currently be in. - * Initially this is the e-closure of m_start_state - * For each character A in the input string, - * the set of next states contains - * all states in transition_map[S][A] for each S in current_states, - * and all states in epsilon_map[S] for each S in current_states. - * After consuming the entire input string, - * the match is successful iff current_states contains m_end_state. - */ - std::set current_states; - epsilon_closure(m_start_state, current_states); - for (unsigned i = 0; i < input.length(); ++i) { - char A = input.at(i); - std::set next_states; - for (std::set::iterator it = current_states.begin(); - it != current_states.end(); ++it) { - unsigned S = *it; - // check transition_map - if (transition_map[S].find(A) != transition_map[S].end()) { - next_states.insert(transition_map[S][A]); - } - } - - // take e-closure over next_states to compute the actual next_states - std::set epsilon_next_states; - for (std::set::iterator it = next_states.begin(); it != next_states.end(); ++it) { - unsigned S = *it; - std::set closure; - epsilon_closure(S, closure); - epsilon_next_states.insert(closure.begin(), closure.end()); - } - current_states = epsilon_next_states; - } - if (current_states.find(m_end_state) != current_states.end()) { - return true; - } else { - return false; - } -} - - -br_status str_rewriter::mk_str_Concat(expr * arg0, expr * arg1, expr_ref & result) { - TRACE("t_str_rw", tout << "rewrite (Concat " << mk_pp(arg0, m()) << " " << mk_pp(arg1, m()) << ")" << std::endl;); - if(m_strutil.is_string(arg0) && m_strutil.is_string(arg1)) { - TRACE("t_str_rw", tout << "evaluating concat of two constant strings" << std::endl;); - std::string arg0Str = m_strutil.get_string_constant_value(arg0); - std::string arg1Str = m_strutil.get_string_constant_value(arg1); - std::string resultStr = arg0Str + arg1Str; - result = m_strutil.mk_string(resultStr); - return BR_DONE; - } else { - return BR_FAILED; - } -} - -br_status str_rewriter::mk_str_Length(expr * arg0, expr_ref & result) { - TRACE("t_str_rw", tout << "rewrite (Length " << mk_pp(arg0, m()) << ")" << std::endl;); - if (m_strutil.is_string(arg0)) { - TRACE("t_str_rw", tout << "evaluating length of constant string" << std::endl;); - std::string arg0Str = m_strutil.get_string_constant_value(arg0); - rational arg0Len((unsigned)arg0Str.length()); - result = m_autil.mk_numeral(arg0Len, true); - TRACE("t_str_rw", tout << "result is " << mk_pp(result, m()) << std::endl;); - return BR_DONE; - } else { - return BR_FAILED; - } -} - -br_status str_rewriter::mk_str_CharAt(expr * arg0, expr * arg1, expr_ref & result) { - TRACE("t_str_rw", tout << "rewrite (CharAt " << mk_pp(arg0, m()) << " " << mk_pp(arg1, m()) << ")" << std::endl;); - // if arg0 is a string constant and arg1 is an integer constant, - // we can rewrite this by evaluating the expression - rational arg1Int; - if (m_strutil.is_string(arg0) && m_autil.is_numeral(arg1, arg1Int)) { - TRACE("t_str_rw", tout << "evaluating constant CharAt expression" << std::endl;); - std::string arg0Str = m_strutil.get_string_constant_value(arg0); - std::string resultStr; - if (arg1Int >= rational(0) && arg1Int <= rational((unsigned)arg0Str.length())) { - resultStr = arg0Str.at(arg1Int.get_unsigned()); - TRACE("t_str_rw", tout << "result is '" << resultStr << "'" << std::endl;); - } else { - resultStr = ""; - TRACE("t_str_rw", tout << "bogus length argument, result is empty string" << std::endl;); - } - result = m_strutil.mk_string(resultStr); - return BR_DONE; - } else { - return BR_FAILED; - } -} - -br_status str_rewriter::mk_str_StartsWith(expr * haystack, expr * needle, expr_ref & result) { - TRACE("t_str_rw", tout << "rewrite (StartsWith " << mk_pp(haystack, m()) << " " << mk_pp(needle, m()) << ")" << std::endl;); - if (m_strutil.is_string(haystack) && m_strutil.is_string(needle)) { - TRACE("t_str_rw", tout << "evaluating constant StartsWith predicate" << std::endl;); - std::string haystackStr = m_strutil.get_string_constant_value(haystack); - std::string needleStr = m_strutil.get_string_constant_value(needle); - if (haystackStr.length() < needleStr.length()) { - result = m().mk_false(); - return BR_DONE; - } else { - if (haystackStr.substr(0, needleStr.length()) == needleStr) { - result = m().mk_true(); - } else { - result = m().mk_false(); - } - return BR_DONE; - } - } else { - return BR_FAILED; - } -} - -br_status str_rewriter::mk_str_EndsWith(expr * haystack, expr * needle, expr_ref & result) { - TRACE("t_str_rw", tout << "rewrite (EndsWith " << mk_pp(haystack, m()) << " " << mk_pp(needle, m()) << ")" << std::endl;); - if (m_strutil.is_string(haystack) && m_strutil.is_string(needle)) { - TRACE("t_str_rw", tout << "evaluating constant EndsWith predicate" << std::endl;); - std::string haystackStr = m_strutil.get_string_constant_value(haystack); - std::string needleStr = m_strutil.get_string_constant_value(needle); - if (haystackStr.length() < needleStr.length()) { - result = m().mk_false(); - return BR_DONE; - } else { - if (haystackStr.substr(haystackStr.length() - needleStr.length(), needleStr.length()) == needleStr) { - result = m().mk_true(); - } else { - result = m().mk_false(); - } - return BR_DONE; - } - } else { - return BR_FAILED; - } -} - -br_status str_rewriter::mk_str_Contains(expr * haystack, expr * needle, expr_ref & result) { - TRACE("t_str_rw", tout << "rewrite (Contains " << mk_pp(haystack, m()) << " " << mk_pp(needle, m()) << ")" << std::endl;); - if (haystack == needle) { - TRACE("t_str_rw", tout << "eliminate (Contains) over identical terms" << std::endl;); - result = m().mk_true(); - return BR_DONE; - } else if (m_strutil.is_string(haystack) && m_strutil.is_string(needle)) { - TRACE("t_str_rw", tout << "evaluating constant Contains predicate" << std::endl;); - std::string haystackStr = m_strutil.get_string_constant_value(haystack); - std::string needleStr = m_strutil.get_string_constant_value(needle); - if (haystackStr.find(needleStr) != std::string::npos) { - result = m().mk_true(); - } else { - result = m().mk_false(); - } - return BR_DONE; - } else { - return BR_FAILED; - } -} - -br_status str_rewriter::mk_str_Indexof(expr * haystack, expr * needle, expr_ref & result) { - TRACE("t_str_rw", tout << "rewrite (Indexof " << mk_pp(haystack, m()) << " " << mk_pp(needle, m()) << ")" << std::endl;); - if (m_strutil.is_string(haystack) && m_strutil.is_string(needle)) { - TRACE("t_str_rw", tout << "evaluating constant Indexof expression" << std::endl;); - std::string haystackStr = m_strutil.get_string_constant_value(haystack); - std::string needleStr = m_strutil.get_string_constant_value(needle); - if (haystackStr.find(needleStr) != std::string::npos) { - int index = haystackStr.find(needleStr); - result = m_autil.mk_numeral(rational(index), true); - } else { - result = m_autil.mk_numeral(rational(-1), true); - } - return BR_DONE; - } else { - return BR_FAILED; - } -} - -br_status str_rewriter::mk_str_Indexof2(expr * arg0, expr * arg1, expr * arg2, expr_ref & result) { - TRACE("t_str_rw", tout << "rewrite (Indexof2 " << mk_pp(arg0, m()) << " " << mk_pp(arg1, m()) << " " << mk_pp(arg2, m()) << ")" << std::endl;); - //if (getNodeType(t, args[0]) == my_Z3_ConstStr && getNodeType(t, args[1]) == my_Z3_ConstStr && getNodeType(t, args[2]) == my_Z3_Num) { - rational arg2Int; - if (m_strutil.is_string(arg0) && m_strutil.is_string(arg1) && m_autil.is_numeral(arg2, arg2Int)) { - TRACE("t_str_rw", tout << "evaluating constant Indexof2 expression" << std::endl;); - std::string arg0str = m_strutil.get_string_constant_value(arg0); - std::string arg1str = m_strutil.get_string_constant_value(arg1); - if (arg2Int >= rational((unsigned)arg0str.length())) { - result = m_autil.mk_numeral(rational(-1), true); - } else if (arg2Int < rational(0)) { - int index = arg0str.find(arg1str); - result = m_autil.mk_numeral(rational(index), true); - } else { - std::string suffixStr = arg0str.substr(arg2Int.get_unsigned(), arg0str.length() - arg2Int.get_unsigned()); - if (suffixStr.find(arg1str) != std::string::npos) { - int index = suffixStr.find(arg1str) + arg2Int.get_unsigned(); - result = m_autil.mk_numeral(rational(index), true); - } else { - result = m_autil.mk_numeral(rational(-1), true); - } - } - return BR_DONE; - } else { - return BR_FAILED; - } -} - -br_status str_rewriter::mk_str_LastIndexof(expr * haystack, expr * needle, expr_ref & result) { - TRACE("t_str_rw", tout << "rewrite (LastIndexof " << mk_pp(haystack, m()) << " " << mk_pp(needle, m()) << ")" << std::endl;); - if (m_strutil.is_string(haystack) && m_strutil.is_string(needle)) { - TRACE("t_str_rw", tout << "evaluating constant LastIndexof expression" << std::endl;); - std::string arg0Str = m_strutil.get_string_constant_value(haystack); - std::string arg1Str = m_strutil.get_string_constant_value(needle); - if (arg0Str.rfind(arg1Str) != std::string::npos) { - int index = arg0Str.rfind(arg1Str); - result = m_autil.mk_numeral(rational(index), true); - } else { - result = m_autil.mk_numeral(rational(-1), true); - } - return BR_DONE; - } else { - return BR_FAILED; - } -} - -br_status str_rewriter::mk_str_Replace(expr * base, expr * source, expr * target, expr_ref & result) { - TRACE("t_str_rw", tout << "rewrite (Replace " << mk_pp(base, m()) << " " << mk_pp(source, m()) << " " << mk_pp(target, m()) << ")" << std::endl;); - if (m_strutil.is_string(base) && m_strutil.is_string(source) && m_strutil.is_string(target)) { - std::string arg0Str = m_strutil.get_string_constant_value(base); - std::string arg1Str = m_strutil.get_string_constant_value(source); - std::string arg2Str = m_strutil.get_string_constant_value(target); - if (arg0Str.find(arg1Str) != std::string::npos) { - int index1 = arg0Str.find(arg1Str); - int index2 = index1 + arg1Str.length(); - std::string substr0 = arg0Str.substr(0, index1); - std::string substr2 = arg0Str.substr(index2); - std::string replaced = substr0 + arg2Str + substr2; - result = m_strutil.mk_string(replaced); - } else { - result = base; - } - return BR_DONE; - } else { - return BR_FAILED; - } -} - -br_status str_rewriter::mk_str_prefixof(expr * pre, expr * full, expr_ref & result) { - TRACE("t_str_rw", tout << "rewrite (str.prefixof " << mk_pp(pre, m()) << " " << mk_pp(full, m()) << ")" << std::endl;); - result = m_strutil.mk_str_StartsWith(full, pre); - return BR_REWRITE_FULL; -} - -br_status str_rewriter::mk_str_suffixof(expr * post, expr * full, expr_ref & result) { - TRACE("t_str_rw", tout << "rewrite (str.suffixof" << mk_pp(post, m()) << " " << mk_pp(full, m()) << ")" << std::endl;); - result = m_strutil.mk_str_EndsWith(full, post); - return BR_REWRITE_FULL; -} - -br_status str_rewriter::mk_str_to_int(expr * arg0, expr_ref & result) { - TRACE("t_str_rw", tout << "rewrite (str.to-int " << mk_pp(arg0, m()) << ")" << std::endl;); - - if (m_strutil.is_string(arg0)) { - std::string str = m_strutil.get_string_constant_value(arg0); - if (str.length() == 0) { - result = m_autil.mk_numeral(rational::zero(), true); - return BR_DONE; - } - - // interpret str as a natural number and rewrite to the corresponding integer. - // if this is not valid, rewrite to -1 - rational convertedRepresentation(0); - rational ten(10); - for (unsigned i = 0; i < str.length(); ++i) { - char digit = str.at(i); - if (isdigit((int)digit)) { - std::string sDigit(1, digit); - int val = atoi(sDigit.c_str()); - convertedRepresentation = (ten * convertedRepresentation) + rational(val); - } else { - // not a digit, invalid - TRACE("t_str_rw", tout << "str.to-int argument contains non-digit character '" << digit << "'" << std::endl;); - convertedRepresentation = rational::minus_one(); - break; - } - } - result = m_autil.mk_numeral(convertedRepresentation, true); - return BR_DONE; - } - return BR_FAILED; - -} - -br_status str_rewriter::mk_str_from_int(expr * arg0, expr_ref & result) { - TRACE("t_str_rw", tout << "rewrite (str.from-int " << mk_pp(arg0, m()) << ")" << std::endl;); - rational arg0Int; - if (m_autil.is_numeral(arg0, arg0Int)) { - // (str.from-int N) with N non-negative is the corresponding string in decimal notation. - // otherwise it is the empty string - if (arg0Int.is_nonneg()) { - std::string str = arg0Int.to_string(); - result = m_strutil.mk_string(str); - TRACE("t_str_rw", tout << "convert non-negative integer constant to " << str << std::endl;); - } else { - result = m_strutil.mk_string(""); - TRACE("t_str_rw", tout << "convert invalid integer constant to empty string" << std::endl;); - } - return BR_DONE; - } - return BR_FAILED; -} - -br_status str_rewriter::mk_str_Substr(expr * base, expr * start, expr * len, expr_ref & result) { - TRACE("t_str_rw", tout << "rewrite (Substr " << mk_pp(base, m()) << " " << mk_pp(start, m()) << " " << mk_pp(len, m()) << ")" << std::endl;); - - bool constant_base = m_strutil.is_string(base); - std::string baseStr; - if (constant_base) { - baseStr = m_strutil.get_string_constant_value(base); - } - rational startVal; - bool constant_start = m_autil.is_numeral(start, startVal); - rational lenVal; - bool constant_len = m_autil.is_numeral(len, lenVal); - - // case 1: start < 0 or len < 0 - if ( (constant_start && startVal.is_neg()) || (constant_len && lenVal.is_neg()) ) { - TRACE("t_str_rw", tout << "start/len of substr is negative" << std::endl;); - result = m_strutil.mk_string(""); - return BR_DONE; - } - // case 1.1: start >= length(base) - if (constant_start && constant_base) { - rational baseStrlen((unsigned int)baseStr.length()); - if (startVal >= baseStrlen) { - TRACE("t_str_rw", tout << "start >= strlen for substr" << std::endl;); - result = m_strutil.mk_string(""); - return BR_DONE; - } - } - - if (constant_base && constant_start && constant_len) { - rational baseStrlen((unsigned int)baseStr.length()); - std::string retval; - if (startVal + lenVal >= baseStrlen) { - // case 2: pos+len goes past the end of the string - retval = baseStr.substr(startVal.get_unsigned(), std::string::npos); - } else { - // case 3: pos+len still within string - retval = baseStr.substr(startVal.get_unsigned(), lenVal.get_unsigned()); - } - result = m_strutil.mk_string(retval); - return BR_DONE; - } - - return BR_FAILED; -} - -br_status str_rewriter::mk_re_Str2Reg(expr * str, expr_ref & result) { - // the argument to Str2Reg *must* be a string constant - ENSURE(m_strutil.is_string(str)); - return BR_FAILED; -} - -br_status str_rewriter::mk_re_RegexIn(expr * str, expr * re, expr_ref & result) { - // fast path: - // (RegexIn E (Str2Reg S)) --> (= E S) - if (m_strutil.is_re_Str2Reg(re)) { - expr * regexStr = to_app(re)->get_arg(0); - ENSURE(m_strutil.is_string(regexStr)); - result = m().mk_eq(str, regexStr); - TRACE("t_str_rw", tout << "RegexIn fast path: " << mk_pp(str, m()) << " in " << mk_pp(re, m()) << " ==> " << mk_pp(result, m()) << std::endl;); - return BR_REWRITE_FULL; - } - - // necessary for model validation - if (m_strutil.is_string(str)) { - TRACE("t_str_rw", tout << "RegexIn with constant string argument" << std::endl;); - nfa regex_nfa(m_strutil, re); - ENSURE(regex_nfa.is_valid()); - std::string input = m_strutil.get_string_constant_value(str); - if (regex_nfa.matches(input)) { - result = m().mk_true(); - } else { - result = m().mk_false(); - } - return BR_DONE; - } - - return BR_FAILED; -} - -br_status str_rewriter::mk_re_RegexStar(expr * re, expr_ref & result) { - if (m_strutil.is_re_RegexStar(re)) { - result = re; - return BR_REWRITE_FULL; - } else { - return BR_FAILED; - } -} - -br_status str_rewriter::mk_re_RegexConcat(expr * r0, expr * r1, expr_ref & result) { - TRACE("t_str_rw", tout << "rewrite (RegexConcat " << mk_pp(r0, m()) << " " << mk_pp(r1, m()) << ")" << std::endl;); - // (RegexConcat (Str2Reg "A") (Str2Reg "B")) --> (Str2Reg "AB") - if (m_strutil.is_re_Str2Reg(r0) && m_strutil.is_re_Str2Reg(r1)) { - expr * r0str = to_app(r0)->get_arg(0); - expr * r1str = to_app(r1)->get_arg(0); - ENSURE(m_strutil.is_string(r0str)); - ENSURE(m_strutil.is_string(r1str)); - std::string r0val = m_strutil.get_string_constant_value(r0str); - std::string r1val = m_strutil.get_string_constant_value(r1str); - std::string simplifyVal = r0val + r1val; - TRACE("t_str_rw", tout << "RegexConcat fast path: both sides are Str2Reg, simplify to (Str2Reg \"" << simplifyVal << "\")" << std::endl;); - result = m_strutil.mk_re_Str2Reg(simplifyVal); - return BR_DONE; - } - - return BR_FAILED; -} - -br_status str_rewriter::mk_re_RegexPlus(expr * re, expr_ref & result) { - /* - * Two optimizations are possible if we inspect 're'. - * If 're' is (RegexPlus X), then reduce to 're'. - * If 're' is (RegexStar X), then reduce to 're'. - * Otherwise, reduce to (RegexConcat re (RegexStar re)). - */ - - if (m_strutil.is_re_RegexPlus(re)) { - result = re; - return BR_REWRITE_FULL; - } else if (m_strutil.is_re_RegexStar(re)) { - // Z3str2 re-created the AST under 're' here, but I don't think we need to do that - result = re; - return BR_REWRITE_FULL; - } else { - result = m_strutil.mk_re_RegexConcat(re, m_strutil.mk_re_RegexStar(re)); - return BR_REWRITE_FULL; - } -} - -br_status str_rewriter::mk_re_RegexCharRange(expr * start, expr * end, expr_ref & result) { - TRACE("t_str_rw", tout << "rewrite (RegexCharRange " << mk_pp(start, m()) << " " << mk_pp(end, m()) << ")" << std::endl;); - // both 'start' and 'end' must be string constants - ENSURE(m_strutil.is_string(start) && m_strutil.is_string(end)); - std::string arg0Value = m_strutil.get_string_constant_value(start); - std::string arg1Value = m_strutil.get_string_constant_value(end); - ENSURE(arg0Value.length() == 1 && arg1Value.length() == 1); - char low = arg0Value[0]; - char high = arg1Value[0]; - if (low > high) { - char t = low; - low = high; - high = t; - } - - char c = low; - std::string cStr; - cStr.push_back(c); - expr * res = m_strutil.mk_re_Str2Reg(cStr); - c++; - for (; c <= high; c++) { - cStr.clear(); - cStr.push_back(c); - res = m_strutil.mk_re_RegexUnion(res, m_strutil.mk_re_Str2Reg(cStr)); - } - result = res; - return BR_DONE; -} - -br_status str_rewriter::mk_app_core(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result) { - SASSERT(f->get_family_id() == get_fid()); - - TRACE("t_str_rw", tout << "rewrite app: " << f->get_name() << std::endl;); - - switch(f->get_decl_kind()) { - case OP_STRCAT: - SASSERT(num_args == 2); - return mk_str_Concat(args[0], args[1], result); - case OP_STRLEN: - SASSERT(num_args == 1); - return mk_str_Length(args[0], result); - case OP_STR_CHARAT: - SASSERT(num_args == 2); - return mk_str_CharAt(args[0], args[1], result); - case OP_STR_STARTSWITH: - SASSERT(num_args == 2); - return mk_str_StartsWith(args[0], args[1], result); - case OP_STR_ENDSWITH: - SASSERT(num_args == 2); - return mk_str_EndsWith(args[0], args[1], result); - case OP_STR_CONTAINS: - SASSERT(num_args == 2); - return mk_str_Contains(args[0], args[1], result); - case OP_STR_INDEXOF: - SASSERT(num_args == 2); - return mk_str_Indexof(args[0], args[1], result); - case OP_STR_INDEXOF2: - SASSERT(num_args == 3); - return mk_str_Indexof2(args[0], args[1], args[2], result); - case OP_STR_LASTINDEXOF: - SASSERT(num_args == 2); - return mk_str_LastIndexof(args[0], args[1], result); - case OP_STR_REPLACE: - SASSERT(num_args == 3); - return mk_str_Replace(args[0], args[1], args[2], result); - case OP_STR_PREFIXOF: - SASSERT(num_args == 2); - return mk_str_prefixof(args[0], args[1], result); - case OP_STR_SUFFIXOF: - SASSERT(num_args == 2); - return mk_str_suffixof(args[0], args[1], result); - case OP_STR_STR2INT: - SASSERT(num_args == 1); - return mk_str_to_int(args[0], result); - case OP_STR_INT2STR: - SASSERT(num_args == 1); - return mk_str_from_int(args[0], result); - case OP_STR_SUBSTR: - SASSERT(num_args == 3); - return mk_str_Substr(args[0], args[1], args[2], result); - case OP_RE_STR2REGEX: - SASSERT(num_args == 1); - return mk_re_Str2Reg(args[0], result); - case OP_RE_REGEXIN: - SASSERT(num_args == 2); - return mk_re_RegexIn(args[0], args[1], result); - case OP_RE_REGEXPLUS: - SASSERT(num_args == 1); - return mk_re_RegexPlus(args[0], result); - case OP_RE_REGEXSTAR: - SASSERT(num_args == 1); - return mk_re_RegexStar(args[0], result); - case OP_RE_REGEXCONCAT: - SASSERT(num_args == 2); - return mk_re_RegexConcat(args[0], args[1], result); - case OP_RE_REGEXCHARRANGE: - SASSERT(num_args == 2); - return mk_re_RegexCharRange(args[0], args[1], result); - default: - return BR_FAILED; - } -} - -br_status str_rewriter::mk_eq_core(expr * l, expr * r, expr_ref & result) { - // from seq_rewriter - expr_ref_vector lhs(m()), rhs(m()), res(m()); - bool changed = false; - if (!reduce_eq(l, r, lhs, rhs, changed)) { - result = m().mk_false(); - return BR_DONE; - } - if (!changed) { - return BR_FAILED; - } - for (unsigned i = 0; i < lhs.size(); ++i) { - res.push_back(m().mk_eq(lhs[i].get(), rhs[i].get())); - } - result = mk_and(res); - return BR_REWRITE3; -} - -bool str_rewriter::reduce_eq(expr * l, expr * r, expr_ref_vector & lhs, expr_ref_vector & rhs, bool & change) { - change = false; - return true; -} - -bool str_rewriter::reduce_eq(expr_ref_vector& ls, expr_ref_vector& rs, expr_ref_vector& lhs, expr_ref_vector& rhs, bool& change) { - change = false; - return true; -} - -#endif /* disable */ diff --git a/src/ast/rewriter/str_rewriter.h b/src/ast/rewriter/str_rewriter.h deleted file mode 100644 index 8d6041a51..000000000 --- a/src/ast/rewriter/str_rewriter.h +++ /dev/null @@ -1,120 +0,0 @@ -/*++ -Copyright (c) 2016 Microsoft Corporation - -Module Name: - - str_rewriter.h - -Abstract: - - AST rewriting rules for string terms. - -Author: - - Murphy Berzish - -Notes: - ---*/ - -#if 0 - -#include"str_decl_plugin.h" -#include"arith_decl_plugin.h" -#include"rewriter_types.h" -#include"params.h" -#include -#include - -class str_rewriter { - str_util m_strutil; - arith_util m_autil; - -public: - str_rewriter(ast_manager & m, params_ref const & p = params_ref()) : - m_strutil(m), m_autil(m) { - } - - ast_manager & m() const { return m_strutil.get_manager(); } - family_id get_fid() const { return m_strutil.get_family_id(); } - - void updt_params(params_ref const & p) {} - static void get_param_descrs(param_descrs & r) {} - - br_status mk_app_core(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result); - br_status mk_eq_core(expr * lhs, expr * rhs, expr_ref & result); - - br_status mk_str_Concat(expr * arg0, expr * arg1, expr_ref & result); - br_status mk_str_Length(expr * arg0, expr_ref & result); - br_status mk_str_CharAt(expr * arg0, expr * arg1, expr_ref & result); - br_status mk_str_StartsWith(expr * haystack, expr * needle, expr_ref & result); - br_status mk_str_EndsWith(expr * haystack, expr * needle, expr_ref & result); - br_status mk_str_Contains(expr * haystack, expr * needle, expr_ref & result); - br_status mk_str_Indexof(expr * haystack, expr * needle, expr_ref & result); - br_status mk_str_Indexof2(expr * arg0, expr * arg1, expr * arg2, expr_ref & result); - br_status mk_str_LastIndexof(expr * haystack, expr * needle, expr_ref & result); - br_status mk_str_Replace(expr * base, expr * source, expr * target, expr_ref & result); - br_status mk_str_Substr(expr * base, expr * start, expr * len, expr_ref & result); - br_status mk_str_prefixof(expr * pre, expr * full, expr_ref & result); - br_status mk_str_suffixof(expr * post, expr * full, expr_ref & result); - br_status mk_str_to_int(expr * arg0, expr_ref & result); - br_status mk_str_from_int(expr * arg0, expr_ref & result); - - br_status mk_re_Str2Reg(expr * str, expr_ref & result); - br_status mk_re_RegexIn(expr * str, expr * re, expr_ref & result); - br_status mk_re_RegexPlus(expr * re, expr_ref & result); - br_status mk_re_RegexStar(expr * re, expr_ref & result); - br_status mk_re_RegexConcat(expr * r0, expr * r1, expr_ref & result); - br_status mk_re_RegexCharRange(expr * start, expr * end, expr_ref & result); - - bool reduce_eq(expr * l, expr * r, expr_ref_vector & lhs, expr_ref_vector & rhs, bool & change); - bool reduce_eq(expr_ref_vector& ls, expr_ref_vector& rs, expr_ref_vector& lhs, expr_ref_vector& rhs, bool& change); - -}; - -class nfa { -protected: - bool m_valid; - unsigned m_next_id; - - unsigned next_id() { - unsigned retval = m_next_id; - ++m_next_id; - return retval; - } - - unsigned m_start_state; - unsigned m_end_state; - - std::map > transition_map; - std::map > epsilon_map; - - void make_transition(unsigned start, char symbol, unsigned end) { - transition_map[start][symbol] = end; - } - - void make_epsilon_move(unsigned start, unsigned end) { - epsilon_map[start].insert(end); - } - - // Convert a regular expression to an e-NFA using Thompson's construction - void convert_re(expr * e, unsigned & start, unsigned & end, str_util & m_strutil); - -public: - nfa(str_util & m_strutil, expr * e) -: m_valid(true), m_next_id(0), m_start_state(0), m_end_state(0) { - convert_re(e, m_start_state, m_end_state, m_strutil); - } - - nfa() : m_valid(false), m_next_id(0), m_start_state(0), m_end_state(0) {} - - bool is_valid() const { - return m_valid; - } - - void epsilon_closure(unsigned start, std::set & closure); - - bool matches(std::string input); -}; - -#endif /* disable */ diff --git a/src/ast/str_decl_plugin.cpp b/src/ast/str_decl_plugin.cpp deleted file mode 100644 index 067420f04..000000000 --- a/src/ast/str_decl_plugin.cpp +++ /dev/null @@ -1,501 +0,0 @@ -/*++ -Module Name: - - str_decl_plugin.h - -Abstract: - - - -Author: - - Murphy Berzish (mtrberzi) 2015-09-02. - -Revision History: - ---*/ - -#if 0 - -#include -#include"str_decl_plugin.h" -#include"string_buffer.h" -#include"warning.h" -#include"ast_pp.h" -#include"ast_smt2_pp.h" - -str_decl_plugin::str_decl_plugin(): - m_strv_sym("String"), - m_str_decl(0), - m_regex_decl(0), - m_concat_decl(0), - m_length_decl(0), - m_charat_decl(0), - m_startswith_decl(0), - m_endswith_decl(0), - m_contains_decl(0), - m_indexof_decl(0), - m_indexof2_decl(0), - m_lastindexof_decl(0), - m_substr_decl(0), - m_replace_decl(0), - m_str2int_decl(0), - m_int2str_decl(0), - m_prefixof_decl(0), - m_suffixof_decl(0), - m_re_str2regex_decl(0), - m_re_regexin_decl(0), - m_re_regexconcat_decl(0), - m_re_regexstar_decl(0), - m_re_regexunion_decl(0), - m_re_unroll_decl(0), - m_re_regexplus_decl(0), - m_re_regexcharrange_decl(0), - m_arith_plugin(0), - m_arith_fid(0), - m_int_sort(0){ -} - -str_decl_plugin::~str_decl_plugin(){ -} - -void str_decl_plugin::finalize(void) { - #define DEC_REF(decl) if (decl) { m_manager->dec_ref(decl); } ((void) 0) - DEC_REF(m_str_decl); - DEC_REF(m_regex_decl); - DEC_REF(m_concat_decl); - DEC_REF(m_length_decl); - DEC_REF(m_charat_decl); - DEC_REF(m_startswith_decl); - DEC_REF(m_endswith_decl); - DEC_REF(m_contains_decl); - DEC_REF(m_indexof_decl); - DEC_REF(m_indexof2_decl); - DEC_REF(m_lastindexof_decl); - DEC_REF(m_substr_decl); - DEC_REF(m_replace_decl); - DEC_REF(m_prefixof_decl); - DEC_REF(m_suffixof_decl); - DEC_REF(m_str2int_decl); - DEC_REF(m_int2str_decl); - DEC_REF(m_re_str2regex_decl); - DEC_REF(m_re_regexin_decl); - DEC_REF(m_re_regexconcat_decl); - DEC_REF(m_re_regexstar_decl); - DEC_REF(m_re_regexunion_decl); - DEC_REF(m_re_regexplus_decl); - DEC_REF(m_re_regexcharrange_decl); - DEC_REF(m_re_unroll_decl); - DEC_REF(m_int_sort); -} - -void str_decl_plugin::set_manager(ast_manager * m, family_id id) { - decl_plugin::set_manager(m, id); - m_str_decl = m->mk_sort(symbol("String"), sort_info(id, STRING_SORT)); - m->inc_ref(m_str_decl); - sort * s = m_str_decl; - - m_regex_decl = m->mk_sort(symbol("Regex"), sort_info(id, REGEX_SORT)); - m->inc_ref(m_regex_decl); - sort * re = m_regex_decl; - - SASSERT(m_manager->has_plugin(symbol("arith"))); - m_arith_fid = m_manager->mk_family_id("arith"); - m_arith_plugin = static_cast(m_manager->get_plugin(m_arith_fid)); - SASSERT(m_arith_plugin); - - m_int_sort = m_manager->mk_sort(m_arith_fid, INT_SORT); - SASSERT(m_int_sort != 0); // arith_decl_plugin must be installed before str_decl_plugin. - m_manager->inc_ref(m_int_sort); - sort * i = m_int_sort; - - sort* boolT = m_manager->mk_bool_sort(); - -#define MK_OP(FIELD, NAME, KIND, SORT) \ - FIELD = m->mk_func_decl(symbol(NAME), SORT, SORT, SORT, func_decl_info(id, KIND)); \ - m->inc_ref(FIELD) - - MK_OP(m_concat_decl, "str.++", OP_STRCAT, s); - - m_length_decl = m->mk_func_decl(symbol("str.len"), s, i, func_decl_info(id, OP_STRLEN)); - m_manager->inc_ref(m_length_decl); - - m_charat_decl = m->mk_func_decl(symbol("str.at"), s, i, s, func_decl_info(id, OP_STR_CHARAT)); - m_manager->inc_ref(m_charat_decl); - - m_startswith_decl = m->mk_func_decl(symbol("StartsWith"), s, s, boolT, func_decl_info(id, OP_STR_STARTSWITH)); - m_manager->inc_ref(m_startswith_decl); - - m_endswith_decl = m->mk_func_decl(symbol("EndsWith"), s, s, boolT, func_decl_info(id, OP_STR_ENDSWITH)); - m_manager->inc_ref(m_endswith_decl); - - m_contains_decl = m->mk_func_decl(symbol("str.contains"), s, s, boolT, func_decl_info(id, OP_STR_CONTAINS)); - m_manager->inc_ref(m_contains_decl); - - m_indexof_decl = m->mk_func_decl(symbol("str.indexof"), s, s, i, func_decl_info(id, OP_STR_INDEXOF)); - m_manager->inc_ref(m_indexof_decl); - - { - sort * d[3] = { s, s, i }; - m_indexof2_decl = m->mk_func_decl(symbol("Indexof2"), 3, d, i, func_decl_info(id, OP_STR_INDEXOF2)); - m_manager->inc_ref(m_indexof2_decl); - } - - m_lastindexof_decl = m->mk_func_decl(symbol("str.lastindexof"), s, s, i, func_decl_info(id, OP_STR_LASTINDEXOF)); - m_manager->inc_ref(m_lastindexof_decl); - - { - sort * d[3] = {s, i, i }; - m_substr_decl = m->mk_func_decl(symbol("str.substr"), 3, d, s, func_decl_info(id, OP_STR_SUBSTR)); - m_manager->inc_ref(m_substr_decl); - } - - { - sort * d[3] = {s, s, s}; - m_replace_decl = m->mk_func_decl(symbol("str.replace"), 3, d, s, func_decl_info(id, OP_STR_REPLACE)); - m_manager->inc_ref(m_replace_decl); - } - - m_prefixof_decl = m->mk_func_decl(symbol("str.prefixof"), s, s, boolT, func_decl_info(id, OP_STR_PREFIXOF)); - m_manager->inc_ref(m_prefixof_decl); - - m_suffixof_decl = m->mk_func_decl(symbol("str.suffixof"), s, s, boolT, func_decl_info(id, OP_STR_SUFFIXOF)); - m_manager->inc_ref(m_suffixof_decl); - - m_str2int_decl = m->mk_func_decl(symbol("str.to-int"), s, i, func_decl_info(id, OP_STR_STR2INT)); - m_manager->inc_ref(m_str2int_decl); - - m_int2str_decl = m->mk_func_decl(symbol("str.from-int"), i, s, func_decl_info(id, OP_STR_INT2STR)); - m_manager->inc_ref(m_int2str_decl); - - m_re_str2regex_decl = m->mk_func_decl(symbol("str.to.re"), s, re, func_decl_info(id, OP_RE_STR2REGEX)); - m_manager->inc_ref(m_re_str2regex_decl); - - m_re_regexin_decl = m->mk_func_decl(symbol("str.in.re"), s, re, boolT, func_decl_info(id, OP_RE_REGEXIN)); - m_manager->inc_ref(m_re_regexin_decl); - - m_re_regexconcat_decl = m->mk_func_decl(symbol("re.++"), re, re, re, func_decl_info(id, OP_RE_REGEXCONCAT)); - m_manager->inc_ref(m_re_regexconcat_decl); - - m_re_regexstar_decl = m->mk_func_decl(symbol("re.*"), re, re, func_decl_info(id, OP_RE_REGEXSTAR)); - m_manager->inc_ref(m_re_regexstar_decl); - - m_re_regexplus_decl = m->mk_func_decl(symbol("re.+"), re, re, func_decl_info(id, OP_RE_REGEXPLUS)); - m_manager->inc_ref(m_re_regexplus_decl); - - m_re_regexunion_decl = m->mk_func_decl(symbol("re.union"), re, re, re, func_decl_info(id, OP_RE_REGEXUNION)); - m_manager->inc_ref(m_re_regexunion_decl); - - m_re_unroll_decl = m->mk_func_decl(symbol("Unroll"), re, i, s, func_decl_info(id, OP_RE_UNROLL)); - m_manager->inc_ref(m_re_unroll_decl); - - m_re_regexcharrange_decl = m->mk_func_decl(symbol("re.range"), s, s, re, func_decl_info(id, OP_RE_REGEXCHARRANGE)); - m_manager->inc_ref(m_re_regexcharrange_decl); - -} - -decl_plugin * str_decl_plugin::mk_fresh() { - return alloc(str_decl_plugin); -} - -sort * str_decl_plugin::mk_sort(decl_kind k, unsigned num_parameters, parameter const * parameters) { - switch (k) { - case STRING_SORT: return m_str_decl; - case REGEX_SORT: return m_regex_decl; - default: return 0; - } -} - -func_decl * str_decl_plugin::mk_func_decl(decl_kind k) { - switch(k) { - case OP_STRCAT: return m_concat_decl; - case OP_STRLEN: return m_length_decl; - case OP_STR_CHARAT: return m_charat_decl; - case OP_STR_STARTSWITH: return m_startswith_decl; - case OP_STR_ENDSWITH: return m_endswith_decl; - case OP_STR_CONTAINS: return m_contains_decl; - case OP_STR_INDEXOF: return m_indexof_decl; - case OP_STR_INDEXOF2: return m_indexof2_decl; - case OP_STR_LASTINDEXOF: return m_lastindexof_decl; - case OP_STR_SUBSTR: return m_substr_decl; - case OP_STR_REPLACE: return m_replace_decl; - case OP_STR_PREFIXOF: return m_prefixof_decl; - case OP_STR_SUFFIXOF: return m_suffixof_decl; - case OP_STR_STR2INT: return m_str2int_decl; - case OP_STR_INT2STR: return m_int2str_decl; - case OP_RE_STR2REGEX: return m_re_str2regex_decl; - case OP_RE_REGEXIN: return m_re_regexin_decl; - case OP_RE_REGEXCONCAT: return m_re_regexconcat_decl; - case OP_RE_REGEXSTAR: return m_re_regexstar_decl; - case OP_RE_REGEXPLUS: return m_re_regexplus_decl; - case OP_RE_REGEXUNION: return m_re_regexunion_decl; - case OP_RE_UNROLL: return m_re_unroll_decl; - case OP_RE_REGEXCHARRANGE: return m_re_regexcharrange_decl; - default: return 0; - } -} - -func_decl * str_decl_plugin::mk_func_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, - unsigned arity, sort * const * domain, sort * range) { - if (k == OP_STR) { - m_manager->raise_exception("OP_STR not yet implemented in mk_func_decl!"); - return 0; - } - if (arity == 0) { - m_manager->raise_exception("no arguments supplied to string operator"); - return 0; - } - return mk_func_decl(k); -} - -app * str_decl_plugin::mk_string(std::string & val) { - std::map::iterator it = string_cache.find(val); - //if (it == string_cache.end()) { - if (true) { - char * new_buffer = alloc_svect(char, (val.length() + 1)); - strcpy(new_buffer, val.c_str()); - parameter p[1] = {parameter(new_buffer)}; - func_decl * d; - d = m_manager->mk_const_decl(m_strv_sym, m_str_decl, func_decl_info(m_family_id, OP_STR, 1, p)); - app * str = m_manager->mk_const(d); - string_cache[val] = str; - return str; - } else { - return it->second; - } -} - -app * str_decl_plugin::mk_string(const char * val) { - std::string key(val); - return mk_string(key); -} - -app * str_decl_plugin::mk_fresh_string() { - // cheating. - // take the longest string in the cache, append the letter "A", and call it fresh. - std::string longestString = ""; - std::map::iterator it = string_cache.begin(); - for (; it != string_cache.end(); ++it) { - if (it->first.length() > longestString.length()) { - longestString = it->first; - } - } - longestString += "A"; - return mk_string(longestString); -} - -void str_decl_plugin::get_op_names(svector & op_names, symbol const & logic) { - op_names.push_back(builtin_name("str.++", OP_STRCAT)); - op_names.push_back(builtin_name("str.len", OP_STRLEN)); - op_names.push_back(builtin_name("str.at", OP_STR_CHARAT)); - op_names.push_back(builtin_name("StartsWith", OP_STR_STARTSWITH)); - op_names.push_back(builtin_name("EndsWith", OP_STR_ENDSWITH)); - op_names.push_back(builtin_name("str.contains", OP_STR_CONTAINS)); - op_names.push_back(builtin_name("str.indexof", OP_STR_INDEXOF)); - op_names.push_back(builtin_name("Indexof2", OP_STR_INDEXOF2)); - op_names.push_back(builtin_name("str.lastindexof", OP_STR_LASTINDEXOF)); - op_names.push_back(builtin_name("str.substr", OP_STR_SUBSTR)); - op_names.push_back(builtin_name("str.replace", OP_STR_REPLACE)); - op_names.push_back(builtin_name("str.prefixof", OP_STR_PREFIXOF)); - op_names.push_back(builtin_name("str.suffixof", OP_STR_SUFFIXOF)); - op_names.push_back(builtin_name("str.to-int", OP_STR_STR2INT)); - op_names.push_back(builtin_name("str.from-int", OP_STR_INT2STR)); - op_names.push_back(builtin_name("str.to.re", OP_RE_STR2REGEX)); - op_names.push_back(builtin_name("str.in.re", OP_RE_REGEXIN)); - op_names.push_back(builtin_name("re.++", OP_RE_REGEXCONCAT)); - op_names.push_back(builtin_name("re.*", OP_RE_REGEXSTAR)); - op_names.push_back(builtin_name("re.union", OP_RE_REGEXUNION)); - op_names.push_back(builtin_name("re.+", OP_RE_REGEXPLUS)); - op_names.push_back(builtin_name("Unroll", OP_RE_UNROLL)); - op_names.push_back(builtin_name("re.range", OP_RE_REGEXCHARRANGE)); -} - -void str_decl_plugin::get_sort_names(svector & sort_names, symbol const & logic) { - sort_names.push_back(builtin_name("String", STRING_SORT)); - sort_names.push_back(builtin_name("Regex", REGEX_SORT)); -} - -bool str_decl_plugin::is_value(app * e) const { - if (e->get_family_id() != m_family_id) { - return false; - } - switch (e->get_decl_kind()) { - case OP_STR: - return true; - default: - return false; - } -} - -bool str_recognizers::is_string(expr const * n, const char ** val) const { - if (!is_app_of(n, m_afid, OP_STR)) - return false; - func_decl * decl = to_app(n)->get_decl(); - *val = decl->get_parameter(0).get_string(); - return true; -} - -bool str_recognizers::is_string(expr const * n) const { - const char * tmp = 0; - return is_string(n, & tmp); -} - -std::string str_recognizers::get_string_constant_value(expr const *n) const { - const char * cstr = 0; - bool isString = is_string(n, & cstr); - SASSERT(isString); - std::string strval(cstr); - return strval; -} - -str_util::str_util(ast_manager &m) : - str_recognizers(m.mk_family_id(symbol("str"))), - m_manager(m) { - SASSERT(m.has_plugin(symbol("str"))); - m_plugin = static_cast(m.get_plugin(m.mk_family_id(symbol("str")))); - m_fid = m_plugin->get_family_id(); -} - -/* - * Scan through the string 'val' and interpret each instance of "backslash followed by a character" - * as a possible escape sequence. Emit all other characters as-is. - * This exists because the SMT-LIB 2.5 standard does not recognize escape sequences other than "" -> " . - * The escape sequences recognized are as follows: - * \a \b \e \f \n \r \t \v \\ : as specified by the C++ standard - * \ooo : produces the ASCII character corresponding to the octal value "ooo", where each "o" is a - * single octal digit and between 1 and 3 valid digits are given - * \xhh : produces the ASCII character corresponding to the hexadecimal value "hh", where each "h" is a - * single case-insensitive hex digit (0-9A-F) and exactly 2 digits are given - * \C, for any character C that does not start a legal escape sequence : the backslash is ignored and "C" is produced. - */ -app * str_util::mk_string_with_escape_characters(std::string & val) { - std::string parsedStr; - parsedStr.reserve(val.length()); - for (unsigned i = 0; i < val.length(); ++i) { - char nextChar = val.at(i); - - if (nextChar == '\\') { - // check escape sequence - i++; - if (i >= val.length()) { - get_manager().raise_exception("invalid escape sequence"); - } - char escapeChar1 = val.at(i); - if (escapeChar1 == 'a') { - parsedStr.push_back('\a'); - } else if (escapeChar1 == 'b') { - parsedStr.push_back('\b'); - } else if (escapeChar1 == 'e') { - parsedStr.push_back('\e'); - } else if (escapeChar1 == 'f') { - parsedStr.push_back('\f'); - } else if (escapeChar1 == 'n') { - parsedStr.push_back('\n'); - } else if (escapeChar1 == 'r') { - parsedStr.push_back('\r'); - } else if (escapeChar1 == 't') { - parsedStr.push_back('\t'); - } else if (escapeChar1 == 'v') { - parsedStr.push_back('\v'); - } else if (escapeChar1 == '\\') { - parsedStr.push_back('\\'); - } else if (escapeChar1 == 'x') { - // hex escape: we expect 'x' to be followed by exactly two hex digits - // which means that i+2 must be a valid index - if (i+2 >= val.length()) { - get_manager().raise_exception("invalid hex escape: \\x must be followed by exactly two hex digits"); - } - char hexDigitHi = val.at(i+1); - char hexDigitLo = val.at(i+2); - i += 2; - if (!isxdigit((int)hexDigitHi) || !isxdigit((int)hexDigitLo)) { - get_manager().raise_exception("invalid hex escape: \\x must be followed by exactly two hex digits"); - } - char tmp[3] = {hexDigitHi, hexDigitLo, '\0'}; - long converted = strtol(tmp, NULL, 16); - unsigned char convChar = (unsigned char)converted; - parsedStr.push_back(convChar); - } else if (escapeChar1 == '0' || escapeChar1 == '1' || escapeChar1 == '2' || escapeChar1 == '3' || - escapeChar1 == '4' || escapeChar1 == '5' || escapeChar1 == '6' || escapeChar1 == '7') { - // octal escape: we expect exactly three octal digits - // which means that val[i], val[i+1], val[i+2] must all be octal digits - // and that i+2 must be a valid index - if (i+2 >= val.length()) { - get_manager().raise_exception("invalid octal escape: exactly three octal digits required"); - } - char c2 = escapeChar1; - char c1 = val.at(i+1); - char c0 = val.at(i+2); - i += 2; - - if (!isdigit(c2) || !isdigit(c1) || !isdigit(c0)) { - get_manager().raise_exception("invalid octal escape: exactly three octal digits required"); - } - - if (c2 == '8' || c2 == '9' || c1 == '8' || c1 == '9' || c0 == '8' || c0 == '9') { - get_manager().raise_exception("invalid octal escape: exactly three octal digits required"); - } - - char tmp[4] = {c2, c1, c0, '\0'}; - long converted = strtol(tmp, NULL, 8); - unsigned char convChar = (unsigned char)converted; - parsedStr.push_back(convChar); - } else { - // unrecognized escape sequence -- just emit that character - parsedStr.push_back(escapeChar1); - } - } else { - parsedStr.push_back(nextChar); - } - - // i is incremented at the end of this loop. - // If it is modified, ensure that it points to the index before - // the next character. - } - return mk_string(parsedStr); -} - -static std::string str2RegexStr(std::string str) { - std::string res = ""; - int len = str.size(); - for (int i = 0; i < len; i++) { - char nc = str[i]; - // 12 special chars - if (nc == '\\' || nc == '^' || nc == '$' || nc == '.' || nc == '|' || nc == '?' - || nc == '*' || nc == '+' || nc == '(' || nc == ')' || nc == '[' || nc == '{') { - res.append(1, '\\'); - } - res.append(1, str[i]); - } - return res; -} - -std::string str_util::get_std_regex_str(expr * regex) { - app * a_regex = to_app(regex); - if (is_re_Str2Reg(a_regex)) { - expr * regAst = a_regex->get_arg(0); - std::string regStr = str2RegexStr(get_string_constant_value(regAst)); - return regStr; - } else if (is_re_RegexConcat(a_regex)) { - expr * reg1Ast = a_regex->get_arg(0); - expr * reg2Ast = a_regex->get_arg(1); - std::string reg1Str = get_std_regex_str(reg1Ast); - std::string reg2Str = get_std_regex_str(reg2Ast); - return "(" + reg1Str + ")(" + reg2Str + ")"; - } else if (is_re_RegexUnion(a_regex)) { - expr * reg1Ast = a_regex->get_arg(0); - expr * reg2Ast = a_regex->get_arg(1); - std::string reg1Str = get_std_regex_str(reg1Ast); - std::string reg2Str = get_std_regex_str(reg2Ast); - return "(" + reg1Str + ")|(" + reg2Str + ")"; - } else if (is_re_RegexStar(a_regex)) { - expr * reg1Ast = a_regex->get_arg(0); - std::string reg1Str = get_std_regex_str(reg1Ast); - return "(" + reg1Str + ")*"; - } else { - TRACE("t_str", tout << "BUG: unrecognized regex term " << mk_pp(regex, get_manager()) << std::endl;); - UNREACHABLE(); return ""; - } -} - -#endif /* disable */ diff --git a/src/ast/str_decl_plugin.h b/src/ast/str_decl_plugin.h deleted file mode 100644 index 28ecd1e43..000000000 --- a/src/ast/str_decl_plugin.h +++ /dev/null @@ -1,218 +0,0 @@ -/*++ -Module Name: - - str_decl_plugin.h - -Abstract: - - - -Author: - - Murphy Berzish (mtrberzi) 2015-09-02. - -Revision History: - ---*/ - -#if 0 - -#ifndef _STR_DECL_PLUGIN_H_ -#define _STR_DECL_PLUGIN_H_ - -#include"ast.h" -#include"arith_decl_plugin.h" -#include - -enum str_sort_kind { - STRING_SORT, - REGEX_SORT, -}; - -enum str_op_kind { - OP_STR, /* string constants */ - // basic string operators - OP_STRCAT, - OP_STRLEN, - // higher-level string functions -- these are reduced to basic operations - OP_STR_CHARAT, - OP_STR_STARTSWITH, - OP_STR_ENDSWITH, - OP_STR_CONTAINS, - OP_STR_INDEXOF, - OP_STR_INDEXOF2, - OP_STR_LASTINDEXOF, - OP_STR_SUBSTR, - OP_STR_REPLACE, - // SMT-LIB 2.5 standard operators -- these are rewritten to internal ones - OP_STR_PREFIXOF, - OP_STR_SUFFIXOF, - // string-integer conversion - OP_STR_STR2INT, - OP_STR_INT2STR, OP_STR_PLACEHOLDER1, OP_STR_PLACEHOLDER2, - // regular expression operators - OP_RE_STR2REGEX, - OP_RE_REGEXIN, - OP_RE_REGEXCONCAT, - OP_RE_REGEXSTAR, - OP_RE_REGEXUNION, - OP_RE_UNROLL, - // higher-level regex operators - OP_RE_REGEXPLUS, - OP_RE_REGEXCHARRANGE, - // end - LAST_STR_OP -}; - -class str_decl_plugin : public decl_plugin { -protected: - symbol m_strv_sym; - sort * m_str_decl; - sort * m_regex_decl; - - func_decl * m_concat_decl; - func_decl * m_length_decl; - - func_decl * m_charat_decl; - func_decl * m_startswith_decl; - func_decl * m_endswith_decl; - func_decl * m_contains_decl; - func_decl * m_indexof_decl; - func_decl * m_indexof2_decl; - func_decl * m_lastindexof_decl; - func_decl * m_substr_decl; - func_decl * m_replace_decl; - func_decl * m_str2int_decl; - func_decl * m_int2str_decl; - func_decl * m_prefixof_decl; - func_decl * m_suffixof_decl; - - func_decl * m_re_str2regex_decl; - func_decl * m_re_regexin_decl; - func_decl * m_re_regexconcat_decl; - func_decl * m_re_regexstar_decl; - func_decl * m_re_regexunion_decl; - func_decl * m_re_unroll_decl; - func_decl * m_re_regexplus_decl; - func_decl * m_re_regexcharrange_decl; - - arith_decl_plugin * m_arith_plugin; - family_id m_arith_fid; - sort * m_int_sort; - - std::map string_cache; - - virtual void set_manager(ast_manager * m, family_id id); - - func_decl * mk_func_decl(decl_kind k); -public: - str_decl_plugin(); - virtual ~str_decl_plugin(); - virtual void finalize(); - - virtual decl_plugin * mk_fresh(); - virtual sort * mk_sort(decl_kind k, unsigned num_parameters, parameter const * parameters); - virtual func_decl * mk_func_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, - unsigned arity, sort * const * domain, sort * range); - - app * mk_string(const char * val); - app * mk_string(std::string & val); - app * mk_fresh_string(); - - virtual void get_op_names(svector & op_names, symbol const & logic); - virtual void get_sort_names(svector & sort_names, symbol const & logic); - - virtual bool is_value(app * e) const; - virtual bool is_unique_value(app * e) const { return is_value(e); } -}; - -class str_recognizers { - family_id m_afid; -public: - str_recognizers(family_id fid):m_afid(fid) {} - family_id get_fid() const { return m_afid; } - family_id get_family_id() const { return get_fid(); } - - bool is_str_sort(sort* s) const { return is_sort_of(s, m_afid, STRING_SORT); } - - bool is_string(expr const * n, const char ** val) const; - bool is_string(expr const * n) const; - - bool is_re_Str2Reg(expr const * n) const { return is_app_of(n, get_fid(), OP_RE_STR2REGEX); } - bool is_re_RegexConcat(expr const * n) const { return is_app_of(n, get_fid(), OP_RE_REGEXCONCAT); } - bool is_re_RegexUnion(expr const * n) const { return is_app_of(n, get_fid(), OP_RE_REGEXUNION); } - bool is_re_RegexStar(expr const * n) const { return is_app_of(n, get_fid(), OP_RE_REGEXSTAR); } - bool is_re_RegexPlus(expr const * n) const { return is_app_of(n, get_fid(), OP_RE_REGEXPLUS); } - - std::string get_string_constant_value(expr const *n) const; -}; - -class str_util : public str_recognizers { - ast_manager & m_manager; - str_decl_plugin * m_plugin; - family_id m_fid; -public: - str_util(ast_manager & m); - ast_manager & get_manager() const { return m_manager; } - str_decl_plugin & plugin() { return *m_plugin; } - - sort* mk_string_sort() const { return get_manager().mk_sort(m_fid, STRING_SORT, 0, 0); } - - app * mk_string(const char * val) { - return m_plugin->mk_string(val); - } - app * mk_string(std::string & val) { - return m_plugin->mk_string(val); - } - - app * mk_fresh_string() { - return m_plugin->mk_fresh_string(); - } - - app * mk_string_with_escape_characters(const char * val) { - std::string str(val); - return mk_string_with_escape_characters(str); - } - app * mk_string_with_escape_characters(std::string & val); - - app * mk_str_StartsWith(expr * haystack, expr * needle) { - expr * es[2] = {haystack, needle}; - return m_manager.mk_app(get_fid(), OP_STR_STARTSWITH, 2, es); - } - - app * mk_str_EndsWith(expr * haystack, expr * needle) { - expr * es[2] = {haystack, needle}; - return m_manager.mk_app(get_fid(), OP_STR_ENDSWITH, 2, es); - } - - app * mk_re_Str2Reg(expr * s) { - expr * es[1] = {s}; - return m_manager.mk_app(get_fid(), OP_RE_STR2REGEX, 1, es); - } - - app * mk_re_Str2Reg(std::string s) { - return mk_re_Str2Reg(mk_string(s)); - } - - app * mk_re_RegexUnion(expr * e1, expr * e2) { - expr * es[2] = {e1, e2}; - return m_manager.mk_app(get_fid(), OP_RE_REGEXUNION, 2, es); - } - - app * mk_re_RegexConcat(expr * e1, expr * e2) { - expr * es[2] = {e1, e2}; - return m_manager.mk_app(get_fid(), OP_RE_REGEXCONCAT, 2, es); - } - - app * mk_re_RegexStar(expr * r) { - expr * es[1] = {r}; - return m_manager.mk_app(get_fid(), OP_RE_REGEXSTAR, 1, es); - } - - std::string get_std_regex_str(expr * regex); - -}; - -#endif /* _STR_DECL_PLUGIN_H_ */ - -#endif /* disable */ From 43f9a0a2bdf3d9be5a5deae97d35b590e010726c Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Fri, 17 Mar 2017 13:48:30 -0400 Subject: [PATCH 363/410] fix unterminated char* --- src/smt/theory_str.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 0554ae2c2..ff32e6f38 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -1642,7 +1642,7 @@ static zstring str2RegexStr(zstring str) { || nc == '*' || nc == '+' || nc == '(' || nc == ')' || nc == '[' || nc == '{') { res = res + zstring("\\"); } - char tmp[1] = {(char)str[i]}; + char tmp[2] = {(char)str[i], '\0'}; res = res + zstring(tmp); } return res; From 19de682b58901a17c5be8e13fcbee508ff667ae2 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 4 Apr 2017 17:22:55 -0400 Subject: [PATCH 364/410] remove references to m_str_fid in api --- src/api/api_context.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/api/api_context.h b/src/api/api_context.h index 6e34f6d6e..4685fd04e 100644 --- a/src/api/api_context.h +++ b/src/api/api_context.h @@ -82,7 +82,6 @@ namespace api { family_id m_pb_fid; family_id m_fpa_fid; family_id m_seq_fid; - family_id m_str_fid; datatype_decl_plugin * m_dt_plugin; std::string m_string_buffer; // temporary buffer used to cache strings sent to the "external" world. @@ -136,7 +135,6 @@ namespace api { family_id get_pb_fid() const { return m_pb_fid; } family_id get_fpa_fid() const { return m_fpa_fid; } family_id get_seq_fid() const { return m_seq_fid; } - family_id get_str_fid() const { return m_str_fid; } datatype_decl_plugin * get_dt_plugin() const { return m_dt_plugin; } Z3_error_code get_error_code() const { return m_error_code; } From f881e854702972d99ca611ea74bd2255222880dd Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 4 Apr 2017 17:54:18 -0400 Subject: [PATCH 365/410] remove old theory_str enums from api --- src/api/z3_api.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/api/z3_api.h b/src/api/z3_api.h index 23d04f0be..272c94dda 100644 --- a/src/api/z3_api.h +++ b/src/api/z3_api.h @@ -1169,10 +1169,6 @@ typedef enum { Z3_OP_RE_FULL_SET, Z3_OP_RE_COMPLEMENT, - // theory_str - Z3_OP_STR_CONCAT, - Z3_OP_STR_LENGTH, - // Auxiliary Z3_OP_LABEL = 0x700, Z3_OP_LABEL_LIT, From eef2bbadad9dd51a7c9ea0ded1a986dd7e0ba04a Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 4 Apr 2017 20:29:48 -0400 Subject: [PATCH 366/410] remove obsolete PARAM_STRING from ast --- src/ast/ast.cpp | 4 ---- src/ast/ast.h | 9 ++------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/src/ast/ast.cpp b/src/ast/ast.cpp index 7271048b1..5f2de5170 100644 --- a/src/ast/ast.cpp +++ b/src/ast/ast.cpp @@ -59,7 +59,6 @@ parameter& parameter::operator=(parameter const& other) { case PARAM_SYMBOL: new (m_symbol) symbol(other.get_symbol()); break; case PARAM_RATIONAL: new (m_rational) rational(other.get_rational()); break; case PARAM_DOUBLE: m_dval = other.m_dval; break; - case PARAM_STRING: m_string = other.m_string; break; case PARAM_EXTERNAL: m_ext_id = other.m_ext_id; break; default: UNREACHABLE(); @@ -95,7 +94,6 @@ bool parameter::operator==(parameter const & p) const { case PARAM_SYMBOL: return get_symbol() == p.get_symbol(); case PARAM_RATIONAL: return get_rational() == p.get_rational(); case PARAM_DOUBLE: return m_dval == p.m_dval; - case PARAM_STRING: return (m_string == NULL && p.m_string == NULL) || strcmp(m_string, p.m_string)==0; case PARAM_EXTERNAL: return m_ext_id == p.m_ext_id; default: UNREACHABLE(); return false; } @@ -109,7 +107,6 @@ unsigned parameter::hash() const { case PARAM_SYMBOL: b = get_symbol().hash(); break; case PARAM_RATIONAL: b = get_rational().hash(); break; case PARAM_DOUBLE: b = static_cast(m_dval); break; - case PARAM_STRING: /* TODO */ b = 42; break; case PARAM_EXTERNAL: b = m_ext_id; break; } return (b << 2) | m_kind; @@ -122,7 +119,6 @@ std::ostream& parameter::display(std::ostream& out) const { case PARAM_RATIONAL: return out << get_rational(); case PARAM_AST: return out << "#" << get_ast()->get_id(); case PARAM_DOUBLE: return out << m_dval; - case PARAM_STRING: return out << m_string; case PARAM_EXTERNAL: return out << "@" << m_ext_id; default: UNREACHABLE(); diff --git a/src/ast/ast.h b/src/ast/ast.h index 066265bb8..6bb3b01c9 100644 --- a/src/ast/ast.h +++ b/src/ast/ast.h @@ -87,7 +87,6 @@ public: PARAM_SYMBOL, PARAM_RATIONAL, PARAM_DOUBLE, - PARAM_STRING, // PARAM_EXTERNAL is used for handling decl_plugin specific parameters. // For example, it is used for handling mpf numbers in float_decl_plugin, // and irrational algebraic numbers in arith_decl_plugin. @@ -106,7 +105,6 @@ private: char m_symbol[sizeof(symbol)]; // for PARAM_SYMBOL char m_rational[sizeof(rational)]; // for PARAM_RATIONAL double m_dval; // for PARAM_DOUBLE (remark: this is not used in float_decl_plugin) - const char* m_string; // for PARAM_STRING unsigned m_ext_id; // for PARAM_EXTERNAL }; @@ -119,8 +117,8 @@ public: explicit parameter(symbol const & s): m_kind(PARAM_SYMBOL) { new (m_symbol) symbol(s); } explicit parameter(rational const & r): m_kind(PARAM_RATIONAL) { new (m_rational) rational(r); } explicit parameter(double d):m_kind(PARAM_DOUBLE), m_dval(d) {} - explicit parameter(const char *s):m_kind(PARAM_STRING), m_string(s) { - TRACE("parse_string", tout << "parameter(const char *): " << s << "\n";); + explicit parameter(const char *s):m_kind(PARAM_SYMBOL) { + new (m_symbol) symbol(s); } explicit parameter(unsigned ext_id, bool):m_kind(PARAM_EXTERNAL), m_ext_id(ext_id) {} parameter(parameter const&); @@ -135,7 +133,6 @@ public: bool is_symbol() const { return m_kind == PARAM_SYMBOL; } bool is_rational() const { return m_kind == PARAM_RATIONAL; } bool is_double() const { return m_kind == PARAM_DOUBLE; } - bool is_string() const { return m_kind == PARAM_STRING; } bool is_external() const { return m_kind == PARAM_EXTERNAL; } bool is_int(int & i) const { return is_int() && (i = get_int(), true); } @@ -143,7 +140,6 @@ public: bool is_symbol(symbol & s) const { return is_symbol() && (s = get_symbol(), true); } bool is_rational(rational & r) const { return is_rational() && (r = get_rational(), true); } bool is_double(double & d) const { return is_double() && (d = get_double(), true); } - // TODO is_string(char*) bool is_external(unsigned & id) const { return is_external() && (id = get_ext_id(), true); } /** @@ -163,7 +159,6 @@ public: symbol const & get_symbol() const { SASSERT(is_symbol()); return *(reinterpret_cast(m_symbol)); } rational const & get_rational() const { SASSERT(is_rational()); return *(reinterpret_cast(m_rational)); } double get_double() const { SASSERT(is_double()); return m_dval; } - const char * get_string() const { SASSERT(is_string()); return m_string; } unsigned get_ext_id() const { SASSERT(is_external()); return m_ext_id; } bool operator==(parameter const & p) const; From 7207cabc9710d7b13b4b91ce8e2f4f4bbbcc6767 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 12 Apr 2017 17:09:35 -0400 Subject: [PATCH 367/410] experimental new unsat core based overlap detection --- src/smt/smt_context.cpp | 61 ++++++++++++++++++++++++++++++++--------- src/smt/smt_context.h | 20 ++++++++++++++ src/smt/smt_setup.cpp | 2 ++ src/smt/theory_str.cpp | 12 ++++++++ src/smt/theory_str.h | 1 - 5 files changed, 82 insertions(+), 14 deletions(-) diff --git a/src/smt/smt_context.cpp b/src/smt/smt_context.cpp index 4fd027031..dfe396f2b 100644 --- a/src/smt/smt_context.cpp +++ b/src/smt/smt_context.cpp @@ -37,7 +37,7 @@ Revision History: #include"model_pp.h" #include"ast_smt2_pp.h" #include"ast_translation.h" -#include"theory_str.h" +#include"theory_seq.h" namespace smt { @@ -76,6 +76,8 @@ namespace smt { m_unsat_proof(m), m_unknown("unknown"), m_unsat_core(m), + m_use_theory_str_overlap_assumption(false), + m_theoryStrOverlapAssumption_term(m_manager), #ifdef Z3DEBUG m_trail_enabled(true), #endif @@ -3269,21 +3271,38 @@ namespace smt { // PATCH for theory_str: // UNSAT + overlapping variables => UNKNOWN - if (r == l_false) { - ptr_vector::iterator it = m_theory_set.begin(); - ptr_vector::iterator end = m_theory_set.end(); - for (; it != end; ++it) { - theory * th = *it; - if (strcmp(th->get_name(), "strings") == 0) { - theory_str * str = (theory_str*)th; - if (str->overlapping_variables_detected()) { - TRACE("t_str", tout << "WARNING: overlapping variables detected, UNSAT changed to UNKNOWN!" << std::endl;); - TRACE("context", tout << "WARNING: overlapping variables detected in theory_str. UNSAT changed to UNKNOWN!" << std::endl;); - r = l_undef; - } + if (r == l_false && use_theory_str_overlap_assumption()) { + // check the unsat core for an assumption from theory_str relating to overlaps. + // if we find this assumption, we have to answer UNKNOWN + // otherwise, we can pass through UNSAT + TRACE("t_str", tout << "unsat core:\n"; + unsigned sz = m_unsat_core.size(); + for (unsigned i = 0; i < sz; i++) { + tout << mk_pp(m_unsat_core.get(i), m_manager) << "\n"; + }); + + bool assumptionFound = false; + unsigned sz = m_unsat_core.size(); + app * target_term = to_app(m_manager.mk_not(m_theoryStrOverlapAssumption_term)); + internalize_term(target_term); + for (unsigned i = 0; i < sz; ++i) { + app * core_term = to_app(m_unsat_core.get(i)); + // not sure if this is the correct way to compare exprs in this context + enode * e1; + enode * e2; + e1 = get_enode(target_term); + e2 = get_enode(core_term); + if (e1 == e2) { + // found match + TRACE("t_str", tout << "overlap detected in unsat core; changing UNSAT to UNKNOWN" << std::endl;); + assumptionFound = true; + r = l_undef; break; } } + if (!assumptionFound) { + TRACE("t_str", tout << "no overlaps detected in unsat core, answering UNSAT" << std::endl;); + } } return r; @@ -3302,6 +3321,22 @@ namespace smt { SASSERT(m_scope_lvl == 0); SASSERT(!m_setup.already_configured()); setup_context(m_fparams.m_auto_config); + + // theory_str requires the context to be set up with a special assumption. + // we need to wait until after setup_context() to know whether this is the case + if (m_use_theory_str_overlap_assumption) { + TRACE("t_str", tout << "enabling theory_str overlap assumption" << std::endl;); + // TODO maybe refactor this a bit + symbol strOverlap("!!TheoryStrOverlapAssumption!!"); + expr_ref_vector assumption(get_manager()); + seq_util m_sequtil(m_manager); + sort * s = m_manager.mk_bool_sort(); + m_theoryStrOverlapAssumption_term = expr_ref(m_manager.mk_const(strOverlap, s), m_manager); + assumption.push_back(m_manager.mk_not(m_theoryStrOverlapAssumption_term)); + // this might work, even though we already did a bit of setup + return check(assumption.size(), assumption.c_ptr(), reset_cancel); + } + internalize_assertions(); lbool r = l_undef; if (m_asserted_formulas.inconsistent()) { diff --git a/src/smt/smt_context.h b/src/smt/smt_context.h index 47ed5d671..0cf3f8d68 100644 --- a/src/smt/smt_context.h +++ b/src/smt/smt_context.h @@ -226,6 +226,9 @@ namespace smt { literal2assumption m_literal2assumption; // maps an expression associated with a literal to the original assumption expr_ref_vector m_unsat_core; + // Unsat core assumption hint for theory_str + bool m_use_theory_str_overlap_assumption; + // ----------------------------------- // // Theory case split @@ -846,6 +849,23 @@ namespace smt { */ void add_theory_aware_branching_info(bool_var v, double priority, lbool phase); + // unsat core assumption hint for theory_str + void set_use_theory_str_overlap_assumption(bool f) { + m_use_theory_str_overlap_assumption = f; + } + + bool use_theory_str_overlap_assumption() const { + return m_use_theory_str_overlap_assumption; + } + + expr_ref get_theory_str_overlap_assumption_term() { + return m_theoryStrOverlapAssumption_term; + } + + protected: + expr_ref m_theoryStrOverlapAssumption_term; + public: + // helper function for trail void undo_th_case_split(literal l); diff --git a/src/smt/smt_setup.cpp b/src/smt/smt_setup.cpp index 78a295e27..fdcf33c0e 100644 --- a/src/smt/smt_setup.cpp +++ b/src/smt/smt_setup.cpp @@ -706,6 +706,7 @@ namespace smt { } void setup::setup_QF_S() { + m_context.set_use_theory_str_overlap_assumption(true); m_context.register_plugin(alloc(smt::theory_mi_arith, m_manager, m_params)); m_context.register_plugin(alloc(smt::theory_str, m_manager, m_params)); } @@ -841,6 +842,7 @@ namespace smt { void setup::setup_str() { setup_arith(); + m_context.set_use_theory_str_overlap_assumption(true); m_context.register_plugin(alloc(theory_str, m_manager, m_params)); } diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index ff32e6f38..b69ebda4c 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -4304,6 +4304,8 @@ void theory_str::process_concat_eq_type6(expr * concatAst1, expr * concatAst2) { add_nonempty_constraint(commonVar); } + bool overlapAssumptionUsed = false; + expr_ref_vector arrangement_disjunction(mgr); int pos = 1; @@ -4339,6 +4341,12 @@ void theory_str::process_concat_eq_type6(expr * concatAst1, expr * concatAst2) { } else { TRACE("t_str", tout << "AVOID LOOP: SKIPPED." << std::endl;); TRACE("t_str", print_cut_var(m, tout); print_cut_var(y, tout);); + + // only add the overlap assumption one time + if (!overlapAssumptionUsed) { + arrangement_disjunction.push_back(ctx.get_theory_str_overlap_assumption_term()); + overlapAssumptionUsed = true; + } } } @@ -7239,6 +7247,9 @@ void theory_str::init_search_eh() { ast_manager & m = get_manager(); context & ctx = get_context(); + // safety + SASSERT(ctx.use_theory_str_overlap_assumption()); + TRACE("t_str_detail", tout << "dumping all asserted formulas:" << std::endl; unsigned nFormulas = ctx.get_num_asserted_formulas(); @@ -7301,6 +7312,7 @@ void theory_str::new_eq_eh(theory_var x, theory_var y) { //TRACE("t_str_detail", tout << "new eq: v#" << x << " = v#" << y << std::endl;); TRACE("t_str", tout << "new eq: " << mk_ismt2_pp(get_enode(x)->get_owner(), get_manager()) << " = " << mk_ismt2_pp(get_enode(y)->get_owner(), get_manager()) << std::endl;); + /* if (m_find.find(x) == m_find.find(y)) { return; diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 3ea4db7d4..a8857de24 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -389,7 +389,6 @@ namespace smt { // finite model finding data // maps a finite model tester var to a list of variables that will be tested obj_map > finite_model_test_varlists; - protected: void assert_axiom(expr * e); void assert_implication(expr * premise, expr * conclusion); From a7f72bf4ef1a8c2aeb459accd2779c3f465f915f Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Thu, 13 Apr 2017 13:46:23 -0400 Subject: [PATCH 368/410] add overlap assumption to other cases in theory_str --- src/smt/theory_str.cpp | 49 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index b69ebda4c..9d3fef6d7 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -2898,6 +2898,9 @@ bool theory_str::is_concat_eq_type1(expr * concatAst1, expr * concatAst2) { void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { ast_manager & mgr = get_manager(); context & ctx = get_context(); + + bool overlapAssumptionUsed = false; + TRACE("t_str_detail", tout << "process_concat_eq TYPE 1" << std::endl << "concatAst1 = " << mk_ismt2_pp(concatAst1, mgr) << std::endl << "concatAst2 = " << mk_ismt2_pp(concatAst2, mgr) << std::endl; @@ -3074,6 +3077,11 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { } else { TRACE("t_str", tout << "AVOID LOOP: SKIPPED" << std::endl;); TRACE("t_str_detail", {print_cut_var(m, tout); print_cut_var(y, tout);}); + + if (!overlapAssumptionUsed) { + overlapAssumptionUsed = true; + assert_implication(ax_l, ctx.get_theory_str_overlap_assumption_term()); + } } } } else if (splitType == 1) { @@ -3132,6 +3140,11 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { } else { TRACE("t_str", tout << "AVOID LOOP: SKIPPED" << std::endl;); TRACE("t_str_detail", {print_cut_var(m, tout); print_cut_var(y, tout);}); + + if (!overlapAssumptionUsed) { + overlapAssumptionUsed = true; + assert_implication(ax_l, ctx.get_theory_str_overlap_assumption_term()); + } } } } else if (splitType == -1) { @@ -3183,6 +3196,11 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { } else { TRACE("t_str", tout << "AVOID LOOP: SKIPPED" << std::endl;); TRACE("t_str_detail", {print_cut_var(m, tout); print_cut_var(y, tout);}); + + if (!overlapAssumptionUsed) { + overlapAssumptionUsed = true; + arrangement_disjunction.push_back(ctx.get_theory_str_overlap_assumption_term()); + } } } @@ -3227,6 +3245,11 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { } else { TRACE("t_str", tout << "AVOID LOOP: SKIPPED" << std::endl;); TRACE("t_str_detail", {print_cut_var(x, tout); print_cut_var(n, tout);}); + + if (!overlapAssumptionUsed) { + overlapAssumptionUsed = true; + arrangement_disjunction.push_back(ctx.get_theory_str_overlap_assumption_term()); + } } } @@ -3286,6 +3309,9 @@ bool theory_str::is_concat_eq_type2(expr * concatAst1, expr * concatAst2) { void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { ast_manager & mgr = get_manager(); context & ctx = get_context(); + + bool overlapAssumptionUsed = false; + TRACE("t_str_detail", tout << "process_concat_eq TYPE 2" << std::endl << "concatAst1 = " << mk_ismt2_pp(concatAst1, mgr) << std::endl << "concatAst2 = " << mk_ismt2_pp(concatAst2, mgr) << std::endl; @@ -3466,6 +3492,11 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { } else { TRACE("t_str", tout << "AVOID LOOP: SKIP" << std::endl;); TRACE("t_str_detail", {print_cut_var(m, tout); print_cut_var(y, tout);}); + + if (!overlapAssumptionUsed) { + overlapAssumptionUsed = true; + assert_implication(ax_l, ctx.get_theory_str_overlap_assumption_term()); + } } } } @@ -3567,6 +3598,11 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { } else { TRACE("t_str", tout << "AVOID LOOP: SKIPPED" << std::endl;); TRACE("t_str_detail", {print_cut_var(m, tout); print_cut_var(y, tout);}); + + if (!overlapAssumptionUsed) { + overlapAssumptionUsed = true; + arrangement_disjunction.push_back(ctx.get_theory_str_overlap_assumption_term()); + } } } } @@ -3636,6 +3672,9 @@ bool theory_str::is_concat_eq_type3(expr * concatAst1, expr * concatAst2) { void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { ast_manager & mgr = get_manager(); context & ctx = get_context(); + + bool overlapAssumptionUsed = false; + TRACE("t_str_detail", tout << "process_concat_eq TYPE 3" << std::endl << "concatAst1 = " << mk_ismt2_pp(concatAst1, mgr) << std::endl << "concatAst2 = " << mk_ismt2_pp(concatAst2, mgr) << std::endl; @@ -3861,6 +3900,11 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { } else { TRACE("t_str", tout << "AVOID LOOP: SKIPPED" << std::endl;); TRACE("t_str_detail", {print_cut_var(x, tout); print_cut_var(n, tout);}); + + if (!overlapAssumptionUsed) { + overlapAssumptionUsed = true; + assert_implication(ax_l, ctx.get_theory_str_overlap_assumption_term()); + } } } } @@ -3940,6 +3984,11 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { } else { TRACE("t_str", tout << "AVOID LOOP: SKIPPED." << std::endl;); TRACE("t_str_detail", {print_cut_var(x, tout); print_cut_var(n, tout);}); + + if (!overlapAssumptionUsed) { + overlapAssumptionUsed = true; + arrangement_disjunction.push_back(ctx.get_theory_str_overlap_assumption_term()); + } } } } From bef64961ae985969ca4a02e9d8e11f0aecb49a26 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 18 Apr 2017 13:12:03 -0400 Subject: [PATCH 369/410] add pre-init assumptions for smt theories --- src/smt/smt_context.cpp | 17 ++++++++++++++++- src/smt/smt_context.h | 15 --------------- src/smt/smt_setup.cpp | 2 -- src/smt/smt_theory.h | 7 +++++++ src/smt/theory_str.cpp | 31 +++++++++++++++++++------------ src/smt/theory_str.h | 2 ++ 6 files changed, 44 insertions(+), 30 deletions(-) diff --git a/src/smt/smt_context.cpp b/src/smt/smt_context.cpp index dfe396f2b..db09552ef 100644 --- a/src/smt/smt_context.cpp +++ b/src/smt/smt_context.cpp @@ -77,7 +77,6 @@ namespace smt { m_unknown("unknown"), m_unsat_core(m), m_use_theory_str_overlap_assumption(false), - m_theoryStrOverlapAssumption_term(m_manager), #ifdef Z3DEBUG m_trail_enabled(true), #endif @@ -3269,6 +3268,7 @@ namespace smt { r = l_undef; } + /* // PATCH for theory_str: // UNSAT + overlapping variables => UNKNOWN if (r == l_false && use_theory_str_overlap_assumption()) { @@ -3304,6 +3304,7 @@ namespace smt { TRACE("t_str", tout << "no overlaps detected in unsat core, answering UNSAT" << std::endl;); } } + */ return r; } @@ -3322,6 +3323,7 @@ namespace smt { SASSERT(!m_setup.already_configured()); setup_context(m_fparams.m_auto_config); + /* // theory_str requires the context to be set up with a special assumption. // we need to wait until after setup_context() to know whether this is the case if (m_use_theory_str_overlap_assumption) { @@ -3336,6 +3338,19 @@ namespace smt { // this might work, even though we already did a bit of setup return check(assumption.size(), assumption.c_ptr(), reset_cancel); } + */ + + expr_ref_vector theory_assumptions(m_manager); + ptr_vector::iterator it = m_theory_set.begin(); + ptr_vector::iterator end = m_theory_set.end(); + for (; it != end; ++it) { + (*it)->add_theory_assumptions(theory_assumptions); + } + if (!theory_assumptions.empty()) { + TRACE("search", tout << "Adding theory assumptions to context" << std::endl;); + // this works even though we already did part of setup + return check(theory_assumptions.size(), theory_assumptions.c_ptr(), reset_cancel); + } internalize_assertions(); lbool r = l_undef; diff --git a/src/smt/smt_context.h b/src/smt/smt_context.h index 0cf3f8d68..0667f622e 100644 --- a/src/smt/smt_context.h +++ b/src/smt/smt_context.h @@ -849,21 +849,6 @@ namespace smt { */ void add_theory_aware_branching_info(bool_var v, double priority, lbool phase); - // unsat core assumption hint for theory_str - void set_use_theory_str_overlap_assumption(bool f) { - m_use_theory_str_overlap_assumption = f; - } - - bool use_theory_str_overlap_assumption() const { - return m_use_theory_str_overlap_assumption; - } - - expr_ref get_theory_str_overlap_assumption_term() { - return m_theoryStrOverlapAssumption_term; - } - - protected: - expr_ref m_theoryStrOverlapAssumption_term; public: // helper function for trail diff --git a/src/smt/smt_setup.cpp b/src/smt/smt_setup.cpp index fdcf33c0e..78a295e27 100644 --- a/src/smt/smt_setup.cpp +++ b/src/smt/smt_setup.cpp @@ -706,7 +706,6 @@ namespace smt { } void setup::setup_QF_S() { - m_context.set_use_theory_str_overlap_assumption(true); m_context.register_plugin(alloc(smt::theory_mi_arith, m_manager, m_params)); m_context.register_plugin(alloc(smt::theory_str, m_manager, m_params)); } @@ -842,7 +841,6 @@ namespace smt { void setup::setup_str() { setup_arith(); - m_context.set_use_theory_str_overlap_assumption(true); m_context.register_plugin(alloc(theory_str, m_manager, m_params)); } diff --git a/src/smt/smt_theory.h b/src/smt/smt_theory.h index cee36535f..e412f2f1b 100644 --- a/src/smt/smt_theory.h +++ b/src/smt/smt_theory.h @@ -177,6 +177,13 @@ namespace smt { virtual void restart_eh() { } + /** + \brief This method is called by smt_context before the search starts to get any + extra assumptions the theory wants to use. (see theory_str for an example) + */ + virtual void add_theory_assumptions(expr_ref_vector & assumptions) { + } + /** \brief This method is invoked before the search starts. */ diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 9d3fef6d7..354589318 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -56,6 +56,7 @@ theory_str::theory_str(ast_manager & m, theory_str_params const & params): tmpValTestVarCount(0), avoidLoopCut(true), loopDetected(false), + m_theoryStrOverlapAssumption_term(m), contains_map(m), string_int_conversion_terms(m), totalCacheAccessCount(0), @@ -3080,7 +3081,7 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { if (!overlapAssumptionUsed) { overlapAssumptionUsed = true; - assert_implication(ax_l, ctx.get_theory_str_overlap_assumption_term()); + assert_implication(ax_l, m_theoryStrOverlapAssumption_term); } } } @@ -3143,7 +3144,7 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { if (!overlapAssumptionUsed) { overlapAssumptionUsed = true; - assert_implication(ax_l, ctx.get_theory_str_overlap_assumption_term()); + assert_implication(ax_l, m_theoryStrOverlapAssumption_term); } } } @@ -3199,7 +3200,7 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { if (!overlapAssumptionUsed) { overlapAssumptionUsed = true; - arrangement_disjunction.push_back(ctx.get_theory_str_overlap_assumption_term()); + arrangement_disjunction.push_back(m_theoryStrOverlapAssumption_term); } } } @@ -3248,7 +3249,7 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { if (!overlapAssumptionUsed) { overlapAssumptionUsed = true; - arrangement_disjunction.push_back(ctx.get_theory_str_overlap_assumption_term()); + arrangement_disjunction.push_back(m_theoryStrOverlapAssumption_term); } } } @@ -3495,7 +3496,7 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { if (!overlapAssumptionUsed) { overlapAssumptionUsed = true; - assert_implication(ax_l, ctx.get_theory_str_overlap_assumption_term()); + assert_implication(ax_l, m_theoryStrOverlapAssumption_term); } } } @@ -3601,7 +3602,7 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { if (!overlapAssumptionUsed) { overlapAssumptionUsed = true; - arrangement_disjunction.push_back(ctx.get_theory_str_overlap_assumption_term()); + arrangement_disjunction.push_back(m_theoryStrOverlapAssumption_term); } } } @@ -3903,7 +3904,7 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { if (!overlapAssumptionUsed) { overlapAssumptionUsed = true; - assert_implication(ax_l, ctx.get_theory_str_overlap_assumption_term()); + assert_implication(ax_l, m_theoryStrOverlapAssumption_term); } } } @@ -3987,7 +3988,7 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { if (!overlapAssumptionUsed) { overlapAssumptionUsed = true; - arrangement_disjunction.push_back(ctx.get_theory_str_overlap_assumption_term()); + arrangement_disjunction.push_back(m_theoryStrOverlapAssumption_term); } } } @@ -4393,7 +4394,7 @@ void theory_str::process_concat_eq_type6(expr * concatAst1, expr * concatAst2) { // only add the overlap assumption one time if (!overlapAssumptionUsed) { - arrangement_disjunction.push_back(ctx.get_theory_str_overlap_assumption_term()); + arrangement_disjunction.push_back(m_theoryStrOverlapAssumption_term); overlapAssumptionUsed = true; } } @@ -7292,13 +7293,19 @@ void theory_str::set_up_axioms(expr * ex) { } } +void theory_str::add_theory_assumptions(expr_ref_vector & assumptions) { + TRACE("t_str", tout << "add overlap assumption for theory_str" << std::endl;); + symbol strOverlap("!!TheoryStrOverlapAssumption!!"); + seq_util m_sequtil(get_manager()); + sort * s = get_manager().mk_bool_sort(); + m_theoryStrOverlapAssumption_term = expr_ref(get_manager().mk_const(strOverlap, s), get_manager()); + assumptions.push_back(get_manager().mk_not(m_theoryStrOverlapAssumption_term)); +} + void theory_str::init_search_eh() { ast_manager & m = get_manager(); context & ctx = get_context(); - // safety - SASSERT(ctx.use_theory_str_overlap_assumption()); - TRACE("t_str_detail", tout << "dumping all asserted formulas:" << std::endl; unsigned nFormulas = ctx.get_num_asserted_formulas(); diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index a8857de24..3c273d4e2 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -291,6 +291,7 @@ namespace smt { bool avoidLoopCut; bool loopDetected; obj_map > cut_var_map; + expr_ref m_theoryStrOverlapAssumption_term; obj_hashtable variable_set; obj_hashtable internal_variable_set; @@ -627,6 +628,7 @@ namespace smt { virtual theory* mk_fresh(context*) { return alloc(theory_str, get_manager(), m_params); } virtual void init_search_eh(); + virtual void add_theory_assumptions(expr_ref_vector & assumptions); virtual void relevant_eh(app * n); virtual void assign_eh(bool_var v, bool is_true); virtual void push_scope_eh(); From 5cfe5e15aca60262ed9fcc1764ef065914fc46fa Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Fri, 21 Apr 2017 17:51:14 -0400 Subject: [PATCH 370/410] unsat core validation for smt theories --- src/smt/smt_context.cpp | 100 +++++++++++++++------------------------- src/smt/smt_context.h | 2 +- src/smt/smt_theory.h | 8 ++++ src/smt/theory_str.cpp | 22 +++++++++ src/smt/theory_str.h | 1 + 5 files changed, 68 insertions(+), 65 deletions(-) diff --git a/src/smt/smt_context.cpp b/src/smt/smt_context.cpp index db09552ef..412e7b13d 100644 --- a/src/smt/smt_context.cpp +++ b/src/smt/smt_context.cpp @@ -3211,11 +3211,11 @@ namespace smt { m_assumptions.reset(); } - void context::mk_unsat_core() { + lbool context::mk_unsat_core() { SASSERT(inconsistent()); if (!tracking_assumptions()) { SASSERT(m_assumptions.empty()); - return; + return l_false; } uint_set already_found_assumptions; literal_vector::const_iterator it = m_conflict_resolution->begin_unsat_core(); @@ -3240,7 +3240,19 @@ namespace smt { for (unsigned i = 0; i < sz; i++) { tout << mk_pp(m_unsat_core.get(i), m_manager) << "\n"; }); - validate_unsat_core(); + validate_unsat_core(); + // theory validation of unsat core + + ptr_vector::iterator th_it = m_theory_set.begin(); + ptr_vector::iterator th_end = m_theory_set.end(); + for (; th_it != th_end; ++th_it) { + lbool theory_result = (*th_it)->validate_unsat_core(m_unsat_core); + if (theory_result == l_undef) { + return l_undef; + } + } + + return l_false; } /** @@ -3267,45 +3279,6 @@ namespace smt { if (r == l_true && get_cancel_flag()) { r = l_undef; } - - /* - // PATCH for theory_str: - // UNSAT + overlapping variables => UNKNOWN - if (r == l_false && use_theory_str_overlap_assumption()) { - // check the unsat core for an assumption from theory_str relating to overlaps. - // if we find this assumption, we have to answer UNKNOWN - // otherwise, we can pass through UNSAT - TRACE("t_str", tout << "unsat core:\n"; - unsigned sz = m_unsat_core.size(); - for (unsigned i = 0; i < sz; i++) { - tout << mk_pp(m_unsat_core.get(i), m_manager) << "\n"; - }); - - bool assumptionFound = false; - unsigned sz = m_unsat_core.size(); - app * target_term = to_app(m_manager.mk_not(m_theoryStrOverlapAssumption_term)); - internalize_term(target_term); - for (unsigned i = 0; i < sz; ++i) { - app * core_term = to_app(m_unsat_core.get(i)); - // not sure if this is the correct way to compare exprs in this context - enode * e1; - enode * e2; - e1 = get_enode(target_term); - e2 = get_enode(core_term); - if (e1 == e2) { - // found match - TRACE("t_str", tout << "overlap detected in unsat core; changing UNSAT to UNKNOWN" << std::endl;); - assumptionFound = true; - r = l_undef; - break; - } - } - if (!assumptionFound) { - TRACE("t_str", tout << "no overlaps detected in unsat core, answering UNSAT" << std::endl;); - } - } - */ - return r; } @@ -3323,23 +3296,6 @@ namespace smt { SASSERT(!m_setup.already_configured()); setup_context(m_fparams.m_auto_config); - /* - // theory_str requires the context to be set up with a special assumption. - // we need to wait until after setup_context() to know whether this is the case - if (m_use_theory_str_overlap_assumption) { - TRACE("t_str", tout << "enabling theory_str overlap assumption" << std::endl;); - // TODO maybe refactor this a bit - symbol strOverlap("!!TheoryStrOverlapAssumption!!"); - expr_ref_vector assumption(get_manager()); - seq_util m_sequtil(m_manager); - sort * s = m_manager.mk_bool_sort(); - m_theoryStrOverlapAssumption_term = expr_ref(m_manager.mk_const(strOverlap, s), m_manager); - assumption.push_back(m_manager.mk_not(m_theoryStrOverlapAssumption_term)); - // this might work, even though we already did a bit of setup - return check(assumption.size(), assumption.c_ptr(), reset_cancel); - } - */ - expr_ref_vector theory_assumptions(m_manager); ptr_vector::iterator it = m_theory_set.begin(); ptr_vector::iterator end = m_theory_set.end(); @@ -3413,7 +3369,7 @@ namespace smt { (*it)->setup(); } - lbool context::check(unsigned num_assumptions, expr * const * assumptions, bool reset_cancel) { + lbool context::check(unsigned ext_num_assumptions, expr * const * ext_assumptions, bool reset_cancel) { m_stats.m_num_checks++; TRACE("check_bug", tout << "STARTING check(num_assumptions, assumptions)\n"; tout << "inconsistent: " << inconsistent() << ", m_unsat_core.empty(): " << m_unsat_core.empty() << "\n"; @@ -3424,6 +3380,22 @@ namespace smt { m_unsat_core.reset(); if (!check_preamble(reset_cancel)) return l_undef; + + expr_ref_vector theory_assumptions(m_manager); + for (unsigned i = 0; i < ext_num_assumptions; ++i) { + theory_assumptions.push_back(ext_assumptions[i]); + } + ptr_vector::iterator it = m_theory_set.begin(); + ptr_vector::iterator end = m_theory_set.end(); + for (; it != end; ++it) { + (*it)->add_theory_assumptions(theory_assumptions); + } + if (!theory_assumptions.empty()) { + TRACE("search", tout << "Adding theory assumptions to context" << std::endl;); + } + unsigned num_assumptions = theory_assumptions.size(); + expr * const * assumptions = theory_assumptions.c_ptr(); + if (!validate_assumptions(num_assumptions, assumptions)) return l_undef; TRACE("check_bug", tout << "inconsistent: " << inconsistent() << ", m_unsat_core.empty(): " << m_unsat_core.empty() << "\n";); @@ -3447,13 +3419,13 @@ namespace smt { TRACE("after_internalization", display(tout);); if (inconsistent()) { VERIFY(!resolve_conflict()); // build the proof - mk_unsat_core(); - r = l_false; + r = mk_unsat_core(); } else { r = search(); - if (r == l_false) - mk_unsat_core(); + if (r == l_false) { + r = mk_unsat_core(); // validation may change an l_false to l_undef here + } } } } diff --git a/src/smt/smt_context.h b/src/smt/smt_context.h index 0667f622e..0943662e8 100644 --- a/src/smt/smt_context.h +++ b/src/smt/smt_context.h @@ -1094,7 +1094,7 @@ namespace smt { void reset_assumptions(); - void mk_unsat_core(); + lbool mk_unsat_core(); void validate_unsat_core(); diff --git a/src/smt/smt_theory.h b/src/smt/smt_theory.h index e412f2f1b..ff29c7413 100644 --- a/src/smt/smt_theory.h +++ b/src/smt/smt_theory.h @@ -199,6 +199,14 @@ namespace smt { return FC_DONE; } + /** + \brief This method is called from the smt_context when an unsat core is generated. + The theory may change the answer to UNKNOWN by returning l_undef from this method. + */ + virtual lbool validate_unsat_core(expr_ref_vector & unsat_core) { + return l_false; + } + /** \brief Parametric theories (e.g. Arrays) should implement this method. See example in context::is_shared diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 354589318..bddd0b78e 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -7302,6 +7302,28 @@ void theory_str::add_theory_assumptions(expr_ref_vector & assumptions) { assumptions.push_back(get_manager().mk_not(m_theoryStrOverlapAssumption_term)); } +lbool theory_str::validate_unsat_core(expr_ref_vector & unsat_core) { + bool assumptionFound = false; + + app * target_term = to_app(get_manager().mk_not(m_theoryStrOverlapAssumption_term)); + internalize_term(target_term); + for (unsigned i = 0; i < unsat_core.size(); ++i) { + app * core_term = to_app(unsat_core.get(i)); + // not sure if this is the correct way to compare terms in this context + enode * e1; + enode * e2; + e1 = get_context().get_enode(target_term); + e2 = get_context().get_enode(core_term); + if (e1 == e2) { + TRACE("t_str", tout << "overlap detected in unsat core, changing UNSAT to UNKNOWN" << std::endl;); + assumptionFound = true; + return l_undef; + } + } + + return l_false; +} + void theory_str::init_search_eh() { ast_manager & m = get_manager(); context & ctx = get_context(); diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 3c273d4e2..7c2df9e12 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -629,6 +629,7 @@ namespace smt { virtual theory* mk_fresh(context*) { return alloc(theory_str, get_manager(), m_params); } virtual void init_search_eh(); virtual void add_theory_assumptions(expr_ref_vector & assumptions); + virtual lbool validate_unsat_core(expr_ref_vector & unsat_core); virtual void relevant_eh(app * n); virtual void assign_eh(bool_var v, bool is_true); virtual void push_scope_eh(); From c46f95a629c07ae2e983ce68ab25f255a0197137 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Mon, 24 Apr 2017 12:39:55 -0400 Subject: [PATCH 371/410] remove unused parameter from smt_context --- src/smt/smt_context.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/smt/smt_context.cpp b/src/smt/smt_context.cpp index 3d0652093..f003dfa37 100644 --- a/src/smt/smt_context.cpp +++ b/src/smt/smt_context.cpp @@ -76,7 +76,6 @@ namespace smt { m_unsat_proof(m), m_unknown("unknown"), m_unsat_core(m), - m_use_theory_str_overlap_assumption(false), #ifdef Z3DEBUG m_trail_enabled(true), #endif From 8ce93b4ee528776bba150a7fa88d10bce790b777 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Mon, 24 Apr 2017 15:39:25 -0400 Subject: [PATCH 372/410] unify tracing in theory_str to 'str' tag --- src/smt/theory_str.cpp | 832 ++++++++++++++++++++--------------------- 1 file changed, 416 insertions(+), 416 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index bddd0b78e..01123a22c 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -174,7 +174,7 @@ void theory_str::assert_axiom(expr * e) { } if (get_manager().is_true(e)) return; - TRACE("t_str_detail", tout << "asserting " << mk_ismt2_pp(e, get_manager()) << std::endl;); + TRACE("str", tout << "asserting " << mk_ismt2_pp(e, get_manager()) << std::endl;); context & ctx = get_context(); if (!ctx.b_internalized(e)) { ctx.internalize(e, false); @@ -186,7 +186,7 @@ void theory_str::assert_axiom(expr * e) { // crash/error avoidance: add all axioms to the trail m_trail.push_back(e); - //TRACE("t_str_detail", tout << "done asserting " << mk_ismt2_pp(e, get_manager()) << std::endl;); + //TRACE("str", tout << "done asserting " << mk_ismt2_pp(e, get_manager()) << std::endl;); } expr * theory_str::rewrite_implication(expr * premise, expr * conclusion) { @@ -196,7 +196,7 @@ expr * theory_str::rewrite_implication(expr * premise, expr * conclusion) { void theory_str::assert_implication(expr * premise, expr * conclusion) { ast_manager & m = get_manager(); - TRACE("t_str_detail", tout << "asserting implication " << mk_ismt2_pp(premise, m) << " -> " << mk_ismt2_pp(conclusion, m) << std::endl;); + TRACE("str", tout << "asserting implication " << mk_ismt2_pp(premise, m) << " -> " << mk_ismt2_pp(conclusion, m) << std::endl;); expr_ref axiom(m.mk_or(m.mk_not(premise), conclusion), m); assert_axiom(axiom); } @@ -210,7 +210,7 @@ bool theory_str::internalize_term(app * term) { ast_manager & m = get_manager(); SASSERT(term->get_family_id() == get_family_id()); - TRACE("t_str_detail", tout << "internalizing term: " << mk_ismt2_pp(term, get_manager()) << std::endl;); + TRACE("str", tout << "internalizing term: " << mk_ismt2_pp(term, get_manager()) << std::endl;); // emulation of user_smt_theory::internalize_term() @@ -234,14 +234,14 @@ bool theory_str::internalize_term(app * term) { for (unsigned i = 0; i < num_args; ++i) { enode * arg = e->get_arg(i); theory_var v_arg = mk_var(arg); - TRACE("t_str_detail", tout << "arg has theory var #" << v_arg << std::endl;); + TRACE("str", tout << "arg has theory var #" << v_arg << std::endl;); } theory_var v = mk_var(e); - TRACE("t_str_detail", tout << "term has theory var #" << v << std::endl;); + TRACE("str", tout << "term has theory var #" << v << std::endl;); if (opt_EagerStringConstantLengthAssertions && u.str.is_string(term)) { - TRACE("t_str", tout << "eagerly asserting length of string term " << mk_pp(term, m) << std::endl;); + TRACE("str", tout << "eagerly asserting length of string term " << mk_pp(term, m) << std::endl;); m_basicstr_axiom_todo.insert(e); } return true; @@ -260,23 +260,23 @@ enode* theory_str::ensure_enode(expr* e) { void theory_str::refresh_theory_var(expr * e) { enode * en = ensure_enode(e); theory_var v = mk_var(en); - TRACE("t_str_detail", tout << "refresh " << mk_pp(e, get_manager()) << ": v#" << v << std::endl;); + TRACE("str", tout << "refresh " << mk_pp(e, get_manager()) << ": v#" << v << std::endl;); m_basicstr_axiom_todo.push_back(en); } theory_var theory_str::mk_var(enode* n) { - TRACE("t_str_detail", tout << "mk_var for " << mk_pp(n->get_owner(), get_manager()) << std::endl;); + TRACE("str", tout << "mk_var for " << mk_pp(n->get_owner(), get_manager()) << std::endl;); ast_manager & m = get_manager(); if (!(m.get_sort(n->get_owner()) == u.str.mk_string_sort())) { return null_theory_var; } if (is_attached_to_var(n)) { - TRACE("t_str_detail", tout << "already attached to theory var" << std::endl;); + TRACE("str", tout << "already attached to theory var" << std::endl;); return n->get_th_var(get_id()); } else { theory_var v = theory::mk_var(n); m_find.mk_var(); - TRACE("t_str_detail", tout << "new theory var v#" << v << std::endl;); + TRACE("str", tout << "new theory var v#" << v << std::endl;); get_context().attach_th_var(n, this, v); get_context().mark_as_relevant(n); return v; @@ -320,14 +320,14 @@ void theory_str::add_cut_info_one_node(expr * baseNode, int slevel, expr * node) varInfo->vars[node] = 1; cut_var_map.insert(baseNode, std::stack()); cut_var_map[baseNode].push(varInfo); - TRACE("t_str_cut_var_map", tout << "add var info for baseNode=" << mk_pp(baseNode, get_manager()) << ", node=" << mk_pp(node, get_manager()) << " [" << slevel << "]" << std::endl;); + TRACE("str", tout << "add var info for baseNode=" << mk_pp(baseNode, get_manager()) << ", node=" << mk_pp(node, get_manager()) << " [" << slevel << "]" << std::endl;); } else { if (cut_var_map[baseNode].empty()) { T_cut * varInfo = alloc(T_cut); varInfo->level = slevel; varInfo->vars[node] = 1; cut_var_map[baseNode].push(varInfo); - TRACE("t_str_cut_var_map", tout << "add var info for baseNode=" << mk_pp(baseNode, get_manager()) << ", node=" << mk_pp(node, get_manager()) << " [" << slevel << "]" << std::endl;); + TRACE("str", tout << "add var info for baseNode=" << mk_pp(baseNode, get_manager()) << ", node=" << mk_pp(node, get_manager()) << " [" << slevel << "]" << std::endl;); } else { if (cut_var_map[baseNode].top()->level < slevel) { T_cut * varInfo = alloc(T_cut); @@ -335,10 +335,10 @@ void theory_str::add_cut_info_one_node(expr * baseNode, int slevel, expr * node) cut_vars_map_copy(varInfo->vars, cut_var_map[baseNode].top()->vars); varInfo->vars[node] = 1; cut_var_map[baseNode].push(varInfo); - TRACE("t_str_cut_var_map", tout << "add var info for baseNode=" << mk_pp(baseNode, get_manager()) << ", node=" << mk_pp(node, get_manager()) << " [" << slevel << "]" << std::endl;); + TRACE("str", tout << "add var info for baseNode=" << mk_pp(baseNode, get_manager()) << ", node=" << mk_pp(node, get_manager()) << " [" << slevel << "]" << std::endl;); } else if (cut_var_map[baseNode].top()->level == slevel) { cut_var_map[baseNode].top()->vars[node] = 1; - TRACE("t_str_cut_var_map", tout << "add var info for baseNode=" << mk_pp(baseNode, get_manager()) << ", node=" << mk_pp(node, get_manager()) << " [" << slevel << "]" << std::endl;); + TRACE("str", tout << "add var info for baseNode=" << mk_pp(baseNode, get_manager()) << ", node=" << mk_pp(node, get_manager()) << " [" << slevel << "]" << std::endl;); } else { get_manager().raise_exception("entered illegal state during add_cut_info_one_node()"); } @@ -364,7 +364,7 @@ void theory_str::add_cut_info_merge(expr * destNode, int slevel, expr * srcNode) cut_vars_map_copy(varInfo->vars, cut_var_map[srcNode].top()->vars); cut_var_map.insert(destNode, std::stack()); cut_var_map[destNode].push(varInfo); - TRACE("t_str_cut_var_map", tout << "merge var info for destNode=" << mk_pp(destNode, get_manager()) << ", srcNode=" << mk_pp(srcNode, get_manager()) << " [" << slevel << "]" << std::endl;); + TRACE("str", tout << "merge var info for destNode=" << mk_pp(destNode, get_manager()) << ", srcNode=" << mk_pp(srcNode, get_manager()) << " [" << slevel << "]" << std::endl;); } else { if (cut_var_map[destNode].empty() || cut_var_map[destNode].top()->level < slevel) { T_cut * varInfo = alloc(T_cut); @@ -372,10 +372,10 @@ void theory_str::add_cut_info_merge(expr * destNode, int slevel, expr * srcNode) cut_vars_map_copy(varInfo->vars, cut_var_map[destNode].top()->vars); cut_vars_map_copy(varInfo->vars, cut_var_map[srcNode].top()->vars); cut_var_map[destNode].push(varInfo); - TRACE("t_str_cut_var_map", tout << "merge var info for destNode=" << mk_pp(destNode, get_manager()) << ", srcNode=" << mk_pp(srcNode, get_manager()) << " [" << slevel << "]" << std::endl;); + TRACE("str", tout << "merge var info for destNode=" << mk_pp(destNode, get_manager()) << ", srcNode=" << mk_pp(srcNode, get_manager()) << " [" << slevel << "]" << std::endl;); } else if (cut_var_map[destNode].top()->level == slevel) { cut_vars_map_copy(cut_var_map[destNode].top()->vars, cut_var_map[srcNode].top()->vars); - TRACE("t_str_cut_var_map", tout << "merge var info for destNode=" << mk_pp(destNode, get_manager()) << ", srcNode=" << mk_pp(srcNode, get_manager()) << " [" << slevel << "]" << std::endl;); + TRACE("str", tout << "merge var info for destNode=" << mk_pp(destNode, get_manager()) << ", srcNode=" << mk_pp(srcNode, get_manager()) << " [" << slevel << "]" << std::endl;); } else { get_manager().raise_exception("illegal state in add_cut_info_merge(): inconsistent slevels"); } @@ -446,7 +446,7 @@ app * theory_str::mk_int_var(std::string name) { context & ctx = get_context(); ast_manager & m = get_manager(); - TRACE("t_str_detail", tout << "creating integer variable " << name << " at scope level " << sLevel << std::endl;); + TRACE("str", tout << "creating integer variable " << name << " at scope level " << sLevel << std::endl;); sort * int_sort = m.mk_sort(m_autil.get_family_id(), INT_SORT); app * a = m.mk_fresh_const(name.c_str(), int_sort); @@ -481,12 +481,12 @@ app * theory_str::mk_str_var(std::string name) { context & ctx = get_context(); ast_manager & m = get_manager(); - TRACE("t_str_detail", tout << "creating string variable " << name << " at scope level " << sLevel << std::endl;); + TRACE("str", tout << "creating string variable " << name << " at scope level " << sLevel << std::endl;); sort * string_sort = u.str.mk_string_sort(); app * a = m.mk_fresh_const(name.c_str(), string_sort); - TRACE("t_str_detail", tout << "a->get_family_id() = " << a->get_family_id() << std::endl + TRACE("str", tout << "a->get_family_id() = " << a->get_family_id() << std::endl << "this->get_family_id() = " << this->get_family_id() << std::endl;); // I have a hunch that this may not get internalized for free... @@ -496,7 +496,7 @@ app * theory_str::mk_str_var(std::string name) { // this might help?? mk_var(ctx.get_enode(a)); m_basicstr_axiom_todo.push_back(ctx.get_enode(a)); - TRACE("t_str_axiom_bug", tout << "add " << mk_pp(a, m) << " to m_basicstr_axiom_todo" << std::endl;); + TRACE("str", tout << "add " << mk_pp(a, m) << " to m_basicstr_axiom_todo" << std::endl;); m_trail.push_back(a); variable_set.insert(a); @@ -518,7 +518,7 @@ app * theory_str::mk_regex_rep_var() { SASSERT(ctx.e_internalized(a)); mk_var(ctx.get_enode(a)); m_basicstr_axiom_todo.push_back(ctx.get_enode(a)); - TRACE("t_str_axiom_bug", tout << "add " << mk_pp(a, m) << " to m_basicstr_axiom_todo" << std::endl;); + TRACE("str", tout << "add " << mk_pp(a, m) << " to m_basicstr_axiom_todo" << std::endl;); m_trail.push_back(a); variable_set.insert(a); @@ -560,7 +560,7 @@ app * theory_str::mk_nonempty_str_var() { tmpStringVarCount++; std::string name = "$$_str" + ss.str(); - TRACE("t_str_detail", tout << "creating nonempty string variable " << name << " at scope level " << sLevel << std::endl;); + TRACE("str", tout << "creating nonempty string variable " << name << " at scope level " << sLevel << std::endl;); sort * string_sort = u.str.mk_string_sort(); app * a = m.mk_fresh_const(name.c_str(), string_sort); @@ -784,12 +784,12 @@ bool theory_str::can_propagate() { void theory_str::propagate() { context & ctx = get_context(); while (can_propagate()) { - TRACE("t_str_detail", tout << "propagating..." << std::endl;); + TRACE("str", tout << "propagating..." << std::endl;); for (unsigned i = 0; i < m_basicstr_axiom_todo.size(); ++i) { instantiate_basic_string_axioms(m_basicstr_axiom_todo[i]); } m_basicstr_axiom_todo.reset(); - TRACE("t_str_axiom_bug", tout << "reset m_basicstr_axiom_todo" << std::endl;); + TRACE("str", tout << "reset m_basicstr_axiom_todo" << std::endl;); for (unsigned i = 0; i < m_str_eq_todo.size(); ++i) { std::pair pair = m_str_eq_todo[i]; @@ -840,7 +840,7 @@ void theory_str::propagate() { } else if (u.str.is_in_re(a)) { instantiate_axiom_RegexIn(e); } else { - TRACE("t_str", tout << "BUG: unhandled library-aware term " << mk_pp(e->get_owner(), get_manager()) << std::endl;); + TRACE("str", tout << "BUG: unhandled library-aware term " << mk_pp(e->get_owner(), get_manager()) << std::endl;); NOT_IMPLEMENTED_YET(); } } @@ -868,7 +868,7 @@ void theory_str::try_eval_concat(enode * cat) { context & ctx = get_context(); ast_manager & m = get_manager(); - TRACE("t_str_detail", tout << "attempting to flatten " << mk_pp(a_cat, m) << std::endl;); + TRACE("str", tout << "attempting to flatten " << mk_pp(a_cat, m) << std::endl;); std::stack worklist; zstring flattenedString(""); @@ -894,13 +894,13 @@ void theory_str::try_eval_concat(enode * cat) { worklist.push(arg1); worklist.push(arg0); } else { - TRACE("t_str_detail", tout << "non-constant term in concat -- giving up." << std::endl;); + TRACE("str", tout << "non-constant term in concat -- giving up." << std::endl;); constOK = false; break; } } if (constOK) { - TRACE("t_str_detail", tout << "flattened to \"" << flattenedString.encode().c_str() << "\"" << std::endl;); + TRACE("str", tout << "flattened to \"" << flattenedString.encode().c_str() << "\"" << std::endl;); expr_ref constStr(mk_string(flattenedString), m); expr_ref axiom(ctx.mk_eq_atom(a_cat, constStr), m); assert_axiom(axiom); @@ -917,7 +917,7 @@ void theory_str::instantiate_concat_axiom(enode * cat) { ast_manager & m = get_manager(); - TRACE("t_str_detail", tout << "instantiating concat axiom for " << mk_ismt2_pp(a_cat, m) << std::endl;); + TRACE("str", tout << "instantiating concat axiom for " << mk_ismt2_pp(a_cat, m) << std::endl;); // build LHS expr_ref len_xy(m); @@ -960,11 +960,11 @@ void theory_str::instantiate_basic_string_axioms(enode * str) { context & ctx = get_context(); ast_manager & m = get_manager(); - TRACE("t_str_axiom_bug", tout << "set up basic string axioms on " << mk_pp(str->get_owner(), m) << std::endl;); + TRACE("str", tout << "set up basic string axioms on " << mk_pp(str->get_owner(), m) << std::endl;); // TESTING: attempt to avoid a crash here when a variable goes out of scope if (str->get_iscope_lvl() > ctx.get_scope_level()) { - TRACE("t_str_detail", tout << "WARNING: skipping axiom setup on out-of-scope string term" << std::endl;); + TRACE("str", tout << "WARNING: skipping axiom setup on out-of-scope string term" << std::endl;); return; } @@ -977,7 +977,7 @@ void theory_str::instantiate_basic_string_axioms(enode * str) { zstring strconst; u.str.is_string(str->get_owner(), strconst); - TRACE("t_str_detail", tout << "instantiating constant string axioms for \"" << strconst.encode().c_str() << "\"" << std::endl;); + TRACE("str", tout << "instantiating constant string axioms for \"" << strconst.encode().c_str() << "\"" << std::endl;); unsigned int l = strconst.length(); expr_ref len(m_autil.mk_numeral(rational(l), true), m); @@ -998,7 +998,7 @@ void theory_str::instantiate_basic_string_axioms(enode * str) { // build LHS >= RHS and assert app * lhs_ge_rhs = m_autil.mk_ge(len_str, zero); SASSERT(lhs_ge_rhs); - TRACE("t_str_detail", tout << "string axiom 1: " << mk_ismt2_pp(lhs_ge_rhs, m) << std::endl;); + TRACE("str", tout << "string axiom 1: " << mk_ismt2_pp(lhs_ge_rhs, m) << std::endl;); assert_axiom(lhs_ge_rhs); } @@ -1022,7 +1022,7 @@ void theory_str::instantiate_basic_string_axioms(enode * str) { rhs = ctx.mk_eq_atom(a_str, empty_str); SASSERT(rhs); // build LHS <=> RHS and assert - TRACE("t_str_detail", tout << "string axiom 2: " << mk_ismt2_pp(lhs, m) << " <=> " << mk_ismt2_pp(rhs, m) << std::endl;); + TRACE("str", tout << "string axiom 2: " << mk_ismt2_pp(lhs, m) << " <=> " << mk_ismt2_pp(rhs, m) << std::endl;); literal l(mk_eq(lhs, rhs, true)); ctx.mark_as_relevant(l); ctx.mk_th_axiom(get_id(), 1, &l); @@ -1052,7 +1052,7 @@ void theory_str::instantiate_str_eq_length_axiom(enode * lhs, enode * rhs) { SASSERT(len_rhs); expr_ref conclusion(ctx.mk_eq_atom(len_lhs, len_rhs), m); - TRACE("t_str_detail", tout << "string-eq length-eq axiom: " + TRACE("str", tout << "string-eq length-eq axiom: " << mk_ismt2_pp(premise, m) << " -> " << mk_ismt2_pp(conclusion, m) << std::endl;); assert_implication(premise, conclusion); } @@ -1063,12 +1063,12 @@ void theory_str::instantiate_axiom_CharAt(enode * e) { app * expr = e->get_owner(); if (axiomatized_terms.contains(expr)) { - TRACE("t_str_detail", tout << "already set up CharAt axiom for " << mk_pp(expr, m) << std::endl;); + TRACE("str", tout << "already set up CharAt axiom for " << mk_pp(expr, m) << std::endl;); return; } axiomatized_terms.insert(expr); - TRACE("t_str_detail", tout << "instantiate CharAt axiom for " << mk_pp(expr, m) << std::endl;); + TRACE("str", tout << "instantiate CharAt axiom for " << mk_pp(expr, m) << std::endl;); expr_ref ts0(mk_str_var("ts0"), m); expr_ref ts1(mk_str_var("ts1"), m); @@ -1106,12 +1106,12 @@ void theory_str::instantiate_axiom_prefixof(enode * e) { app * expr = e->get_owner(); if (axiomatized_terms.contains(expr)) { - TRACE("t_str_detail", tout << "already set up prefixof axiom for " << mk_pp(expr, m) << std::endl;); + TRACE("str", tout << "already set up prefixof axiom for " << mk_pp(expr, m) << std::endl;); return; } axiomatized_terms.insert(expr); - TRACE("t_str_detail", tout << "instantiate prefixof axiom for " << mk_pp(expr, m) << std::endl;); + TRACE("str", tout << "instantiate prefixof axiom for " << mk_pp(expr, m) << std::endl;); expr_ref ts0(mk_str_var("ts0"), m); expr_ref ts1(mk_str_var("ts1"), m); @@ -1143,12 +1143,12 @@ void theory_str::instantiate_axiom_suffixof(enode * e) { app * expr = e->get_owner(); if (axiomatized_terms.contains(expr)) { - TRACE("t_str_detail", tout << "already set up suffixof axiom for " << mk_pp(expr, m) << std::endl;); + TRACE("str", tout << "already set up suffixof axiom for " << mk_pp(expr, m) << std::endl;); return; } axiomatized_terms.insert(expr); - TRACE("t_str_detail", tout << "instantiate suffixof axiom for " << mk_pp(expr, m) << std::endl;); + TRACE("str", tout << "instantiate suffixof axiom for " << mk_pp(expr, m) << std::endl;); expr_ref ts0(mk_str_var("ts0"), m); expr_ref ts1(mk_str_var("ts1"), m); @@ -1180,7 +1180,7 @@ void theory_str::instantiate_axiom_Contains(enode * e) { app * ex = e->get_owner(); if (axiomatized_terms.contains(ex)) { - TRACE("t_str_detail", tout << "already set up Contains axiom for " << mk_pp(ex, m) << std::endl;); + TRACE("str", tout << "already set up Contains axiom for " << mk_pp(ex, m) << std::endl;); return; } axiomatized_terms.insert(ex); @@ -1189,7 +1189,7 @@ void theory_str::instantiate_axiom_Contains(enode * e) { // at minimum it should fix z3str/concat-006.smt2 zstring haystackStr, needleStr; if (u.str.is_string(ex->get_arg(0), haystackStr) && u.str.is_string(ex->get_arg(1), needleStr)) { - TRACE("t_str_detail", tout << "eval constant Contains term " << mk_pp(ex, m) << std::endl;); + TRACE("str", tout << "eval constant Contains term " << mk_pp(ex, m) << std::endl;); if (haystackStr.contains(needleStr)) { assert_axiom(ex); } else { @@ -1208,7 +1208,7 @@ void theory_str::instantiate_axiom_Contains(enode * e) { contain_pair_idx_map[substr].insert(key); } - TRACE("t_str_detail", tout << "instantiate Contains axiom for " << mk_pp(ex, m) << std::endl;); + TRACE("str", tout << "instantiate Contains axiom for " << mk_pp(ex, m) << std::endl;); expr_ref ts0(mk_str_var("ts0"), m); expr_ref ts1(mk_str_var("ts1"), m); @@ -1224,12 +1224,12 @@ void theory_str::instantiate_axiom_Indexof(enode * e) { app * expr = e->get_owner(); if (axiomatized_terms.contains(expr)) { - TRACE("t_str_detail", tout << "already set up Indexof axiom for " << mk_pp(expr, m) << std::endl;); + TRACE("str", tout << "already set up Indexof axiom for " << mk_pp(expr, m) << std::endl;); return; } axiomatized_terms.insert(expr); - TRACE("t_str_detail", tout << "instantiate Indexof axiom for " << mk_pp(expr, m) << std::endl;); + TRACE("str", tout << "instantiate Indexof axiom for " << mk_pp(expr, m) << std::endl;); expr_ref x1(mk_str_var("x1"), m); expr_ref x2(mk_str_var("x2"), m); @@ -1280,12 +1280,12 @@ void theory_str::instantiate_axiom_Indexof2(enode * e) { app * expr = e->get_owner(); if (axiomatized_terms.contains(expr)) { - TRACE("t_str_detail", tout << "already set up Indexof2 axiom for " << mk_pp(expr, m) << std::endl;); + TRACE("str", tout << "already set up Indexof2 axiom for " << mk_pp(expr, m) << std::endl;); return; } axiomatized_terms.insert(expr); - TRACE("t_str_detail", tout << "instantiate Indexof2 axiom for " << mk_pp(expr, m) << std::endl;); + TRACE("str", tout << "instantiate Indexof2 axiom for " << mk_pp(expr, m) << std::endl;); // ------------------------------------------------------------------------------- // if (arg[2] >= length(arg[0])) // ite2 @@ -1348,12 +1348,12 @@ void theory_str::instantiate_axiom_LastIndexof(enode * e) { app * expr = e->get_owner(); if (axiomatized_terms.contains(expr)) { - TRACE("t_str_detail", tout << "already set up LastIndexof axiom for " << mk_pp(expr, m) << std::endl;); + TRACE("str", tout << "already set up LastIndexof axiom for " << mk_pp(expr, m) << std::endl;); return; } axiomatized_terms.insert(expr); - TRACE("t_str_detail", tout << "instantiate LastIndexof axiom for " << mk_pp(expr, m) << std::endl;); + TRACE("str", tout << "instantiate LastIndexof axiom for " << mk_pp(expr, m) << std::endl;); expr_ref x1(mk_str_var("x1"), m); expr_ref x2(mk_str_var("x2"), m); @@ -1417,12 +1417,12 @@ void theory_str::instantiate_axiom_Substr(enode * e) { app * expr = e->get_owner(); if (axiomatized_terms.contains(expr)) { - TRACE("t_str_detail", tout << "already set up Substr axiom for " << mk_pp(expr, m) << std::endl;); + TRACE("str", tout << "already set up Substr axiom for " << mk_pp(expr, m) << std::endl;); return; } axiomatized_terms.insert(expr); - TRACE("t_str_detail", tout << "instantiate Substr axiom for " << mk_pp(expr, m) << std::endl;); + TRACE("str", tout << "instantiate Substr axiom for " << mk_pp(expr, m) << std::endl;); expr_ref substrBase(expr->get_arg(0), m); expr_ref substrPos(expr->get_arg(1), m); @@ -1510,12 +1510,12 @@ void theory_str::instantiate_axiom_Replace(enode * e) { app * expr = e->get_owner(); if (axiomatized_terms.contains(expr)) { - TRACE("t_str_detail", tout << "already set up Replace axiom for " << mk_pp(expr, m) << std::endl;); + TRACE("str", tout << "already set up Replace axiom for " << mk_pp(expr, m) << std::endl;); return; } axiomatized_terms.insert(expr); - TRACE("t_str_detail", tout << "instantiate Replace axiom for " << mk_pp(expr, m) << std::endl;); + TRACE("str", tout << "instantiate Replace axiom for " << mk_pp(expr, m) << std::endl;); expr_ref x1(mk_str_var("x1"), m); expr_ref x2(mk_str_var("x2"), m); @@ -1560,12 +1560,12 @@ void theory_str::instantiate_axiom_str_to_int(enode * e) { app * ex = e->get_owner(); if (axiomatized_terms.contains(ex)) { - TRACE("t_str_detail", tout << "already set up str.to-int axiom for " << mk_pp(ex, m) << std::endl;); + TRACE("str", tout << "already set up str.to-int axiom for " << mk_pp(ex, m) << std::endl;); return; } axiomatized_terms.insert(ex); - TRACE("t_str_detail", tout << "instantiate str.to-int axiom for " << mk_pp(ex, m) << std::endl;); + TRACE("str", tout << "instantiate str.to-int axiom for " << mk_pp(ex, m) << std::endl;); // let expr = (str.to-int S) // axiom 1: expr >= -1 @@ -1607,12 +1607,12 @@ void theory_str::instantiate_axiom_int_to_str(enode * e) { app * ex = e->get_owner(); if (axiomatized_terms.contains(ex)) { - TRACE("t_str_detail", tout << "already set up str.from-int axiom for " << mk_pp(ex, m) << std::endl;); + TRACE("str", tout << "already set up str.from-int axiom for " << mk_pp(ex, m) << std::endl;); return; } axiomatized_terms.insert(ex); - TRACE("t_str_detail", tout << "instantiate str.from-int axiom for " << mk_pp(ex, m) << std::endl;); + TRACE("str", tout << "instantiate str.from-int axiom for " << mk_pp(ex, m) << std::endl;); // axiom 1: N < 0 <==> (str.from-int N) = "" expr * N = ex->get_arg(0); @@ -1674,7 +1674,7 @@ zstring theory_str::get_std_regex_str(expr * regex) { zstring reg1Str = get_std_regex_str(reg1Ast); return zstring("(") + reg1Str + zstring(")*"); } else { - TRACE("t_str", tout << "BUG: unrecognized regex term " << mk_pp(regex, get_manager()) << std::endl;); + TRACE("str", tout << "BUG: unrecognized regex term " << mk_pp(regex, get_manager()) << std::endl;); UNREACHABLE(); return zstring(""); } } @@ -1685,12 +1685,12 @@ void theory_str::instantiate_axiom_RegexIn(enode * e) { app * ex = e->get_owner(); if (axiomatized_terms.contains(ex)) { - TRACE("t_str_detail", tout << "already set up RegexIn axiom for " << mk_pp(ex, m) << std::endl;); + TRACE("str", tout << "already set up RegexIn axiom for " << mk_pp(ex, m) << std::endl;); return; } axiomatized_terms.insert(ex); - TRACE("t_str_detail", tout << "instantiate RegexIn axiom for " << mk_pp(ex, m) << std::endl;); + TRACE("str", tout << "instantiate RegexIn axiom for " << mk_pp(ex, m) << std::endl;); { zstring regexStr = get_std_regex_str(ex->get_arg(1)); @@ -1710,7 +1710,7 @@ void theory_str::instantiate_axiom_RegexIn(enode * e) { expr_ref finalAxiom(m.mk_iff(ex, rhs), m); SASSERT(finalAxiom); assert_axiom(finalAxiom); - TRACE("t_str", tout << "set up Str2Reg: (RegexIn " << mk_pp(str, m) << " " << mk_pp(regex, m) << ")" << std::endl;); + TRACE("str", tout << "set up Str2Reg: (RegexIn " << mk_pp(str, m) << " " << mk_pp(regex, m) << ")" << std::endl;); } else if (u.re.is_concat(regex)) { expr_ref var1(mk_regex_rep_var(), m); expr_ref var2(mk_regex_rep_var(), m); @@ -1753,7 +1753,7 @@ void theory_str::instantiate_axiom_RegexIn(enode * e) { SASSERT(finalAxiom); assert_axiom(finalAxiom); } else { - TRACE("t_str_detail", tout << "ERROR: unknown regex expression " << mk_pp(regex, m) << "!" << std::endl;); + TRACE("str", tout << "ERROR: unknown regex expression " << mk_pp(regex, m) << "!" << std::endl;); NOT_IMPLEMENTED_YET(); } } @@ -1762,11 +1762,11 @@ void theory_str::attach_new_th_var(enode * n) { context & ctx = get_context(); theory_var v = mk_var(n); ctx.attach_th_var(n, this, v); - TRACE("t_str_detail", tout << "new theory var: " << mk_ismt2_pp(n->get_owner(), get_manager()) << " := v#" << v << std::endl;); + TRACE("str", tout << "new theory var: " << mk_ismt2_pp(n->get_owner(), get_manager()) << " := v#" << v << std::endl;); } void theory_str::reset_eh() { - TRACE("t_str", tout << "resetting" << std::endl;); + TRACE("str", tout << "resetting" << std::endl;); m_trail_stack.reset(); m_basicstr_axiom_todo.reset(); @@ -1804,19 +1804,19 @@ bool theory_str::new_eq_check(expr * lhs, expr * rhs) { do { expr * eqc_nn2 = rhs; do { - TRACE("t_str_detail", tout << "checking whether " << mk_pp(eqc_nn1, m) << " and " << mk_pp(eqc_nn2, m) << " can be equal" << std::endl;); + TRACE("str", tout << "checking whether " << mk_pp(eqc_nn1, m) << " and " << mk_pp(eqc_nn2, m) << " can be equal" << std::endl;); // inconsistency check: value if (!can_two_nodes_eq(eqc_nn1, eqc_nn2)) { - TRACE("t_str", tout << "inconsistency detected: " << mk_pp(eqc_nn1, m) << " cannot be equal to " << mk_pp(eqc_nn2, m) << std::endl;); + TRACE("str", tout << "inconsistency detected: " << mk_pp(eqc_nn1, m) << " cannot be equal to " << mk_pp(eqc_nn2, m) << std::endl;); expr_ref to_assert(m.mk_not(ctx.mk_eq_atom(eqc_nn1, eqc_nn2)), m); assert_axiom(to_assert); // this shouldn't use the integer theory at all, so we don't allow the option of quick-return return false; } if (!check_length_consistency(eqc_nn1, eqc_nn2)) { - TRACE("t_str", tout << "inconsistency detected: " << mk_pp(eqc_nn1, m) << " and " << mk_pp(eqc_nn2, m) << " have inconsistent lengths" << std::endl;); + TRACE("str", tout << "inconsistency detected: " << mk_pp(eqc_nn1, m) << " and " << mk_pp(eqc_nn2, m) << " have inconsistent lengths" << std::endl;); if (opt_NoQuickReturn_IntegerTheory){ - TRACE("t_str_detail", tout << "continuing in new_eq_check() due to opt_NoQuickReturn_IntegerTheory" << std::endl;); + TRACE("str", tout << "continuing in new_eq_check() due to opt_NoQuickReturn_IntegerTheory" << std::endl;); } else { return false; } @@ -1831,7 +1831,7 @@ bool theory_str::new_eq_check(expr * lhs, expr * rhs) { } if (!regex_in_bool_map.empty()) { - TRACE("t_str", tout << "checking regex consistency" << std::endl;); + TRACE("str", tout << "checking regex consistency" << std::endl;); check_regex_in(lhs, rhs); } @@ -1963,7 +1963,7 @@ void theory_str::simplify_parent(expr * nn, expr * eq_str) { ast_manager & m = get_manager(); context & ctx = get_context(); - TRACE("t_str", tout << "simplifying parents of " << mk_ismt2_pp(nn, m) + TRACE("str", tout << "simplifying parents of " << mk_ismt2_pp(nn, m) << " with respect to " << mk_ismt2_pp(eq_str, m) << std::endl;); ctx.internalize(nn, false); @@ -1973,7 +1973,7 @@ void theory_str::simplify_parent(expr * nn, expr * eq_str) { expr * n_eqNode = nn; do { enode * n_eq_enode = ctx.get_enode(n_eqNode); - TRACE("t_str_detail", tout << "considering all parents of " << mk_ismt2_pp(n_eqNode, m) << std::endl + TRACE("str", tout << "considering all parents of " << mk_ismt2_pp(n_eqNode, m) << std::endl << "associated n_eq_enode has " << n_eq_enode->get_num_parents() << " parents" << std::endl;); // the goal of this next bit is to avoid dereferencing a bogus e_parent in the following loop. @@ -1990,7 +1990,7 @@ void theory_str::simplify_parent(expr * nn, expr * eq_str) { SASSERT(e_parent != NULL); app * a_parent = e_parent->get_owner(); - TRACE("t_str_detail", tout << "considering parent " << mk_ismt2_pp(a_parent, m) << std::endl;); + TRACE("str", tout << "considering parent " << mk_ismt2_pp(a_parent, m) << std::endl;); if (u.str.is_concat(a_parent)) { expr * arg0 = a_parent->get_arg(0); @@ -2004,7 +2004,7 @@ void theory_str::simplify_parent(expr * nn, expr * eq_str) { bool arg0Len_exists = get_len_value(eq_str, arg0Len); bool arg1Len_exists = get_len_value(arg1, arg1Len); - TRACE("t_str_detail", + TRACE("str", tout << "simplify_parent #1:" << std::endl << "* parent = " << mk_ismt2_pp(a_parent, m) << std::endl << "* |parent| = " << rational_to_string_if_exists(parentLen, parentLen_exists) << std::endl @@ -2013,7 +2013,7 @@ void theory_str::simplify_parent(expr * nn, expr * eq_str) { ); if (parentLen_exists && !arg1Len_exists) { - TRACE("t_str_detail", tout << "make up len for arg1" << std::endl;); + TRACE("str", tout << "make up len for arg1" << std::endl;); expr_ref implyL11(m.mk_and(ctx.mk_eq_atom(mk_strlen(a_parent), mk_int(parentLen)), ctx.mk_eq_atom(mk_strlen(arg0), mk_int(arg0Len))), m); rational makeUpLenArg1 = parentLen - arg0Len; @@ -2075,7 +2075,7 @@ void theory_str::simplify_parent(expr * nn, expr * eq_str) { bool arg0Len_exists = get_len_value(arg0, arg0Len); bool arg1Len_exists = get_len_value(eq_str, arg1Len); - TRACE("t_str_detail", + TRACE("str", tout << "simplify_parent #2:" << std::endl << "* parent = " << mk_ismt2_pp(a_parent, m) << std::endl << "* |parent| = " << rational_to_string_if_exists(parentLen, parentLen_exists) << std::endl @@ -2083,7 +2083,7 @@ void theory_str::simplify_parent(expr * nn, expr * eq_str) { << "* |arg1| = " << rational_to_string_if_exists(arg1Len, arg1Len_exists) << std::endl; ); if (parentLen_exists && !arg0Len_exists) { - TRACE("t_str_detail", tout << "make up len for arg0" << std::endl;); + TRACE("str", tout << "make up len for arg0" << std::endl;); expr_ref implyL11(m.mk_and(ctx.mk_eq_atom(mk_strlen(a_parent), mk_int(parentLen)), ctx.mk_eq_atom(mk_strlen(arg1), mk_int(arg1Len))), m); rational makeUpLenArg0 = parentLen - arg1Len; @@ -2144,7 +2144,7 @@ void theory_str::simplify_parent(expr * nn, expr * eq_str) { // Case (2-1) begin: (Concat n_eqNode (Concat str var)) if (arg0 == n_eqNode && u.str.is_concat(to_app(arg1))) { app * a_arg1 = to_app(arg1); - TRACE("t_str_detail", tout << "simplify_parent #3" << std::endl;); + TRACE("str", tout << "simplify_parent #3" << std::endl;); expr * r_concat_arg0 = a_arg1->get_arg(0); if (u.str.is_string(r_concat_arg0)) { expr * combined_str = eval_concat(eq_str, r_concat_arg0); @@ -2168,7 +2168,7 @@ void theory_str::simplify_parent(expr * nn, expr * eq_str) { // Case (2-2) begin: (Concat (Concat var str) n_eqNode) if (u.str.is_concat(to_app(arg0)) && arg1 == n_eqNode) { app * a_arg0 = to_app(arg0); - TRACE("t_str_detail", tout << "simplify_parent #4" << std::endl;); + TRACE("str", tout << "simplify_parent #4" << std::endl;); expr * l_concat_arg1 = a_arg0->get_arg(1); if (u.str.is_string(l_concat_arg1)) { expr * combined_str = eval_concat(l_concat_arg1, eq_str); @@ -2199,7 +2199,7 @@ void theory_str::simplify_parent(expr * nn, expr * eq_str) { expr * concat_parent_arg0 = concat_parent->get_arg(0); expr * concat_parent_arg1 = concat_parent->get_arg(1); if (concat_parent_arg0 == a_parent && u.str.is_string(concat_parent_arg1)) { - TRACE("t_str_detail", tout << "simplify_parent #5" << std::endl;); + TRACE("str", tout << "simplify_parent #5" << std::endl;); expr * combinedStr = eval_concat(eq_str, concat_parent_arg1); SASSERT(combinedStr); expr_ref implyL(m); @@ -2225,7 +2225,7 @@ void theory_str::simplify_parent(expr * nn, expr * eq_str) { expr * concat_parent_arg0 = concat_parent->get_arg(0); expr * concat_parent_arg1 = concat_parent->get_arg(1); if (concat_parent_arg1 == a_parent && u.str.is_string(concat_parent_arg0)) { - TRACE("t_str_detail", tout << "simplify_parent #6" << std::endl;); + TRACE("str", tout << "simplify_parent #6" << std::endl;); expr * combinedStr = eval_concat(concat_parent_arg0, eq_str); SASSERT(combinedStr); expr_ref implyL(m); @@ -2275,10 +2275,10 @@ expr * theory_str::simplify_concat(expr * node) { expr * vArg = get_eqc_value(argVec[i], vArgHasEqcValue); resultAst = mk_concat(resultAst, vArg); } - TRACE("t_str_detail", tout << mk_ismt2_pp(node, m) << " is simplified to " << mk_ismt2_pp(resultAst, m) << std::endl;); + TRACE("str", tout << mk_ismt2_pp(node, m) << " is simplified to " << mk_ismt2_pp(resultAst, m) << std::endl;); if (in_same_eqc(node, resultAst)) { - TRACE("t_str_detail", tout << "SKIP: both concats are already in the same equivalence class" << std::endl;); + TRACE("str", tout << "SKIP: both concats are already in the same equivalence class" << std::endl;); } else { expr_ref_vector items(m); int pos = 0; @@ -2327,7 +2327,7 @@ bool theory_str::infer_len_concat(expr * n, rational & nLen) { expr_ref axl(m.mk_and(l_items.size(), l_items.c_ptr()), m); rational nnLen = arg0_len + arg1_len; expr_ref axr(ctx.mk_eq_atom(mk_strlen(n), mk_int(nnLen)), m); - TRACE("t_str_detail", tout << "inferred (Length " << mk_pp(n, m) << ") = " << nnLen << std::endl;); + TRACE("str", tout << "inferred (Length " << mk_pp(n, m) << ") = " << nnLen << std::endl;); assert_implication(axl, axr); nLen = nnLen; return true; @@ -2507,10 +2507,10 @@ void theory_str::simplify_concat_equality(expr * nn1, expr * nn2) { bool a2_arg0_len_exists = get_len_value(a2_arg0, a2_arg0_len); bool a2_arg1_len_exists = get_len_value(a2_arg1, a2_arg1_len); - TRACE("t_str", tout << "nn1 = " << mk_ismt2_pp(nn1, m) << std::endl + TRACE("str", tout << "nn1 = " << mk_ismt2_pp(nn1, m) << std::endl << "nn2 = " << mk_ismt2_pp(nn2, m) << std::endl;); - TRACE("t_str_detail", tout + TRACE("str", tout << "len(" << mk_pp(a1_arg0, m) << ") = " << (a1_arg0_len_exists ? a1_arg0_len.to_string() : "?") << std::endl << "len(" << mk_pp(a1_arg1, m) << ") = " << (a1_arg1_len_exists ? a1_arg1_len.to_string() : "?") << std::endl << "len(" << mk_pp(a2_arg0, m) << ") = " << (a2_arg0_len_exists ? a2_arg0_len.to_string() : "?") << std::endl @@ -2527,7 +2527,7 @@ void theory_str::simplify_concat_equality(expr * nn1, expr * nn2) { expr_ref conclusion(m.mk_and(eq1, eq2), m); assert_implication(premise, conclusion); } - TRACE("t_str_detail", tout << "SKIP: a1_arg0 == a2_arg0" << std::endl;); + TRACE("str", tout << "SKIP: a1_arg0 == a2_arg0" << std::endl;); return; } @@ -2539,7 +2539,7 @@ void theory_str::simplify_concat_equality(expr * nn1, expr * nn2) { expr_ref conclusion(m.mk_and(eq1, eq2), m); assert_implication(premise, conclusion); } - TRACE("t_str_detail", tout << "SKIP: a1_arg1 == a2_arg1" << std::endl;); + TRACE("str", tout << "SKIP: a1_arg1 == a2_arg1" << std::endl;); return; } @@ -2547,10 +2547,10 @@ void theory_str::simplify_concat_equality(expr * nn1, expr * nn2) { if (in_same_eqc(a1_arg0, a2_arg0)) { if (in_same_eqc(a1_arg1, a2_arg1)) { - TRACE("t_str_detail", tout << "SKIP: a1_arg0 =~ a2_arg0 and a1_arg1 =~ a2_arg1" << std::endl;); + TRACE("str", tout << "SKIP: a1_arg0 =~ a2_arg0 and a1_arg1 =~ a2_arg1" << std::endl;); return; } else { - TRACE("t_str_detail", tout << "quick path 1-1: a1_arg0 =~ a2_arg0" << std::endl;); + TRACE("str", tout << "quick path 1-1: a1_arg0 =~ a2_arg0" << std::endl;); expr_ref premise(m.mk_and(ctx.mk_eq_atom(nn1, nn2), ctx.mk_eq_atom(a1_arg0, a2_arg0)), m); expr_ref conclusion(m.mk_and(ctx.mk_eq_atom(a1_arg1, a2_arg1), ctx.mk_eq_atom(mk_strlen(a1_arg1), mk_strlen(a2_arg1))), m); assert_implication(premise, conclusion); @@ -2558,7 +2558,7 @@ void theory_str::simplify_concat_equality(expr * nn1, expr * nn2) { } } else { if (in_same_eqc(a1_arg1, a2_arg1)) { - TRACE("t_str_detail", tout << "quick path 1-2: a1_arg1 =~ a2_arg1" << std::endl;); + TRACE("str", tout << "quick path 1-2: a1_arg1 =~ a2_arg1" << std::endl;); expr_ref premise(m.mk_and(ctx.mk_eq_atom(nn1, nn2), ctx.mk_eq_atom(a1_arg1, a2_arg1)), m); expr_ref conclusion(m.mk_and(ctx.mk_eq_atom(a1_arg0, a2_arg0), ctx.mk_eq_atom(mk_strlen(a1_arg0), mk_strlen(a2_arg0))), m); assert_implication(premise, conclusion); @@ -2569,7 +2569,7 @@ void theory_str::simplify_concat_equality(expr * nn1, expr * nn2) { // quick path 2-1 if (a1_arg0_len_exists && a2_arg0_len_exists && a1_arg0_len == a2_arg0_len) { if (!in_same_eqc(a1_arg0, a2_arg0)) { - TRACE("t_str_detail", tout << "quick path 2-1: len(nn1.arg0) == len(nn2.arg0)" << std::endl;); + TRACE("str", tout << "quick path 2-1: len(nn1.arg0) == len(nn2.arg0)" << std::endl;); expr_ref ax_l1(ctx.mk_eq_atom(nn1, nn2), m); expr_ref ax_l2(ctx.mk_eq_atom(mk_strlen(a1_arg0), mk_strlen(a2_arg0)), m); expr_ref ax_r1(ctx.mk_eq_atom(a1_arg0, a2_arg0), m); @@ -2581,7 +2581,7 @@ void theory_str::simplify_concat_equality(expr * nn1, expr * nn2) { assert_implication(premise, conclusion); if (opt_NoQuickReturn_IntegerTheory) { - TRACE("t_str_detail", tout << "bypassing quick return from the end of this case" << std::endl;); + TRACE("str", tout << "bypassing quick return from the end of this case" << std::endl;); } else { return; } @@ -2590,7 +2590,7 @@ void theory_str::simplify_concat_equality(expr * nn1, expr * nn2) { if (a1_arg1_len_exists && a2_arg1_len_exists && a1_arg1_len == a2_arg1_len) { if (!in_same_eqc(a1_arg1, a2_arg1)) { - TRACE("t_str_detail", tout << "quick path 2-2: len(nn1.arg1) == len(nn2.arg1)" << std::endl;); + TRACE("str", tout << "quick path 2-2: len(nn1.arg1) == len(nn2.arg1)" << std::endl;); expr_ref ax_l1(ctx.mk_eq_atom(nn1, nn2), m); expr_ref ax_l2(ctx.mk_eq_atom(mk_strlen(a1_arg1), mk_strlen(a2_arg1)), m); expr_ref ax_r1(ctx.mk_eq_atom(a1_arg0, a2_arg0), m); @@ -2601,7 +2601,7 @@ void theory_str::simplify_concat_equality(expr * nn1, expr * nn2) { assert_implication(premise, conclusion); if (opt_NoQuickReturn_IntegerTheory) { - TRACE("t_str_detail", tout << "bypassing quick return from the end of this case" << std::endl;); + TRACE("str", tout << "bypassing quick return from the end of this case" << std::endl;); } else { return; } @@ -2613,17 +2613,17 @@ void theory_str::simplify_concat_equality(expr * nn1, expr * nn2) { app * a_new_nn1 = to_app(new_nn1); app * a_new_nn2 = to_app(new_nn2); - TRACE("t_str_detail", tout << "new_nn1 = " << mk_ismt2_pp(new_nn1, m) << std::endl + TRACE("str", tout << "new_nn1 = " << mk_ismt2_pp(new_nn1, m) << std::endl << "new_nn2 = " << mk_ismt2_pp(new_nn2, m) << std::endl;); if (new_nn1 == new_nn2) { - TRACE("t_str_detail", tout << "equal concats, return" << std::endl;); + TRACE("str", tout << "equal concats, return" << std::endl;); return; } if (!can_two_nodes_eq(new_nn1, new_nn2)) { expr_ref detected(m.mk_not(ctx.mk_eq_atom(new_nn1, new_nn2)), m); - TRACE("t_str_detail", tout << "inconsistency detected: " << mk_ismt2_pp(detected, m) << std::endl;); + TRACE("str", tout << "inconsistency detected: " << mk_ismt2_pp(detected, m) << std::endl;); assert_axiom(detected); return; } @@ -2633,13 +2633,13 @@ void theory_str::simplify_concat_equality(expr * nn1, expr * nn2) { bool n1IsConcat = u.str.is_concat(a_new_nn1); bool n2IsConcat = u.str.is_concat(a_new_nn2); if (!n1IsConcat && n2IsConcat) { - TRACE("t_str_detail", tout << "nn1_new is not a concat" << std::endl;); + TRACE("str", tout << "nn1_new is not a concat" << std::endl;); if (u.str.is_string(a_new_nn1)) { simplify_parent(new_nn2, new_nn1); } return; } else if (n1IsConcat && !n2IsConcat) { - TRACE("t_str_detail", tout << "nn2_new is not a concat" << std::endl;); + TRACE("str", tout << "nn2_new is not a concat" << std::endl;); if (u.str.is_string(a_new_nn2)) { simplify_parent(new_nn1, new_nn2); } @@ -2647,7 +2647,7 @@ void theory_str::simplify_concat_equality(expr * nn1, expr * nn2) { } else if (!n1IsConcat && !n2IsConcat) { // normally this should never happen, because group_terms_by_eqc() should have pre-simplified // as much as possible. however, we make a defensive check here just in case - TRACE("t_str_detail", tout << "WARNING: nn1_new and nn2_new both simplify to non-concat terms" << std::endl;); + TRACE("str", tout << "WARNING: nn1_new and nn2_new both simplify to non-concat terms" << std::endl;); return; } @@ -2750,7 +2750,7 @@ bool theory_str::will_result_in_overlap(expr * lhs, expr * rhs) { expr * v2_arg0 = a_new_nn2->get_arg(0); expr * v2_arg1 = a_new_nn2->get_arg(1); - TRACE("t_str_detail", tout << "checking whether " << mk_pp(new_nn1, m) << " and " << mk_pp(new_nn1, m) << " might overlap." << std::endl;); + TRACE("str", tout << "checking whether " << mk_pp(new_nn1, m) << " and " << mk_pp(new_nn1, m) << " might overlap." << std::endl;); check_and_init_cut_var(v1_arg0); check_and_init_cut_var(v1_arg1); @@ -2761,17 +2761,17 @@ bool theory_str::will_result_in_overlap(expr * lhs, expr * rhs) { // case 1: concat(x, y) = concat(m, n) //************************************************************* if (is_concat_eq_type1(new_nn1, new_nn2)) { - TRACE("t_str_detail", tout << "Type 1 check." << std::endl;); + TRACE("str", tout << "Type 1 check." << std::endl;); expr * x = to_app(new_nn1)->get_arg(0); expr * y = to_app(new_nn1)->get_arg(1); expr * m = to_app(new_nn2)->get_arg(0); expr * n = to_app(new_nn2)->get_arg(1); if (has_self_cut(m, y)) { - TRACE("t_str_detail", tout << "Possible overlap found" << std::endl; print_cut_var(m, tout); print_cut_var(y, tout);); + TRACE("str", tout << "Possible overlap found" << std::endl; print_cut_var(m, tout); print_cut_var(y, tout);); return true; } else if (has_self_cut(x, n)) { - TRACE("t_str_detail", tout << "Possible overlap found" << std::endl; print_cut_var(x, tout); print_cut_var(n, tout);); + TRACE("str", tout << "Possible overlap found" << std::endl; print_cut_var(x, tout); print_cut_var(n, tout);); return true; } else { return false; @@ -2799,7 +2799,7 @@ bool theory_str::will_result_in_overlap(expr * lhs, expr * rhs) { } if (has_self_cut(m, y)) { - TRACE("t_str_detail", tout << "Possible overlap found" << std::endl; print_cut_var(m, tout); print_cut_var(y, tout);); + TRACE("str", tout << "Possible overlap found" << std::endl; print_cut_var(m, tout); print_cut_var(y, tout);); return true; } else { return false; @@ -2826,7 +2826,7 @@ bool theory_str::will_result_in_overlap(expr * lhs, expr * rhs) { x = v1_arg0; } if (has_self_cut(x, n)) { - TRACE("t_str_detail", tout << "Possible overlap found" << std::endl; print_cut_var(x, tout); print_cut_var(n, tout);); + TRACE("str", tout << "Possible overlap found" << std::endl; print_cut_var(x, tout); print_cut_var(n, tout);); return true; } else { return false; @@ -2868,14 +2868,14 @@ bool theory_str::will_result_in_overlap(expr * lhs, expr * rhs) { m = v1_arg0; } if (has_self_cut(m, y)) { - TRACE("t_str_detail", tout << "Possible overlap found" << std::endl; print_cut_var(m, tout); print_cut_var(y, tout);); + TRACE("str", tout << "Possible overlap found" << std::endl; print_cut_var(m, tout); print_cut_var(y, tout);); return true; } else { return false; } } - TRACE("t_str_detail", tout << "warning: unrecognized concat case" << std::endl;); + TRACE("str", tout << "warning: unrecognized concat case" << std::endl;); return false; } @@ -2902,17 +2902,17 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { bool overlapAssumptionUsed = false; - TRACE("t_str_detail", tout << "process_concat_eq TYPE 1" << std::endl + TRACE("str", tout << "process_concat_eq TYPE 1" << std::endl << "concatAst1 = " << mk_ismt2_pp(concatAst1, mgr) << std::endl << "concatAst2 = " << mk_ismt2_pp(concatAst2, mgr) << std::endl; ); if (!u.str.is_concat(to_app(concatAst1))) { - TRACE("t_str_detail", tout << "concatAst1 is not a concat function" << std::endl;); + TRACE("str", tout << "concatAst1 is not a concat function" << std::endl;); return; } if (!u.str.is_concat(to_app(concatAst2))) { - TRACE("t_str_detail", tout << "concatAst2 is not a concat function" << std::endl;); + TRACE("str", tout << "concatAst2 is not a concat function" << std::endl;); return; } expr * x = to_app(concatAst1)->get_arg(0); @@ -2928,7 +2928,7 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { int splitType = -1; if (x_len_exists && m_len_exists) { - TRACE("t_str_int", tout << "length values found: x/m" << std::endl;); + TRACE("str", tout << "length values found: x/m" << std::endl;); if (x_len < m_len) { splitType = 0; } else if (x_len == m_len) { @@ -2939,7 +2939,7 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { } if (splitType == -1 && y_len_exists && n_len_exists) { - TRACE("t_str_int", tout << "length values found: y/n" << std::endl;); + TRACE("str", tout << "length values found: y/n" << std::endl;); if (y_len > n_len) { splitType = 0; } else if (y_len == n_len) { @@ -2949,7 +2949,7 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { } } - TRACE("t_str_detail", tout + TRACE("str", tout << "len(x) = " << (x_len_exists ? x_len.to_string() : "?") << std::endl << "len(y) = " << (y_len_exists ? y_len.to_string() : "?") << std::endl << "len(m) = " << (m_len_exists ? m_len.to_string() : "?") << std::endl @@ -2996,7 +2996,7 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { } } - TRACE("t_str_detail", tout << "entry 1 " << (entry1InScope ? "in scope" : "not in scope") << std::endl + TRACE("str", tout << "entry 1 " << (entry1InScope ? "in scope" : "not in scope") << std::endl << "entry 2 " << (entry2InScope ? "in scope" : "not in scope") << std::endl;); if (!entry1InScope && !entry2InScope) { @@ -3076,8 +3076,8 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { assert_implication(ax_l, tester); add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true); } else { - TRACE("t_str", tout << "AVOID LOOP: SKIPPED" << std::endl;); - TRACE("t_str_detail", {print_cut_var(m, tout); print_cut_var(y, tout);}); + TRACE("str", tout << "AVOID LOOP: SKIPPED" << std::endl;); + TRACE("str", {print_cut_var(m, tout); print_cut_var(y, tout);}); if (!overlapAssumptionUsed) { overlapAssumptionUsed = true; @@ -3139,8 +3139,8 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { assert_implication(ax_l, tester); add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true); } else { - TRACE("t_str", tout << "AVOID LOOP: SKIPPED" << std::endl;); - TRACE("t_str_detail", {print_cut_var(m, tout); print_cut_var(y, tout);}); + TRACE("str", tout << "AVOID LOOP: SKIPPED" << std::endl;); + TRACE("str", {print_cut_var(m, tout); print_cut_var(y, tout);}); if (!overlapAssumptionUsed) { overlapAssumptionUsed = true; @@ -3195,8 +3195,8 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { arrangement_disjunction.push_back(tester); add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true); } else { - TRACE("t_str", tout << "AVOID LOOP: SKIPPED" << std::endl;); - TRACE("t_str_detail", {print_cut_var(m, tout); print_cut_var(y, tout);}); + TRACE("str", tout << "AVOID LOOP: SKIPPED" << std::endl;); + TRACE("str", {print_cut_var(m, tout); print_cut_var(y, tout);}); if (!overlapAssumptionUsed) { overlapAssumptionUsed = true; @@ -3244,8 +3244,8 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { arrangement_disjunction.push_back(tester); add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true); } else { - TRACE("t_str", tout << "AVOID LOOP: SKIPPED" << std::endl;); - TRACE("t_str_detail", {print_cut_var(x, tout); print_cut_var(n, tout);}); + TRACE("str", tout << "AVOID LOOP: SKIPPED" << std::endl;); + TRACE("str", {print_cut_var(x, tout); print_cut_var(n, tout);}); if (!overlapAssumptionUsed) { overlapAssumptionUsed = true; @@ -3282,7 +3282,7 @@ void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { // assert mutual exclusion between each branch of the arrangement generate_mutual_exclusion(arrangement_disjunction); } else { - TRACE("t_str", tout << "STOP: no split option found for two EQ concats." << std::endl;); + TRACE("str", tout << "STOP: no split option found for two EQ concats." << std::endl;); } } // (splitType == -1) } @@ -3313,17 +3313,17 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { bool overlapAssumptionUsed = false; - TRACE("t_str_detail", tout << "process_concat_eq TYPE 2" << std::endl + TRACE("str", tout << "process_concat_eq TYPE 2" << std::endl << "concatAst1 = " << mk_ismt2_pp(concatAst1, mgr) << std::endl << "concatAst2 = " << mk_ismt2_pp(concatAst2, mgr) << std::endl; ); if (!u.str.is_concat(to_app(concatAst1))) { - TRACE("t_str_detail", tout << "concatAst1 is not a concat function" << std::endl;); + TRACE("str", tout << "concatAst1 is not a concat function" << std::endl;); return; } if (!u.str.is_concat(to_app(concatAst2))) { - TRACE("t_str_detail", tout << "concatAst2 is not a concat function" << std::endl;); + TRACE("str", tout << "concatAst2 is not a concat function" << std::endl;); return; } @@ -3400,7 +3400,7 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { } } - TRACE("t_str_detail", tout << "entry 1 " << (entry1InScope ? "in scope" : "not in scope") << std::endl + TRACE("str", tout << "entry 1 " << (entry1InScope ? "in scope" : "not in scope") << std::endl << "entry 2 " << (entry2InScope ? "in scope" : "not in scope") << std::endl;); @@ -3439,7 +3439,7 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { splitType = 2; } - TRACE("t_str_detail", tout << "Split type " << splitType << std::endl;); + TRACE("str", tout << "Split type " << splitType << std::endl;); // Provide fewer split options when length information is available. @@ -3491,8 +3491,8 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { assert_implication(ax_l, tester); add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true); } else { - TRACE("t_str", tout << "AVOID LOOP: SKIP" << std::endl;); - TRACE("t_str_detail", {print_cut_var(m, tout); print_cut_var(y, tout);}); + TRACE("str", tout << "AVOID LOOP: SKIP" << std::endl;); + TRACE("str", {print_cut_var(m, tout); print_cut_var(y, tout);}); if (!overlapAssumptionUsed) { overlapAssumptionUsed = true; @@ -3526,7 +3526,7 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { l_items.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_int(y_len))); lenDelta = str_len - y_len; } - TRACE("t_str", + TRACE("str", tout << "xLen? " << (x_len_exists ? "yes" : "no") << std::endl << "mLen? " << (m_len_exists ? "yes" : "no") << std::endl @@ -3562,7 +3562,7 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { } } else { // negate! It's impossible to split str with these lengths - TRACE("t_str", tout << "CONFLICT: Impossible to split str with these lengths." << std::endl;); + TRACE("str", tout << "CONFLICT: Impossible to split str with these lengths." << std::endl;); expr_ref ax_l(mk_and(l_items), mgr); assert_axiom(mgr.mk_not(ax_l)); } @@ -3597,8 +3597,8 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { arrangement_disjunction.push_back(tester); add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true); } else { - TRACE("t_str", tout << "AVOID LOOP: SKIPPED" << std::endl;); - TRACE("t_str_detail", {print_cut_var(m, tout); print_cut_var(y, tout);}); + TRACE("str", tout << "AVOID LOOP: SKIPPED" << std::endl;); + TRACE("str", {print_cut_var(m, tout); print_cut_var(y, tout);}); if (!overlapAssumptionUsed) { overlapAssumptionUsed = true; @@ -3645,7 +3645,7 @@ void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { } generate_mutual_exclusion(arrangement_disjunction); } else { - TRACE("t_str", tout << "STOP: Should not split two EQ concats." << std::endl;); + TRACE("str", tout << "STOP: Should not split two EQ concats." << std::endl;); } } // (splitType == -1) } @@ -3676,17 +3676,17 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { bool overlapAssumptionUsed = false; - TRACE("t_str_detail", tout << "process_concat_eq TYPE 3" << std::endl + TRACE("str", tout << "process_concat_eq TYPE 3" << std::endl << "concatAst1 = " << mk_ismt2_pp(concatAst1, mgr) << std::endl << "concatAst2 = " << mk_ismt2_pp(concatAst2, mgr) << std::endl; ); if (!u.str.is_concat(to_app(concatAst1))) { - TRACE("t_str_detail", tout << "concatAst1 is not a concat function" << std::endl;); + TRACE("str", tout << "concatAst1 is not a concat function" << std::endl;); return; } if (!u.str.is_concat(to_app(concatAst2))) { - TRACE("t_str_detail", tout << "concatAst2 is not a concat function" << std::endl;); + TRACE("str", tout << "concatAst2 is not a concat function" << std::endl;); return; } @@ -3756,7 +3756,7 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { } } - TRACE("t_str_detail", tout << "entry 1 " << (entry1InScope ? "in scope" : "not in scope") << std::endl + TRACE("str", tout << "entry 1 " << (entry1InScope ? "in scope" : "not in scope") << std::endl << "entry 2 " << (entry2InScope ? "in scope" : "not in scope") << std::endl;); @@ -3798,7 +3798,7 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { splitType = 2; } - TRACE("t_str_detail", tout << "Split type " << splitType << std::endl;); + TRACE("str", tout << "Split type " << splitType << std::endl;); // Provide fewer split options when length information is available. if (splitType == 0) { @@ -3836,7 +3836,7 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { } } else { // negate! It's impossible to split str with these lengths - TRACE("t_str", tout << "CONFLICT: Impossible to split str with these lengths." << std::endl;); + TRACE("str", tout << "CONFLICT: Impossible to split str with these lengths." << std::endl;); assert_axiom(mgr.mk_not(ax_l)); } } @@ -3899,8 +3899,8 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { assert_implication(ax_l, tester); add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true); } else { - TRACE("t_str", tout << "AVOID LOOP: SKIPPED" << std::endl;); - TRACE("t_str_detail", {print_cut_var(x, tout); print_cut_var(n, tout);}); + TRACE("str", tout << "AVOID LOOP: SKIPPED" << std::endl;); + TRACE("str", {print_cut_var(x, tout); print_cut_var(n, tout);}); if (!overlapAssumptionUsed) { overlapAssumptionUsed = true; @@ -3983,8 +3983,8 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { arrangement_disjunction.push_back(tester); add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true); } else { - TRACE("t_str", tout << "AVOID LOOP: SKIPPED." << std::endl;); - TRACE("t_str_detail", {print_cut_var(x, tout); print_cut_var(n, tout);}); + TRACE("str", tout << "AVOID LOOP: SKIPPED." << std::endl;); + TRACE("str", {print_cut_var(x, tout); print_cut_var(n, tout);}); if (!overlapAssumptionUsed) { overlapAssumptionUsed = true; @@ -4007,7 +4007,7 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { } generate_mutual_exclusion(arrangement_disjunction); } else { - TRACE("t_str", tout << "STOP: should not split two eq. concats" << std::endl;); + TRACE("str", tout << "STOP: should not split two eq. concats" << std::endl;); } } @@ -4033,17 +4033,17 @@ bool theory_str::is_concat_eq_type4(expr * concatAst1, expr * concatAst2) { void theory_str::process_concat_eq_type4(expr * concatAst1, expr * concatAst2) { ast_manager & mgr = get_manager(); context & ctx = get_context(); - TRACE("t_str_detail", tout << "process_concat_eq TYPE 4" << std::endl + TRACE("str", tout << "process_concat_eq TYPE 4" << std::endl << "concatAst1 = " << mk_ismt2_pp(concatAst1, mgr) << std::endl << "concatAst2 = " << mk_ismt2_pp(concatAst2, mgr) << std::endl; ); if (!u.str.is_concat(to_app(concatAst1))) { - TRACE("t_str_detail", tout << "concatAst1 is not a concat function" << std::endl;); + TRACE("str", tout << "concatAst1 is not a concat function" << std::endl;); return; } if (!u.str.is_concat(to_app(concatAst2))) { - TRACE("t_str_detail", tout << "concatAst2 is not a concat function" << std::endl;); + TRACE("str", tout << "concatAst2 is not a concat function" << std::endl;); return; } @@ -4066,7 +4066,7 @@ void theory_str::process_concat_eq_type4(expr * concatAst1, expr * concatAst2) { int commonLen = (str1Len > str2Len) ? str2Len : str1Len; if (str1Value.extract(0, commonLen) != str2Value.extract(0, commonLen)) { - TRACE("t_str_detail", tout << "Conflict: " << mk_ismt2_pp(concatAst1, mgr) + TRACE("str", tout << "Conflict: " << mk_ismt2_pp(concatAst1, mgr) << " has no common prefix with " << mk_ismt2_pp(concatAst2, mgr) << std::endl;); expr_ref toNegate(mgr.mk_not(ctx.mk_eq_atom(concatAst1, concatAst2)), mgr); assert_axiom(toNegate); @@ -4134,17 +4134,17 @@ bool theory_str::is_concat_eq_type5(expr * concatAst1, expr * concatAst2) { void theory_str::process_concat_eq_type5(expr * concatAst1, expr * concatAst2) { ast_manager & mgr = get_manager(); context & ctx = get_context(); - TRACE("t_str_detail", tout << "process_concat_eq TYPE 5" << std::endl + TRACE("str", tout << "process_concat_eq TYPE 5" << std::endl << "concatAst1 = " << mk_ismt2_pp(concatAst1, mgr) << std::endl << "concatAst2 = " << mk_ismt2_pp(concatAst2, mgr) << std::endl; ); if (!u.str.is_concat(to_app(concatAst1))) { - TRACE("t_str_detail", tout << "concatAst1 is not a concat function" << std::endl;); + TRACE("str", tout << "concatAst1 is not a concat function" << std::endl;); return; } if (!u.str.is_concat(to_app(concatAst2))) { - TRACE("t_str_detail", tout << "concatAst2 is not a concat function" << std::endl;); + TRACE("str", tout << "concatAst2 is not a concat function" << std::endl;); return; } @@ -4167,7 +4167,7 @@ void theory_str::process_concat_eq_type5(expr * concatAst1, expr * concatAst2) { int cLen = (str1Len > str2Len) ? str2Len : str1Len; if (str1Value.extract(str1Len - cLen, cLen) != str2Value.extract(str2Len - cLen, cLen)) { - TRACE("t_str_detail", tout << "Conflict: " << mk_ismt2_pp(concatAst1, mgr) + TRACE("str", tout << "Conflict: " << mk_ismt2_pp(concatAst1, mgr) << " has no common suffix with " << mk_ismt2_pp(concatAst2, mgr) << std::endl;); expr_ref toNegate(mgr.mk_not(ctx.mk_eq_atom(concatAst1, concatAst2)), mgr); assert_axiom(toNegate); @@ -4235,17 +4235,17 @@ bool theory_str::is_concat_eq_type6(expr * concatAst1, expr * concatAst2) { void theory_str::process_concat_eq_type6(expr * concatAst1, expr * concatAst2) { ast_manager & mgr = get_manager(); context & ctx = get_context(); - TRACE("t_str_detail", tout << "process_concat_eq TYPE 6" << std::endl + TRACE("str", tout << "process_concat_eq TYPE 6" << std::endl << "concatAst1 = " << mk_ismt2_pp(concatAst1, mgr) << std::endl << "concatAst2 = " << mk_ismt2_pp(concatAst2, mgr) << std::endl; ); if (!u.str.is_concat(to_app(concatAst1))) { - TRACE("t_str_detail", tout << "concatAst1 is not a concat function" << std::endl;); + TRACE("str", tout << "concatAst1 is not a concat function" << std::endl;); return; } if (!u.str.is_concat(to_app(concatAst2))) { - TRACE("t_str_detail", tout << "concatAst2 is not a concat function" << std::endl;); + TRACE("str", tout << "concatAst2 is not a concat function" << std::endl;); return; } @@ -4334,7 +4334,7 @@ void theory_str::process_concat_eq_type6(expr * concatAst1, expr * concatAst2) { } } - TRACE("t_str_detail", tout << "entry 1 " << (entry1InScope ? "in scope" : "not in scope") << std::endl + TRACE("str", tout << "entry 1 " << (entry1InScope ? "in scope" : "not in scope") << std::endl << "entry 2 " << (entry2InScope ? "in scope" : "not in scope") << std::endl;); if (!entry1InScope && !entry2InScope) { @@ -4389,8 +4389,8 @@ void theory_str::process_concat_eq_type6(expr * concatAst1, expr * concatAst2) { arrangement_disjunction.push_back(tester); add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true); } else { - TRACE("t_str", tout << "AVOID LOOP: SKIPPED." << std::endl;); - TRACE("t_str", print_cut_var(m, tout); print_cut_var(y, tout);); + TRACE("str", tout << "AVOID LOOP: SKIPPED." << std::endl;); + TRACE("str", print_cut_var(m, tout); print_cut_var(y, tout);); // only add the overlap assumption one time if (!overlapAssumptionUsed) { @@ -4465,7 +4465,7 @@ void theory_str::process_unroll_eq_const_str(expr * unrollFunc, expr * constStr) zstring strValue; u.str.is_string(constStr, strValue); - TRACE("t_str_detail", tout << "unrollFunc: " << mk_pp(unrollFunc, m) << std::endl + TRACE("str", tout << "unrollFunc: " << mk_pp(unrollFunc, m) << std::endl << "constStr: " << mk_pp(constStr, m) << std::endl;); if (strValue == "") { @@ -4482,7 +4482,7 @@ void theory_str::process_concat_eq_unroll(expr * concat, expr * unroll) { context & ctx = get_context(); ast_manager & mgr = get_manager(); - TRACE("t_str_detail", tout << "concat = " << mk_pp(concat, mgr) << ", unroll = " << mk_pp(unroll, mgr) << std::endl;); + TRACE("str", tout << "concat = " << mk_pp(concat, mgr) << ", unroll = " << mk_pp(unroll, mgr) << std::endl;); std::pair key = std::make_pair(concat, unroll); expr_ref toAssert(mgr); @@ -4613,7 +4613,7 @@ static theory_mi_arith* get_th_arith(context& ctx, theory_id afid, expr* e) { bool theory_str::get_value(expr* e, rational& val) const { if (opt_DisableIntegerTheoryIntegration) { - TRACE("t_str_detail", tout << "WARNING: integer theory integration disabled" << std::endl;); + TRACE("str", tout << "WARNING: integer theory integration disabled" << std::endl;); return false; } @@ -4623,28 +4623,28 @@ bool theory_str::get_value(expr* e, rational& val) const { if (!tha) { return false; } - TRACE("t_str_int", tout << "checking eqc of " << mk_pp(e, m) << " for arithmetic value" << std::endl;); + TRACE("str", tout << "checking eqc of " << mk_pp(e, m) << " for arithmetic value" << std::endl;); expr_ref _val(m); enode * en_e = ctx.get_enode(e); enode * it = en_e; do { if (m_autil.is_numeral(it->get_owner(), val) && val.is_int()) { // found an arithmetic term - TRACE("t_str_int", tout << mk_pp(it->get_owner(), m) << " is an integer ( ~= " << val << " )" + TRACE("str", tout << mk_pp(it->get_owner(), m) << " is an integer ( ~= " << val << " )" << std::endl;); return true; } else { - TRACE("t_str_int", tout << mk_pp(it->get_owner(), m) << " not a numeral" << std::endl;); + TRACE("str", tout << mk_pp(it->get_owner(), m) << " not a numeral" << std::endl;); } it = it->get_next(); } while (it != en_e); - TRACE("t_str_int", tout << "no arithmetic values found in eqc" << std::endl;); + TRACE("str", tout << "no arithmetic values found in eqc" << std::endl;); return false; } bool theory_str::lower_bound(expr* _e, rational& lo) { if (opt_DisableIntegerTheoryIntegration) { - TRACE("t_str_detail", tout << "WARNING: integer theory integration disabled" << std::endl;); + TRACE("str", tout << "WARNING: integer theory integration disabled" << std::endl;); return false; } @@ -4658,7 +4658,7 @@ bool theory_str::lower_bound(expr* _e, rational& lo) { bool theory_str::upper_bound(expr* _e, rational& hi) { if (opt_DisableIntegerTheoryIntegration) { - TRACE("t_str_detail", tout << "WARNING: integer theory integration disabled" << std::endl;); + TRACE("str", tout << "WARNING: integer theory integration disabled" << std::endl;); return false; } @@ -4672,7 +4672,7 @@ bool theory_str::upper_bound(expr* _e, rational& hi) { bool theory_str::get_len_value(expr* e, rational& val) { if (opt_DisableIntegerTheoryIntegration) { - TRACE("t_str_detail", tout << "WARNING: integer theory integration disabled" << std::endl;); + TRACE("str", tout << "WARNING: integer theory integration disabled" << std::endl;); return false; } @@ -4681,16 +4681,16 @@ bool theory_str::get_len_value(expr* e, rational& val) { theory* th = ctx.get_theory(m_autil.get_family_id()); if (!th) { - TRACE("t_str_int", tout << "oops, can't get m_autil's theory" << std::endl;); + TRACE("str", tout << "oops, can't get m_autil's theory" << std::endl;); return false; } theory_mi_arith* tha = dynamic_cast(th); if (!tha) { - TRACE("t_str_int", tout << "oops, can't cast to theory_mi_arith" << std::endl;); + TRACE("str", tout << "oops, can't cast to theory_mi_arith" << std::endl;); return false; } - TRACE("t_str_int", tout << "checking len value of " << mk_ismt2_pp(e, m) << std::endl;); + TRACE("str", tout << "checking len value of " << mk_ismt2_pp(e, m) << std::endl;); rational val1; expr_ref len(m), len_val(m); @@ -4717,7 +4717,7 @@ bool theory_str::get_len_value(expr* e, rational& val) { len = mk_strlen(c); // debugging - TRACE("t_str_int", { + TRACE("str", { tout << mk_pp(len, m) << ":" << std::endl << (ctx.is_relevant(len.get()) ? "relevant" : "not relevant") << std::endl << (ctx.e_internalized(len) ? "internalized" : "not internalized") << std::endl @@ -4742,16 +4742,16 @@ bool theory_str::get_len_value(expr* e, rational& val) { if (ctx.e_internalized(len) && get_value(len, val1)) { val += val1; - TRACE("t_str_int", tout << "integer theory: subexpression " << mk_ismt2_pp(len, m) << " has length " << val1 << std::endl;); + TRACE("str", tout << "integer theory: subexpression " << mk_ismt2_pp(len, m) << " has length " << val1 << std::endl;); } else { - TRACE("t_str_int", tout << "integer theory: subexpression " << mk_ismt2_pp(len, m) << " has no length assignment; bailing out" << std::endl;); + TRACE("str", tout << "integer theory: subexpression " << mk_ismt2_pp(len, m) << " has no length assignment; bailing out" << std::endl;); return false; } } } - TRACE("t_str_int", tout << "length of " << mk_ismt2_pp(e, m) << " is " << val << std::endl;); + TRACE("str", tout << "length of " << mk_ismt2_pp(e, m) << " is " << val << std::endl;); return val.is_int(); } @@ -4769,11 +4769,11 @@ bool theory_str::in_same_eqc(expr * n1, expr * n2) { // that we've set this up properly for the context if (!ctx.e_internalized(n1)) { - TRACE("t_str_detail", tout << "WARNING: expression " << mk_ismt2_pp(n1, m) << " was not internalized" << std::endl;); + TRACE("str", tout << "WARNING: expression " << mk_ismt2_pp(n1, m) << " was not internalized" << std::endl;); ctx.internalize(n1, false); } if (!ctx.e_internalized(n2)) { - TRACE("t_str_detail", tout << "WARNING: expression " << mk_ismt2_pp(n2, m) << " was not internalized" << std::endl;); + TRACE("str", tout << "WARNING: expression " << mk_ismt2_pp(n2, m) << " was not internalized" << std::endl;); ctx.internalize(n2, false); } @@ -4824,7 +4824,7 @@ void theory_str::check_contain_by_eqc_val(expr * varNode, expr * constNode) { context & ctx = get_context(); ast_manager & m = get_manager(); - TRACE("t_str_detail", tout << "varNode = " << mk_pp(varNode, m) << ", constNode = " << mk_pp(constNode, m) << std::endl;); + TRACE("str", tout << "varNode = " << mk_pp(varNode, m) << ", constNode = " << mk_pp(constNode, m) << std::endl;); expr_ref_vector litems(m); @@ -4836,7 +4836,7 @@ void theory_str::check_contain_by_eqc_val(expr * varNode, expr * constNode) { expr * boolVar; if (!contain_pair_bool_map.find(strAst, substrAst, boolVar)) { - TRACE("t_str_detail", tout << "warning: no entry for boolVar in contain_pair_bool_map" << std::endl;); + TRACE("str", tout << "warning: no entry for boolVar in contain_pair_bool_map" << std::endl;); } // boolVar is actually a Contains term app * containsApp = to_app(boolVar); @@ -4844,13 +4844,13 @@ void theory_str::check_contain_by_eqc_val(expr * varNode, expr * constNode) { // we only want to inspect the Contains terms where either of strAst or substrAst // are equal to varNode. - TRACE("t_str_detail", tout << "considering Contains with strAst = " << mk_pp(strAst, m) << ", substrAst = " << mk_pp(substrAst, m) << "..." << std::endl;); + TRACE("t_str_detail", tout << "considering Contains with strAst = "str", substrAst = " << mk_pp(substrAst, m) << "..." << std::endl;); if (varNode != strAst && varNode != substrAst) { - TRACE("t_str_detail", tout << "varNode not equal to strAst or substrAst, skip" << std::endl;); + TRACE("str", tout << "varNode not equal to strAst or substrAst, skip" << std::endl;); continue; } - TRACE("t_str_detail", tout << "varNode matched one of strAst or substrAst. Continuing" << std::endl;); + TRACE("str", tout << "varNode matched one of strAst or substrAst. Continuing" << std::endl;); // varEqcNode is str if (strAst == varNode) { @@ -4873,7 +4873,7 @@ void theory_str::check_contain_by_eqc_val(expr * varNode, expr * constNode) { zstring subStrConst; u.str.is_string(substrValue, subStrConst); - TRACE("t_str_detail", tout << "strConst = " << strConst << ", subStrConst = " << subStrConst << "\n";); + TRACE("t_str_detail", tout << "strConst = "str", subStrConst = " << subStrConst << "\n";); if (strConst.contains(subStrConst)) { //implyR = ctx.mk_eq(ctx, boolVar, Z3_mk_true(ctx)); @@ -4914,7 +4914,7 @@ void theory_str::check_contain_by_eqc_val(expr * varNode, expr * constNode) { } } if (counterEgFound) { - TRACE("t_str_detail", tout << "Inconsistency found!" << std::endl;); + TRACE("str", tout << "Inconsistency found!" << std::endl;); break; } } @@ -4975,7 +4975,7 @@ void theory_str::check_contain_by_substr(expr * varNode, expr_ref_vector & willE expr * boolVar; if (!contain_pair_bool_map.find(strAst, substrAst, boolVar)) { - TRACE("t_str_detail", tout << "warning: no entry for boolVar in contain_pair_bool_map" << std::endl;); + TRACE("str", tout << "warning: no entry for boolVar in contain_pair_bool_map" << std::endl;); } // boolVar is actually a Contains term app * containsApp = to_app(boolVar); @@ -4983,19 +4983,19 @@ void theory_str::check_contain_by_substr(expr * varNode, expr_ref_vector & willE // we only want to inspect the Contains terms where either of strAst or substrAst // are equal to varNode. - TRACE("t_str_detail", tout << "considering Contains with strAst = " << mk_pp(strAst, m) << ", substrAst = " << mk_pp(substrAst, m) << "..." << std::endl;); + TRACE("t_str_detail", tout << "considering Contains with strAst = "str", substrAst = " << mk_pp(substrAst, m) << "..." << std::endl;); if (varNode != strAst && varNode != substrAst) { - TRACE("t_str_detail", tout << "varNode not equal to strAst or substrAst, skip" << std::endl;); + TRACE("str", tout << "varNode not equal to strAst or substrAst, skip" << std::endl;); continue; } - TRACE("t_str_detail", tout << "varNode matched one of strAst or substrAst. Continuing" << std::endl;); + TRACE("str", tout << "varNode matched one of strAst or substrAst. Continuing" << std::endl;); if (substrAst == varNode) { bool strAstHasVal = false; expr * strValue = get_eqc_value(strAst, strAstHasVal); if (strAstHasVal) { - TRACE("t_str_detail", tout << mk_pp(strAst, m) << " has constant eqc value " << mk_pp(strValue, m) << std::endl;); + TRACE("str", tout << mk_pp(strAst, m) << " has constant eqc value " << mk_pp(strValue, m) << std::endl;); if (strValue != strAst) { litems.push_back(ctx.mk_eq_atom(strAst, strValue)); } @@ -5014,7 +5014,7 @@ void theory_str::check_contain_by_substr(expr * varNode, expr_ref_vector & willE zstring pieceStr; u.str.is_string(*cstItor, pieceStr); if (!strConst.contains(pieceStr)) { - TRACE("t_str_detail", tout << "Inconsistency found!" << std::endl;); + TRACE("str", tout << "Inconsistency found!" << std::endl;); counterEgFound = true; if (aConcat != substrAst) { litems.push_back(ctx.mk_eq_atom(substrAst, aConcat)); @@ -5082,7 +5082,7 @@ void theory_str::check_contain_by_eq_nodes(expr * n1, expr * n2) { expr * subValue1 = get_eqc_value(subAst1, subAst1HasValue); expr * subValue2 = get_eqc_value(subAst2, subAst2HasValue); - TRACE("t_str_detail", + TRACE("str", tout << "(Contains " << mk_pp(n1, m) << " " << mk_pp(subAst1, m) << ")" << std::endl; tout << "(Contains " << mk_pp(n2, m) << " " << mk_pp(subAst2, m) << ")" << std::endl; if (subAst1 != subValue1) { @@ -5182,7 +5182,7 @@ void theory_str::check_contain_by_eq_nodes(expr * n1, expr * n2) { } std::pair tryKey1 = std::make_pair(eqSubVar1, eqSubVar2); if (contain_pair_bool_map.contains(tryKey1)) { - TRACE("t_str_detail", tout << "(Contains " << mk_pp(eqSubVar1, m) << " " << mk_pp(eqSubVar2, m) << ")" << std::endl;); + TRACE("str", tout << "(Contains " << mk_pp(eqSubVar1, m) << " " << mk_pp(eqSubVar2, m) << ")" << std::endl;); litems3.push_back(contain_pair_bool_map[tryKey1]); expr_ref implR(rewrite_implication(contain_pair_bool_map[key1], contain_pair_bool_map[key2]), m); assert_implication(mk_and(litems3), implR); @@ -5207,7 +5207,7 @@ void theory_str::check_contain_by_eq_nodes(expr * n1, expr * n2) { } std::pair tryKey2 = std::make_pair(eqSubVar2, eqSubVar1); if (contain_pair_bool_map.contains(tryKey2)) { - TRACE("t_str_detail", tout << "(Contains " << mk_pp(eqSubVar2, m) << " " << mk_pp(eqSubVar1, m) << ")" << std::endl;); + TRACE("str", tout << "(Contains " << mk_pp(eqSubVar2, m) << " " << mk_pp(eqSubVar1, m) << ")" << std::endl;); litems4.push_back(contain_pair_bool_map[tryKey2]); expr_ref implR(rewrite_implication(contain_pair_bool_map[key2], contain_pair_bool_map[key1]), m); assert_implication(mk_and(litems4), implR); @@ -5229,7 +5229,7 @@ void theory_str::check_contain_by_eq_nodes(expr * n1, expr * n2) { expr * strVal1 = get_eqc_value(str1, str1HasValue); expr * strVal2 = get_eqc_value(str2, str2HasValue); - TRACE("t_str_detail", + TRACE("str", tout << "(Contains " << mk_pp(str1, m) << " " << mk_pp(n1, m) << ")" << std::endl; tout << "(Contains " << mk_pp(str2, m) << " " << mk_pp(n2, m) << ")" << std::endl; if (str1 != strVal1) { @@ -5328,7 +5328,7 @@ void theory_str::check_contain_by_eq_nodes(expr * n1, expr * n2) { } std::pair tryKey1 = std::make_pair(eqStrVar1, eqStrVar2); if (contain_pair_bool_map.contains(tryKey1)) { - TRACE("t_str_detail", tout << "(Contains " << mk_pp(eqStrVar1, m) << " " << mk_pp(eqStrVar2, m) << ")" << std::endl;); + TRACE("str", tout << "(Contains " << mk_pp(eqStrVar1, m) << " " << mk_pp(eqStrVar2, m) << ")" << std::endl;); litems3.push_back(contain_pair_bool_map[tryKey1]); // ------------ @@ -5356,7 +5356,7 @@ void theory_str::check_contain_by_eq_nodes(expr * n1, expr * n2) { std::pair tryKey2 = std::make_pair(eqStrVar2, eqStrVar1); if (contain_pair_bool_map.contains(tryKey2)) { - TRACE("t_str_detail", tout << "(Contains " << mk_pp(eqStrVar2, m) << " " << mk_pp(eqStrVar1, m) << ")" << std::endl;); + TRACE("str", tout << "(Contains " << mk_pp(eqStrVar2, m) << " " << mk_pp(eqStrVar1, m) << ")" << std::endl;); litems4.push_back(contain_pair_bool_map[tryKey2]); // ------------ // key1.first = key2.first /\ containPairBoolMap[] @@ -5388,14 +5388,14 @@ void theory_str::check_contain_in_new_eq(expr * n1, expr * n2) { context & ctx = get_context(); ast_manager & m = get_manager(); - TRACE("t_str_detail", tout << "consistency check for contains wrt. " << mk_pp(n1, m) << " and " << mk_pp(n2, m) << std::endl;); + TRACE("str", tout << "consistency check for contains wrt. " << mk_pp(n1, m) << " and " << mk_pp(n2, m) << std::endl;); expr_ref_vector willEqClass(m); expr * constStrAst_1 = collect_eq_nodes(n1, willEqClass); expr * constStrAst_2 = collect_eq_nodes(n2, willEqClass); expr * constStrAst = (constStrAst_1 != NULL) ? constStrAst_1 : constStrAst_2; - TRACE("t_str_detail", tout << "eqc of n1 is {"; + TRACE("str", tout << "eqc of n1 is {"; for (expr_ref_vector::iterator it = willEqClass.begin(); it != willEqClass.end(); ++it) { expr * el = *it; tout << " " << mk_pp(el, m); @@ -5589,11 +5589,11 @@ void theory_str::get_grounded_concats(expr* node, std::map & varAl void theory_str::print_grounded_concat(expr * node, std::map, std::set > > & groundedMap) { ast_manager & m = get_manager(); - TRACE("t_str_detail", tout << mk_pp(node, m) << std::endl;); + TRACE("str", tout << mk_pp(node, m) << std::endl;); if (groundedMap.find(node) != groundedMap.end()) { std::map, std::set >::iterator itor = groundedMap[node].begin(); for (; itor != groundedMap[node].end(); ++itor) { - TRACE("t_str_detail", + TRACE("str", tout << "\t[grounded] "; std::vector::const_iterator vIt = itor->first.begin(); for (; vIt != itor->first.end(); ++vIt) { @@ -5609,7 +5609,7 @@ void theory_str::print_grounded_concat(expr * node, std::map string constant (len = " << strLen << ")" << std::endl;); + TRACE("str", tout << "inconsistent length: concat (len = " << sumLen << ") <==> string constant (len = " << strLen << ")" << std::endl;); assert_axiom(toAssert); return false; } @@ -5948,7 +5948,7 @@ bool theory_str::check_length_const_string(expr * n1, expr * constStr) { rational oLen; bool oLen_exists = get_len_value(n1, oLen); if (oLen_exists && oLen != strLen) { - TRACE("t_str_detail", tout << "inconsistent length: var (len = " << oLen << ") <==> string constant (len = " << strLen << ")" << std::endl;); + TRACE("str", tout << "inconsistent length: var (len = " << oLen << ") <==> string constant (len = " << strLen << ")" << std::endl;); expr_ref l(ctx.mk_eq_atom(n1, constStr), mgr); expr_ref r(ctx.mk_eq_atom(mk_strlen(n1), mk_strlen(constStr)), mgr); assert_implication(l, r); @@ -6027,7 +6027,7 @@ bool theory_str::check_length_concat_concat(expr * n1, expr * n2) { } if (conflict) { - TRACE("t_str_detail", tout << "inconsistent length detected in concat <==> concat" << std::endl;); + TRACE("str", tout << "inconsistent length detected in concat <==> concat" << std::endl;); expr_ref toAssert(mgr.mk_not(mk_and(items)), mgr); assert_axiom(toAssert); return false; @@ -6058,7 +6058,7 @@ bool theory_str::check_length_concat_var(expr * concat, expr * var) { } sumLen += argLen; if (sumLen > varLen) { - TRACE("t_str_detail", tout << "inconsistent length detected in concat <==> var" << std::endl;); + TRACE("str", tout << "inconsistent length detected in concat <==> var" << std::endl;); items.push_back(ctx.mk_eq_atom(mk_strlen(var), mk_int(varLen))); items.push_back(ctx.mk_eq_atom(concat, var)); expr_ref toAssert(mgr.mk_not(mk_and(items)), mgr); @@ -6080,7 +6080,7 @@ bool theory_str::check_length_var_var(expr * var1, expr * var2) { bool var2Len_exists = get_len_value(var2, var2Len); if (var1Len_exists && var2Len_exists && var1Len != var2Len) { - TRACE("t_str_detail", tout << "inconsistent length detected in var <==> var" << std::endl;); + TRACE("str", tout << "inconsistent length detected in var <==> var" << std::endl;); expr_ref_vector items(mgr); items.push_back(ctx.mk_eq_atom(mk_strlen(var1), mk_int(var1Len))); items.push_back(ctx.mk_eq_atom(mk_strlen(var2), mk_int(var2Len))); @@ -6164,7 +6164,7 @@ void nfa::convert_re(expr * e, unsigned & start, unsigned & end, seq_util & u) { expr * arg_str = a->get_arg(0); zstring str; if (u.str.is_string(arg_str, str)) { - TRACE("t_str_rw", tout << "build NFA for '" << str << "'" << "\n";); + TRACE("str", tout << "build NFA for '" << str << "'" << "\n";); /* * For an n-character string, we make (n-1) intermediate states, @@ -6176,14 +6176,14 @@ void nfa::convert_re(expr * e, unsigned & start, unsigned & end, seq_util & u) { for (int i = 0; i <= ((int)str.length()) - 2; ++i) { unsigned i_state = next_id(); make_transition(last, str[i], i_state); - TRACE("t_str_rw", tout << "string transition " << last << "--" << str[i] << "--> " << i_state << "\n";); + TRACE("str", tout << "string transition " << last << "--" << str[i] << "--> " << i_state << "\n";); last = i_state; } make_transition(last, str[(str.length() - 1)], end); - TRACE("t_str_rw", tout << "string transition " << last << "--" << str[(str.length() - 1)] << "--> " << end << "\n";); - TRACE("t_str_rw", tout << "string NFA: start = " << start << ", end = " << end << std::endl;); + TRACE("str", tout << "string transition " << last << "--" << str[(str.length() - 1)] << "--> " << end << "\n";); + TRACE("t_str_rw", tout << "str", end = " << end << std::endl;); } else { - TRACE("t_str_rw", tout << "invalid string constant in Str2Reg" << std::endl;); + TRACE("str", tout << "invalid string constant in Str2Reg" << std::endl;); m_valid = false; return; } @@ -6199,7 +6199,7 @@ void nfa::convert_re(expr * e, unsigned & start, unsigned & end, seq_util & u) { make_epsilon_move(start, start1); make_epsilon_move(end1, start2); make_epsilon_move(end2, end); - TRACE("t_str_rw", tout << "concat NFA: start = " << start << ", end = " << end << std::endl;); + TRACE("str", tout << "concat NFA: start = " << start << ", end = " << end << std::endl;); } else if (u.re.is_union(e)) { app * a = to_app(e); expr * re1 = a->get_arg(0); @@ -6215,7 +6215,7 @@ void nfa::convert_re(expr * e, unsigned & start, unsigned & end, seq_util & u) { make_epsilon_move(start, start2); make_epsilon_move(end1, end); make_epsilon_move(end2, end); - TRACE("t_str_rw", tout << "union NFA: start = " << start << ", end = " << end << std::endl;); + TRACE("str", tout << "union NFA: start = " << start << ", end = " << end << std::endl;); } else if (u.re.is_star(e)) { app * a = to_app(e); expr * subex = a->get_arg(0); @@ -6227,9 +6227,9 @@ void nfa::convert_re(expr * e, unsigned & start, unsigned & end, seq_util & u) { make_epsilon_move(start, end); make_epsilon_move(end_subex, start_subex); make_epsilon_move(end_subex, end); - TRACE("t_str_rw", tout << "star NFA: start = " << start << ", end = " << end << std::endl;); + TRACE("str", tout << "star NFA: start = " << start << ", end = " << end << std::endl;); } else { - TRACE("t_str_rw", tout << "invalid regular expression" << std::endl;); + TRACE("str", tout << "invalid regular expression" << std::endl;); m_valid = false; return; } @@ -6327,17 +6327,17 @@ void theory_str::check_regex_in(expr * nn1, expr * nn2) { // TODO figure out regex NFA stuff if (regex_nfa_cache.find(regexTerm) == regex_nfa_cache.end()) { - TRACE("t_str_detail", tout << "regex_nfa_cache: cache miss" << std::endl;); + TRACE("str", tout << "regex_nfa_cache: cache miss" << std::endl;); regex_nfa_cache[regexTerm] = nfa(u, regexTerm); } else { - TRACE("t_str_detail", tout << "regex_nfa_cache: cache hit" << std::endl;); + TRACE("str", tout << "regex_nfa_cache: cache hit" << std::endl;); } nfa regexNFA = regex_nfa_cache[regexTerm]; ENSURE(regexNFA.is_valid()); bool matchRes = regexNFA.matches(constStrValue); - TRACE("t_str_detail", tout << mk_pp(*itor, m) << " in " << regStr << " : " << (matchRes ? "yes" : "no") << std::endl;); + TRACE("str", tout << mk_pp(*itor, m) << " in " << regStr << " : " << (matchRes ? "yes" : "no") << std::endl;); expr_ref implyL(ctx.mk_eq_atom(*itor, constStr), m); if (matchRes) { @@ -6362,7 +6362,7 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { ast_manager & m = get_manager(); context & ctx = get_context(); - TRACE("t_str_detail", tout << mk_ismt2_pp(concat, m) << " == " << mk_ismt2_pp(str, m) << std::endl;); + TRACE("str", tout << mk_ismt2_pp(concat, m) << " == " << mk_ismt2_pp(str, m) << std::endl;); zstring const_str; if (u.str.is_concat(to_app(concat)) && u.str.is_string(to_app(str), const_str)) { @@ -6372,7 +6372,7 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { expr * a2 = a_concat->get_arg(1); if (const_str.empty()) { - TRACE("t_str", tout << "quick path: concat == \"\"" << std::endl;); + TRACE("str", tout << "quick path: concat == \"\"" << std::endl;); // assert the following axiom: // ( (Concat a1 a2) == "" ) -> ( (a1 == "") AND (a2 == "") ) @@ -6391,7 +6391,7 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { expr * arg2 = get_eqc_value(a2, arg2_has_eqc_value); expr_ref newConcat(m); if (arg1 != a1 || arg2 != a2) { - TRACE("t_str", tout << "resolved concat argument(s) to eqc string constants" << std::endl;); + TRACE("str", tout << "resolved concat argument(s) to eqc string constants" << std::endl;); int iPos = 0; expr_ref_vector item1(m); if (a1 != arg1) { @@ -6419,7 +6419,7 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { } if (arg1_has_eqc_value && arg2_has_eqc_value) { // Case 1: Concat(const, const) == const - TRACE("t_str", tout << "Case 1: Concat(const, const) == const" << std::endl;); + TRACE("str", tout << "Case 1: Concat(const, const) == const" << std::endl;); zstring arg1_str, arg2_str; u.str.is_string(arg1, arg1_str); u.str.is_string(arg2, arg2_str); @@ -6427,7 +6427,7 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { zstring result_str = arg1_str + arg2_str; if (result_str != const_str) { // Inconsistency - TRACE("t_str", tout << "inconsistency detected: \"" + TRACE("str", tout << "inconsistency detected: \"" << arg1_str << "\" + \"" << arg2_str << "\" != \"" << const_str << "\"" << "\n";); expr_ref equality(ctx.mk_eq_atom(concat, str), m); @@ -6437,14 +6437,14 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { } } else if (!arg1_has_eqc_value && arg2_has_eqc_value) { // Case 2: Concat(var, const) == const - TRACE("t_str", tout << "Case 2: Concat(var, const) == const" << std::endl;); + TRACE("str", tout << "Case 2: Concat(var, const) == const" << std::endl;); zstring arg2_str; u.str.is_string(arg2, arg2_str); unsigned int resultStrLen = const_str.length(); unsigned int arg2StrLen = arg2_str.length(); if (resultStrLen < arg2StrLen) { // Inconsistency - TRACE("t_str", tout << "inconsistency detected: \"" + TRACE("str", tout << "inconsistency detected: \"" << arg2_str << "\" is longer than \"" << const_str << "\"," << " so cannot be concatenated with anything to form it" << "\n";); @@ -6458,7 +6458,7 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { zstring secondPart = const_str.extract(varStrLen, arg2StrLen); if (arg2_str != secondPart) { // Inconsistency - TRACE("t_str", tout << "inconsistency detected: " + TRACE("str", tout << "inconsistency detected: " << "suffix of concatenation result expected \"" << secondPart << "\", " << "actually \"" << arg2_str << "\"" << "\n";); @@ -6476,14 +6476,14 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { } } else if (arg1_has_eqc_value && !arg2_has_eqc_value) { // Case 3: Concat(const, var) == const - TRACE("t_str", tout << "Case 3: Concat(const, var) == const" << std::endl;); + TRACE("str", tout << "Case 3: Concat(const, var) == const" << std::endl;); zstring arg1_str; u.str.is_string(arg1, arg1_str); unsigned int resultStrLen = const_str.length(); unsigned int arg1StrLen = arg1_str.length(); if (resultStrLen < arg1StrLen) { // Inconsistency - TRACE("t_str", tout << "inconsistency detected: \"" + TRACE("str", tout << "inconsistency detected: \"" << arg1_str << "\" is longer than \"" << const_str << "\"," << " so cannot be concatenated with anything to form it" << "\n";); @@ -6497,7 +6497,7 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { zstring secondPart = const_str.extract(arg1StrLen, varStrLen); if (arg1_str != firstPart) { // Inconsistency - TRACE("t_str", tout << "inconsistency detected: " + TRACE("str", tout << "inconsistency detected: " << "prefix of concatenation result expected \"" << secondPart << "\", " << "actually \"" << arg1_str << "\"" << "\n";); @@ -6515,7 +6515,7 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { } } else { // Case 4: Concat(var, var) == const - TRACE("t_str", tout << "Case 4: Concat(var, var) == const" << std::endl;); + TRACE("str", tout << "Case 4: Concat(var, var) == const" << std::endl;); if (eval_concat(arg1, arg2) == NULL) { rational arg1Len, arg2Len; bool arg1Len_exists = get_len_value(arg1, arg1Len); @@ -6527,12 +6527,12 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { zstring prefixStr, suffixStr; if (arg1Len_exists) { if (arg1Len.is_neg()) { - TRACE("t_str_detail", tout << "length conflict: arg1Len = " << arg1Len << ", concatStrLen = " << concatStrLen << std::endl;); + TRACE("str", tout << "length conflict: arg1Len = " << arg1Len << ", concatStrLen = " << concatStrLen << std::endl;); expr_ref toAssert(m_autil.mk_ge(mk_strlen(arg1), mk_int(0)), m); assert_axiom(toAssert); return; } else if (arg1Len > concatStrLen) { - TRACE("t_str_detail", tout << "length conflict: arg1Len = " << arg1Len << ", concatStrLen = " << concatStrLen << std::endl;); + TRACE("str", tout << "length conflict: arg1Len = " << arg1Len << ", concatStrLen = " << concatStrLen << std::endl;); expr_ref ax_r1(m_autil.mk_le(mk_strlen(arg1), mk_int(concatStrLen)), m); assert_implication(ax_l1, ax_r1); return; @@ -6545,12 +6545,12 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { } else { // arg2's length is available if (arg2Len.is_neg()) { - TRACE("t_str_detail", tout << "length conflict: arg2Len = " << arg2Len << ", concatStrLen = " << concatStrLen << std::endl;); + TRACE("str", tout << "length conflict: arg2Len = " << arg2Len << ", concatStrLen = " << concatStrLen << std::endl;); expr_ref toAssert(m_autil.mk_ge(mk_strlen(arg2), mk_int(0)), m); assert_axiom(toAssert); return; } else if (arg2Len > concatStrLen) { - TRACE("t_str_detail", tout << "length conflict: arg2Len = " << arg2Len << ", concatStrLen = " << concatStrLen << std::endl;); + TRACE("str", tout << "length conflict: arg2Len = " << arg2Len << ", concatStrLen = " << concatStrLen << std::endl;); expr_ref ax_r1(m_autil.mk_le(mk_strlen(arg2), mk_int(concatStrLen)), m); assert_implication(ax_l1, ax_r1); return; @@ -6597,18 +6597,18 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { bool entry1InScope; if (entry1 == varForBreakConcat.end()) { - TRACE("t_str_detail", tout << "key1 no entry" << std::endl;); + TRACE("str", tout << "key1 no entry" << std::endl;); entry1InScope = false; } else { // OVERRIDE. entry1InScope = true; - TRACE("t_str_detail", tout << "key1 entry" << std::endl;); + TRACE("str", tout << "key1 entry" << std::endl;); /* if (internal_variable_set.find((entry1->second)[0]) == internal_variable_set.end()) { - TRACE("t_str_detail", tout << "key1 entry not in scope" << std::endl;); + TRACE("str", tout << "key1 entry not in scope" << std::endl;); entry1InScope = false; } else { - TRACE("t_str_detail", tout << "key1 entry in scope" << std::endl;); + TRACE("str", tout << "key1 entry in scope" << std::endl;); entry1InScope = true; } */ @@ -6616,24 +6616,24 @@ void theory_str::solve_concat_eq_str(expr * concat, expr * str) { bool entry2InScope; if (entry2 == varForBreakConcat.end()) { - TRACE("t_str_detail", tout << "key2 no entry" << std::endl;); + TRACE("str", tout << "key2 no entry" << std::endl;); entry2InScope = false; } else { // OVERRIDE. entry2InScope = true; - TRACE("t_str_detail", tout << "key2 entry" << std::endl;); + TRACE("str", tout << "key2 entry" << std::endl;); /* if (internal_variable_set.find((entry2->second)[0]) == internal_variable_set.end()) { - TRACE("t_str_detail", tout << "key2 entry not in scope" << std::endl;); + TRACE("str", tout << "key2 entry not in scope" << std::endl;); entry2InScope = false; } else { - TRACE("t_str_detail", tout << "key2 entry in scope" << std::endl;); + TRACE("str", tout << "key2 entry in scope" << std::endl;); entry2InScope = true; } */ } - TRACE("t_str_detail", tout << "entry 1 " << (entry1InScope ? "in scope" : "not in scope") << std::endl + TRACE("str", tout << "entry 1 " << (entry1InScope ? "in scope" : "not in scope") << std::endl << "entry 2 " << (entry2InScope ? "in scope" : "not in scope") << std::endl;); if (!entry1InScope && !entry2InScope) { @@ -6702,14 +6702,14 @@ expr_ref theory_str::set_up_finite_model_test(expr * lhs, expr * rhs) { context & ctx = get_context(); ast_manager & m = get_manager(); - TRACE("t_str", tout << "activating finite model testing for overlapping concats " + TRACE("str", tout << "activating finite model testing for overlapping concats " << mk_pp(lhs, m) << " and " << mk_pp(rhs, m) << std::endl;); std::map concatMap; std::map unrollMap; std::map varMap; classify_ast_by_type(lhs, varMap, concatMap, unrollMap); classify_ast_by_type(rhs, varMap, concatMap, unrollMap); - TRACE("t_str_detail", tout << "found vars:"; + TRACE("str", tout << "found vars:"; for (std::map::iterator it = varMap.begin(); it != varMap.end(); ++it) { tout << " " << mk_pp(it->first, m); } @@ -6743,20 +6743,20 @@ void theory_str::finite_model_test(expr * testvar, expr * str) { zstring s; if (!u.str.is_string(str, s)) return; if (s == "yes") { - TRACE("t_str", tout << "start finite model test for " << mk_pp(testvar, m) << std::endl;); + TRACE("str", tout << "start finite model test for " << mk_pp(testvar, m) << std::endl;); ptr_vector & vars = finite_model_test_varlists[testvar]; for (ptr_vector::iterator it = vars.begin(); it != vars.end(); ++it) { expr * v = *it; bool v_has_eqc = false; get_eqc_value(v, v_has_eqc); if (v_has_eqc) { - TRACE("t_str_detail", tout << "variable " << mk_pp(v,m) << " already equivalent to a string constant" << std::endl;); + TRACE("str", tout << "variable " << mk_pp(v,m) << " already equivalent to a string constant" << std::endl;); continue; } // check for any sort of existing length tester we might interfere with if (m_params.m_UseBinarySearch) { if (binary_search_len_tester_stack.contains(v) && !binary_search_len_tester_stack[v].empty()) { - TRACE("t_str_detail", tout << "already found existing length testers for " << mk_pp(v, m) << std::endl;); + TRACE("str", tout << "already found existing length testers for " << mk_pp(v, m) << std::endl;); continue; } else { // start binary search as normal @@ -6783,19 +6783,19 @@ void theory_str::finite_model_test(expr * testvar, expr * str) { } if (map_effectively_empty) { - TRACE("t_str_detail", tout << "no existing length testers for " << mk_pp(v, m) << std::endl;); + TRACE("str", tout << "no existing length testers for " << mk_pp(v, m) << std::endl;); rational v_len; rational v_lower_bound; rational v_upper_bound; expr_ref vLengthExpr(mk_strlen(v), m); if (get_len_value(v, v_len)) { - TRACE("t_str_detail", tout << "length = " << v_len.to_string() << std::endl;); + TRACE("str", tout << "length = " << v_len.to_string() << std::endl;); v_lower_bound = v_len; v_upper_bound = v_len; } else { bool lower_bound_exists = lower_bound(vLengthExpr, v_lower_bound); bool upper_bound_exists = upper_bound(vLengthExpr, v_upper_bound); - TRACE("t_str_detail", tout << "bounds = [" << (lower_bound_exists?v_lower_bound.to_string():"?") + TRACE("str", tout << "bounds = [" << (lower_bound_exists?v_lower_bound.to_string():"?") << ".." << (upper_bound_exists?v_upper_bound.to_string():"?") << "]" << std::endl;); // make sure the bounds are non-negative @@ -6849,7 +6849,7 @@ void theory_str::finite_model_test(expr * testvar, expr * str) { expr_ref implRhs(mk_and(andList), m); assert_implication(implLhs, implRhs); } else { - TRACE("t_str_detail", tout << "already found existing length testers for " << mk_pp(v, m) << std::endl;); + TRACE("str", tout << "already found existing length testers for " << mk_pp(v, m) << std::endl;); continue; } } @@ -6862,7 +6862,7 @@ void theory_str::more_len_tests(expr * lenTester, zstring lenTesterValue) { if (lenTester_fvar_map.contains(lenTester)) { expr * fVar = lenTester_fvar_map[lenTester]; expr * toAssert = gen_len_val_options_for_free_var(fVar, lenTester, lenTesterValue); - TRACE("t_str_detail", tout << "asserting more length tests for free variable " << mk_ismt2_pp(fVar, m) << std::endl;); + TRACE("str", tout << "asserting more length tests for free variable " << mk_ismt2_pp(fVar, m) << std::endl;); if (toAssert != NULL) { assert_axiom(toAssert); } @@ -6875,24 +6875,24 @@ void theory_str::more_value_tests(expr * valTester, zstring valTesterValue) { expr * fVar = valueTester_fvar_map[valTester]; if (m_params.m_UseBinarySearch) { if (!binary_search_len_tester_stack.contains(fVar) || binary_search_len_tester_stack[fVar].empty()) { - TRACE("t_str_binary_search", tout << "WARNING: no active length testers for " << mk_pp(fVar, m) << std::endl;); + TRACE("str", tout << "WARNING: no active length testers for " << mk_pp(fVar, m) << std::endl;); NOT_IMPLEMENTED_YET(); } expr * effectiveLenInd = binary_search_len_tester_stack[fVar].back(); bool hasEqcValue; expr * len_indicator_value = get_eqc_value(effectiveLenInd, hasEqcValue); if (!hasEqcValue) { - TRACE("t_str_binary_search", tout << "WARNING: length tester " << mk_pp(effectiveLenInd, m) << " at top of stack for " << mk_pp(fVar, m) << " has no EQC value" << std::endl;); + TRACE("str", tout << "WARNING: length tester " << mk_pp(effectiveLenInd, m) << " at top of stack for " << mk_pp(fVar, m) << " has no EQC value" << std::endl;); } else { // safety check zstring effectiveLenIndiStr; u.str.is_string(len_indicator_value, effectiveLenIndiStr); if (effectiveLenIndiStr == "more" || effectiveLenIndiStr == "less") { - TRACE("t_str_binary_search", tout << "ERROR: illegal state -- requesting 'more value tests' but a length tester is not yet concrete!" << std::endl;); + TRACE("str", tout << "ERROR: illegal state -- requesting 'more value tests' but a length tester is not yet concrete!" << std::endl;); UNREACHABLE(); } expr * valueAssert = gen_free_var_options(fVar, effectiveLenInd, effectiveLenIndiStr, valTester, valTesterValue); - TRACE("t_str_detail", tout << "asserting more value tests for free variable " << mk_ismt2_pp(fVar, m) << std::endl;); + TRACE("str", tout << "asserting more value tests for free variable " << mk_ismt2_pp(fVar, m) << std::endl;); if (valueAssert != NULL) { assert_axiom(valueAssert); } @@ -6917,7 +6917,7 @@ void theory_str::more_value_tests(expr * valTester, zstring valTesterValue) { } } expr * valueAssert = gen_free_var_options(fVar, effectiveLenInd, effectiveLenIndiStr, valTester, valTesterValue); - TRACE("t_str_detail", tout << "asserting more value tests for free variable " << mk_ismt2_pp(fVar, m) << std::endl;); + TRACE("str", tout << "asserting more value tests for free variable " << mk_ismt2_pp(fVar, m) << std::endl;); if (valueAssert != NULL) { assert_axiom(valueAssert); } @@ -6928,13 +6928,13 @@ bool theory_str::free_var_attempt(expr * nn1, expr * nn2) { ast_manager & m = get_manager(); zstring nn2_str; if (internal_lenTest_vars.contains(nn1) && u.str.is_string(nn2, nn2_str)) { - TRACE("t_str", tout << "acting on equivalence between length tester var " << mk_ismt2_pp(nn1, m) + TRACE("str", tout << "acting on equivalence between length tester var " << mk_ismt2_pp(nn1, m) << " and constant " << mk_ismt2_pp(nn2, m) << std::endl;); more_len_tests(nn1, nn2_str); return true; } else if (internal_valTest_vars.contains(nn1) && u.str.is_string(nn2, nn2_str)) { if (nn2_str == "more") { - TRACE("t_str", tout << "acting on equivalence between value var " << mk_ismt2_pp(nn1, m) + TRACE("str", tout << "acting on equivalence between value var " << mk_ismt2_pp(nn1, m) << " and constant " << mk_ismt2_pp(nn2, m) << std::endl;); more_value_tests(nn1, nn2_str); } @@ -6955,7 +6955,7 @@ void theory_str::handle_equality(expr * lhs, expr * rhs) { sort * str_sort = u.str.mk_string_sort(); if (lhs_sort != str_sort || rhs_sort != str_sort) { - TRACE("t_str_detail", tout << "skip equality: not String sort" << std::endl;); + TRACE("str", tout << "skip equality: not String sort" << std::endl;); return; } @@ -6990,18 +6990,18 @@ void theory_str::handle_equality(expr * lhs, expr * rhs) { expr * nn2_arg0 = to_app(rhs)->get_arg(0); expr * nn2_arg1 = to_app(rhs)->get_arg(1); if (nn1_arg0 == nn2_arg0 && in_same_eqc(nn1_arg1, nn2_arg1)) { - TRACE("t_str_detail", tout << "skip: lhs arg0 == rhs arg0" << std::endl;); + TRACE("str", tout << "skip: lhs arg0 == rhs arg0" << std::endl;); return; } if (nn1_arg1 == nn2_arg1 && in_same_eqc(nn1_arg0, nn2_arg0)) { - TRACE("t_str_detail", tout << "skip: lhs arg1 == rhs arg1" << std::endl;); + TRACE("str", tout << "skip: lhs arg1 == rhs arg1" << std::endl;); return; } } if (opt_DeferEQCConsistencyCheck) { - TRACE("t_str_detail", tout << "opt_DeferEQCConsistencyCheck is set; deferring new_eq_check call" << std::endl;); + TRACE("str", tout << "opt_DeferEQCConsistencyCheck is set; deferring new_eq_check call" << std::endl;); } else { // newEqCheck() -- check consistency wrt. existing equivalence classes if (!new_eq_check(lhs, rhs)) { @@ -7050,7 +7050,7 @@ void theory_str::handle_equality(expr * lhs, expr * rhs) { std::set eqc_const_rhs; group_terms_by_eqc(rhs, eqc_concat_rhs, eqc_var_rhs, eqc_const_rhs); - TRACE("t_str_detail", + TRACE("str", tout << "lhs eqc:" << std::endl; tout << "Concats:" << std::endl; for (std::set::iterator it = eqc_concat_lhs.begin(); it != eqc_concat_lhs.end(); ++it) { @@ -7112,10 +7112,10 @@ void theory_str::handle_equality(expr * lhs, expr * rhs) { for (itor2 = eqc_concat_rhs.begin(); itor2 != eqc_concat_rhs.end() && !found; ++itor2) { expr * concat_rhs = *itor2; if (will_result_in_overlap(concat_lhs, concat_rhs)) { - TRACE("t_str_detail", tout << "Concats " << mk_pp(concat_lhs, m) << " and " + TRACE("str", tout << "Concats " << mk_pp(concat_lhs, m) << " and " << mk_pp(concat_rhs, m) << " will result in overlap; skipping." << std::endl;); } else { - TRACE("t_str_detail", tout << "Concats " << mk_pp(concat_lhs, m) << " and " + TRACE("str", tout << "Concats " << mk_pp(concat_lhs, m) << " and " << mk_pp(concat_rhs, m) << " won't overlap. Simplifying here." << std::endl;); simplify_concat_equality(concat_lhs, concat_rhs); found = true; @@ -7124,7 +7124,7 @@ void theory_str::handle_equality(expr * lhs, expr * rhs) { } } if (!found) { - TRACE("t_str_detail", tout << "All pairs of concats expected to overlap, falling back." << std::endl;); + TRACE("str", tout << "All pairs of concats expected to overlap, falling back." << std::endl;); simplify_concat_equality(*(eqc_concat_lhs.begin()), *(eqc_concat_rhs.begin())); } } else { @@ -7197,13 +7197,13 @@ void theory_str::set_up_axioms(expr * ex) { sort * int_sort = m.mk_sort(m_arith_fid, INT_SORT); if (ex_sort == str_sort) { - TRACE("t_str_detail", tout << "setting up axioms for " << mk_ismt2_pp(ex, get_manager()) << + TRACE("str", tout << "setting up axioms for " << mk_ismt2_pp(ex, get_manager()) << ": expr is of sort String" << std::endl;); // set up basic string axioms enode * n = ctx.get_enode(ex); SASSERT(n); m_basicstr_axiom_todo.push_back(n); - TRACE("t_str_axiom_bug", tout << "add " << mk_pp(ex, m) << " to m_basicstr_axiom_todo" << std::endl;); + TRACE("str", tout << "add " << mk_pp(ex, m) << " to m_basicstr_axiom_todo" << std::endl;); if (is_app(ex)) { @@ -7225,21 +7225,21 @@ void theory_str::set_up_axioms(expr * ex) { } else if (u.str.is_at(ap) || u.str.is_extract(ap) || u.str.is_replace(ap)) { m_library_aware_axiom_todo.push_back(n); } else if (u.str.is_itos(ap)) { - TRACE("t_str_detail", tout << "found string-integer conversion term: " << mk_pp(ex, get_manager()) << std::endl;); + TRACE("str", tout << "found string-integer conversion term: " << mk_pp(ex, get_manager()) << std::endl;); string_int_conversion_terms.push_back(ap); m_library_aware_axiom_todo.push_back(n); } else if (ap->get_num_args() == 0 && !u.str.is_string(ap)) { // if ex is a variable, add it to our list of variables - TRACE("t_str_detail", tout << "tracking variable " << mk_ismt2_pp(ap, get_manager()) << std::endl;); + TRACE("str", tout << "tracking variable " << mk_ismt2_pp(ap, get_manager()) << std::endl;); variable_set.insert(ex); ctx.mark_as_relevant(ex); // this might help?? theory_var v = mk_var(n); - TRACE("t_str_detail", tout << "variable " << mk_ismt2_pp(ap, get_manager()) << " is #" << v << std::endl;); + TRACE("str", tout << "variable " << mk_ismt2_pp(ap, get_manager()) << " is #" << v << std::endl;); } } } else if (ex_sort == bool_sort) { - TRACE("t_str_detail", tout << "setting up axioms for " << mk_ismt2_pp(ex, get_manager()) << + TRACE("str", tout << "setting up axioms for " << mk_ismt2_pp(ex, get_manager()) << ": expr is of sort Bool" << std::endl;); // set up axioms for boolean terms @@ -7255,13 +7255,13 @@ void theory_str::set_up_axioms(expr * ex) { } } } else { - TRACE("t_str_detail", tout << "WARNING: Bool term " << mk_ismt2_pp(ex, get_manager()) << " not internalized. Delaying axiom setup to prevent a crash." << std::endl;); + TRACE("str", tout << "WARNING: Bool term " << mk_ismt2_pp(ex, get_manager()) << " not internalized. Delaying axiom setup to prevent a crash." << std::endl;); ENSURE(!search_started); // infinite loop prevention m_delayed_axiom_setup_terms.push_back(ex); return; } } else if (ex_sort == int_sort) { - TRACE("t_str_detail", tout << "setting up axioms for " << mk_ismt2_pp(ex, get_manager()) << + TRACE("str", tout << "setting up axioms for " << mk_ismt2_pp(ex, get_manager()) << ": expr is of sort Int" << std::endl;); // set up axioms for integer terms enode * n = ensure_enode(ex); @@ -7273,13 +7273,13 @@ void theory_str::set_up_axioms(expr * ex) { if (u.str.is_index(ap) /* || is_Indexof2(ap) || is_LastIndexof(ap) */) { m_library_aware_axiom_todo.push_back(n); } else if (u.str.is_stoi(ap)) { - TRACE("t_str_detail", tout << "found string-integer conversion term: " << mk_pp(ex, get_manager()) << std::endl;); + TRACE("str", tout << "found string-integer conversion term: " << mk_pp(ex, get_manager()) << std::endl;); string_int_conversion_terms.push_back(ap); m_library_aware_axiom_todo.push_back(n); } } } else { - TRACE("t_str_detail", tout << "setting up axioms for " << mk_ismt2_pp(ex, get_manager()) << + TRACE("str", tout << "setting up axioms for " << mk_ismt2_pp(ex, get_manager()) << ": expr is of wrong sort, ignoring" << std::endl;); } @@ -7294,7 +7294,7 @@ void theory_str::set_up_axioms(expr * ex) { } void theory_str::add_theory_assumptions(expr_ref_vector & assumptions) { - TRACE("t_str", tout << "add overlap assumption for theory_str" << std::endl;); + TRACE("str", tout << "add overlap assumption for theory_str" << std::endl;); symbol strOverlap("!!TheoryStrOverlapAssumption!!"); seq_util m_sequtil(get_manager()); sort * s = get_manager().mk_bool_sort(); @@ -7315,7 +7315,7 @@ lbool theory_str::validate_unsat_core(expr_ref_vector & unsat_core) { e1 = get_context().get_enode(target_term); e2 = get_context().get_enode(core_term); if (e1 == e2) { - TRACE("t_str", tout << "overlap detected in unsat core, changing UNSAT to UNKNOWN" << std::endl;); + TRACE("str", tout << "overlap detected in unsat core, changing UNSAT to UNKNOWN" << std::endl;); assumptionFound = true; return l_undef; } @@ -7328,7 +7328,7 @@ void theory_str::init_search_eh() { ast_manager & m = get_manager(); context & ctx = get_context(); - TRACE("t_str_detail", + TRACE("str", tout << "dumping all asserted formulas:" << std::endl; unsigned nFormulas = ctx.get_num_asserted_formulas(); for (unsigned i = 0; i < nFormulas; ++i) { @@ -7360,7 +7360,7 @@ void theory_str::init_search_eh() { for (expr_ref_vector::iterator i = assignments.begin(); i != assignments.end(); ++i) { expr * ex = *i; if (m.is_eq(ex)) { - TRACE("t_str_detail", tout << "processing assignment " << mk_ismt2_pp(ex, m) << + TRACE("str", tout << "processing assignment " << mk_ismt2_pp(ex, m) << ": expr is equality" << std::endl;); app * eq = (app*)ex; SASSERT(eq->get_num_args() == 2); @@ -7372,7 +7372,7 @@ void theory_str::init_search_eh() { std::pair eq_pair(e_lhs, e_rhs); m_str_eq_todo.push_back(eq_pair); } else { - TRACE("t_str_detail", tout << "processing assignment " << mk_ismt2_pp(ex, m) + TRACE("str", tout << "processing assignment " << mk_ismt2_pp(ex, m) << ": expr ignored" << std::endl;); } } @@ -7382,13 +7382,13 @@ void theory_str::init_search_eh() { // before the first call to new_eq_eh() propagate(); - TRACE("t_str", tout << "search started" << std::endl;); + TRACE("str", tout << "search started" << std::endl;); search_started = true; } void theory_str::new_eq_eh(theory_var x, theory_var y) { - //TRACE("t_str_detail", tout << "new eq: v#" << x << " = v#" << y << std::endl;); - TRACE("t_str", tout << "new eq: " << mk_ismt2_pp(get_enode(x)->get_owner(), get_manager()) << " = " << + //TRACE("str", tout << "new eq: v#" << x << " = v#" << y << std::endl;); + TRACE("str", tout << "new eq: " << mk_ismt2_pp(get_enode(x)->get_owner(), get_manager()) << " = " << mk_ismt2_pp(get_enode(y)->get_owner(), get_manager()) << std::endl;); /* @@ -7403,18 +7403,18 @@ void theory_str::new_eq_eh(theory_var x, theory_var y) { } void theory_str::new_diseq_eh(theory_var x, theory_var y) { - //TRACE("t_str_detail", tout << "new diseq: v#" << x << " != v#" << y << std::endl;); - TRACE("t_str", tout << "new diseq: " << mk_ismt2_pp(get_enode(x)->get_owner(), get_manager()) << " != " << + //TRACE("str", tout << "new diseq: v#" << x << " != v#" << y << std::endl;); + TRACE("str", tout << "new diseq: " << mk_ismt2_pp(get_enode(x)->get_owner(), get_manager()) << " != " << mk_ismt2_pp(get_enode(y)->get_owner(), get_manager()) << std::endl;); } void theory_str::relevant_eh(app * n) { - TRACE("t_str", tout << "relevant: " << mk_ismt2_pp(n, get_manager()) << std::endl;); + TRACE("str", tout << "relevant: " << mk_ismt2_pp(n, get_manager()) << std::endl;); } void theory_str::assign_eh(bool_var v, bool is_true) { context & ctx = get_context(); - TRACE("t_str", tout << "assert: v" << v << " #" << ctx.bool_var2expr(v)->get_id() << " is_true: " << is_true << std::endl;); + TRACE("str", tout << "assert: v" << v << " #" << ctx.bool_var2expr(v)->get_id() << " is_true: " << is_true << std::endl;); } void theory_str::push_scope_eh() { @@ -7422,7 +7422,7 @@ void theory_str::push_scope_eh() { m_trail_stack.push_scope(); sLevel += 1; - TRACE("t_str", tout << "push to " << sLevel << std::endl;); + TRACE("str", tout << "push to " << sLevel << std::endl;); TRACE_CODE(if (is_trace_enabled("t_str_dump_assign_on_scope_change")) { dump_assignments(); }); } @@ -7446,7 +7446,7 @@ void theory_str::recursive_check_variable_scope(expr * ex) { // assume var if (variable_set.find(ex) == variable_set.end() && internal_variable_set.find(ex) == internal_variable_set.end()) { - TRACE("t_str_detail", tout << "WARNING: possible reference to out-of-scope variable " << mk_pp(ex, m) << std::endl;); + TRACE("str", tout << "WARNING: possible reference to out-of-scope variable " << mk_pp(ex, m) << std::endl;); } } } else { @@ -7466,7 +7466,7 @@ void theory_str::check_variable_scope() { return; } - TRACE("t_str_detail", tout << "checking scopes of variables in the current assignment" << std::endl;); + TRACE("str", tout << "checking scopes of variables in the current assignment" << std::endl;); context & ctx = get_context(); ast_manager & m = get_manager(); @@ -7481,7 +7481,7 @@ void theory_str::check_variable_scope() { void theory_str::pop_scope_eh(unsigned num_scopes) { sLevel -= num_scopes; - TRACE("t_str", tout << "pop " << num_scopes << " to " << sLevel << std::endl;); + TRACE("str", tout << "pop " << num_scopes << " to " << sLevel << std::endl;); context & ctx = get_context(); ast_manager & m = get_manager(); @@ -7495,7 +7495,7 @@ void theory_str::pop_scope_eh(unsigned num_scopes) { expr * e = varItor->m_key; std::stack & val = cut_var_map[varItor->m_key]; while ((val.size() > 0) && (val.top()->level != 0) && (val.top()->level >= sLevel)) { - TRACE("t_str_cut_var_map", tout << "remove cut info for " << mk_pp(e, m) << std::endl; print_cut_var(e, tout);); + TRACE("str", tout << "remove cut info for " << mk_pp(e, m) << std::endl; print_cut_var(e, tout);); T_cut * aCut = val.top(); val.pop(); // dealloc(aCut); @@ -7518,7 +7518,7 @@ void theory_str::pop_scope_eh(unsigned num_scopes) { for (ptr_vector::iterator it = m_basicstr_axiom_todo.begin(); it != m_basicstr_axiom_todo.end(); ++it) { enode * e = *it; app * a = e->get_owner(); - TRACE("t_str_axiom_bug", tout << "consider deleting " << mk_pp(a, get_manager()) + TRACE("str", tout << "consider deleting " << mk_pp(a, get_manager()) << ", enode scope level is " << e->get_iscope_lvl() << std::endl;); if (e->get_iscope_lvl() <= (unsigned)sLevel) { @@ -7559,7 +7559,7 @@ void theory_str::classify_ast_by_type(expr * node, std::map & varMap && internal_valTest_vars.find(node) == internal_valTest_vars.end() && internal_unrollTest_vars.find(node) == internal_unrollTest_vars.end()) { if (varMap[node] != 1) { - TRACE("t_str_detail", tout << "new variable: " << mk_pp(node, get_manager()) << std::endl;); + TRACE("str", tout << "new variable: " << mk_pp(node, get_manager()) << std::endl;); } varMap[node] = 1; } @@ -7622,7 +7622,7 @@ void theory_str::classify_ast_by_type_in_positive_context(std::map & // so we bypass a huge amount of work by doing the following... if (m.is_eq(argAst)) { - TRACE("t_str_detail", tout + TRACE("str", tout << "eq ast " << mk_pp(argAst, m) << " is between args of sort " << m.get_sort(to_app(argAst)->get_arg(0))->get_name() << std::endl;); @@ -7846,7 +7846,7 @@ int theory_str::ctx_dep_analysis(std::map & strVarMap, std::map::iterator it = variable_set.begin(); it != variable_set.end(); ++it) { expr* var = *it; if (internal_variable_set.find(var) == internal_variable_set.end()) { - TRACE("t_str_detail", tout << "new variable: " << mk_pp(var, m) << std::endl;); + TRACE("str", tout << "new variable: " << mk_pp(var, m) << std::endl;); strVarMap[*it] = 1; } } @@ -8030,7 +8030,7 @@ int theory_str::ctx_dep_analysis(std::map & strVarMap, std::map & strVarMap, std::map::iterator itor2 = inVarMap.begin(); itor2 != inVarMap.end(); itor2++) { expr * varInFunc = get_alias_index_ast(aliasIndexMap, itor2->first); - TRACE("t_str_detail", tout << "var in unroll = " << + TRACE("str", tout << "var in unroll = " << mk_ismt2_pp(itor2->first, m) << std::endl << "dealiased var = " << mk_ismt2_pp(varInFunc, m) << std::endl;); @@ -8255,7 +8255,7 @@ int theory_str::ctx_dep_analysis(std::map & strVarMap, std::map >::iterator itor = depMap.begin(); itor != depMap.end(); itor++) { tout << mk_pp(itor->first, m); @@ -8422,7 +8422,7 @@ bool theory_str::finalcheck_str2int(app * a) { rational Ival; bool Ival_exists = get_value(a, Ival); if (Ival_exists) { - TRACE("t_str_detail", tout << "integer theory assigns " << mk_pp(a, m) << " = " << Ival.to_string() << std::endl;); + TRACE("str", tout << "integer theory assigns " << mk_pp(a, m) << " = " << Ival.to_string() << std::endl;); // if that value is not -1, we can assert (str.to-int S) = Ival --> S = "Ival" if (!Ival.is_minus_one()) { zstring Ival_str(Ival.to_string().c_str()); @@ -8437,7 +8437,7 @@ bool theory_str::finalcheck_str2int(app * a) { } } } else { - TRACE("t_str_detail", tout << "integer theory has no assignment for " << mk_pp(a, m) << std::endl;); + TRACE("str", tout << "integer theory has no assignment for " << mk_pp(a, m) << std::endl;); NOT_IMPLEMENTED_YET(); } @@ -8457,7 +8457,7 @@ bool theory_str::finalcheck_int2str(app * a) { if (Sval_expr_exists) { zstring Sval; u.str.is_string(Sval_expr, Sval); - TRACE("t_str_detail", tout << "string theory assigns \"" << mk_pp(a, m) << " = " << Sval << "\n";); + TRACE("str", tout << "string theory assigns \"" << mk_pp(a, m) << " = " << Sval << "\n";); // empty string --> integer value < 0 if (Sval.empty()) { // ignore this. we should already assert the axiom for what happens when the string is "" @@ -8474,7 +8474,7 @@ bool theory_str::finalcheck_int2str(app * a) { convertedRepresentation = (ten * convertedRepresentation) + rational(val); } else { // not a digit, invalid - TRACE("t_str_rw", tout << "str.to-int argument contains non-digit character '" << digit << "'" << std::endl;); + TRACE("str", tout << "str.to-int argument contains non-digit character '" << digit << "'" << std::endl;); conversionOK = false; break; } @@ -8497,7 +8497,7 @@ bool theory_str::finalcheck_int2str(app * a) { } } } else { - TRACE("t_str_detail", tout << "string theory has no assignment for " << mk_pp(a, m) << std::endl;); + TRACE("str", tout << "string theory has no assignment for " << mk_pp(a, m) << std::endl;); NOT_IMPLEMENTED_YET(); } return axiomAdd; @@ -8535,7 +8535,7 @@ bool theory_str::propagate_length_within_eqc(expr * var) { ast_manager & m = get_manager(); context & ctx = get_context(); - TRACE("t_str_length", tout << "propagate_length_within_eqc: " << mk_ismt2_pp(var, m) << std::endl ;); + TRACE("str", tout << "propagate_length_within_eqc: " << mk_ismt2_pp(var, m) << std::endl ;); enode * n_eq_enode = ctx.get_enode(var); rational varLen; @@ -8565,7 +8565,7 @@ bool theory_str::propagate_length_within_eqc(expr * var) { expr_ref varLen(mk_strlen(var), m); expr_ref axr(ctx.mk_eq_atom(varLen, mk_int(varLen)), m); assert_implication(axl, axr); - TRACE("t_str_length", tout << mk_ismt2_pp(axl, m) << std::endl << " ---> " << std::endl << mk_ismt2_pp(axr, m);); + TRACE("str", tout << mk_ismt2_pp(axl, m) << std::endl << " ---> " << std::endl << mk_ismt2_pp(axr, m);); res = true; } } @@ -8598,7 +8598,7 @@ bool theory_str::propagate_length(std::set & varSet, std::set & co // the length fo concat is unresolved yet if (get_len_value(concat, lenValue)) { // but all leaf nodes have length information - TRACE("t_str_length", tout << "* length pop-up: " << mk_ismt2_pp(concat, m) << "| = " << lenValue << std::endl;); + TRACE("str", tout << "* length pop-up: " << mk_ismt2_pp(concat, m) << "| = " << lenValue << std::endl;); std::set leafNodes; get_unique_non_concat_nodes(concat, leafNodes); expr_ref_vector l_items(m); @@ -8619,7 +8619,7 @@ bool theory_str::propagate_length(std::set & varSet, std::set & co expr_ref lenValueExpr (mk_int(lenValue), m); expr_ref axr(ctx.mk_eq_atom(concatlenExpr, lenValueExpr), m); assert_implication(axl, axr); - TRACE("t_str_length", tout << mk_ismt2_pp(axl, m) << std::endl << " ---> " << std::endl << mk_ismt2_pp(axr, m)<< std::endl;); + TRACE("str", tout << mk_ismt2_pp(axl, m) << std::endl << " ---> " << std::endl << mk_ismt2_pp(axr, m)<< std::endl;); axiomAdded = true; } } @@ -8668,12 +8668,12 @@ final_check_status theory_str::final_check_eh() { finalCheckProgressIndicator = false; } - TRACE("t_str", tout << "final check" << std::endl;); + TRACE("str", tout << "final check" << std::endl;); TRACE_CODE(if (is_trace_enabled("t_str_dump_assign")) { dump_assignments(); }); check_variable_scope(); if (opt_DeferEQCConsistencyCheck) { - TRACE("t_str_detail", tout << "performing deferred EQC consistency check" << std::endl;); + TRACE("str", tout << "performing deferred EQC consistency check" << std::endl;); std::set eqc_roots; for (ptr_vector::const_iterator it = ctx.begin_enodes(); it != ctx.end_enodes(); ++it) { enode * e = *it; @@ -8687,16 +8687,16 @@ final_check_status theory_str::final_check_eh() { enode * e = *it; app * a = e->get_owner(); if (!(m.get_sort(a) == u.str.mk_string_sort())) { - TRACE("t_str_detail", tout << "EQC root " << mk_pp(a, m) << " not a string term; skipping" << std::endl;); + TRACE("str", tout << "EQC root " << mk_pp(a, m) << " not a string term; skipping" << std::endl;); } else { - TRACE("t_str_detail", tout << "EQC root " << mk_pp(a, m) << " is a string term. Checking this EQC" << std::endl;); + TRACE("str", tout << "EQC root " << mk_pp(a, m) << " is a string term. Checking this EQC" << std::endl;); // first call check_concat_len_in_eqc() on each member of the eqc enode * e_it = e; enode * e_root = e_it; do { bool status = check_concat_len_in_eqc(e_it->get_owner()); if (!status) { - TRACE("t_str_detail", tout << "concat-len check asserted an axiom on " << mk_pp(e_it->get_owner(), m) << std::endl;); + TRACE("str", tout << "concat-len check asserted an axiom on " << mk_pp(e_it->get_owner(), m) << std::endl;); found_inconsistency = true; } e_it = e_it->get_next(); @@ -8706,10 +8706,10 @@ final_check_status theory_str::final_check_eh() { enode * e1 = e; enode * e2 = e1->get_next(); if (e1 != e2) { - TRACE("t_str_detail", tout << "deferred new_eq_check() over EQC of " << mk_pp(e1->get_owner(), m) << " and " << mk_pp(e2->get_owner(), m) << std::endl;); + TRACE("str", tout << "deferred new_eq_check() over EQC of " << mk_pp(e1->get_owner(), m) << " and " << mk_pp(e2->get_owner(), m) << std::endl;); bool result = new_eq_check(e1->get_owner(), e2->get_owner()); if (!result) { - TRACE("t_str_detail", tout << "new_eq_check found inconsistencies" << std::endl;); + TRACE("str", tout << "new_eq_check found inconsistencies" << std::endl;); found_inconsistency = true; } } @@ -8717,10 +8717,10 @@ final_check_status theory_str::final_check_eh() { } if (found_inconsistency) { - TRACE("t_str", tout << "Found inconsistency in final check! Returning to search." << std::endl;); + TRACE("str", tout << "Found inconsistency in final check! Returning to search." << std::endl;); return FC_CONTINUE; } else { - TRACE("t_str", tout << "Deferred consistency check passed. Continuing in final check." << std::endl;); + TRACE("str", tout << "Deferred consistency check passed. Continuing in final check." << std::endl;); } } @@ -8753,7 +8753,7 @@ final_check_status theory_str::final_check_eh() { expr * concat_rhs_str = get_eqc_value(concat_rhs, concat_rhs_haseqc); expr * var_str = get_eqc_value(var, var_haseqc); if (concat_lhs_haseqc && concat_rhs_haseqc && !var_haseqc) { - TRACE("t_str_detail", tout << "backpropagate into " << mk_pp(var, m) << " = " << mk_pp(concat, m) << std::endl + TRACE("str", tout << "backpropagate into " << mk_pp(var, m) << " = " << mk_pp(concat, m) << std::endl << "LHS ~= " << mk_pp(concat_lhs_str, m) << " RHS ~= " << mk_pp(concat_rhs_str, m) << std::endl;); zstring lhsString, rhsString; u.str.is_string(concat_lhs_str, lhsString); @@ -8770,7 +8770,7 @@ final_check_status theory_str::final_check_eh() { } if (backpropagation_occurred) { - TRACE("t_str", tout << "Resuming search due to axioms added by backpropagation." << std::endl;); + TRACE("str", tout << "Resuming search due to axioms added by backpropagation." << std::endl;); return FC_CONTINUE; } @@ -8782,7 +8782,7 @@ final_check_status theory_str::final_check_eh() { bool length_propagation_occurred = propagate_length(varSet, concatSet, exprLenMap); if (length_propagation_occurred) { - TRACE("t_str", tout << "Resuming search due to axioms added by length propagation." << std::endl;); + TRACE("str", tout << "Resuming search due to axioms added by length propagation." << std::endl;); return FC_CONTINUE; } } @@ -8801,19 +8801,19 @@ final_check_status theory_str::final_check_eh() { if (internal_variable_set.find(itor->first) != internal_variable_set.end() || regex_variable_set.find(itor->first) != regex_variable_set.end()) { // this can be ignored, I think - TRACE("t_str_detail", tout << "free internal variable " << mk_pp(itor->first, m) << " ignored" << std::endl;); + TRACE("str", tout << "free internal variable " << mk_pp(itor->first, m) << " ignored" << std::endl;); continue; } bool hasEqcValue = false; expr * eqcString = get_eqc_value(itor->first, hasEqcValue); if (!hasEqcValue) { - TRACE("t_str_detail", tout << "found free variable " << mk_pp(itor->first, m) << std::endl;); + TRACE("str", tout << "found free variable " << mk_pp(itor->first, m) << std::endl;); needToAssignFreeVars = true; free_variables.insert(itor->first); // break; } else { // debug - TRACE("t_str_detail", tout << "variable " << mk_pp(itor->first, m) << " = " << mk_pp(eqcString, m) << std::endl;); + TRACE("str", tout << "variable " << mk_pp(itor->first, m) << " = " << mk_pp(eqcString, m) << std::endl;); } } } @@ -8839,15 +8839,15 @@ final_check_status theory_str::final_check_eh() { } } if (addedStrIntAxioms) { - TRACE("t_str", tout << "Resuming search due to addition of string-integer conversion axioms." << std::endl;); + TRACE("str", tout << "Resuming search due to addition of string-integer conversion axioms." << std::endl;); return FC_CONTINUE; } if (unused_internal_variables.empty()) { - TRACE("t_str", tout << "All variables are assigned. Done!" << std::endl;); + TRACE("str", tout << "All variables are assigned. Done!" << std::endl;); return FC_DONE; } else { - TRACE("t_str", tout << "Assigning decoy values to free internal variables." << std::endl;); + TRACE("str", tout << "Assigning decoy values to free internal variables." << std::endl;); for (std::set::iterator it = unused_internal_variables.begin(); it != unused_internal_variables.end(); ++it) { expr * var = *it; expr_ref assignment(m.mk_eq(var, mk_string("**unused**")), m); @@ -8857,7 +8857,7 @@ final_check_status theory_str::final_check_eh() { } } - CTRACE("t_str", needToAssignFreeVars, + CTRACE("str", needToAssignFreeVars, tout << "Need to assign values to the following free variables:" << std::endl; for (std::set::iterator itx = free_variables.begin(); itx != free_variables.end(); ++itx) { tout << mk_ismt2_pp(*itx, m) << std::endl; @@ -8890,7 +8890,7 @@ final_check_status theory_str::final_check_eh() { for (std::map >::iterator fvIt3 = fv_unrolls_map.begin(); fvIt3 != fv_unrolls_map.end(); fvIt3++) { expr * var = fvIt3->first; - TRACE("t_str_detail", tout << "erase free variable " << mk_pp(var, m) << " from freeVar_map, it is bounded by an Unroll" << std::endl;); + TRACE("str", tout << "erase free variable " << mk_pp(var, m) << " from freeVar_map, it is bounded by an Unroll" << std::endl;); freeVar_map.erase(var); } @@ -8954,7 +8954,7 @@ final_check_status theory_str::final_check_eh() { } } for (std::set::iterator vItor = fvUnrollSet.begin(); vItor != fvUnrollSet.end(); vItor++) { - TRACE("t_str_detail", tout << "remove " << mk_pp(*vItor, m) << " from freeVar_map" << std::endl;); + TRACE("str", tout << "remove " << mk_pp(*vItor, m) << " from freeVar_map" << std::endl;); freeVar_map.erase(*vItor); } @@ -8964,7 +8964,7 @@ final_check_status theory_str::final_check_eh() { constValue = NULL; { - TRACE("t_str_detail", tout << "free var map (#" << freeVar_map.size() << "):" << std::endl; + TRACE("str", tout << "free var map (#" << freeVar_map.size() << "):" << std::endl; for (std::map::iterator freeVarItor1 = freeVar_map.begin(); freeVarItor1 != freeVar_map.end(); freeVarItor1++) { expr * freeVar = freeVarItor1->first; rational lenValue; @@ -9010,7 +9010,7 @@ final_check_status theory_str::final_check_eh() { // experimental free variable assignment - end // now deal with removed free variables that are bounded by an unroll - TRACE("t_str", tout << "fv_unrolls_map (#" << fv_unrolls_map.size() << "):" << std::endl;); + TRACE("str", tout << "fv_unrolls_map (#" << fv_unrolls_map.size() << "):" << std::endl;); for (std::map >::iterator fvIt1 = fv_unrolls_map.begin(); fvIt1 != fv_unrolls_map.end(); fvIt1++) { expr * var = fvIt1->first; @@ -9027,7 +9027,7 @@ final_check_status theory_str::final_check_eh() { } if (opt_VerifyFinalCheckProgress && !finalCheckProgressIndicator) { - TRACE("t_str", tout << "BUG: no progress in final check, giving up!!" << std::endl;); + TRACE("str", tout << "BUG: no progress in final check, giving up!!" << std::endl;); m.raise_exception("no progress in theory_str final check"); } @@ -9049,7 +9049,7 @@ inline std::string longlong_to_string(long long i) { void theory_str::print_value_tester_list(svector > & testerList) { ast_manager & m = get_manager(); - TRACE("t_str_detail", + TRACE("str", int ss = testerList.size(); tout << "valueTesterList = {"; for (int i = 0; i < ss; ++i) { @@ -9084,7 +9084,7 @@ zstring theory_str::gen_val_string(int len, int_vector & encoding) { bool theory_str::get_next_val_encode(int_vector & base, int_vector & next) { SASSERT(charSetSize > 0); - TRACE("t_str_value_test_bug", tout << "base vector: [ "; + TRACE("str", tout << "base vector: [ "; for (unsigned i = 0; i < base.size(); ++i) { tout << base[i] << " "; } @@ -9140,7 +9140,7 @@ expr * theory_str::gen_val_options(expr * freeVar, expr * len_indicator, expr * svector options; int_vector base; - TRACE("t_str_detail", tout + TRACE("str", tout << "freeVar = " << mk_ismt2_pp(freeVar, m) << std::endl << "len_indicator = " << mk_ismt2_pp(len_indicator, m) << std::endl << "val_indicator = " << mk_ismt2_pp(val_indicator, m) << std::endl @@ -9156,7 +9156,7 @@ expr * theory_str::gen_val_options(expr * freeVar, expr * len_indicator, expr * coverAll = false; } else { expr * lastestValIndi = fvar_valueTester_map[freeVar][len][tries - 1].second; - TRACE("t_str_detail", tout << "last value tester = " << mk_ismt2_pp(lastestValIndi, m) << std::endl;); + TRACE("str", tout << "last value tester = " << mk_ismt2_pp(lastestValIndi, m) << std::endl;); coverAll = get_next_val_encode(val_range_map[lastestValIndi], base); } @@ -9171,7 +9171,7 @@ expr * theory_str::gen_val_options(expr * freeVar, expr * len_indicator, expr * } val_range_map[val_indicator] = options[options.size() - 1]; - TRACE("t_str_detail", + TRACE("str", tout << "value tester encoding " << "{" << std::endl; int_vector vec = val_range_map[val_indicator]; @@ -9266,7 +9266,7 @@ expr * theory_str::gen_free_var_options(expr * freeVar, expr * len_indicator, int len = atoi(len_valueStr.encode().c_str()); // check whether any value tester is actually in scope - TRACE("t_str_detail", tout << "checking scope of previous value testers" << std::endl;); + TRACE("str", tout << "checking scope of previous value testers" << std::endl;); bool map_effectively_empty = true; if (fvar_valueTester_map[freeVar].find(len) != fvar_valueTester_map[freeVar].end()) { // there's *something* in the map, but check its scope @@ -9275,9 +9275,9 @@ expr * theory_str::gen_free_var_options(expr * freeVar, expr * len_indicator, std::pair entry = *it; expr * aTester = entry.second; if (internal_variable_set.find(aTester) == internal_variable_set.end()) { - TRACE("t_str_detail", tout << mk_pp(aTester, m) << " out of scope" << std::endl;); + TRACE("str", tout << mk_pp(aTester, m) << " out of scope" << std::endl;); } else { - TRACE("t_str_detail", tout << mk_pp(aTester, m) << " in scope" << std::endl;); + TRACE("str", tout << mk_pp(aTester, m) << " in scope" << std::endl;); map_effectively_empty = false; break; } @@ -9285,7 +9285,7 @@ expr * theory_str::gen_free_var_options(expr * freeVar, expr * len_indicator, } if (map_effectively_empty) { - TRACE("t_str_detail", tout << "no previous value testers, or none of them were in scope" << std::endl;); + TRACE("str", tout << "no previous value testers, or none of them were in scope" << std::endl;); int tries = 0; expr * val_indicator = mk_internal_valTest_var(freeVar, len, tries); valueTester_fvar_map[val_indicator] = freeVar; @@ -9293,7 +9293,7 @@ expr * theory_str::gen_free_var_options(expr * freeVar, expr * len_indicator, print_value_tester_list(fvar_valueTester_map[freeVar][len]); return gen_val_options(freeVar, len_indicator, val_indicator, len_valueStr, tries); } else { - TRACE("t_str_detail", tout << "checking previous value testers" << std::endl;); + TRACE("str", tout << "checking previous value testers" << std::endl;); print_value_tester_list(fvar_valueTester_map[freeVar][len]); // go through all previous value testers @@ -9305,7 +9305,7 @@ expr * theory_str::gen_free_var_options(expr * freeVar, expr * len_indicator, // it's probably worth checking scope here, actually if (internal_variable_set.find(aTester) == internal_variable_set.end()) { - TRACE("t_str_detail", tout << "value tester " << mk_pp(aTester, m) << " out of scope, skipping" << std::endl;); + TRACE("str", tout << "value tester " << mk_pp(aTester, m) << " out of scope, skipping" << std::endl;); continue; } @@ -9317,17 +9317,17 @@ expr * theory_str::gen_free_var_options(expr * freeVar, expr * len_indicator, // Z3_ast anEqc = get_eqc_value(t, aTester, anEqcHasValue); expr * aTester_eqc_value = get_eqc_value(aTester, anEqcHasValue); if (!anEqcHasValue) { - TRACE("t_str_detail", tout << "value tester " << mk_ismt2_pp(aTester, m) + TRACE("str", tout << "value tester " << mk_ismt2_pp(aTester, m) << " doesn't have an equivalence class value." << std::endl;); refresh_theory_var(aTester); expr * makeupAssert = gen_val_options(freeVar, len_indicator, aTester, len_valueStr, i); - TRACE("t_str_detail", tout << "var: " << mk_ismt2_pp(freeVar, m) << std::endl + TRACE("str", tout << "var: " << mk_ismt2_pp(freeVar, m) << std::endl << mk_ismt2_pp(makeupAssert, m) << std::endl;); assert_axiom(makeupAssert); } else { - TRACE("t_str_detail", tout << "value tester " << mk_ismt2_pp(aTester, m) + TRACE("str", tout << "value tester " << mk_ismt2_pp(aTester, m) << " == " << mk_ismt2_pp(aTester_eqc_value, m) << std::endl;); } } @@ -9355,7 +9355,7 @@ void theory_str::reduce_virtual_regex_in(expr * var, expr * regex, expr_ref_vect context & ctx = get_context(); ast_manager & mgr = get_manager(); - TRACE("t_str_detail", tout << "reduce regex " << mk_pp(regex, mgr) << " with respect to variable " << mk_pp(var, mgr) << std::endl;); + TRACE("str", tout << "reduce regex " << mk_pp(regex, mgr) << " with respect to variable " << mk_pp(var, mgr) << std::endl;); app * regexFuncDecl = to_app(regex); if (u.re.is_to_re(regexFuncDecl)) { @@ -9443,7 +9443,7 @@ void theory_str::gen_assign_unroll_reg(std::set & unrolls) { expr_ref_vector items(mgr); for (std::set::iterator itor = unrolls.begin(); itor != unrolls.end(); itor++) { expr * unrFunc = *itor; - TRACE("t_str_detail", tout << "generating assignment for unroll " << mk_pp(unrFunc, mgr) << std::endl;); + TRACE("str", tout << "generating assignment for unroll " << mk_pp(unrFunc, mgr) << std::endl;); expr * regexInUnr = to_app(unrFunc)->get_arg(0); expr * cntInUnr = to_app(unrFunc)->get_arg(1); @@ -9453,7 +9453,7 @@ void theory_str::gen_assign_unroll_reg(std::set & unrolls) { bool low_exists = lower_bound(cntInUnr, low); bool high_exists = upper_bound(cntInUnr, high); - TRACE("t_str_detail", + TRACE("str", tout << "unroll " << mk_pp(unrFunc, mgr) << std::endl; rational unrLenValue; bool unrLenValue_exists = get_len_value(unrFunc, unrLenValue); @@ -9599,7 +9599,7 @@ expr * theory_str::gen_unroll_conditional_options(expr * var, std::set & expr_ref moreAst(mk_string("more"), mgr); for (std::set::iterator itor = unrolls.begin(); itor != unrolls.end(); itor++) { expr_ref item(ctx.mk_eq_atom(var, *itor), mgr); - TRACE("t_str_detail", tout << "considering unroll " << mk_pp(item, mgr) << std::endl;); + TRACE("str", tout << "considering unroll " << mk_pp(item, mgr) << std::endl;); litems.push_back(item); } @@ -9611,7 +9611,7 @@ expr * theory_str::gen_unroll_conditional_options(expr * var, std::set & it != unroll_tries_map[var][unrolls].end(); ++it) { expr * tester = *it; bool inScope = (internal_unrollTest_vars.find(tester) != internal_unrollTest_vars.end()); - TRACE("t_str_detail", tout << "unroll test var " << mk_pp(tester, mgr) + TRACE("str", tout << "unroll test var " << mk_pp(tester, mgr) << (inScope ? " in scope" : " out of scope") << std::endl;); if (!inScope) { @@ -9644,13 +9644,13 @@ expr * theory_str::gen_unroll_conditional_options(expr * var, std::set & expr_ref rImp(gen_unroll_assign(var, lcmStr, tester, l, h), mgr); SASSERT(lImp); - TRACE("t_str_detail", tout << "lImp = " << mk_pp(lImp, mgr) << std::endl;); + TRACE("str", tout << "lImp = " << mk_pp(lImp, mgr) << std::endl;); SASSERT(rImp); - TRACE("t_str_detail", tout << "rImp = " << mk_pp(rImp, mgr) << std::endl;); + TRACE("str", tout << "rImp = " << mk_pp(rImp, mgr) << std::endl;); expr_ref toAssert(mgr.mk_or(mgr.mk_not(lImp), rImp), mgr); SASSERT(toAssert); - TRACE("t_str_detail", tout << "Making up assignments for variable which is equal to unbounded Unroll" << std::endl;); + TRACE("str", tout << "Making up assignments for variable which is equal to unbounded Unroll" << std::endl;); m_trail.push_back(toAssert); return toAssert; @@ -9662,7 +9662,7 @@ expr * theory_str::gen_unroll_conditional_options(expr * var, std::set & } else { zstring testerStr; u.str.is_string(testerVal, testerStr); - TRACE("t_str_detail", tout << "Tester [" << mk_pp(tester, mgr) << "] = " << testerStr << "\n";); + TRACE("str", tout << "Tester [" << mk_pp(tester, mgr) << "] = " << testerStr << "\n";); if (testerStr == "more") { litems.push_back(ctx.mk_eq_atom(tester, moreAst)); } @@ -9678,7 +9678,7 @@ expr * theory_str::gen_unroll_conditional_options(expr * var, std::set & SASSERT(rImp); expr_ref toAssert(mgr.mk_or(mgr.mk_not(lImp), rImp), mgr); SASSERT(toAssert); - TRACE("t_str_detail", tout << "Generating assignment for variable which is equal to unbounded Unroll" << std::endl;); + TRACE("str", tout << "Generating assignment for variable which is equal to unbounded Unroll" << std::endl;); m_trail.push_back(toAssert); return toAssert; } @@ -9687,11 +9687,11 @@ expr * theory_str::gen_unroll_assign(expr * var, zstring lcmStr, expr * testerVa context & ctx = get_context(); ast_manager & mgr = get_manager(); - TRACE("t_str_detail", tout << "entry: var = " << mk_pp(var, mgr) << ", lcmStr = " << lcmStr + TRACE("str", tout << "entry: var = " << mk_pp(var, mgr) << ", lcmStr = " << lcmStr << ", l = " << l << ", h = " << h << "\n";); if (m_params.m_AggressiveUnrollTesting) { - TRACE("t_str_detail", tout << "note: aggressive unroll testing is active" << std::endl;); + TRACE("str", tout << "note: aggressive unroll testing is active" << std::endl;); } expr_ref_vector orItems(mgr); @@ -9700,7 +9700,7 @@ expr * theory_str::gen_unroll_assign(expr * var, zstring lcmStr, expr * testerVa for (int i = l; i < h; i++) { zstring iStr = int_to_string(i); expr_ref testerEqAst(ctx.mk_eq_atom(testerVar, mk_string(iStr)), mgr); - TRACE("t_str_detail", tout << "testerEqAst = " << mk_pp(testerEqAst, mgr) << std::endl;); + TRACE("str", tout << "testerEqAst = " << mk_pp(testerEqAst, mgr) << std::endl;); if (m_params.m_AggressiveUnrollTesting) { literal l = mk_eq(testerVar, mk_string(iStr), false); ctx.mark_as_relevant(l); @@ -9711,15 +9711,15 @@ expr * theory_str::gen_unroll_assign(expr * var, zstring lcmStr, expr * testerVa zstring unrollStrInstance = get_unrolled_string(lcmStr, i); expr_ref x1(ctx.mk_eq_atom(testerEqAst, ctx.mk_eq_atom(var, mk_string(unrollStrInstance))), mgr); - TRACE("t_str_detail", tout << "x1 = " << mk_pp(x1, mgr) << std::endl;); + TRACE("str", tout << "x1 = " << mk_pp(x1, mgr) << std::endl;); andItems.push_back(x1); expr_ref x2(ctx.mk_eq_atom(testerEqAst, ctx.mk_eq_atom(mk_strlen(var), mk_int(i * lcmStr.length()))), mgr); - TRACE("t_str_detail", tout << "x2 = " << mk_pp(x2, mgr) << std::endl;); + TRACE("str", tout << "x2 = " << mk_pp(x2, mgr) << std::endl;); andItems.push_back(x2); } expr_ref testerEqMore(ctx.mk_eq_atom(testerVar, mk_string("more")), mgr); - TRACE("t_str_detail", tout << "testerEqMore = " << mk_pp(testerEqMore, mgr) << std::endl;); + TRACE("str", tout << "testerEqMore = " << mk_pp(testerEqMore, mgr) << std::endl;); if (m_params.m_AggressiveUnrollTesting) { literal l = mk_eq(testerVar, mk_string("more"), false); ctx.mark_as_relevant(l); @@ -9732,15 +9732,15 @@ expr * theory_str::gen_unroll_assign(expr * var, zstring lcmStr, expr * testerVa //Z3_mk_ge(mk_length(t, var), mk_int(ctx, nextLowerLenBound)) m_autil.mk_ge(m_autil.mk_add(mk_strlen(var), mk_int(-1 * nextLowerLenBound)), mk_int(0)) ), mgr); - TRACE("t_str_detail", tout << "more2 = " << mk_pp(more2, mgr) << std::endl;); + TRACE("str", tout << "more2 = " << mk_pp(more2, mgr) << std::endl;); andItems.push_back(more2); expr_ref finalOR(mgr.mk_or(orItems.size(), orItems.c_ptr()), mgr); - TRACE("t_str_detail", tout << "finalOR = " << mk_pp(finalOR, mgr) << std::endl;); + TRACE("str", tout << "finalOR = " << mk_pp(finalOR, mgr) << std::endl;); andItems.push_back(mk_or(orItems)); expr_ref finalAND(mgr.mk_and(andItems.size(), andItems.c_ptr()), mgr); - TRACE("t_str_detail", tout << "finalAND = " << mk_pp(finalAND, mgr) << std::endl;); + TRACE("str", tout << "finalAND = " << mk_pp(finalAND, mgr) << std::endl;); // doing the following avoids a segmentation fault m_trail.push_back(finalAND); @@ -9761,7 +9761,7 @@ expr * theory_str::gen_len_test_options(expr * freeVar, expr * indicator, int tr int l = (tries - 1) * distance; int h = tries * distance; - TRACE("t_str_detail", + TRACE("str", tout << "building andList and orList" << std::endl; if (m_params.m_AggressiveLengthTesting) { tout << "note: aggressive length testing is active" << std::endl; @@ -9848,11 +9848,11 @@ expr * theory_str::gen_len_test_options(expr * freeVar, expr * indicator, int tr and_items.push_back(andList.get(i)); } - TRACE("t_str_detail", tout << "check: " << mk_pp(mk_and(and_items), m) << std::endl;); + TRACE("str", tout << "check: " << mk_pp(mk_and(and_items), m) << std::endl;); expr_ref lenTestAssert = mk_and(and_items); SASSERT(lenTestAssert); - TRACE("t_str_detail", tout << "crash avoidance lenTestAssert: " << mk_pp(lenTestAssert, m) << std::endl;); + TRACE("str", tout << "crash avoidance lenTestAssert: " << mk_pp(lenTestAssert, m) << std::endl;); int testerCount = tries - 1; if (testerCount > 0) { @@ -9861,10 +9861,10 @@ expr * theory_str::gen_len_test_options(expr * freeVar, expr * indicator, int tr for (int i = 0; i < testerCount; ++i) { expr * indicator = fvar_lenTester_map[freeVar][i]; if (internal_variable_set.find(indicator) == internal_variable_set.end()) { - TRACE("t_str_detail", tout << "indicator " << mk_pp(indicator, m) << " out of scope; continuing" << std::endl;); + TRACE("str", tout << "indicator " << mk_pp(indicator, m) << " out of scope; continuing" << std::endl;); continue; } else { - TRACE("t_str_detail", tout << "indicator " << mk_pp(indicator, m) << " in scope" << std::endl;); + TRACE("str", tout << "indicator " << mk_pp(indicator, m) << " in scope" << std::endl;); and_items_LHS.push_back(ctx.mk_eq_atom(indicator, moreAst)); } } @@ -9872,10 +9872,10 @@ expr * theory_str::gen_len_test_options(expr * freeVar, expr * indicator, int tr SASSERT(assertL); expr * finalAxiom = m.mk_or(m.mk_not(assertL), lenTestAssert.get()); SASSERT(finalAxiom != NULL); - TRACE("t_str_detail", tout << "crash avoidance finalAxiom: " << mk_pp(finalAxiom, m) << std::endl;); + TRACE("str", tout << "crash avoidance finalAxiom: " << mk_pp(finalAxiom, m) << std::endl;); return finalAxiom; } else { - TRACE("t_str_detail", tout << "crash avoidance lenTestAssert.get(): " << mk_pp(lenTestAssert.get(), m) << std::endl;); + TRACE("str", tout << "crash avoidance lenTestAssert.get(): " << mk_pp(lenTestAssert.get(), m) << std::endl;); m_trail.push_back(lenTestAssert.get()); return lenTestAssert.get(); } @@ -9892,7 +9892,7 @@ expr_ref theory_str::binary_search_case_split(expr * freeVar, expr * tester, bin rational N_plus_one = N + rational::one(); expr_ref lenFreeVar(mk_strlen(freeVar), m); - TRACE("t_str_binary_search", tout << "create case split for free var " << mk_pp(freeVar, m) + TRACE("str", tout << "create case split for free var " << mk_pp(freeVar, m) << " over " << mk_pp(tester, m) << " with midpoint " << N << std::endl;); expr_ref_vector combinedCaseSplit(m); @@ -9924,7 +9924,7 @@ expr_ref theory_str::binary_search_case_split(expr * freeVar, expr * tester, bin expr_ref final_term(mk_and(combinedCaseSplit), m); SASSERT(final_term); - TRACE("t_str_binary_search", tout << "final term: " << mk_pp(final_term, m) << std::endl;); + TRACE("str", tout << "final term: " << mk_pp(final_term, m) << std::endl;); return final_term; } @@ -9933,7 +9933,7 @@ expr * theory_str::binary_search_length_test(expr * freeVar, expr * previousLenT context & ctx = get_context(); if (binary_search_len_tester_stack.contains(freeVar) && !binary_search_len_tester_stack[freeVar].empty()) { - TRACE("t_str_binary_search", tout << "checking existing length testers for " << mk_pp(freeVar, m) << std::endl; + TRACE("str", tout << "checking existing length testers for " << mk_pp(freeVar, m) << std::endl; for (ptr_vector::const_iterator it = binary_search_len_tester_stack[freeVar].begin(); it != binary_search_len_tester_stack[freeVar].end(); ++it) { expr * tester = *it; @@ -9959,35 +9959,35 @@ expr * theory_str::binary_search_length_test(expr * freeVar, expr * previousLenT expr * lastTesterValue = get_eqc_value(lastTester, lastTesterHasEqcValue); zstring lastTesterConstant; if (!lastTesterHasEqcValue) { - TRACE("t_str_binary_search", tout << "length tester " << mk_pp(lastTester, m) << " at top of stack doesn't have an EQC value yet" << std::endl;); + TRACE("str", tout << "length tester " << mk_pp(lastTester, m) << " at top of stack doesn't have an EQC value yet" << std::endl;); // check previousLenTester if (previousLenTester == lastTester) { lastTesterConstant = previousLenTesterValue; - TRACE("t_str_binary_search", tout << "invoked with previousLenTester info matching top of stack" << std::endl;); + TRACE("str", tout << "invoked with previousLenTester info matching top of stack" << std::endl;); } else { - TRACE("t_str_binary_search", tout << "WARNING: unexpected reordering of length testers!" << std::endl;); + TRACE("str", tout << "WARNING: unexpected reordering of length testers!" << std::endl;); UNREACHABLE(); return NULL; } } else { u.str.is_string(lastTesterValue, lastTesterConstant); } - TRACE("t_str_binary_search", tout << "last length tester is assigned \"" << lastTesterConstant << "\"" << "\n";); + TRACE("str", tout << "last length tester is assigned \"" << lastTesterConstant << "\"" << "\n";); if (lastTesterConstant == "more" || lastTesterConstant == "less") { // use the previous bounds info to generate a new midpoint binary_search_info lastBounds; if (!binary_search_len_tester_info.find(lastTester, lastBounds)) { // unexpected - TRACE("t_str_binary_search", tout << "WARNING: no bounds information available for last tester!" << std::endl;); + TRACE("str", tout << "WARNING: no bounds information available for last tester!" << std::endl;); UNREACHABLE(); } - TRACE("t_str_binary_search", tout << "last bounds are [" << lastBounds.lowerBound << " | " << lastBounds.midPoint << " | " << lastBounds.upperBound << "]!" << lastBounds.windowSize << std::endl;); + TRACE("str", tout << "last bounds are [" << lastBounds.lowerBound << " | " << lastBounds.midPoint << " | " << lastBounds.upperBound << "]!" << lastBounds.windowSize << std::endl;); binary_search_info newBounds; expr * newTester; if (lastTesterConstant == "more") { // special case: if the midpoint, upper bound, and window size are all equal, // we double the window size and adjust the bounds if (lastBounds.midPoint == lastBounds.upperBound && lastBounds.upperBound == lastBounds.windowSize) { - TRACE("t_str_binary_search", tout << "search hit window size; expanding" << std::endl;); + TRACE("str", tout << "search hit window size; expanding" << std::endl;); newBounds.lowerBound = lastBounds.windowSize + rational::one(); newBounds.windowSize = lastBounds.windowSize * rational(2); newBounds.upperBound = newBounds.windowSize; @@ -10024,7 +10024,7 @@ expr * theory_str::binary_search_length_test(expr * freeVar, expr * previousLenT } refresh_theory_var(newTester); } - TRACE("t_str_binary_search", tout << "new bounds are [" << newBounds.lowerBound << " | " << newBounds.midPoint << " | " << newBounds.upperBound << "]!" << newBounds.windowSize << std::endl;); + TRACE("str", tout << "new bounds are [" << newBounds.lowerBound << " | " << newBounds.midPoint << " | " << newBounds.upperBound << "]!" << newBounds.windowSize << std::endl;); binary_search_len_tester_stack[freeVar].push_back(newTester); m_trail_stack.push(binary_search_trail(binary_search_len_tester_stack, freeVar)); binary_search_len_tester_info.insert(newTester, newBounds); @@ -10036,16 +10036,16 @@ expr * theory_str::binary_search_length_test(expr * freeVar, expr * previousLenT // ctx.mk_th_case_split(case_split_literals.size(), case_split_literals.c_ptr()); return next_case_split; } else { // lastTesterConstant is a concrete value - TRACE("t_str", tout << "length is fixed; generating models for free var" << std::endl;); + TRACE("str", tout << "length is fixed; generating models for free var" << std::endl;); // defensive check that this length did not converge on a negative value. binary_search_info lastBounds; if (!binary_search_len_tester_info.find(lastTester, lastBounds)) { // unexpected - TRACE("t_str_binary_search", tout << "WARNING: no bounds information available for last tester!" << std::endl;); + TRACE("str", tout << "WARNING: no bounds information available for last tester!" << std::endl;); UNREACHABLE(); } if (lastBounds.midPoint.is_neg()) { - TRACE("t_str_binary_search", tout << "WARNING: length search converged on a negative value. Negating this constraint." << std::endl;); + TRACE("str", tout << "WARNING: length search converged on a negative value. Negating this constraint." << std::endl;); expr_ref axiom(m_autil.mk_ge(mk_strlen(freeVar), m_autil.mk_numeral(rational::zero(), true)), m); return axiom; } @@ -10055,7 +10055,7 @@ expr * theory_str::binary_search_length_test(expr * freeVar, expr * previousLenT } } else { // no length testers yet - TRACE("t_str_binary_search", tout << "no length testers for " << mk_pp(freeVar, m) << std::endl;); + TRACE("str", tout << "no length testers for " << mk_pp(freeVar, m) << std::endl;); binary_search_len_tester_stack.insert(freeVar, ptr_vector()); expr * firstTester; @@ -10098,15 +10098,15 @@ expr * theory_str::gen_len_val_options_for_free_var(expr * freeVar, expr * lenTe ast_manager & m = get_manager(); - TRACE("t_str_detail", tout << "gen for free var " << mk_ismt2_pp(freeVar, m) << std::endl;); + TRACE("str", tout << "gen for free var " << mk_ismt2_pp(freeVar, m) << std::endl;); if (m_params.m_UseBinarySearch) { - TRACE("t_str_detail", tout << "using binary search heuristic" << std::endl;); + TRACE("str", tout << "using binary search heuristic" << std::endl;); return binary_search_length_test(freeVar, lenTesterInCbEq, lenTesterValue); } else { bool map_effectively_empty = false; if (!fvar_len_count_map.contains(freeVar)) { - TRACE("t_str_detail", tout << "fvar_len_count_map is empty" << std::endl;); + TRACE("str", tout << "fvar_len_count_map is empty" << std::endl;); map_effectively_empty = true; } @@ -10120,18 +10120,18 @@ expr * theory_str::gen_len_val_options_for_free_var(expr * freeVar, expr * lenTe for (ptr_vector::iterator it = indicator_set.begin(); it != indicator_set.end(); ++it) { expr * indicator = *it; if (internal_variable_set.find(indicator) != internal_variable_set.end()) { - TRACE("t_str_detail", tout <<"found active internal variable " << mk_ismt2_pp(indicator, m) + TRACE("str", tout <<"found active internal variable " << mk_ismt2_pp(indicator, m) << " in fvar_lenTester_map[freeVar]" << std::endl;); map_effectively_empty = false; break; } } - CTRACE("t_str_detail", map_effectively_empty, tout << "all variables in fvar_lenTester_map[freeVar] out of scope" << std::endl;); + CTRACE("str", map_effectively_empty, tout << "all variables in fvar_lenTester_map[freeVar] out of scope" << std::endl;); } if (map_effectively_empty) { // no length assertions for this free variable have ever been added. - TRACE("t_str_detail", tout << "no length assertions yet" << std::endl;); + TRACE("str", tout << "no length assertions yet" << std::endl;); fvar_len_count_map.insert(freeVar, 1); unsigned int testNum = fvar_len_count_map[freeVar]; @@ -10148,13 +10148,13 @@ expr * theory_str::gen_len_val_options_for_free_var(expr * freeVar, expr * lenTe SASSERT(lenTestAssert != NULL); return lenTestAssert; } else { - TRACE("t_str_detail", tout << "found previous in-scope length assertions" << std::endl;); + TRACE("str", tout << "found previous in-scope length assertions" << std::endl;); expr * effectiveLenInd = NULL; zstring effectiveLenIndiStr(""); int lenTesterCount = (int) fvar_lenTester_map[freeVar].size(); - TRACE("t_str_detail", + TRACE("str", tout << lenTesterCount << " length testers in fvar_lenTester_map[" << mk_pp(freeVar, m) << "]:" << std::endl; for (int i = 0; i < lenTesterCount; ++i) { expr * len_indicator = fvar_lenTester_map[freeVar][i]; @@ -10170,13 +10170,13 @@ expr * theory_str::gen_len_val_options_for_free_var(expr * freeVar, expr * lenTe expr * len_indicator_pre = fvar_lenTester_map[freeVar][i]; // check whether this is in scope as well if (internal_variable_set.find(len_indicator_pre) == internal_variable_set.end()) { - TRACE("t_str_detail", tout << "length indicator " << mk_pp(len_indicator_pre, m) << " not in scope" << std::endl;); + TRACE("str", tout << "length indicator " << mk_pp(len_indicator_pre, m) << " not in scope" << std::endl;); continue; } bool indicatorHasEqcValue = false; expr * len_indicator_value = get_eqc_value(len_indicator_pre, indicatorHasEqcValue); - TRACE("t_str_detail", tout << "length indicator " << mk_ismt2_pp(len_indicator_pre, m) << + TRACE("str", tout << "length indicator " << mk_ismt2_pp(len_indicator_pre, m) << " = " << mk_ismt2_pp(len_indicator_value, m) << std::endl;); if (indicatorHasEqcValue) { zstring len_pIndiStr; @@ -10188,7 +10188,7 @@ expr * theory_str::gen_len_val_options_for_free_var(expr * freeVar, expr * lenTe } } else { if (lenTesterInCbEq != len_indicator_pre) { - TRACE("t_str", tout << "WARNING: length indicator " << mk_ismt2_pp(len_indicator_pre, m) + TRACE("str", tout << "WARNING: length indicator " << mk_ismt2_pp(len_indicator_pre, m) << " does not have an equivalence class value." << " i = " << i << ", lenTesterCount = " << lenTesterCount << std::endl;); if (i > 0) { @@ -10196,7 +10196,7 @@ expr * theory_str::gen_len_val_options_for_free_var(expr * freeVar, expr * lenTe bool effectiveHasEqcValue; expr * effective_eqc_value = get_eqc_value(effectiveLenInd, effectiveHasEqcValue); bool effectiveInScope = (internal_variable_set.find(effectiveLenInd) != internal_variable_set.end()); - TRACE("t_str_detail", tout << "checking effective length indicator " << mk_pp(effectiveLenInd, m) << ": " + TRACE("str", tout << "checking effective length indicator " << mk_pp(effectiveLenInd, m) << ": " << (effectiveInScope ? "in scope" : "NOT in scope") << ", "; if (effectiveHasEqcValue) { tout << "~= " << mk_pp(effective_eqc_value, m); @@ -10227,11 +10227,11 @@ expr * theory_str::gen_len_val_options_for_free_var(expr * freeVar, expr * lenTe } // !indicatorHasEqcValue } // for (i : [0..lenTesterCount-1]) if (effectiveLenIndiStr == "more" || effectiveLenIndiStr == "") { - TRACE("t_str", tout << "length is not fixed; generating length tester options for free var" << std::endl;); + TRACE("str", tout << "length is not fixed; generating length tester options for free var" << std::endl;); expr_ref indicator(m); unsigned int testNum = 0; - TRACE("t_str", tout << "effectiveLenIndiStr = " << effectiveLenIndiStr + TRACE("str", tout << "effectiveLenIndiStr = " << effectiveLenIndiStr << ", i = " << i << ", lenTesterCount = " << lenTesterCount << "\n";); if (i == lenTesterCount) { @@ -10249,7 +10249,7 @@ expr * theory_str::gen_len_val_options_for_free_var(expr * freeVar, expr * lenTe SASSERT(lenTestAssert != NULL); return lenTestAssert; } else { - TRACE("t_str", tout << "length is fixed; generating models for free var" << std::endl;); + TRACE("str", tout << "length is fixed; generating models for free var" << std::endl;); // length is fixed expr * valueAssert = gen_free_var_options(freeVar, effectiveLenInd, effectiveLenIndiStr, NULL, zstring("")); return valueAssert; @@ -10318,7 +10318,7 @@ void theory_str::process_free_var(std::map & freeVar_map) { } } if (duplicated && dupVar != NULL) { - TRACE("t_str_detail", tout << "Duplicated free variable found:" << mk_ismt2_pp(freeVar, m) + TRACE("str", tout << "Duplicated free variable found:" << mk_ismt2_pp(freeVar, m) << " = " << mk_ismt2_pp(dupVar, m) << " (SKIP)" << std::endl;); continue; } else { @@ -10428,7 +10428,7 @@ void theory_str::get_eqc_simpleUnroll(expr * n, expr * &constStr, std::setget_owner(), get_manager()) << + TRACE("str", tout << "mk_value for: " << mk_ismt2_pp(n->get_owner(), get_manager()) << " (sort " << mk_ismt2_pp(get_manager().get_sort(n->get_owner()), get_manager()) << ")" << std::endl;); ast_manager & m = get_manager(); context & ctx = get_context(); @@ -10489,7 +10489,7 @@ model_value_proc * theory_str::mk_value(enode * n, model_generator & mg) { if (val != NULL) { return alloc(expr_wrapper_proc, val); } else { - TRACE("t_str", tout << "WARNING: failed to find a concrete value, falling back" << std::endl;); + TRACE("str", tout << "WARNING: failed to find a concrete value, falling back" << std::endl;); return alloc(expr_wrapper_proc, to_app(mk_string("**UNUSED**"))); } } From 3fe49137d0f678b09340d0c20beded6521812c64 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Mon, 24 Apr 2017 19:25:35 -0400 Subject: [PATCH 373/410] fix trace typos --- src/ast/seq_decl_plugin.h | 4 ++++ src/ast/static_features.cpp | 7 +++++++ src/ast/static_features.h | 4 ++++ src/smt/params/smt_params.cpp | 3 ++- src/smt/params/smt_params.h | 10 +++++++++- src/smt/params/smt_params_helper.pyg | 1 + src/smt/smt_setup.cpp | 27 +++++++++++++++++++-------- src/smt/smt_setup.h | 2 +- src/smt/theory_str.cpp | 7 +++---- 9 files changed, 50 insertions(+), 15 deletions(-) diff --git a/src/ast/seq_decl_plugin.h b/src/ast/seq_decl_plugin.h index 2882e905d..030b244e5 100644 --- a/src/ast/seq_decl_plugin.h +++ b/src/ast/seq_decl_plugin.h @@ -273,6 +273,10 @@ public: bool is_in_re(expr const* n) const { return is_app_of(n, m_fid, OP_SEQ_IN_RE); } bool is_unit(expr const* n) const { return is_app_of(n, m_fid, OP_SEQ_UNIT); } + bool is_string_term(expr const * n) const { + sort * s = get_sort(n); + return is_sort_of(s, m_fid, _STRING_SORT); + } MATCH_BINARY(is_concat); MATCH_UNARY(is_length); diff --git a/src/ast/static_features.cpp b/src/ast/static_features.cpp index 328128794..9958b3d50 100644 --- a/src/ast/static_features.cpp +++ b/src/ast/static_features.cpp @@ -25,6 +25,7 @@ static_features::static_features(ast_manager & m): m_bvutil(m), m_arrayutil(m), m_fpautil(m), + m_sequtil(m), m_bfid(m.get_basic_family_id()), m_afid(m.mk_family_id("arith")), m_lfid(m.mk_family_id("label")), @@ -77,6 +78,8 @@ void static_features::reset() { m_has_real = false; m_has_bv = false; m_has_fpa = false; + m_has_str = false; + m_has_seq_non_str = false; m_has_arrays = false; m_arith_k_sum .reset(); m_num_arith_terms = 0; @@ -279,6 +282,10 @@ void static_features::update_core(expr * e) { m_has_fpa = true; if (!m_has_arrays && m_arrayutil.is_array(e)) m_has_arrays = true; + if (!m_has_str && m_sequtil.str.is_string_term(e)) + m_has_str = true; + if (!m_has_seq_non_str && m_sequtil.is_seq(e)) + m_has_seq_non_str = true; if (is_app(e)) { family_id fid = to_app(e)->get_family_id(); mark_theory(fid); diff --git a/src/ast/static_features.h b/src/ast/static_features.h index 8b20c5463..e7f69e041 100644 --- a/src/ast/static_features.h +++ b/src/ast/static_features.h @@ -24,6 +24,7 @@ Revision History: #include"bv_decl_plugin.h" #include"array_decl_plugin.h" #include"fpa_decl_plugin.h" +#include"seq_decl_plugin.h" #include"map.h" struct static_features { @@ -32,6 +33,7 @@ struct static_features { bv_util m_bvutil; array_util m_arrayutil; fpa_util m_fpautil; + seq_util m_sequtil; family_id m_bfid; family_id m_afid; family_id m_lfid; @@ -77,6 +79,8 @@ struct static_features { bool m_has_real; // bool m_has_bv; // bool m_has_fpa; // + bool m_has_str; // has String-typed terms + bool m_has_seq_non_str; // has non-String-typed Sequence terms bool m_has_arrays; // rational m_arith_k_sum; // sum of the numerals in arith atoms. unsigned m_num_arith_terms; diff --git a/src/smt/params/smt_params.cpp b/src/smt/params/smt_params.cpp index dcf396531..b8d5fe7b5 100644 --- a/src/smt/params/smt_params.cpp +++ b/src/smt/params/smt_params.cpp @@ -41,6 +41,7 @@ void smt_params::updt_local_params(params_ref const & _p) { m_max_conflicts = p.max_conflicts(); m_core_validate = p.core_validate(); m_logic = _p.get_sym("logic", m_logic); + m_string_solver = _p.get_sym("string_solver", m_string_solver); model_params mp(_p); m_model_compact = mp.compact(); if (_p.get_bool("arith.greatest_error_pivot", false)) @@ -157,4 +158,4 @@ void smt_params::display(std::ostream & out) const { DISPLAY_PARAM(m_check_at_labels); DISPLAY_PARAM(m_dump_goal_as_smt); DISPLAY_PARAM(m_auto_config); -} \ No newline at end of file +} diff --git a/src/smt/params/smt_params.h b/src/smt/params/smt_params.h index a86123a33..295e141cc 100644 --- a/src/smt/params/smt_params.h +++ b/src/smt/params/smt_params.h @@ -216,6 +216,13 @@ struct smt_params : public preprocessor_params, bool m_dump_goal_as_smt; bool m_auto_config; + // ----------------------------------- + // + // Solver selection + // + // ----------------------------------- + symbol m_string_solver; + smt_params(params_ref const & p = params_ref()): m_display_proof(false), m_display_dot_proof(false), @@ -286,7 +293,8 @@ struct smt_params : public preprocessor_params, m_at_labels_cex(false), m_check_at_labels(false), m_dump_goal_as_smt(false), - m_auto_config(true) { + m_auto_config(true), + m_string_solver(symbol("auto")){ updt_local_params(p); } diff --git a/src/smt/params/smt_params_helper.pyg b/src/smt/params/smt_params_helper.pyg index 133d1d527..f99c2df16 100644 --- a/src/smt/params/smt_params_helper.pyg +++ b/src/smt/params/smt_params_helper.pyg @@ -62,6 +62,7 @@ def_module_params(module_name='smt', ('dack.gc_inv_decay', DOUBLE, 0.8, 'Dynamic ackermannization garbage collection decay'), ('dack.threshold', UINT, 10, ' number of times the congruence rule must be used before Leibniz\'s axiom is expanded'), ('core.validate', BOOL, False, 'validate unsat core produced by SMT context'), + ('string_solver', SYMBOL, 'auto', 'solver for string/sequence theories. options are: \'z3str3\' (specialized string solver), \'seq\' (sequence solver), \'auto\' (use static features to choose best solver)'), ('str.strong_arrangements', BOOL, True, 'assert equivalences instead of implications when generating string arrangement axioms'), ('str.aggressive_length_testing', BOOL, False, 'prioritize testing concrete length values over generating more options'), ('str.aggressive_value_testing', BOOL, False, 'prioritize testing concrete string constant values over generating more options'), diff --git a/src/smt/smt_setup.cpp b/src/smt/smt_setup.cpp index 78a295e27..c295801ad 100644 --- a/src/smt/smt_setup.cpp +++ b/src/smt/smt_setup.cpp @@ -206,7 +206,7 @@ namespace smt { void setup::setup_QF_BVRE() { setup_QF_BV(); setup_QF_LIA(); - setup_seq(); + m_context.register_plugin(alloc(theory_seq, m_manager)); } void setup::setup_QF_UF(static_features const & st) { @@ -824,10 +824,21 @@ namespace smt { m_context.register_plugin(mk_theory_dl(m_manager)); } - void setup::setup_seq() { - // TODO proper negotiation of theory_str vs. theory_seq - //m_context.register_plugin(alloc(theory_seq, m_manager)); - setup_str(); + void setup::setup_seq(static_features const & st) { + // check params for what to do here when it's ambiguous + if (m_params.m_string_solver == "z3str3") { + setup_str(); + } else if (m_params.m_string_solver == "seq") { + m_context.register_plugin(alloc(theory_seq, m_manager)); + } else if (m_params.m_string_solver == "auto") { + if (st.m_has_seq_non_str) { + m_context.register_plugin(alloc(theory_seq, m_manager)); + } else { + setup_str(); + } + } else { + throw default_exception("invalid parameter for smt.string_solver, valid options are 'z3str3', 'seq', 'auto'"); + } } void setup::setup_card() { @@ -850,10 +861,10 @@ namespace smt { setup_bv(); setup_datatypes(); setup_dl(); - setup_seq(); + // setup_seq() + m_context.register_plugin(alloc(theory_seq, m_manager)); setup_card(); setup_fpa(); - setup_str(); } void setup::setup_unknown(static_features & st) { @@ -866,7 +877,7 @@ namespace smt { setup_datatypes(); setup_bv(); setup_dl(); - setup_seq(); + setup_seq(st); setup_card(); setup_fpa(); return; diff --git a/src/smt/smt_setup.h b/src/smt/smt_setup.h index 031c65c1f..d30c896e5 100644 --- a/src/smt/smt_setup.h +++ b/src/smt/smt_setup.h @@ -94,7 +94,7 @@ namespace smt { void setup_bv(); void setup_arith(); void setup_dl(); - void setup_seq(); + void setup_seq(static_features const & st); void setup_card(); void setup_i_arith(); void setup_mi_arith(); diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 01123a22c..be268ec5c 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -4844,7 +4844,7 @@ void theory_str::check_contain_by_eqc_val(expr * varNode, expr * constNode) { // we only want to inspect the Contains terms where either of strAst or substrAst // are equal to varNode. - TRACE("t_str_detail", tout << "considering Contains with strAst = "str", substrAst = " << mk_pp(substrAst, m) << "..." << std::endl;); + TRACE("t_str_detail", tout << "considering Contains with strAst = " << mk_pp(strAst, m) << ", substrAst = " << mk_pp(substrAst, m) << "..." << std::endl;); if (varNode != strAst && varNode != substrAst) { TRACE("str", tout << "varNode not equal to strAst or substrAst, skip" << std::endl;); @@ -4873,7 +4873,7 @@ void theory_str::check_contain_by_eqc_val(expr * varNode, expr * constNode) { zstring subStrConst; u.str.is_string(substrValue, subStrConst); - TRACE("t_str_detail", tout << "strConst = "str", subStrConst = " << subStrConst << "\n";); + TRACE("t_str_detail", tout << "strConst = " << strConst << ", subStrConst = " << subStrConst << "\n";); if (strConst.contains(subStrConst)) { //implyR = ctx.mk_eq(ctx, boolVar, Z3_mk_true(ctx)); @@ -4983,7 +4983,7 @@ void theory_str::check_contain_by_substr(expr * varNode, expr_ref_vector & willE // we only want to inspect the Contains terms where either of strAst or substrAst // are equal to varNode. - TRACE("t_str_detail", tout << "considering Contains with strAst = "str", substrAst = " << mk_pp(substrAst, m) << "..." << std::endl;); + TRACE("t_str_detail", tout << "considering Contains with strAst = " << mk_pp(strAst, m) << ", substrAst = " << mk_pp(substrAst, m) << "..." << std::endl;); if (varNode != strAst && varNode != substrAst) { TRACE("str", tout << "varNode not equal to strAst or substrAst, skip" << std::endl;); @@ -6181,7 +6181,6 @@ void nfa::convert_re(expr * e, unsigned & start, unsigned & end, seq_util & u) { } make_transition(last, str[(str.length() - 1)], end); TRACE("str", tout << "string transition " << last << "--" << str[(str.length() - 1)] << "--> " << end << "\n";); - TRACE("t_str_rw", tout << "str", end = " << end << std::endl;); } else { TRACE("str", tout << "invalid string constant in Str2Reg" << std::endl;); m_valid = false; From 54e28a4fe73fd807f1c2aebd12e354dcb46c02b3 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Mon, 24 Apr 2017 21:02:22 -0400 Subject: [PATCH 374/410] string/sequence static features test --- src/ast/seq_decl_plugin.h | 11 +++++++++++ src/ast/static_features.cpp | 3 ++- src/smt/smt_setup.cpp | 16 +++++++++++++--- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/ast/seq_decl_plugin.h b/src/ast/seq_decl_plugin.h index 030b244e5..52abb2c45 100644 --- a/src/ast/seq_decl_plugin.h +++ b/src/ast/seq_decl_plugin.h @@ -278,6 +278,17 @@ public: return is_sort_of(s, m_fid, _STRING_SORT); } + bool is_non_string_sequence(expr const * n) const { + if (is_string_term(n)) + return false; + + sort * s = get_sort(n); + if (u.is_seq(s) && !u.is_string(s)) { + return true; + } + return false; + } + MATCH_BINARY(is_concat); MATCH_UNARY(is_length); MATCH_TERNARY(is_extract); diff --git a/src/ast/static_features.cpp b/src/ast/static_features.cpp index 9958b3d50..daf20e095 100644 --- a/src/ast/static_features.cpp +++ b/src/ast/static_features.cpp @@ -284,8 +284,9 @@ void static_features::update_core(expr * e) { m_has_arrays = true; if (!m_has_str && m_sequtil.str.is_string_term(e)) m_has_str = true; - if (!m_has_seq_non_str && m_sequtil.is_seq(e)) + if (!m_has_seq_non_str && m_sequtil.str.is_non_string_sequence(e)) { m_has_seq_non_str = true; + } if (is_app(e)) { family_id fid = to_app(e)->get_family_id(); mark_theory(fid); diff --git a/src/smt/smt_setup.cpp b/src/smt/smt_setup.cpp index c295801ad..dd94d9473 100644 --- a/src/smt/smt_setup.cpp +++ b/src/smt/smt_setup.cpp @@ -832,6 +832,7 @@ namespace smt { m_context.register_plugin(alloc(theory_seq, m_manager)); } else if (m_params.m_string_solver == "auto") { if (st.m_has_seq_non_str) { + NOT_IMPLEMENTED_YET(); m_context.register_plugin(alloc(theory_seq, m_manager)); } else { setup_str(); @@ -856,13 +857,15 @@ namespace smt { } void setup::setup_unknown() { + static_features st(m_manager); + st.collect(m_context.get_num_asserted_formulas(), m_context.get_asserted_formulas()); + setup_arith(); setup_arrays(); setup_bv(); setup_datatypes(); setup_dl(); - // setup_seq() - m_context.register_plugin(alloc(theory_seq, m_manager)); + setup_seq(st); setup_card(); setup_fpa(); } @@ -966,7 +969,14 @@ namespace smt { return; } - // TODO setup_str() by features + if (st.num_theories() == 2 && st.m_has_str && !st.m_has_seq_non_str) { + setup_QF_S(); + return; + } + + if (st.num_theories() == 2 && st.m_has_seq_non_str) { + m_context.register_plugin(alloc(theory_seq, m_manager)); + } setup_unknown(); } From 6fececaad99d8972e2b0780ef32a7c19ad9620fc Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Mon, 24 Apr 2017 21:47:31 -0400 Subject: [PATCH 375/410] fix str/seq parameter config --- src/smt/params/smt_params.cpp | 2 +- src/smt/smt_setup.cpp | 10 ---------- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/src/smt/params/smt_params.cpp b/src/smt/params/smt_params.cpp index b8d5fe7b5..92ff1de90 100644 --- a/src/smt/params/smt_params.cpp +++ b/src/smt/params/smt_params.cpp @@ -41,7 +41,7 @@ void smt_params::updt_local_params(params_ref const & _p) { m_max_conflicts = p.max_conflicts(); m_core_validate = p.core_validate(); m_logic = _p.get_sym("logic", m_logic); - m_string_solver = _p.get_sym("string_solver", m_string_solver); + m_string_solver = p.string_solver(); model_params mp(_p); m_model_compact = mp.compact(); if (_p.get_bool("arith.greatest_error_pivot", false)) diff --git a/src/smt/smt_setup.cpp b/src/smt/smt_setup.cpp index dd94d9473..4d02218bf 100644 --- a/src/smt/smt_setup.cpp +++ b/src/smt/smt_setup.cpp @@ -832,7 +832,6 @@ namespace smt { m_context.register_plugin(alloc(theory_seq, m_manager)); } else if (m_params.m_string_solver == "auto") { if (st.m_has_seq_non_str) { - NOT_IMPLEMENTED_YET(); m_context.register_plugin(alloc(theory_seq, m_manager)); } else { setup_str(); @@ -969,15 +968,6 @@ namespace smt { return; } - if (st.num_theories() == 2 && st.m_has_str && !st.m_has_seq_non_str) { - setup_QF_S(); - return; - } - - if (st.num_theories() == 2 && st.m_has_seq_non_str) { - m_context.register_plugin(alloc(theory_seq, m_manager)); - } - setup_unknown(); } From 334677a7eb4f5a2395cb621dfe24843561e3fca7 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Thu, 27 Apr 2017 13:58:36 -0400 Subject: [PATCH 376/410] fix is_string_term() --- src/ast/seq_decl_plugin.h | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/ast/seq_decl_plugin.h b/src/ast/seq_decl_plugin.h index 52abb2c45..833455ff4 100644 --- a/src/ast/seq_decl_plugin.h +++ b/src/ast/seq_decl_plugin.h @@ -275,18 +275,12 @@ public: bool is_string_term(expr const * n) const { sort * s = get_sort(n); - return is_sort_of(s, m_fid, _STRING_SORT); + return (u.is_seq(s) && u.is_string(s)); } bool is_non_string_sequence(expr const * n) const { - if (is_string_term(n)) - return false; - sort * s = get_sort(n); - if (u.is_seq(s) && !u.is_string(s)) { - return true; - } - return false; + return (u.is_seq(s) && !u.is_string(s)); } MATCH_BINARY(is_concat); From f1cee803e83bdac80f350e28693ae6801ec0674a Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Fri, 28 Apr 2017 13:44:48 -0400 Subject: [PATCH 377/410] fixup --- src/ast/seq_decl_plugin.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ast/seq_decl_plugin.cpp b/src/ast/seq_decl_plugin.cpp index 353fb975f..bf238d8c5 100644 --- a/src/ast/seq_decl_plugin.cpp +++ b/src/ast/seq_decl_plugin.cpp @@ -573,7 +573,7 @@ void seq_decl_plugin::set_manager(ast_manager* m, family_id id) { m_char = bv.mk_sort(8); m->inc_ref(m_char); parameter param(m_char); - m_string = m->mk_sort(symbol("StringSequence"), sort_info(m_family_id, SEQ_SORT, 1, ¶m)); + m_string = m->mk_sort(symbol("String"), sort_info(m_family_id, SEQ_SORT, 1, ¶m)); m->inc_ref(m_string); parameter paramS(m_string); m_re = m->mk_sort(m_family_id, RE_SORT, 1, ¶mS); From d51ebac10a19520917d3725279a3a421e3e2279d Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Fri, 28 Apr 2017 14:01:44 -0400 Subject: [PATCH 378/410] remove references to str_fid --- src/ast/ast_smt_pp.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/ast/ast_smt_pp.cpp b/src/ast/ast_smt_pp.cpp index c3f1523b1..f41350dc5 100644 --- a/src/ast/ast_smt_pp.cpp +++ b/src/ast/ast_smt_pp.cpp @@ -166,7 +166,6 @@ class smt_printer { fpa_util m_futil; family_id m_basic_fid; family_id m_bv_fid; - family_id m_str_fid; family_id m_arith_fid; family_id m_array_fid; family_id m_dt_fid; @@ -846,7 +845,6 @@ public: m_bv_fid = m.mk_family_id("bv"); m_arith_fid = m.mk_family_id("arith"); m_array_fid = m.mk_family_id("array"); - m_str_fid = m.mk_family_id("str"); m_dt_fid = m.mk_family_id("datatype"); m_fpa_fid = m.mk_family_id("fpa"); } From e6d527c5d576333ce8be7d9ace47aa52603956aa Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 2 May 2017 15:39:15 -0400 Subject: [PATCH 379/410] remove trace code from theory_arith --- src/smt/theory_arith.h | 14 +------------ src/smt/theory_arith_core.h | 41 ++----------------------------------- 2 files changed, 3 insertions(+), 52 deletions(-) diff --git a/src/smt/theory_arith.h b/src/smt/theory_arith.h index eb36a92fb..439adbdff 100644 --- a/src/smt/theory_arith.h +++ b/src/smt/theory_arith.h @@ -577,19 +577,7 @@ namespace smt { return is_free(get_context().get_enode(n)->get_th_var(get_id())); } bool is_fixed(theory_var v) const; - void set_bound_core(theory_var v, bound * new_bound, bool upper) { - TRACE("t_str_int", - tout << "setting " << (upper ? "upper" : "lower") << " bound "; - if (new_bound) { - tout << new_bound->get_value(); - } else { - tout << "(NULL)"; - } - tout << " for theory var v#" << v; - tout << std::endl; - ); - m_bounds[static_cast(upper)][v] = new_bound; - } + void set_bound_core(theory_var v, bound * new_bound, bool upper) { m_bounds[static_cast(upper)][v] = new_bound; } void restore_bound(theory_var v, bound * new_bound, bool upper) { set_bound_core(v, new_bound, upper); } void restore_nl_propagated_flag(unsigned old_trail_size); void set_bound(bound * new_bound, bool upper); diff --git a/src/smt/theory_arith_core.h b/src/smt/theory_arith_core.h index 7fed094da..dd1924e44 100644 --- a/src/smt/theory_arith_core.h +++ b/src/smt/theory_arith_core.h @@ -3263,50 +3263,13 @@ namespace smt { bool theory_arith::get_value(enode * n, expr_ref & r) { theory_var v = n->get_th_var(get_id()); inf_numeral val; - // rewrites for tracing purposes - if (v == null_theory_var) { - TRACE("t_str_int", tout << "WARNING: enode " << mk_pp(n->get_owner(), get_manager()) - << " attached to null theory var" << std::endl; - ); - return false; - } else { - val = get_value(v); - TRACE("t_str_int", tout << "enode " << mk_pp(n->get_owner(), get_manager()) - << " attached to theory var v#" << v - << ", has val = " << val - << std::endl; - ); - if (!is_int(v) || val.is_int()) { - return to_expr(val, is_int(v), r); - } else { - return false; - } - } - // return v != null_theory_var && (val = get_value(v), (!is_int(v) || val.is_int())) && to_expr(val, is_int(v), r); + return v != null_theory_var && (val = get_value(v), (!is_int(v) || val.is_int())) && to_expr(val, is_int(v), r); } template bool theory_arith::get_lower(enode * n, expr_ref & r) { theory_var v = n->get_th_var(get_id()); - bound * b; - if (v == null_theory_var) { - TRACE("t_str_int", tout << "WARNING: enode " << mk_pp(n->get_owner(), get_manager()) - << " attached to null theory var" << std::endl; - ); - b = 0; - } else { - b = lower(v); - TRACE("t_str_int", - tout << "enode " << mk_pp(n->get_owner(), get_manager()) - << " attached to theory var v#" << v - << std::endl; - if (b) { - tout << "lower bound = " << b->get_value() << std::endl; - } else { - tout << "WARNING: b = NULL" << std::endl; - } - ); - } + bound * b = (v == null_theory_var) ? 0 : lower(v); return b && to_expr(b->get_value(), is_int(v), r); } From a418f0c30b8ea589e08cb8aa1c187ebb1f7003bd Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Tue, 2 May 2017 15:52:35 -0400 Subject: [PATCH 380/410] fix spacing --- src/smt/theory_arith_core.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/smt/theory_arith_core.h b/src/smt/theory_arith_core.h index dd1924e44..5c652414a 100644 --- a/src/smt/theory_arith_core.h +++ b/src/smt/theory_arith_core.h @@ -3269,7 +3269,7 @@ namespace smt { template bool theory_arith::get_lower(enode * n, expr_ref & r) { theory_var v = n->get_th_var(get_id()); - bound * b = (v == null_theory_var) ? 0 : lower(v); + bound* b = (v == null_theory_var) ? 0 : lower(v); return b && to_expr(b->get_value(), is_int(v), r); } From ab4fbe40b67b630203ed688269e98b6427b58b89 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 3 May 2017 17:45:56 -0400 Subject: [PATCH 381/410] cleanup --- .gitignore | 1 - src/ast/ast_smt2_pp.h | 2 +- src/ast/ast_smt_pp.cpp | 1 - src/ast/rewriter/rewriter.txt | 1 - src/parsers/smt2/smt2parser.cpp | 3 +-- src/smt/smt_context.cpp | 7 +------ src/smt/smt_theory.h | 16 ++++++++-------- 7 files changed, 11 insertions(+), 20 deletions(-) diff --git a/.gitignore b/.gitignore index 7cc289168..cc1c2a754 100644 --- a/.gitignore +++ b/.gitignore @@ -86,4 +86,3 @@ src/*/*/CMakeLists.txt src/*/*/*/CMakeLists.txt src/api/dotnet/cmake_install_gac.cmake.in src/api/dotnet/cmake_uninstall_gac.cmake.in - diff --git a/src/ast/ast_smt2_pp.h b/src/ast/ast_smt2_pp.h index 244594461..f2d177041 100644 --- a/src/ast/ast_smt2_pp.h +++ b/src/ast/ast_smt2_pp.h @@ -55,8 +55,8 @@ public: virtual format_ns::format * pp_bv_literal(app * t, bool use_bv_lits, bool bv_neg); virtual format_ns::format * pp_arith_literal(app * t, bool decimal, unsigned prec); virtual format_ns::format * pp_float_literal(app * t, bool use_bv_lits, bool use_float_real_lits); - virtual format_ns::format * pp_string_literal(app * t); virtual format_ns::format * pp_datalog_literal(app * t); + virtual format_ns::format * pp_string_literal(app * t); virtual format_ns::format * pp_sort(sort * s); virtual format_ns::format * pp_fdecl_ref(func_decl * f); format_ns::format * pp_fdecl_name(symbol const & fname, unsigned & len) const; diff --git a/src/ast/ast_smt_pp.cpp b/src/ast/ast_smt_pp.cpp index f41350dc5..706f65ac4 100644 --- a/src/ast/ast_smt_pp.cpp +++ b/src/ast/ast_smt_pp.cpp @@ -406,7 +406,6 @@ class smt_printer { void visit_app(app* n) { rational val; - const char *str; bool is_int, pos; buffer names; unsigned bv_size; diff --git a/src/ast/rewriter/rewriter.txt b/src/ast/rewriter/rewriter.txt index a7a9e5eff..9eb016af2 100644 --- a/src/ast/rewriter/rewriter.txt +++ b/src/ast/rewriter/rewriter.txt @@ -8,7 +8,6 @@ The following classes implement theory specific rewriting rules: - datatype_rewriter - fpa_rewriter - seq_rewriter - - str_rewriter Each of them provide the method br_status mk_app_core(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result) diff --git a/src/parsers/smt2/smt2parser.cpp b/src/parsers/smt2/smt2parser.cpp index 491aca8ba..1486f6e6c 100644 --- a/src/parsers/smt2/smt2parser.cpp +++ b/src/parsers/smt2/smt2parser.cpp @@ -66,8 +66,7 @@ namespace smt2 { scoped_ptr m_bv_util; scoped_ptr m_arith_util; - scoped_ptr m_seq_util; - + scoped_ptr m_seq_util; scoped_ptr m_pattern_validator; scoped_ptr m_var_shifter; diff --git a/src/smt/smt_context.cpp b/src/smt/smt_context.cpp index 68a9f980d..535ae3b1e 100644 --- a/src/smt/smt_context.cpp +++ b/src/smt/smt_context.cpp @@ -1714,12 +1714,6 @@ namespace smt { for (unsigned i = 0; i < m_th_eq_propagation_queue.size() && !inconsistent(); i++) { new_th_eq curr = m_th_eq_propagation_queue[i]; theory * th = get_theory(curr.m_th_id); - TRACE("t_str_eq_bug", tout - << "th->name = " << th->get_name() << std::endl - << "m_th_id = " << curr.m_th_id << std::endl - << "m_lhs = " << curr.m_lhs << std::endl - << "m_rhs = " << curr.m_rhs << std::endl - << std::endl;); SASSERT(th); th->new_eq_eh(curr.m_lhs, curr.m_rhs); #ifdef Z3DEBUG @@ -3042,6 +3036,7 @@ namespace smt { // not counting any literals that get assigned by this method // this relies on bcp() to give us its old m_qhead and therefore // bcp() should always be called before this method + unsigned assigned_literal_end = m_assigned_literals.size(); for (; qhead < assigned_literal_end; ++qhead) { literal l = m_assigned_literals[qhead]; diff --git a/src/smt/smt_theory.h b/src/smt/smt_theory.h index 7dd2819e4..67091c601 100644 --- a/src/smt/smt_theory.h +++ b/src/smt/smt_theory.h @@ -185,6 +185,14 @@ namespace smt { virtual void add_theory_assumptions(expr_ref_vector & assumptions) { } + /** + \brief This method is called from the smt_context when an unsat core is generated. + The theory may change the answer to UNKNOWN by returning l_undef from this method. + */ + virtual lbool validate_unsat_core(expr_ref_vector & unsat_core) { + return l_false; + } + /** \brief This method is invoked before the search starts. */ @@ -200,14 +208,6 @@ namespace smt { return FC_DONE; } - /** - \brief This method is called from the smt_context when an unsat core is generated. - The theory may change the answer to UNKNOWN by returning l_undef from this method. - */ - virtual lbool validate_unsat_core(expr_ref_vector & unsat_core) { - return l_false; - } - /** \brief Parametric theories (e.g. Arrays) should implement this method. See example in context::is_shared From c2b5e8cfdafec22eaf7614bfc688566b1c09b42e Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Wed, 3 May 2017 17:46:06 -0400 Subject: [PATCH 382/410] fix overlap detection internalization --- src/smt/theory_str.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index be268ec5c..a26cb2ee2 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -7305,7 +7305,7 @@ lbool theory_str::validate_unsat_core(expr_ref_vector & unsat_core) { bool assumptionFound = false; app * target_term = to_app(get_manager().mk_not(m_theoryStrOverlapAssumption_term)); - internalize_term(target_term); + get_context().internalize(target_term, false); for (unsigned i = 0; i < unsat_core.size(); ++i) { app * core_term = to_app(unsat_core.get(i)); // not sure if this is the correct way to compare terms in this context From 7e1fae418a766532f99cea06a1c6021268661864 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Fri, 5 May 2017 10:59:47 -0400 Subject: [PATCH 383/410] fix #1005, disable expansion of regular expression range to union as it degrades performance significantly Signed-off-by: Nikolaj Bjorner --- src/ast/rewriter/seq_rewriter.cpp | 1 + src/smt/theory_arith.h | 2 +- src/smt/theory_arith_aux.h | 11 +++++++---- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/ast/rewriter/seq_rewriter.cpp b/src/ast/rewriter/seq_rewriter.cpp index 85d2ba749..7aa9329d4 100644 --- a/src/ast/rewriter/seq_rewriter.cpp +++ b/src/ast/rewriter/seq_rewriter.cpp @@ -1434,6 +1434,7 @@ br_status seq_rewriter::mk_re_star(expr* a, expr_ref& result) { * (re.range c_1 c_n) = (re.union (str.to.re c1) (str.to.re c2) ... (str.to.re cn)) */ br_status seq_rewriter::mk_re_range(expr* lo, expr* hi, expr_ref& result) { + return BR_FAILED; TRACE("seq", tout << "rewrite re.range [" << mk_pp(lo, m()) << " " << mk_pp(hi, m()) << "]\n";); zstring str_lo, str_hi; if (m_util.str.is_string(lo, str_lo) && m_util.str.is_string(hi, str_hi)) { diff --git a/src/smt/theory_arith.h b/src/smt/theory_arith.h index 439adbdff..cdc1a3933 100644 --- a/src/smt/theory_arith.h +++ b/src/smt/theory_arith.h @@ -505,7 +505,7 @@ namespace smt { struct var_value_eq { theory_arith & m_th; var_value_eq(theory_arith & th):m_th(th) {} - bool operator()(theory_var v1, theory_var v2) const { return m_th.get_value(v1) == m_th.get_value(v2) && m_th.is_int(v1) == m_th.is_int(v2); } + bool operator()(theory_var v1, theory_var v2) const { return m_th.get_value(v1) == m_th.get_value(v2) && m_th.is_int_src(v1) == m_th.is_int_src(v2); } }; typedef int_hashtable var_value_table; diff --git a/src/smt/theory_arith_aux.h b/src/smt/theory_arith_aux.h index de357c8d3..54b617152 100644 --- a/src/smt/theory_arith_aux.h +++ b/src/smt/theory_arith_aux.h @@ -2201,16 +2201,19 @@ namespace smt { int num = get_num_vars(); for (theory_var v = 0; v < num; v++) { enode * n = get_enode(v); - TRACE("func_interp_bug", tout << "#" << n->get_owner_id() << " -> " << m_value[v] << "\n";); - if (!is_relevant_and_shared(n)) + TRACE("func_interp_bug", tout << mk_pp(n->get_owner(), get_manager()) << " -> " << m_value[v] << " root #" << n->get_root()->get_owner_id() << " " << is_relevant_and_shared(n) << "\n";); + if (!is_relevant_and_shared(n)) { continue; + } theory_var other = null_theory_var; other = m_var_value_table.insert_if_not_there(v); - if (other == v) + if (other == v) { continue; + } enode * n2 = get_enode(other); - if (n->get_root() == n2->get_root()) + if (n->get_root() == n2->get_root()) { continue; + } TRACE("func_interp_bug", tout << "adding to assume_eq queue #" << n->get_owner_id() << " #" << n2->get_owner_id() << "\n";); m_assume_eq_candidates.push_back(std::make_pair(other, v)); result = true; From 75ba4d5a4d537f60f935d47a0f0edf84177de528 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Fri, 5 May 2017 14:54:36 -0400 Subject: [PATCH 384/410] remove unneeded include --- src/smt/smt_context.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/smt/smt_context.cpp b/src/smt/smt_context.cpp index 535ae3b1e..37a6d32b7 100644 --- a/src/smt/smt_context.cpp +++ b/src/smt/smt_context.cpp @@ -37,7 +37,6 @@ Revision History: #include"model_pp.h" #include"ast_smt2_pp.h" #include"ast_translation.h" -#include"theory_seq.h" namespace smt { From 7ddd43e16df6275488cec300cc050f87419b7450 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Fri, 5 May 2017 15:29:58 -0400 Subject: [PATCH 385/410] first-class re.range support in theory_str --- src/smt/theory_str.cpp | 98 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 97 insertions(+), 1 deletion(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index a26cb2ee2..e7c99da69 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -1673,6 +1673,13 @@ zstring theory_str::get_std_regex_str(expr * regex) { expr * reg1Ast = a_regex->get_arg(0); zstring reg1Str = get_std_regex_str(reg1Ast); return zstring("(") + reg1Str + zstring(")*"); + } else if (u.re.is_range(a_regex)) { + expr * range1 = a_regex->get_arg(0); + expr * range2 = a_regex->get_arg(1); + zstring range1val, range2val; + u.str.is_string(range1, range1val); + u.str.is_string(range2, range2val); + return zstring("[") + range1val + zstring("-") + range2val + zstring("]"); } else { TRACE("str", tout << "BUG: unrecognized regex term " << mk_pp(regex, get_manager()) << std::endl;); UNREACHABLE(); return zstring(""); @@ -1752,6 +1759,36 @@ void theory_str::instantiate_axiom_RegexIn(enode * e) { expr_ref finalAxiom(mk_and(items), m); SASSERT(finalAxiom); assert_axiom(finalAxiom); + } else if (u.re.is_range(regex)) { + // (re.range "A" "Z") unfolds to (re.union "A" "B" ... "Z"); + // we rewrite to expr IFF (str = "A" or str = "B" or ... or str = "Z") + expr_ref lo(regex->get_arg(0), m); + expr_ref hi(regex->get_arg(1), m); + zstring str_lo, str_hi; + SASSERT(u.str.is_string(lo)); + SASSERT(u.str.is_string(hi)); + u.str.is_string(lo, str_lo); + u.str.is_string(hi, str_hi); + SASSERT(str_lo.length() == 1); + SASSERT(str_hi.length() == 1); + unsigned int c1 = str_lo[0]; + unsigned int c2 = str_hi[0]; + if (c1 > c2) { + // exchange + unsigned int tmp = c1; + c1 = c2; + c2 = tmp; + } + expr_ref_vector range_cases(m); + for (unsigned int ch = c1; ch <= c2; ++ch) { + zstring s_ch(ch); + expr_ref rhs(ctx.mk_eq_atom(str, u.str.mk_string(s_ch)), m); + range_cases.push_back(rhs); + } + expr_ref rhs(mk_or(range_cases), m); + expr_ref finalAxiom(m.mk_iff(ex, rhs), m); + SASSERT(finalAxiom); + assert_axiom(finalAxiom); } else { TRACE("str", tout << "ERROR: unknown regex expression " << mk_pp(regex, m) << "!" << std::endl;); NOT_IMPLEMENTED_YET(); @@ -6165,7 +6202,6 @@ void nfa::convert_re(expr * e, unsigned & start, unsigned & end, seq_util & u) { zstring str; if (u.str.is_string(arg_str, str)) { TRACE("str", tout << "build NFA for '" << str << "'" << "\n";); - /* * For an n-character string, we make (n-1) intermediate states, * labelled i_(0) through i_(n-2). @@ -6227,6 +6263,33 @@ void nfa::convert_re(expr * e, unsigned & start, unsigned & end, seq_util & u) { make_epsilon_move(end_subex, start_subex); make_epsilon_move(end_subex, end); TRACE("str", tout << "star NFA: start = " << start << ", end = " << end << std::endl;); + } else if (u.re.is_range(e)) { + // range('a', 'z') + // start --'a'--> end + // start --'b'--> end + // ... + // start --'z'--> end + app * a = to_app(e); + expr * c1 = a->get_arg(0); + expr * c2 = a->get_arg(1); + zstring s_c1, s_c2; + u.str.is_string(c1, s_c1); + u.str.is_string(c2, s_c2); + + unsigned int id1 = s_c1[0]; + unsigned int id2 = s_c2[0]; + if (id1 > id2) { + unsigned int tmp = id1; + id1 = id2; + id2 = tmp; + } + + for (unsigned int i = id1; i <= id2; ++i) { + char ch = (char)i; + make_transition(start, ch, end); + } + + TRACE("str", tout << "range NFA: start = " << start << ", end = " << end << std::endl;); } else { TRACE("str", tout << "invalid regular expression" << std::endl;); m_valid = false; @@ -9429,6 +9492,39 @@ void theory_str::reduce_virtual_regex_in(expr * var, expr * regex, expr_ref_vect items.push_back(ctx.mk_eq_atom(var, unrollFunc)); items.push_back(ctx.mk_eq_atom(mk_strlen(var), mk_strlen(unrollFunc))); return; + } + // re.range + else if (u.re.is_range(regexFuncDecl)) { + // var in range("a", "z") + // ==> + // (var = "a" or var = "b" or ... or var = "z") + expr_ref lo(regexFuncDecl->get_arg(0), mgr); + expr_ref hi(regexFuncDecl->get_arg(1), mgr); + zstring str_lo, str_hi; + SASSERT(u.str.is_string(lo)); + SASSERT(u.str.is_string(hi)); + u.str.is_string(lo, str_lo); + u.str.is_string(hi, str_hi); + SASSERT(str_lo.length() == 1); + SASSERT(str_hi.length() == 1); + unsigned int c1 = str_lo[0]; + unsigned int c2 = str_hi[0]; + if (c1 > c2) { + // exchange + unsigned int tmp = c1; + c1 = c2; + c2 = tmp; + } + expr_ref_vector range_cases(mgr); + for (unsigned int ch = c1; ch <= c2; ++ch) { + zstring s_ch(ch); + expr_ref rhs(ctx.mk_eq_atom(var, u.str.mk_string(s_ch)), mgr); + range_cases.push_back(rhs); + } + expr_ref rhs(mk_or(range_cases), mgr); + SASSERT(rhs); + assert_axiom(rhs); + return; } else { get_manager().raise_exception("unrecognized regex operator"); UNREACHABLE(); From 8eb26e25c298ed17ae9a16ab8ed47983eb9316bb Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Fri, 5 May 2017 17:03:03 -0400 Subject: [PATCH 386/410] add new files to cmakelist.txt files Signed-off-by: Nikolaj Bjorner --- contrib/cmake/src/smt/CMakeLists.txt | 1 + contrib/cmake/src/smt/params/CMakeLists.txt | 1 + 2 files changed, 2 insertions(+) diff --git a/contrib/cmake/src/smt/CMakeLists.txt b/contrib/cmake/src/smt/CMakeLists.txt index bd8ad3f31..c344e936f 100644 --- a/contrib/cmake/src/smt/CMakeLists.txt +++ b/contrib/cmake/src/smt/CMakeLists.txt @@ -58,6 +58,7 @@ z3_add_component(smt theory_opt.cpp theory_pb.cpp theory_seq.cpp + theory_str.cpp theory_utvpi.cpp theory_wmaxsat.cpp uses_theory.cpp diff --git a/contrib/cmake/src/smt/params/CMakeLists.txt b/contrib/cmake/src/smt/params/CMakeLists.txt index 67224a287..500423dcc 100644 --- a/contrib/cmake/src/smt/params/CMakeLists.txt +++ b/contrib/cmake/src/smt/params/CMakeLists.txt @@ -8,6 +8,7 @@ z3_add_component(smt_params theory_array_params.cpp theory_bv_params.cpp theory_pb_params.cpp + theory_str_params.cpp COMPONENT_DEPENDENCIES ast bit_blaster From 21c8f4aae01880dba291cc94accd741a394cb3c6 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Fri, 5 May 2017 19:26:15 -0400 Subject: [PATCH 387/410] formatting theory_str.cpp --- src/smt/theory_str.cpp | 20032 +++++++++++++++++++-------------------- 1 file changed, 10015 insertions(+), 10017 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index e7c99da69..4a6a6da5b 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -1,19 +1,19 @@ /*++ -Module Name: + Module Name: - theory_str.cpp + theory_str.cpp -Abstract: + Abstract: - String Theory Plugin + String Theory Plugin -Author: + Author: - Murphy Berzish and Yunhui Zheng + Murphy Berzish and Yunhui Zheng -Revision History: + Revision History: ---*/ + --*/ #include"ast_smt2_pp.h" #include"smt_context.h" #include"theory_str.h" @@ -24,13 +24,11 @@ Revision History: #include #include #include"theory_seq_empty.h" - -#include "../ast/ast.h" #include"theory_arith.h" namespace smt { - -theory_str::theory_str(ast_manager & m, theory_str_params const & params): + + theory_str::theory_str(ast_manager & m, theory_str_params const & params): theory(m.mk_family_id("seq")), m_params(params), /* Options */ @@ -64,3864 +62,3041 @@ theory_str::theory_str(ast_manager & m, theory_str_params const & params): cacheMissCount(0), m_find(*this), m_trail_stack(*this) -{ - initialize_charset(); -} + { + initialize_charset(); + } -theory_str::~theory_str() { - m_trail_stack.reset(); -} + theory_str::~theory_str() { + m_trail_stack.reset(); + } -expr * theory_str::mk_string(zstring const& str) { - if (m_params.m_StringConstantCache) { - ++totalCacheAccessCount; - expr * val; - if (stringConstantCache.find(str, val)) { - return val; + expr * theory_str::mk_string(zstring const& str) { + if (m_params.m_StringConstantCache) { + ++totalCacheAccessCount; + expr * val; + if (stringConstantCache.find(str, val)) { + return val; + } else { + val = u.str.mk_string(str); + m_trail.push_back(val); + stringConstantCache.insert(str, val); + return val; + } } else { - val = u.str.mk_string(str); - m_trail.push_back(val); - stringConstantCache.insert(str, val); - return val; + return u.str.mk_string(str); } - } else { - return u.str.mk_string(str); - } -} - -expr * theory_str::mk_string(const char * str) { - symbol sym(str); - return u.str.mk_string(sym); -} - -void theory_str::initialize_charset() { - bool defaultCharset = true; - if (defaultCharset) { - // valid C strings can't contain the null byte ('\0') - charSetSize = 255; - char_set = alloc_svect(char, charSetSize); - int idx = 0; - // small letters - for (int i = 97; i < 123; i++) { - char_set[idx] = (char) i; - charSetLookupTable[char_set[idx]] = idx; - idx++; - } - // caps - for (int i = 65; i < 91; i++) { - char_set[idx] = (char) i; - charSetLookupTable[char_set[idx]] = idx; - idx++; - } - // numbers - for (int i = 48; i < 58; i++) { - char_set[idx] = (char) i; - charSetLookupTable[char_set[idx]] = idx; - idx++; - } - // printable marks - 1 - for (int i = 32; i < 48; i++) { - char_set[idx] = (char) i; - charSetLookupTable[char_set[idx]] = idx; - idx++; - } - // printable marks - 2 - for (int i = 58; i < 65; i++) { - char_set[idx] = (char) i; - charSetLookupTable[char_set[idx]] = idx; - idx++; - } - // printable marks - 3 - for (int i = 91; i < 97; i++) { - char_set[idx] = (char) i; - charSetLookupTable[char_set[idx]] = idx; - idx++; - } - // printable marks - 4 - for (int i = 123; i < 127; i++) { - char_set[idx] = (char) i; - charSetLookupTable[char_set[idx]] = idx; - idx++; - } - // non-printable - 1 - for (int i = 1; i < 32; i++) { - char_set[idx] = (char) i; - charSetLookupTable[char_set[idx]] = idx; - idx++; - } - // non-printable - 2 - for (int i = 127; i < 256; i++) { - char_set[idx] = (char) i; - charSetLookupTable[char_set[idx]] = idx; - idx++; - } - } else { - const char setset[] = { 'a', 'b', 'c' }; - int fSize = sizeof(setset) / sizeof(char); - - char_set = alloc_svect(char, fSize); - charSetSize = fSize; - for (int i = 0; i < charSetSize; i++) { - char_set[i] = setset[i]; - charSetLookupTable[setset[i]] = i; - } - } -} - -void theory_str::assert_axiom(expr * e) { - if (opt_VerifyFinalCheckProgress) { - finalCheckProgressIndicator = true; } - if (get_manager().is_true(e)) return; - TRACE("str", tout << "asserting " << mk_ismt2_pp(e, get_manager()) << std::endl;); - context & ctx = get_context(); - if (!ctx.b_internalized(e)) { - ctx.internalize(e, false); - } - literal lit(ctx.get_literal(e)); - ctx.mark_as_relevant(lit); - ctx.mk_th_axiom(get_id(), 1, &lit); - - // crash/error avoidance: add all axioms to the trail - m_trail.push_back(e); - - //TRACE("str", tout << "done asserting " << mk_ismt2_pp(e, get_manager()) << std::endl;); -} - -expr * theory_str::rewrite_implication(expr * premise, expr * conclusion) { - ast_manager & m = get_manager(); - return m.mk_or(m.mk_not(premise), conclusion); -} - -void theory_str::assert_implication(expr * premise, expr * conclusion) { - ast_manager & m = get_manager(); - TRACE("str", tout << "asserting implication " << mk_ismt2_pp(premise, m) << " -> " << mk_ismt2_pp(conclusion, m) << std::endl;); - expr_ref axiom(m.mk_or(m.mk_not(premise), conclusion), m); - assert_axiom(axiom); -} - -bool theory_str::internalize_atom(app * atom, bool gate_ctx) { - return internalize_term(atom); -} - -bool theory_str::internalize_term(app * term) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - SASSERT(term->get_family_id() == get_family_id()); - - TRACE("str", tout << "internalizing term: " << mk_ismt2_pp(term, get_manager()) << std::endl;); - - // emulation of user_smt_theory::internalize_term() - - unsigned num_args = term->get_num_args(); - for (unsigned i = 0; i < num_args; ++i) { - ctx.internalize(term->get_arg(i), false); - } - if (ctx.e_internalized(term)) { - enode * e = ctx.get_enode(term); - mk_var(e); - return true; - } - // m_parents.push_back(term); - enode * e = 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.set_enode_flag(bv, true); - } - // make sure every argument is attached to a theory variable - for (unsigned i = 0; i < num_args; ++i) { - enode * arg = e->get_arg(i); - theory_var v_arg = mk_var(arg); - TRACE("str", tout << "arg has theory var #" << v_arg << std::endl;); + expr * theory_str::mk_string(const char * str) { + symbol sym(str); + return u.str.mk_string(sym); } - theory_var v = mk_var(e); - TRACE("str", tout << "term has theory var #" << v << std::endl;); + void theory_str::initialize_charset() { + bool defaultCharset = true; + if (defaultCharset) { + // valid C strings can't contain the null byte ('\0') + charSetSize = 255; + char_set = alloc_svect(char, charSetSize); + int idx = 0; + // small letters + for (int i = 97; i < 123; i++) { + char_set[idx] = (char) i; + charSetLookupTable[char_set[idx]] = idx; + idx++; + } + // caps + for (int i = 65; i < 91; i++) { + char_set[idx] = (char) i; + charSetLookupTable[char_set[idx]] = idx; + idx++; + } + // numbers + for (int i = 48; i < 58; i++) { + char_set[idx] = (char) i; + charSetLookupTable[char_set[idx]] = idx; + idx++; + } + // printable marks - 1 + for (int i = 32; i < 48; i++) { + char_set[idx] = (char) i; + charSetLookupTable[char_set[idx]] = idx; + idx++; + } + // printable marks - 2 + for (int i = 58; i < 65; i++) { + char_set[idx] = (char) i; + charSetLookupTable[char_set[idx]] = idx; + idx++; + } + // printable marks - 3 + for (int i = 91; i < 97; i++) { + char_set[idx] = (char) i; + charSetLookupTable[char_set[idx]] = idx; + idx++; + } + // printable marks - 4 + for (int i = 123; i < 127; i++) { + char_set[idx] = (char) i; + charSetLookupTable[char_set[idx]] = idx; + idx++; + } + // non-printable - 1 + for (int i = 1; i < 32; i++) { + char_set[idx] = (char) i; + charSetLookupTable[char_set[idx]] = idx; + idx++; + } + // non-printable - 2 + for (int i = 127; i < 256; i++) { + char_set[idx] = (char) i; + charSetLookupTable[char_set[idx]] = idx; + idx++; + } + } else { + const char setset[] = { 'a', 'b', 'c' }; + int fSize = sizeof(setset) / sizeof(char); - if (opt_EagerStringConstantLengthAssertions && u.str.is_string(term)) { - TRACE("str", tout << "eagerly asserting length of string term " << mk_pp(term, m) << std::endl;); - m_basicstr_axiom_todo.insert(e); - } - return true; -} - -enode* theory_str::ensure_enode(expr* e) { - context& ctx = get_context(); - if (!ctx.e_internalized(e)) { - ctx.internalize(e, false); - } - enode* n = ctx.get_enode(e); - ctx.mark_as_relevant(n); - return n; -} - -void theory_str::refresh_theory_var(expr * e) { - enode * en = ensure_enode(e); - theory_var v = mk_var(en); - TRACE("str", tout << "refresh " << mk_pp(e, get_manager()) << ": v#" << v << std::endl;); - m_basicstr_axiom_todo.push_back(en); -} - -theory_var theory_str::mk_var(enode* n) { - TRACE("str", tout << "mk_var for " << mk_pp(n->get_owner(), get_manager()) << std::endl;); - ast_manager & m = get_manager(); - if (!(m.get_sort(n->get_owner()) == u.str.mk_string_sort())) { - return null_theory_var; - } - if (is_attached_to_var(n)) { - TRACE("str", tout << "already attached to theory var" << std::endl;); - return n->get_th_var(get_id()); - } else { - theory_var v = theory::mk_var(n); - m_find.mk_var(); - TRACE("str", tout << "new theory var v#" << v << std::endl;); - get_context().attach_th_var(n, this, v); - get_context().mark_as_relevant(n); - return v; - } -} - -static void cut_vars_map_copy(std::map & dest, std::map & src) { - std::map::iterator itor = src.begin(); - for (; itor != src.end(); itor++) { - dest[itor->first] = 1; - } -} - -bool theory_str::has_self_cut(expr * n1, expr * n2) { - if (!cut_var_map.contains(n1)) { - return false; - } - if (!cut_var_map.contains(n2)) { - return false; - } - if (cut_var_map[n1].empty() || cut_var_map[n2].empty()) { - return false; + char_set = alloc_svect(char, fSize); + charSetSize = fSize; + for (int i = 0; i < charSetSize; i++) { + char_set[i] = setset[i]; + charSetLookupTable[setset[i]] = i; + } + } } - std::map::iterator itor = cut_var_map[n1].top()->vars.begin(); - for (; itor != cut_var_map[n1].top()->vars.end(); ++itor) { - if (cut_var_map[n2].top()->vars.find(itor->first) != cut_var_map[n2].top()->vars.end()) { + void theory_str::assert_axiom(expr * e) { + if (opt_VerifyFinalCheckProgress) { + finalCheckProgressIndicator = true; + } + + if (get_manager().is_true(e)) return; + TRACE("str", tout << "asserting " << mk_ismt2_pp(e, get_manager()) << std::endl;); + context & ctx = get_context(); + if (!ctx.b_internalized(e)) { + ctx.internalize(e, false); + } + literal lit(ctx.get_literal(e)); + ctx.mark_as_relevant(lit); + ctx.mk_th_axiom(get_id(), 1, &lit); + + // crash/error avoidance: add all axioms to the trail + m_trail.push_back(e); + + //TRACE("str", tout << "done asserting " << mk_ismt2_pp(e, get_manager()) << std::endl;); + } + + expr * theory_str::rewrite_implication(expr * premise, expr * conclusion) { + ast_manager & m = get_manager(); + return m.mk_or(m.mk_not(premise), conclusion); + } + + void theory_str::assert_implication(expr * premise, expr * conclusion) { + ast_manager & m = get_manager(); + TRACE("str", tout << "asserting implication " << mk_ismt2_pp(premise, m) << " -> " << mk_ismt2_pp(conclusion, m) << std::endl;); + expr_ref axiom(m.mk_or(m.mk_not(premise), conclusion), m); + assert_axiom(axiom); + } + + bool theory_str::internalize_atom(app * atom, bool gate_ctx) { + return internalize_term(atom); + } + + bool theory_str::internalize_term(app * term) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + SASSERT(term->get_family_id() == get_family_id()); + + TRACE("str", tout << "internalizing term: " << mk_ismt2_pp(term, get_manager()) << std::endl;); + + // emulation of user_smt_theory::internalize_term() + + unsigned num_args = term->get_num_args(); + for (unsigned i = 0; i < num_args; ++i) { + ctx.internalize(term->get_arg(i), false); + } + if (ctx.e_internalized(term)) { + enode * e = ctx.get_enode(term); + mk_var(e); return true; } - } - return false; -} + // m_parents.push_back(term); + enode * e = 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.set_enode_flag(bv, true); + } + // make sure every argument is attached to a theory variable + for (unsigned i = 0; i < num_args; ++i) { + enode * arg = e->get_arg(i); + theory_var v_arg = mk_var(arg); + TRACE("str", tout << "arg has theory var #" << v_arg << std::endl;); + } -void theory_str::add_cut_info_one_node(expr * baseNode, int slevel, expr * node) { - // crash avoidance? - m_trail.push_back(baseNode); - m_trail.push_back(node); - if (!cut_var_map.contains(baseNode)) { - T_cut * varInfo = alloc(T_cut); - varInfo->level = slevel; - varInfo->vars[node] = 1; - cut_var_map.insert(baseNode, std::stack()); - cut_var_map[baseNode].push(varInfo); - TRACE("str", tout << "add var info for baseNode=" << mk_pp(baseNode, get_manager()) << ", node=" << mk_pp(node, get_manager()) << " [" << slevel << "]" << std::endl;); - } else { - if (cut_var_map[baseNode].empty()) { + theory_var v = mk_var(e); + TRACE("str", tout << "term has theory var #" << v << std::endl;); + + if (opt_EagerStringConstantLengthAssertions && u.str.is_string(term)) { + TRACE("str", tout << "eagerly asserting length of string term " << mk_pp(term, m) << std::endl;); + m_basicstr_axiom_todo.insert(e); + } + return true; + } + + enode* theory_str::ensure_enode(expr* e) { + context& ctx = get_context(); + if (!ctx.e_internalized(e)) { + ctx.internalize(e, false); + } + enode* n = ctx.get_enode(e); + ctx.mark_as_relevant(n); + return n; + } + + void theory_str::refresh_theory_var(expr * e) { + enode * en = ensure_enode(e); + theory_var v = mk_var(en); + TRACE("str", tout << "refresh " << mk_pp(e, get_manager()) << ": v#" << v << std::endl;); + m_basicstr_axiom_todo.push_back(en); + } + + theory_var theory_str::mk_var(enode* n) { + TRACE("str", tout << "mk_var for " << mk_pp(n->get_owner(), get_manager()) << std::endl;); + ast_manager & m = get_manager(); + if (!(m.get_sort(n->get_owner()) == u.str.mk_string_sort())) { + return null_theory_var; + } + if (is_attached_to_var(n)) { + TRACE("str", tout << "already attached to theory var" << std::endl;); + return n->get_th_var(get_id()); + } else { + theory_var v = theory::mk_var(n); + m_find.mk_var(); + TRACE("str", tout << "new theory var v#" << v << std::endl;); + get_context().attach_th_var(n, this, v); + get_context().mark_as_relevant(n); + return v; + } + } + + static void cut_vars_map_copy(std::map & dest, std::map & src) { + std::map::iterator itor = src.begin(); + for (; itor != src.end(); itor++) { + dest[itor->first] = 1; + } + } + + bool theory_str::has_self_cut(expr * n1, expr * n2) { + if (!cut_var_map.contains(n1)) { + return false; + } + if (!cut_var_map.contains(n2)) { + return false; + } + if (cut_var_map[n1].empty() || cut_var_map[n2].empty()) { + return false; + } + + std::map::iterator itor = cut_var_map[n1].top()->vars.begin(); + for (; itor != cut_var_map[n1].top()->vars.end(); ++itor) { + if (cut_var_map[n2].top()->vars.find(itor->first) != cut_var_map[n2].top()->vars.end()) { + return true; + } + } + return false; + } + + void theory_str::add_cut_info_one_node(expr * baseNode, int slevel, expr * node) { + // crash avoidance? + m_trail.push_back(baseNode); + m_trail.push_back(node); + if (!cut_var_map.contains(baseNode)) { T_cut * varInfo = alloc(T_cut); varInfo->level = slevel; varInfo->vars[node] = 1; + cut_var_map.insert(baseNode, std::stack()); cut_var_map[baseNode].push(varInfo); TRACE("str", tout << "add var info for baseNode=" << mk_pp(baseNode, get_manager()) << ", node=" << mk_pp(node, get_manager()) << " [" << slevel << "]" << std::endl;); } else { - if (cut_var_map[baseNode].top()->level < slevel) { + if (cut_var_map[baseNode].empty()) { T_cut * varInfo = alloc(T_cut); varInfo->level = slevel; - cut_vars_map_copy(varInfo->vars, cut_var_map[baseNode].top()->vars); varInfo->vars[node] = 1; cut_var_map[baseNode].push(varInfo); TRACE("str", tout << "add var info for baseNode=" << mk_pp(baseNode, get_manager()) << ", node=" << mk_pp(node, get_manager()) << " [" << slevel << "]" << std::endl;); - } else if (cut_var_map[baseNode].top()->level == slevel) { - cut_var_map[baseNode].top()->vars[node] = 1; - TRACE("str", tout << "add var info for baseNode=" << mk_pp(baseNode, get_manager()) << ", node=" << mk_pp(node, get_manager()) << " [" << slevel << "]" << std::endl;); } else { - get_manager().raise_exception("entered illegal state during add_cut_info_one_node()"); + if (cut_var_map[baseNode].top()->level < slevel) { + T_cut * varInfo = alloc(T_cut); + varInfo->level = slevel; + cut_vars_map_copy(varInfo->vars, cut_var_map[baseNode].top()->vars); + varInfo->vars[node] = 1; + cut_var_map[baseNode].push(varInfo); + TRACE("str", tout << "add var info for baseNode=" << mk_pp(baseNode, get_manager()) << ", node=" << mk_pp(node, get_manager()) << " [" << slevel << "]" << std::endl;); + } else if (cut_var_map[baseNode].top()->level == slevel) { + cut_var_map[baseNode].top()->vars[node] = 1; + TRACE("str", tout << "add var info for baseNode=" << mk_pp(baseNode, get_manager()) << ", node=" << mk_pp(node, get_manager()) << " [" << slevel << "]" << std::endl;); + } else { + get_manager().raise_exception("entered illegal state during add_cut_info_one_node()"); + } } } } -} -void theory_str::add_cut_info_merge(expr * destNode, int slevel, expr * srcNode) { - // crash avoidance? - m_trail.push_back(destNode); - m_trail.push_back(srcNode); - if (!cut_var_map.contains(srcNode)) { - get_manager().raise_exception("illegal state in add_cut_info_merge(): cut_var_map doesn't contain srcNode"); - } + void theory_str::add_cut_info_merge(expr * destNode, int slevel, expr * srcNode) { + // crash avoidance? + m_trail.push_back(destNode); + m_trail.push_back(srcNode); + if (!cut_var_map.contains(srcNode)) { + get_manager().raise_exception("illegal state in add_cut_info_merge(): cut_var_map doesn't contain srcNode"); + } - if (cut_var_map[srcNode].empty()) { - get_manager().raise_exception("illegal state in add_cut_info_merge(): cut_var_map[srcNode] is empty"); - } + if (cut_var_map[srcNode].empty()) { + get_manager().raise_exception("illegal state in add_cut_info_merge(): cut_var_map[srcNode] is empty"); + } - if (!cut_var_map.contains(destNode)) { - T_cut * varInfo = alloc(T_cut); - varInfo->level = slevel; - cut_vars_map_copy(varInfo->vars, cut_var_map[srcNode].top()->vars); - cut_var_map.insert(destNode, std::stack()); - cut_var_map[destNode].push(varInfo); - TRACE("str", tout << "merge var info for destNode=" << mk_pp(destNode, get_manager()) << ", srcNode=" << mk_pp(srcNode, get_manager()) << " [" << slevel << "]" << std::endl;); - } else { - if (cut_var_map[destNode].empty() || cut_var_map[destNode].top()->level < slevel) { + if (!cut_var_map.contains(destNode)) { T_cut * varInfo = alloc(T_cut); varInfo->level = slevel; - cut_vars_map_copy(varInfo->vars, cut_var_map[destNode].top()->vars); cut_vars_map_copy(varInfo->vars, cut_var_map[srcNode].top()->vars); + cut_var_map.insert(destNode, std::stack()); cut_var_map[destNode].push(varInfo); TRACE("str", tout << "merge var info for destNode=" << mk_pp(destNode, get_manager()) << ", srcNode=" << mk_pp(srcNode, get_manager()) << " [" << slevel << "]" << std::endl;); - } else if (cut_var_map[destNode].top()->level == slevel) { - cut_vars_map_copy(cut_var_map[destNode].top()->vars, cut_var_map[srcNode].top()->vars); - TRACE("str", tout << "merge var info for destNode=" << mk_pp(destNode, get_manager()) << ", srcNode=" << mk_pp(srcNode, get_manager()) << " [" << slevel << "]" << std::endl;); } else { - get_manager().raise_exception("illegal state in add_cut_info_merge(): inconsistent slevels"); - } - } -} - -void theory_str::check_and_init_cut_var(expr * node) { - if (cut_var_map.contains(node)) { - return; - } else if (!u.str.is_string(node)) { - add_cut_info_one_node(node, -1, node); - } -} - -literal theory_str::mk_literal(expr* _e) { - ast_manager & m = get_manager(); - expr_ref e(_e, m); - context& ctx = get_context(); - ensure_enode(e); - return ctx.get_literal(e); -} - -app * theory_str::mk_int(int n) { - return m_autil.mk_numeral(rational(n), true); -} - -app * theory_str::mk_int(rational & q) { - return m_autil.mk_numeral(q, true); -} - -expr * theory_str::mk_internal_lenTest_var(expr * node, int lTries) { - ast_manager & m = get_manager(); - - std::stringstream ss; - ss << "$$_len_" << mk_ismt2_pp(node, m) << "_" << lTries << "_" << tmpLenTestVarCount; - tmpLenTestVarCount += 1; - std::string name = ss.str(); - app * var = mk_str_var(name); - internal_lenTest_vars.insert(var); - m_trail.push_back(var); - return var; -} - -expr * theory_str::mk_internal_valTest_var(expr * node, int len, int vTries) { - ast_manager & m = get_manager(); - std::stringstream ss; - ss << "$$_val_" << mk_ismt2_pp(node, m) << "_" << len << "_" << vTries << "_" << tmpValTestVarCount; - tmpValTestVarCount += 1; - std::string name = ss.str(); - app * var = mk_str_var(name); - internal_valTest_vars.insert(var); - m_trail.push_back(var); - return var; -} - -void theory_str::track_variable_scope(expr * var) { - if (internal_variable_scope_levels.find(sLevel) == internal_variable_scope_levels.end()) { - internal_variable_scope_levels[sLevel] = std::set(); - } - internal_variable_scope_levels[sLevel].insert(var); -} - -app * theory_str::mk_internal_xor_var() { - return mk_int_var("$$_xor"); -} - -app * theory_str::mk_int_var(std::string name) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - - TRACE("str", tout << "creating integer variable " << name << " at scope level " << sLevel << std::endl;); - - sort * int_sort = m.mk_sort(m_autil.get_family_id(), INT_SORT); - app * a = m.mk_fresh_const(name.c_str(), int_sort); - - ctx.internalize(a, false); - SASSERT(ctx.get_enode(a) != NULL); - SASSERT(ctx.e_internalized(a)); - ctx.mark_as_relevant(a); - // I'm assuming that this combination will do the correct thing in the integer theory. - - //mk_var(ctx.get_enode(a)); - m_trail.push_back(a); - //variable_set.insert(a); - //internal_variable_set.insert(a); - //track_variable_scope(a); - - return a; -} - -app * theory_str::mk_unroll_bound_var() { - return mk_int_var("unroll"); -} - -app * theory_str::mk_unroll_test_var() { - app * v = mk_str_var("unrollTest"); // was uRt - internal_unrollTest_vars.insert(v); - track_variable_scope(v); - return v; -} - -app * theory_str::mk_str_var(std::string name) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - - TRACE("str", tout << "creating string variable " << name << " at scope level " << sLevel << std::endl;); - - sort * string_sort = u.str.mk_string_sort(); - app * a = m.mk_fresh_const(name.c_str(), string_sort); - - TRACE("str", tout << "a->get_family_id() = " << a->get_family_id() << std::endl - << "this->get_family_id() = " << this->get_family_id() << std::endl;); - - // I have a hunch that this may not get internalized for free... - ctx.internalize(a, false); - SASSERT(ctx.get_enode(a) != NULL); - SASSERT(ctx.e_internalized(a)); - // this might help?? - mk_var(ctx.get_enode(a)); - m_basicstr_axiom_todo.push_back(ctx.get_enode(a)); - TRACE("str", tout << "add " << mk_pp(a, m) << " to m_basicstr_axiom_todo" << std::endl;); - - m_trail.push_back(a); - variable_set.insert(a); - internal_variable_set.insert(a); - track_variable_scope(a); - - return a; -} - -app * theory_str::mk_regex_rep_var() { - context & ctx = get_context(); - ast_manager & m = get_manager(); - - sort * string_sort = u.str.mk_string_sort(); - app * a = m.mk_fresh_const("regex", string_sort); - - ctx.internalize(a, false); - SASSERT(ctx.get_enode(a) != NULL); - SASSERT(ctx.e_internalized(a)); - mk_var(ctx.get_enode(a)); - m_basicstr_axiom_todo.push_back(ctx.get_enode(a)); - TRACE("str", tout << "add " << mk_pp(a, m) << " to m_basicstr_axiom_todo" << std::endl;); - - m_trail.push_back(a); - variable_set.insert(a); - //internal_variable_set.insert(a); - regex_variable_set.insert(a); - track_variable_scope(a); - - return a; -} - -void theory_str::add_nonempty_constraint(expr * s) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - - expr_ref ax1(m.mk_not(ctx.mk_eq_atom(s, mk_string(""))), m); - assert_axiom(ax1); - - { - // build LHS - expr_ref len_str(mk_strlen(s), m); - SASSERT(len_str); - // build RHS - expr_ref zero(m_autil.mk_numeral(rational(0), true), m); - SASSERT(zero); - // build LHS > RHS and assert - // we have to build !(LHS <= RHS) instead - expr_ref lhs_gt_rhs(m.mk_not(m_autil.mk_le(len_str, zero)), m); - SASSERT(lhs_gt_rhs); - assert_axiom(lhs_gt_rhs); - } -} - -app * theory_str::mk_nonempty_str_var() { - context & ctx = get_context(); - ast_manager & m = get_manager(); - - std::stringstream ss; - ss << tmpStringVarCount; - tmpStringVarCount++; - std::string name = "$$_str" + ss.str(); - - TRACE("str", tout << "creating nonempty string variable " << name << " at scope level " << sLevel << std::endl;); - - sort * string_sort = u.str.mk_string_sort(); - app * a = m.mk_fresh_const(name.c_str(), string_sort); - - ctx.internalize(a, false); - SASSERT(ctx.get_enode(a) != NULL); - // this might help?? - mk_var(ctx.get_enode(a)); - - // assert a variation of the basic string axioms that ensures this string is nonempty - { - // build LHS - expr_ref len_str(mk_strlen(a), m); - SASSERT(len_str); - // build RHS - expr_ref zero(m_autil.mk_numeral(rational(0), true), m); - SASSERT(zero); - // build LHS > RHS and assert - // we have to build !(LHS <= RHS) instead - expr_ref lhs_gt_rhs(m.mk_not(m_autil.mk_le(len_str, zero)), m); - SASSERT(lhs_gt_rhs); - assert_axiom(lhs_gt_rhs); - } - - // add 'a' to variable sets, so we can keep track of it - m_trail.push_back(a); - variable_set.insert(a); - internal_variable_set.insert(a); - track_variable_scope(a); - - return a; -} - -app * theory_str::mk_unroll(expr * n, expr * bound) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - - expr * args[2] = {n, bound}; - app * unrollFunc = get_manager().mk_app(get_id(), _OP_RE_UNROLL, 0, 0, 2, args); - m_trail.push_back(unrollFunc); - - expr_ref_vector items(m); - items.push_back(ctx.mk_eq_atom(ctx.mk_eq_atom(bound, mk_int(0)), ctx.mk_eq_atom(unrollFunc, mk_string("")))); - items.push_back(m_autil.mk_ge(bound, mk_int(0))); - items.push_back(m_autil.mk_ge(mk_strlen(unrollFunc), mk_int(0))); - - expr_ref finalAxiom(mk_and(items), m); - SASSERT(finalAxiom); - assert_axiom(finalAxiom); - return unrollFunc; -} - -app * theory_str::mk_contains(expr * haystack, expr * needle) { - app * contains = u.str.mk_contains(haystack, needle); // TODO double-check semantics/argument order - m_trail.push_back(contains); - // immediately force internalization so that axiom setup does not fail - get_context().internalize(contains, false); - set_up_axioms(contains); - return contains; -} - -app * theory_str::mk_indexof(expr * haystack, expr * needle) { - // TODO check meaning of the third argument here - app * indexof = u.str.mk_index(haystack, needle, mk_int(0)); - m_trail.push_back(indexof); - // immediately force internalization so that axiom setup does not fail - get_context().internalize(indexof, false); - set_up_axioms(indexof); - return indexof; -} - -app * theory_str::mk_strlen(expr * e) { - /*if (m_strutil.is_string(e)) {*/ if (false) { - zstring strval; - u.str.is_string(e, strval); - unsigned int len = strval.length(); - return m_autil.mk_numeral(rational(len), true); - } else { - if (false) { - // use cache - app * lenTerm = NULL; - if (!length_ast_map.find(e, lenTerm)) { - lenTerm = u.str.mk_length(e); - length_ast_map.insert(e, lenTerm); - m_trail.push_back(lenTerm); + if (cut_var_map[destNode].empty() || cut_var_map[destNode].top()->level < slevel) { + T_cut * varInfo = alloc(T_cut); + varInfo->level = slevel; + cut_vars_map_copy(varInfo->vars, cut_var_map[destNode].top()->vars); + cut_vars_map_copy(varInfo->vars, cut_var_map[srcNode].top()->vars); + cut_var_map[destNode].push(varInfo); + TRACE("str", tout << "merge var info for destNode=" << mk_pp(destNode, get_manager()) << ", srcNode=" << mk_pp(srcNode, get_manager()) << " [" << slevel << "]" << std::endl;); + } else if (cut_var_map[destNode].top()->level == slevel) { + cut_vars_map_copy(cut_var_map[destNode].top()->vars, cut_var_map[srcNode].top()->vars); + TRACE("str", tout << "merge var info for destNode=" << mk_pp(destNode, get_manager()) << ", srcNode=" << mk_pp(srcNode, get_manager()) << " [" << slevel << "]" << std::endl;); + } else { + get_manager().raise_exception("illegal state in add_cut_info_merge(): inconsistent slevels"); } - return lenTerm; - } else { - // always regen - return u.str.mk_length(e); } } -} -/* - * Returns the simplified concatenation of two expressions, - * where either both expressions are constant strings - * or one expression is the empty string. - * If this precondition does not hold, the function returns NULL. - * (note: this function was strTheory::Concat()) - */ -expr * theory_str::mk_concat_const_str(expr * n1, expr * n2) { - bool n1HasEqcValue = false; - bool n2HasEqcValue = false; - expr * v1 = get_eqc_value(n1, n1HasEqcValue); - expr * v2 = get_eqc_value(n2, n2HasEqcValue); - if (n1HasEqcValue && n2HasEqcValue) { - zstring n1_str; - u.str.is_string(v1, n1_str); - zstring n2_str; - u.str.is_string(v2, n2_str); - zstring result = n1_str + n2_str; - return mk_string(result); - } else if (n1HasEqcValue && !n2HasEqcValue) { - zstring n1_str; - u.str.is_string(v1, n1_str); - if (n1_str.empty()) { - return n2; - } - } else if (!n1HasEqcValue && n2HasEqcValue) { - zstring n2_str; - u.str.is_string(v2, n2_str); - if (n2_str.empty()) { - return n1; + void theory_str::check_and_init_cut_var(expr * node) { + if (cut_var_map.contains(node)) { + return; + } else if (!u.str.is_string(node)) { + add_cut_info_one_node(node, -1, node); } } - return NULL; -} -expr * theory_str::mk_concat(expr * n1, expr * n2) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - ENSURE(n1 != NULL); - ENSURE(n2 != NULL); - bool n1HasEqcValue = false; - bool n2HasEqcValue = false; - n1 = get_eqc_value(n1, n1HasEqcValue); - n2 = get_eqc_value(n2, n2HasEqcValue); - if (n1HasEqcValue && n2HasEqcValue) { - return mk_concat_const_str(n1, n2); - } else if (n1HasEqcValue && !n2HasEqcValue) { - bool n2_isConcatFunc = u.str.is_concat(to_app(n2)); - zstring n1_str; - u.str.is_string(n1, n1_str); - if (n1_str.empty()) { - return n2; - } - if (n2_isConcatFunc) { - expr * n2_arg0 = to_app(n2)->get_arg(0); - expr * n2_arg1 = to_app(n2)->get_arg(1); - if (u.str.is_string(n2_arg0)) { - n1 = mk_concat_const_str(n1, n2_arg0); // n1 will be a constant - n2 = n2_arg1; - } - } - } else if (!n1HasEqcValue && n2HasEqcValue) { - zstring n2_str; - u.str.is_string(n2, n2_str); - if (n2_str.empty()) { - return n1; - } - - if (u.str.is_concat(to_app(n1))) { - expr * n1_arg0 = to_app(n1)->get_arg(0); - expr * n1_arg1 = to_app(n1)->get_arg(1); - if (u.str.is_string(n1_arg1)) { - n1 = n1_arg0; - n2 = mk_concat_const_str(n1_arg1, n2); // n2 will be a constant - } - } - } else { - if (u.str.is_concat(to_app(n1)) && u.str.is_concat(to_app(n2))) { - expr * n1_arg0 = to_app(n1)->get_arg(0); - expr * n1_arg1 = to_app(n1)->get_arg(1); - expr * n2_arg0 = to_app(n2)->get_arg(0); - expr * n2_arg1 = to_app(n2)->get_arg(1); - if (u.str.is_string(n1_arg1) && u.str.is_string(n2_arg0)) { - expr * tmpN1 = n1_arg0; - expr * tmpN2 = mk_concat_const_str(n1_arg1, n2_arg0); - n1 = mk_concat(tmpN1, tmpN2); - n2 = n2_arg1; - } - } - } - - //------------------------------------------------------ - // * expr * ast1 = mk_2_arg_app(ctx, td->Concat, n1, n2); - // * expr * ast2 = mk_2_arg_app(ctx, td->Concat, n1, n2); - // Z3 treats (ast1) and (ast2) as two different nodes. - //------------------------------------------------------- - - expr * concatAst = NULL; - - if (!concat_astNode_map.find(n1, n2, concatAst)) { - concatAst = u.str.mk_concat(n1, n2); - m_trail.push_back(concatAst); - concat_astNode_map.insert(n1, n2, concatAst); - - expr_ref concat_length(mk_strlen(concatAst), m); - - ptr_vector childrenVector; - get_nodes_in_concat(concatAst, childrenVector); - expr_ref_vector items(m); - for (unsigned int i = 0; i < childrenVector.size(); i++) { - items.push_back(mk_strlen(childrenVector.get(i))); - } - expr_ref lenAssert(ctx.mk_eq_atom(concat_length, m_autil.mk_add(items.size(), items.c_ptr())), m); - assert_axiom(lenAssert); - } - return concatAst; -} - -bool theory_str::can_propagate() { - return !m_basicstr_axiom_todo.empty() || !m_str_eq_todo.empty() - || !m_concat_axiom_todo.empty() || !m_concat_eval_todo.empty() - || !m_library_aware_axiom_todo.empty() - || !m_delayed_axiom_setup_terms.empty(); - ; -} - -void theory_str::propagate() { - context & ctx = get_context(); - while (can_propagate()) { - TRACE("str", tout << "propagating..." << std::endl;); - for (unsigned i = 0; i < m_basicstr_axiom_todo.size(); ++i) { - instantiate_basic_string_axioms(m_basicstr_axiom_todo[i]); - } - m_basicstr_axiom_todo.reset(); - TRACE("str", tout << "reset m_basicstr_axiom_todo" << std::endl;); - - for (unsigned i = 0; i < m_str_eq_todo.size(); ++i) { - std::pair pair = m_str_eq_todo[i]; - enode * lhs = pair.first; - enode * rhs = pair.second; - handle_equality(lhs->get_owner(), rhs->get_owner()); - } - m_str_eq_todo.reset(); - - for (unsigned i = 0; i < m_concat_axiom_todo.size(); ++i) { - instantiate_concat_axiom(m_concat_axiom_todo[i]); - } - m_concat_axiom_todo.reset(); - - for (unsigned i = 0; i < m_concat_eval_todo.size(); ++i) { - try_eval_concat(m_concat_eval_todo[i]); - } - m_concat_eval_todo.reset(); - - for (unsigned i = 0; i < m_library_aware_axiom_todo.size(); ++i) { - enode * e = m_library_aware_axiom_todo[i]; - app * a = e->get_owner(); - if (u.str.is_stoi(a)) { - instantiate_axiom_str_to_int(e); - } else if (u.str.is_itos(a)) { - instantiate_axiom_int_to_str(e); - } else if (u.str.is_at(a)) { - instantiate_axiom_CharAt(e); - } else if (u.str.is_prefix(a)) { - instantiate_axiom_prefixof(e); - } else if (u.str.is_suffix(a)) { - instantiate_axiom_suffixof(e); - } else if (u.str.is_contains(a)) { - instantiate_axiom_Contains(e); - } else if (u.str.is_index(a)) { - instantiate_axiom_Indexof(e); - /* TODO NEXT: Indexof2/Lastindexof rewrite? - } else if (is_Indexof2(e)) { - instantiate_axiom_Indexof2(e); - } else if (is_LastIndexof(e)) { - instantiate_axiom_LastIndexof(e); - */ - } else if (u.str.is_extract(a)) { - // TODO check semantics of substr vs. extract - instantiate_axiom_Substr(e); - } else if (u.str.is_replace(a)) { - instantiate_axiom_Replace(e); - } else if (u.str.is_in_re(a)) { - instantiate_axiom_RegexIn(e); - } else { - TRACE("str", tout << "BUG: unhandled library-aware term " << mk_pp(e->get_owner(), get_manager()) << std::endl;); - NOT_IMPLEMENTED_YET(); - } - } - m_library_aware_axiom_todo.reset(); - - for (unsigned i = 0; i < m_delayed_axiom_setup_terms.size(); ++i) { - // I think this is okay - ctx.internalize(m_delayed_axiom_setup_terms[i].get(), false); - set_up_axioms(m_delayed_axiom_setup_terms[i].get()); - } - m_delayed_axiom_setup_terms.reset(); - } -} - -/* - * Attempt to evaluate a concat over constant strings, - * and if this is possible, assert equality between the - * flattened string and the original term. - */ - -void theory_str::try_eval_concat(enode * cat) { - app * a_cat = cat->get_owner(); - SASSERT(u.str.is_concat(a_cat)); - - context & ctx = get_context(); - ast_manager & m = get_manager(); - - TRACE("str", tout << "attempting to flatten " << mk_pp(a_cat, m) << std::endl;); - - std::stack worklist; - zstring flattenedString(""); - bool constOK = true; - - { - app * arg0 = to_app(a_cat->get_arg(0)); - app * arg1 = to_app(a_cat->get_arg(1)); - - worklist.push(arg1); - worklist.push(arg0); + literal theory_str::mk_literal(expr* _e) { + ast_manager & m = get_manager(); + expr_ref e(_e, m); + context& ctx = get_context(); + ensure_enode(e); + return ctx.get_literal(e); } - while (constOK && !worklist.empty()) { - app * evalArg = worklist.top(); worklist.pop(); - zstring nextStr; - if (u.str.is_string(evalArg, nextStr)) { - flattenedString = flattenedString + nextStr; - } else if (u.str.is_concat(evalArg)) { - app * arg0 = to_app(evalArg->get_arg(0)); - app * arg1 = to_app(evalArg->get_arg(1)); + app * theory_str::mk_int(int n) { + return m_autil.mk_numeral(rational(n), true); + } - worklist.push(arg1); - worklist.push(arg0); - } else { - TRACE("str", tout << "non-constant term in concat -- giving up." << std::endl;); - constOK = false; - break; + app * theory_str::mk_int(rational & q) { + return m_autil.mk_numeral(q, true); + } + + expr * theory_str::mk_internal_lenTest_var(expr * node, int lTries) { + ast_manager & m = get_manager(); + + std::stringstream ss; + ss << "$$_len_" << mk_ismt2_pp(node, m) << "_" << lTries << "_" << tmpLenTestVarCount; + tmpLenTestVarCount += 1; + std::string name = ss.str(); + app * var = mk_str_var(name); + internal_lenTest_vars.insert(var); + m_trail.push_back(var); + return var; + } + + expr * theory_str::mk_internal_valTest_var(expr * node, int len, int vTries) { + ast_manager & m = get_manager(); + std::stringstream ss; + ss << "$$_val_" << mk_ismt2_pp(node, m) << "_" << len << "_" << vTries << "_" << tmpValTestVarCount; + tmpValTestVarCount += 1; + std::string name = ss.str(); + app * var = mk_str_var(name); + internal_valTest_vars.insert(var); + m_trail.push_back(var); + return var; + } + + void theory_str::track_variable_scope(expr * var) { + if (internal_variable_scope_levels.find(sLevel) == internal_variable_scope_levels.end()) { + internal_variable_scope_levels[sLevel] = std::set(); } - } - if (constOK) { - TRACE("str", tout << "flattened to \"" << flattenedString.encode().c_str() << "\"" << std::endl;); - expr_ref constStr(mk_string(flattenedString), m); - expr_ref axiom(ctx.mk_eq_atom(a_cat, constStr), m); - assert_axiom(axiom); - } -} - -/* - * Instantiate an axiom of the following form: - * Length(Concat(x, y)) = Length(x) + Length(y) - */ -void theory_str::instantiate_concat_axiom(enode * cat) { - app * a_cat = cat->get_owner(); - SASSERT(u.str.is_concat(a_cat)); - - ast_manager & m = get_manager(); - - TRACE("str", tout << "instantiating concat axiom for " << mk_ismt2_pp(a_cat, m) << std::endl;); - - // build LHS - expr_ref len_xy(m); - len_xy = mk_strlen(a_cat); - SASSERT(len_xy); - - // build RHS: start by extracting x and y from Concat(x, y) - unsigned nArgs = a_cat->get_num_args(); - SASSERT(nArgs == 2); - app * a_x = to_app(a_cat->get_arg(0)); - app * a_y = to_app(a_cat->get_arg(1)); - - expr_ref len_x(m); - len_x = mk_strlen(a_x); - SASSERT(len_x); - - expr_ref len_y(m); - len_y = mk_strlen(a_y); - SASSERT(len_y); - - // now build len_x + len_y - expr_ref len_x_plus_len_y(m); - len_x_plus_len_y = m_autil.mk_add(len_x, len_y); - SASSERT(len_x_plus_len_y); - - // finally assert equality between the two subexpressions - app * eq = m.mk_eq(len_xy, len_x_plus_len_y); - SASSERT(eq); - assert_axiom(eq); -} - -/* - * Add axioms that are true for any string variable: - * 1. Length(x) >= 0 - * 2. Length(x) == 0 <=> x == "" - * If the term is a string constant, we can assert something stronger: - * Length(x) == strlen(x) - */ -void theory_str::instantiate_basic_string_axioms(enode * str) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - - TRACE("str", tout << "set up basic string axioms on " << mk_pp(str->get_owner(), m) << std::endl;); - - // TESTING: attempt to avoid a crash here when a variable goes out of scope - if (str->get_iscope_lvl() > ctx.get_scope_level()) { - TRACE("str", tout << "WARNING: skipping axiom setup on out-of-scope string term" << std::endl;); - return; + internal_variable_scope_levels[sLevel].insert(var); } - // generate a stronger axiom for constant strings - app * a_str = str->get_owner(); - if (u.str.is_string(a_str)) { - expr_ref len_str(m); - len_str = mk_strlen(a_str); - SASSERT(len_str); + app * theory_str::mk_internal_xor_var() { + return mk_int_var("$$_xor"); + } - zstring strconst; - u.str.is_string(str->get_owner(), strconst); - TRACE("str", tout << "instantiating constant string axioms for \"" << strconst.encode().c_str() << "\"" << std::endl;); - unsigned int l = strconst.length(); - expr_ref len(m_autil.mk_numeral(rational(l), true), m); + app * theory_str::mk_int_var(std::string name) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + TRACE("str", tout << "creating integer variable " << name << " at scope level " << sLevel << std::endl;); + + sort * int_sort = m.mk_sort(m_autil.get_family_id(), INT_SORT); + app * a = m.mk_fresh_const(name.c_str(), int_sort); + + ctx.internalize(a, false); + SASSERT(ctx.get_enode(a) != NULL); + SASSERT(ctx.e_internalized(a)); + ctx.mark_as_relevant(a); + // I'm assuming that this combination will do the correct thing in the integer theory. + + //mk_var(ctx.get_enode(a)); + m_trail.push_back(a); + //variable_set.insert(a); + //internal_variable_set.insert(a); + //track_variable_scope(a); + + return a; + } + + app * theory_str::mk_unroll_bound_var() { + return mk_int_var("unroll"); + } + + app * theory_str::mk_unroll_test_var() { + app * v = mk_str_var("unrollTest"); // was uRt + internal_unrollTest_vars.insert(v); + track_variable_scope(v); + return v; + } + + app * theory_str::mk_str_var(std::string name) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + TRACE("str", tout << "creating string variable " << name << " at scope level " << sLevel << std::endl;); + + sort * string_sort = u.str.mk_string_sort(); + app * a = m.mk_fresh_const(name.c_str(), string_sort); + + TRACE("str", tout << "a->get_family_id() = " << a->get_family_id() << std::endl + << "this->get_family_id() = " << this->get_family_id() << std::endl;); + + // I have a hunch that this may not get internalized for free... + ctx.internalize(a, false); + SASSERT(ctx.get_enode(a) != NULL); + SASSERT(ctx.e_internalized(a)); + // this might help?? + mk_var(ctx.get_enode(a)); + m_basicstr_axiom_todo.push_back(ctx.get_enode(a)); + TRACE("str", tout << "add " << mk_pp(a, m) << " to m_basicstr_axiom_todo" << std::endl;); + + m_trail.push_back(a); + variable_set.insert(a); + internal_variable_set.insert(a); + track_variable_scope(a); + + return a; + } + + app * theory_str::mk_regex_rep_var() { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + sort * string_sort = u.str.mk_string_sort(); + app * a = m.mk_fresh_const("regex", string_sort); + + ctx.internalize(a, false); + SASSERT(ctx.get_enode(a) != NULL); + SASSERT(ctx.e_internalized(a)); + mk_var(ctx.get_enode(a)); + m_basicstr_axiom_todo.push_back(ctx.get_enode(a)); + TRACE("str", tout << "add " << mk_pp(a, m) << " to m_basicstr_axiom_todo" << std::endl;); + + m_trail.push_back(a); + variable_set.insert(a); + //internal_variable_set.insert(a); + regex_variable_set.insert(a); + track_variable_scope(a); + + return a; + } + + void theory_str::add_nonempty_constraint(expr * s) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + expr_ref ax1(m.mk_not(ctx.mk_eq_atom(s, mk_string(""))), m); + assert_axiom(ax1); - literal lit(mk_eq(len_str, len, false)); - ctx.mark_as_relevant(lit); - ctx.mk_th_axiom(get_id(), 1, &lit); - } else { - // build axiom 1: Length(a_str) >= 0 { // build LHS - expr_ref len_str(m); - len_str = mk_strlen(a_str); + expr_ref len_str(mk_strlen(s), m); SASSERT(len_str); // build RHS - expr_ref zero(m); - zero = m_autil.mk_numeral(rational(0), true); + expr_ref zero(m_autil.mk_numeral(rational(0), true), m); SASSERT(zero); - // build LHS >= RHS and assert - app * lhs_ge_rhs = m_autil.mk_ge(len_str, zero); - SASSERT(lhs_ge_rhs); - TRACE("str", tout << "string axiom 1: " << mk_ismt2_pp(lhs_ge_rhs, m) << std::endl;); - assert_axiom(lhs_ge_rhs); + // build LHS > RHS and assert + // we have to build !(LHS <= RHS) instead + expr_ref lhs_gt_rhs(m.mk_not(m_autil.mk_le(len_str, zero)), m); + SASSERT(lhs_gt_rhs); + assert_axiom(lhs_gt_rhs); } + } - // build axiom 2: Length(a_str) == 0 <=> a_str == "" + app * theory_str::mk_nonempty_str_var() { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + std::stringstream ss; + ss << tmpStringVarCount; + tmpStringVarCount++; + std::string name = "$$_str" + ss.str(); + + TRACE("str", tout << "creating nonempty string variable " << name << " at scope level " << sLevel << std::endl;); + + sort * string_sort = u.str.mk_string_sort(); + app * a = m.mk_fresh_const(name.c_str(), string_sort); + + ctx.internalize(a, false); + SASSERT(ctx.get_enode(a) != NULL); + // this might help?? + mk_var(ctx.get_enode(a)); + + // assert a variation of the basic string axioms that ensures this string is nonempty { - // build LHS of iff - expr_ref len_str(m); - len_str = mk_strlen(a_str); + // build LHS + expr_ref len_str(mk_strlen(a), m); SASSERT(len_str); - expr_ref zero(m); - zero = m_autil.mk_numeral(rational(0), true); + // build RHS + expr_ref zero(m_autil.mk_numeral(rational(0), true), m); SASSERT(zero); - expr_ref lhs(m); - lhs = ctx.mk_eq_atom(len_str, zero); - SASSERT(lhs); - // build RHS of iff - expr_ref empty_str(m); - empty_str = mk_string(""); - SASSERT(empty_str); - expr_ref rhs(m); - rhs = ctx.mk_eq_atom(a_str, empty_str); - SASSERT(rhs); - // build LHS <=> RHS and assert - TRACE("str", tout << "string axiom 2: " << mk_ismt2_pp(lhs, m) << " <=> " << mk_ismt2_pp(rhs, m) << std::endl;); - literal l(mk_eq(lhs, rhs, true)); - ctx.mark_as_relevant(l); - ctx.mk_th_axiom(get_id(), 1, &l); + // build LHS > RHS and assert + // we have to build !(LHS <= RHS) instead + expr_ref lhs_gt_rhs(m.mk_not(m_autil.mk_le(len_str, zero)), m); + SASSERT(lhs_gt_rhs); + assert_axiom(lhs_gt_rhs); } - } -} + // add 'a' to variable sets, so we can keep track of it + m_trail.push_back(a); + variable_set.insert(a); + internal_variable_set.insert(a); + track_variable_scope(a); -/* - * Add an axiom of the form: - * (lhs == rhs) -> ( Length(lhs) == Length(rhs) ) - */ -void theory_str::instantiate_str_eq_length_axiom(enode * lhs, enode * rhs) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - - app * a_lhs = lhs->get_owner(); - app * a_rhs = rhs->get_owner(); - - // build premise: (lhs == rhs) - expr_ref premise(ctx.mk_eq_atom(a_lhs, a_rhs), m); - - // build conclusion: ( Length(lhs) == Length(rhs) ) - expr_ref len_lhs(mk_strlen(a_lhs), m); - SASSERT(len_lhs); - expr_ref len_rhs(mk_strlen(a_rhs), m); - SASSERT(len_rhs); - expr_ref conclusion(ctx.mk_eq_atom(len_lhs, len_rhs), m); - - TRACE("str", tout << "string-eq length-eq axiom: " - << mk_ismt2_pp(premise, m) << " -> " << mk_ismt2_pp(conclusion, m) << std::endl;); - assert_implication(premise, conclusion); -} - -void theory_str::instantiate_axiom_CharAt(enode * e) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - - app * expr = e->get_owner(); - if (axiomatized_terms.contains(expr)) { - TRACE("str", tout << "already set up CharAt axiom for " << mk_pp(expr, m) << std::endl;); - return; - } - axiomatized_terms.insert(expr); - - TRACE("str", tout << "instantiate CharAt axiom for " << mk_pp(expr, m) << std::endl;); - - expr_ref ts0(mk_str_var("ts0"), m); - expr_ref ts1(mk_str_var("ts1"), m); - expr_ref ts2(mk_str_var("ts2"), m); - - expr_ref cond(m.mk_and( - m_autil.mk_ge(expr->get_arg(1), mk_int(0)), - // REWRITE for arithmetic theory: - // m_autil.mk_lt(expr->get_arg(1), mk_strlen(expr->get_arg(0))) - m.mk_not(m_autil.mk_ge(m_autil.mk_add(expr->get_arg(1), m_autil.mk_mul(mk_int(-1), mk_strlen(expr->get_arg(0)))), mk_int(0))) - ), m); - - expr_ref_vector and_item(m); - and_item.push_back(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(ts0, mk_concat(ts1, ts2)))); - and_item.push_back(ctx.mk_eq_atom(expr->get_arg(1), mk_strlen(ts0))); - and_item.push_back(ctx.mk_eq_atom(mk_strlen(ts1), mk_int(1))); - - expr_ref thenBranch(m.mk_and(and_item.size(), and_item.c_ptr()), m); - expr_ref elseBranch(ctx.mk_eq_atom(ts1, mk_string("")), m); - - expr_ref axiom(m.mk_ite(cond, thenBranch, elseBranch), m); - expr_ref reductionVar(ctx.mk_eq_atom(expr, ts1), m); - - SASSERT(axiom); - SASSERT(reductionVar); - - expr_ref finalAxiom(m.mk_and(axiom, reductionVar), m); - SASSERT(finalAxiom); - assert_axiom(finalAxiom); -} - -void theory_str::instantiate_axiom_prefixof(enode * e) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - - app * expr = e->get_owner(); - if (axiomatized_terms.contains(expr)) { - TRACE("str", tout << "already set up prefixof axiom for " << mk_pp(expr, m) << std::endl;); - return; - } - axiomatized_terms.insert(expr); - - TRACE("str", tout << "instantiate prefixof axiom for " << mk_pp(expr, m) << std::endl;); - - expr_ref ts0(mk_str_var("ts0"), m); - expr_ref ts1(mk_str_var("ts1"), m); - - expr_ref_vector innerItems(m); - innerItems.push_back(ctx.mk_eq_atom(expr->get_arg(1), mk_concat(ts0, ts1))); - innerItems.push_back(ctx.mk_eq_atom(mk_strlen(ts0), mk_strlen(expr->get_arg(0)))); - innerItems.push_back(m.mk_ite(ctx.mk_eq_atom(ts0, expr->get_arg(0)), expr, m.mk_not(expr))); - expr_ref then1(m.mk_and(innerItems.size(), innerItems.c_ptr()), m); - SASSERT(then1); - - // the top-level condition is Length(arg0) >= Length(arg1) - expr_ref topLevelCond( - m_autil.mk_ge( - m_autil.mk_add( - mk_strlen(expr->get_arg(1)), m_autil.mk_mul(mk_int(-1), mk_strlen(expr->get_arg(0)))), - mk_int(0)) - , m); - SASSERT(topLevelCond); - - expr_ref finalAxiom(m.mk_ite(topLevelCond, then1, m.mk_not(expr)), m); - SASSERT(finalAxiom); - assert_axiom(finalAxiom); -} - -void theory_str::instantiate_axiom_suffixof(enode * e) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - - app * expr = e->get_owner(); - if (axiomatized_terms.contains(expr)) { - TRACE("str", tout << "already set up suffixof axiom for " << mk_pp(expr, m) << std::endl;); - return; - } - axiomatized_terms.insert(expr); - - TRACE("str", tout << "instantiate suffixof axiom for " << mk_pp(expr, m) << std::endl;); - - expr_ref ts0(mk_str_var("ts0"), m); - expr_ref ts1(mk_str_var("ts1"), m); - - expr_ref_vector innerItems(m); - innerItems.push_back(ctx.mk_eq_atom(expr->get_arg(1), mk_concat(ts0, ts1))); - innerItems.push_back(ctx.mk_eq_atom(mk_strlen(ts1), mk_strlen(expr->get_arg(0)))); - innerItems.push_back(m.mk_ite(ctx.mk_eq_atom(ts1, expr->get_arg(0)), expr, m.mk_not(expr))); - expr_ref then1(m.mk_and(innerItems.size(), innerItems.c_ptr()), m); - SASSERT(then1); - - // the top-level condition is Length(arg0) >= Length(arg1) - expr_ref topLevelCond( - m_autil.mk_ge( - m_autil.mk_add( - mk_strlen(expr->get_arg(1)), m_autil.mk_mul(mk_int(-1), mk_strlen(expr->get_arg(0)))), - mk_int(0)) - , m); - SASSERT(topLevelCond); - - expr_ref finalAxiom(m.mk_ite(topLevelCond, then1, m.mk_not(expr)), m); - SASSERT(finalAxiom); - assert_axiom(finalAxiom); -} - -void theory_str::instantiate_axiom_Contains(enode * e) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - - app * ex = e->get_owner(); - if (axiomatized_terms.contains(ex)) { - TRACE("str", tout << "already set up Contains axiom for " << mk_pp(ex, m) << std::endl;); - return; - } - axiomatized_terms.insert(ex); - - // quick path, because this is necessary due to rewriter behaviour - // at minimum it should fix z3str/concat-006.smt2 - zstring haystackStr, needleStr; - if (u.str.is_string(ex->get_arg(0), haystackStr) && u.str.is_string(ex->get_arg(1), needleStr)) { - TRACE("str", tout << "eval constant Contains term " << mk_pp(ex, m) << std::endl;); - if (haystackStr.contains(needleStr)) { - assert_axiom(ex); - } else { - assert_axiom(m.mk_not(ex)); - } - return; + return a; } - { // register Contains() - expr * str = ex->get_arg(0); - expr * substr = ex->get_arg(1); - contains_map.push_back(ex); - std::pair key = std::pair(str, substr); - contain_pair_bool_map.insert(str, substr, ex); - contain_pair_idx_map[str].insert(key); - contain_pair_idx_map[substr].insert(key); - } + app * theory_str::mk_unroll(expr * n, expr * bound) { + context & ctx = get_context(); + ast_manager & m = get_manager(); - TRACE("str", tout << "instantiate Contains axiom for " << mk_pp(ex, m) << std::endl;); + expr * args[2] = {n, bound}; + app * unrollFunc = get_manager().mk_app(get_id(), _OP_RE_UNROLL, 0, 0, 2, args); + m_trail.push_back(unrollFunc); - expr_ref ts0(mk_str_var("ts0"), m); - expr_ref ts1(mk_str_var("ts1"), m); + expr_ref_vector items(m); + items.push_back(ctx.mk_eq_atom(ctx.mk_eq_atom(bound, mk_int(0)), ctx.mk_eq_atom(unrollFunc, mk_string("")))); + items.push_back(m_autil.mk_ge(bound, mk_int(0))); + items.push_back(m_autil.mk_ge(mk_strlen(unrollFunc), mk_int(0))); - expr_ref breakdownAssert(ctx.mk_eq_atom(ex, ctx.mk_eq_atom(ex->get_arg(0), mk_concat(ts0, mk_concat(ex->get_arg(1), ts1)))), m); - SASSERT(breakdownAssert); - assert_axiom(breakdownAssert); -} - -void theory_str::instantiate_axiom_Indexof(enode * e) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - - app * expr = e->get_owner(); - if (axiomatized_terms.contains(expr)) { - TRACE("str", tout << "already set up Indexof axiom for " << mk_pp(expr, m) << std::endl;); - return; - } - axiomatized_terms.insert(expr); - - TRACE("str", tout << "instantiate Indexof axiom for " << mk_pp(expr, m) << std::endl;); - - expr_ref x1(mk_str_var("x1"), m); - expr_ref x2(mk_str_var("x2"), m); - expr_ref indexAst(mk_int_var("index"), m); - - expr_ref condAst(mk_contains(expr->get_arg(0), expr->get_arg(1)), m); - SASSERT(condAst); - - // ----------------------- - // true branch - expr_ref_vector thenItems(m); - // args[0] = x1 . args[1] . x2 - thenItems.push_back(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(x1, mk_concat(expr->get_arg(1), x2)))); - // indexAst = |x1| - thenItems.push_back(ctx.mk_eq_atom(indexAst, mk_strlen(x1))); - // args[0] = x3 . x4 - // /\ |x3| = |x1| + |args[1]| - 1 - // /\ ! contains(x3, args[1]) - expr_ref x3(mk_str_var("x3"), m); - expr_ref x4(mk_str_var("x4"), m); - expr_ref tmpLen(m_autil.mk_add(indexAst, mk_strlen(expr->get_arg(1)), mk_int(-1)), m); - SASSERT(tmpLen); - thenItems.push_back(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(x3, x4))); - thenItems.push_back(ctx.mk_eq_atom(mk_strlen(x3), tmpLen)); - thenItems.push_back(m.mk_not(mk_contains(x3, expr->get_arg(1)))); - expr_ref thenBranch(m.mk_and(thenItems.size(), thenItems.c_ptr()), m); - SASSERT(thenBranch); - - // ----------------------- - // false branch - expr_ref elseBranch(ctx.mk_eq_atom(indexAst, mk_int(-1)), m); - SASSERT(elseBranch); - - expr_ref breakdownAssert(m.mk_ite(condAst, thenBranch, elseBranch), m); - SASSERT(breakdownAssert); - - expr_ref reduceToIndex(ctx.mk_eq_atom(expr, indexAst), m); - SASSERT(reduceToIndex); - - expr_ref finalAxiom(m.mk_and(breakdownAssert, reduceToIndex), m); - SASSERT(finalAxiom); - assert_axiom(finalAxiom); -} - -void theory_str::instantiate_axiom_Indexof2(enode * e) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - - app * expr = e->get_owner(); - if (axiomatized_terms.contains(expr)) { - TRACE("str", tout << "already set up Indexof2 axiom for " << mk_pp(expr, m) << std::endl;); - return; - } - axiomatized_terms.insert(expr); - - TRACE("str", tout << "instantiate Indexof2 axiom for " << mk_pp(expr, m) << std::endl;); - - // ------------------------------------------------------------------------------- - // if (arg[2] >= length(arg[0])) // ite2 - // resAst = -1 - // else - // args[0] = prefix . suffix - // /\ indexAst = indexof(suffix, arg[1]) - // /\ args[2] = len(prefix) - // /\ if (indexAst == -1) resAst = indexAst // ite3 - // else resAst = args[2] + indexAst - // ------------------------------------------------------------------------------- - - expr_ref resAst(mk_int_var("res"), m); - expr_ref indexAst(mk_int_var("index"), m); - expr_ref prefix(mk_str_var("prefix"), m); - expr_ref suffix(mk_str_var("suffix"), m); - expr_ref prefixLen(mk_strlen(prefix), m); - expr_ref zeroAst(mk_int(0), m); - expr_ref negOneAst(mk_int(-1), m); - - expr_ref ite3(m.mk_ite( - ctx.mk_eq_atom(indexAst, negOneAst), - ctx.mk_eq_atom(resAst, negOneAst), - ctx.mk_eq_atom(resAst, m_autil.mk_add(expr->get_arg(2), indexAst)) - ),m); - - expr_ref_vector ite2ElseItems(m); - ite2ElseItems.push_back(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(prefix, suffix))); - ite2ElseItems.push_back(ctx.mk_eq_atom(indexAst, mk_indexof(suffix, expr->get_arg(1)))); - ite2ElseItems.push_back(ctx.mk_eq_atom(expr->get_arg(2), prefixLen)); - ite2ElseItems.push_back(ite3); - expr_ref ite2Else(m.mk_and(ite2ElseItems.size(), ite2ElseItems.c_ptr()), m); - SASSERT(ite2Else); - - expr_ref ite2(m.mk_ite( - //m_autil.mk_ge(expr->get_arg(2), mk_strlen(expr->get_arg(0))), - m_autil.mk_ge(m_autil.mk_add(expr->get_arg(2), m_autil.mk_mul(mk_int(-1), mk_strlen(expr->get_arg(0)))), zeroAst), - ctx.mk_eq_atom(resAst, negOneAst), - ite2Else - ), m); - SASSERT(ite2); - - expr_ref ite1(m.mk_ite( - //m_autil.mk_lt(expr->get_arg(2), zeroAst), - m.mk_not(m_autil.mk_ge(expr->get_arg(2), zeroAst)), - ctx.mk_eq_atom(resAst, mk_indexof(expr->get_arg(0), expr->get_arg(1))), - ite2 - ), m); - SASSERT(ite1); - assert_axiom(ite1); - - expr_ref reduceTerm(ctx.mk_eq_atom(expr, resAst), m); - SASSERT(reduceTerm); - assert_axiom(reduceTerm); -} - -void theory_str::instantiate_axiom_LastIndexof(enode * e) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - - app * expr = e->get_owner(); - if (axiomatized_terms.contains(expr)) { - TRACE("str", tout << "already set up LastIndexof axiom for " << mk_pp(expr, m) << std::endl;); - return; - } - axiomatized_terms.insert(expr); - - TRACE("str", tout << "instantiate LastIndexof axiom for " << mk_pp(expr, m) << std::endl;); - - expr_ref x1(mk_str_var("x1"), m); - expr_ref x2(mk_str_var("x2"), m); - expr_ref indexAst(mk_int_var("index"), m); - expr_ref_vector items(m); - - // args[0] = x1 . args[1] . x2 - expr_ref eq1(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(x1, mk_concat(expr->get_arg(1), x2))), m); - expr_ref arg0HasArg1(mk_contains(expr->get_arg(0), expr->get_arg(1)), m); // arg0HasArg1 = Contains(args[0], args[1]) - items.push_back(ctx.mk_eq_atom(arg0HasArg1, eq1)); - - - expr_ref condAst(arg0HasArg1, m); - //---------------------------- - // true branch - expr_ref_vector thenItems(m); - thenItems.push_back(m_autil.mk_ge(indexAst, mk_int(0))); - // args[0] = x1 . args[1] . x2 - // x1 doesn't contain args[1] - thenItems.push_back(m.mk_not(mk_contains(x2, expr->get_arg(1)))); - thenItems.push_back(ctx.mk_eq_atom(indexAst, mk_strlen(x1))); - - bool canSkip = false; - zstring arg1Str; - if (u.str.is_string(expr->get_arg(1), arg1Str)) { - if (arg1Str.length() == 1) { - canSkip = true; - } - } - - if (!canSkip) { - // args[0] = x3 . x4 /\ |x3| = |x1| + 1 /\ ! contains(x4, args[1]) - expr_ref x3(mk_str_var("x3"), m); - expr_ref x4(mk_str_var("x4"), m); - expr_ref tmpLen(m_autil.mk_add(indexAst, mk_int(1)), m); - thenItems.push_back(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(x3, x4))); - thenItems.push_back(ctx.mk_eq_atom(mk_strlen(x3), tmpLen)); - thenItems.push_back(m.mk_not(mk_contains(x4, expr->get_arg(1)))); - } - //---------------------------- - // else branch - expr_ref_vector elseItems(m); - elseItems.push_back(ctx.mk_eq_atom(indexAst, mk_int(-1))); - - items.push_back(m.mk_ite(condAst, m.mk_and(thenItems.size(), thenItems.c_ptr()), m.mk_and(elseItems.size(), elseItems.c_ptr()))); - - expr_ref breakdownAssert(m.mk_and(items.size(), items.c_ptr()), m); - SASSERT(breakdownAssert); - - expr_ref reduceToIndex(ctx.mk_eq_atom(expr, indexAst), m); - SASSERT(reduceToIndex); - - expr_ref finalAxiom(m.mk_and(breakdownAssert, reduceToIndex), m); - SASSERT(finalAxiom); - assert_axiom(finalAxiom); -} - -void theory_str::instantiate_axiom_Substr(enode * e) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - - app * expr = e->get_owner(); - if (axiomatized_terms.contains(expr)) { - TRACE("str", tout << "already set up Substr axiom for " << mk_pp(expr, m) << std::endl;); - return; - } - axiomatized_terms.insert(expr); - - TRACE("str", tout << "instantiate Substr axiom for " << mk_pp(expr, m) << std::endl;); - - expr_ref substrBase(expr->get_arg(0), m); - expr_ref substrPos(expr->get_arg(1), m); - expr_ref substrLen(expr->get_arg(2), m); - SASSERT(substrBase); - SASSERT(substrPos); - SASSERT(substrLen); - - expr_ref zero(m_autil.mk_numeral(rational::zero(), true), m); - expr_ref minusOne(m_autil.mk_numeral(rational::minus_one(), true), m); - SASSERT(zero); - SASSERT(minusOne); - - expr_ref_vector argumentsValid_terms(m); - // pos >= 0 - argumentsValid_terms.push_back(m_autil.mk_ge(substrPos, zero)); - // pos < strlen(base) - // --> pos + -1*strlen(base) < 0 - argumentsValid_terms.push_back(m.mk_not(m_autil.mk_ge( - m_autil.mk_add(substrPos, m_autil.mk_mul(minusOne, substrLen)), - zero))); - // len >= 0 - argumentsValid_terms.push_back(m_autil.mk_ge(substrLen, zero)); - - expr_ref argumentsValid(mk_and(argumentsValid_terms), m); - SASSERT(argumentsValid); - ctx.internalize(argumentsValid, false); - - // (pos+len) >= strlen(base) - // --> pos + len + -1*strlen(base) >= 0 - expr_ref lenOutOfBounds(m_autil.mk_ge( - m_autil.mk_add(substrPos, substrLen, m_autil.mk_mul(minusOne, mk_strlen(substrBase))), - zero), m); - SASSERT(lenOutOfBounds); - ctx.internalize(argumentsValid, false); - - // Case 1: pos < 0 or pos >= strlen(base) or len < 0 - // ==> (Substr ...) = "" - expr_ref case1_premise(m.mk_not(argumentsValid), m); - SASSERT(case1_premise); - ctx.internalize(case1_premise, false); - expr_ref case1_conclusion(ctx.mk_eq_atom(expr, mk_string("")), m); - SASSERT(case1_conclusion); - ctx.internalize(case1_conclusion, false); - expr_ref case1(rewrite_implication(case1_premise, case1_conclusion), m); - SASSERT(case1); - - // Case 2: (pos >= 0 and pos < strlen(base) and len >= 0) and (pos+len) >= strlen(base) - // ==> base = t0.t1 AND len(t0) = pos AND (Substr ...) = t1 - expr_ref t0(mk_str_var("t0"), m); - expr_ref t1(mk_str_var("t1"), m); - expr_ref case2_conclusion(m.mk_and( - ctx.mk_eq_atom(substrBase, mk_concat(t0,t1)), - ctx.mk_eq_atom(mk_strlen(t0), substrPos), - ctx.mk_eq_atom(expr, t1)), m); - expr_ref case2(rewrite_implication(m.mk_and(argumentsValid, lenOutOfBounds), case2_conclusion), m); - SASSERT(case2); - - // Case 3: (pos >= 0 and pos < strlen(base) and len >= 0) and (pos+len) < strlen(base) - // ==> base = t2.t3.t4 AND len(t2) = pos AND len(t3) = len AND (Substr ...) = t3 - expr_ref t2(mk_str_var("t2"), m); - expr_ref t3(mk_str_var("t3"), m); - expr_ref t4(mk_str_var("t4"), m); - expr_ref_vector case3_conclusion_terms(m); - case3_conclusion_terms.push_back(ctx.mk_eq_atom(substrBase, mk_concat(t2, mk_concat(t3, t4)))); - case3_conclusion_terms.push_back(ctx.mk_eq_atom(mk_strlen(t2), substrPos)); - case3_conclusion_terms.push_back(ctx.mk_eq_atom(mk_strlen(t3), substrLen)); - case3_conclusion_terms.push_back(ctx.mk_eq_atom(expr, t3)); - expr_ref case3_conclusion(mk_and(case3_conclusion_terms), m); - expr_ref case3(rewrite_implication(m.mk_and(argumentsValid, m.mk_not(lenOutOfBounds)), case3_conclusion), m); - SASSERT(case3); - - ctx.internalize(case1, false); - ctx.internalize(case2, false); - ctx.internalize(case3, false); - - expr_ref finalAxiom(m.mk_and(case1, case2, case3), m); - SASSERT(finalAxiom); - assert_axiom(finalAxiom); -} - -void theory_str::instantiate_axiom_Replace(enode * e) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - - app * expr = e->get_owner(); - if (axiomatized_terms.contains(expr)) { - TRACE("str", tout << "already set up Replace axiom for " << mk_pp(expr, m) << std::endl;); - return; - } - axiomatized_terms.insert(expr); - - TRACE("str", tout << "instantiate Replace axiom for " << mk_pp(expr, m) << std::endl;); - - expr_ref x1(mk_str_var("x1"), m); - expr_ref x2(mk_str_var("x2"), m); - expr_ref i1(mk_int_var("i1"), m); - expr_ref result(mk_str_var("result"), m); - - // condAst = Contains(args[0], args[1]) - expr_ref condAst(mk_contains(expr->get_arg(0), expr->get_arg(1)), m); - // ----------------------- - // true branch - expr_ref_vector thenItems(m); - // args[0] = x1 . args[1] . x2 - thenItems.push_back(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(x1, mk_concat(expr->get_arg(1), x2)))); - // i1 = |x1| - thenItems.push_back(ctx.mk_eq_atom(i1, mk_strlen(x1))); - // args[0] = x3 . x4 /\ |x3| = |x1| + |args[1]| - 1 /\ ! contains(x3, args[1]) - expr_ref x3(mk_str_var("x3"), m); - expr_ref x4(mk_str_var("x4"), m); - expr_ref tmpLen(m_autil.mk_add(i1, mk_strlen(expr->get_arg(1)), mk_int(-1)), m); - thenItems.push_back(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(x3, x4))); - thenItems.push_back(ctx.mk_eq_atom(mk_strlen(x3), tmpLen)); - thenItems.push_back(m.mk_not(mk_contains(x3, expr->get_arg(1)))); - thenItems.push_back(ctx.mk_eq_atom(result, mk_concat(x1, mk_concat(expr->get_arg(2), x2)))); - // ----------------------- - // false branch - expr_ref elseBranch(ctx.mk_eq_atom(result, expr->get_arg(0)), m); - - expr_ref breakdownAssert(m.mk_ite(condAst, m.mk_and(thenItems.size(), thenItems.c_ptr()), elseBranch), m); - SASSERT(breakdownAssert); - - expr_ref reduceToResult(ctx.mk_eq_atom(expr, result), m); - SASSERT(reduceToResult); - - expr_ref finalAxiom(m.mk_and(breakdownAssert, reduceToResult), m); - SASSERT(finalAxiom); - assert_axiom(finalAxiom); -} - -void theory_str::instantiate_axiom_str_to_int(enode * e) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - - app * ex = e->get_owner(); - if (axiomatized_terms.contains(ex)) { - TRACE("str", tout << "already set up str.to-int axiom for " << mk_pp(ex, m) << std::endl;); - return; - } - axiomatized_terms.insert(ex); - - TRACE("str", tout << "instantiate str.to-int axiom for " << mk_pp(ex, m) << std::endl;); - - // let expr = (str.to-int S) - // axiom 1: expr >= -1 - // axiom 2: expr = 0 <==> S = "0" - // axiom 3: expr >= 1 ==> len(S) > 0 AND S[0] != "0" - - expr * S = ex->get_arg(0); - { - expr_ref axiom1(m_autil.mk_ge(ex, m_autil.mk_numeral(rational::minus_one(), true)), m); - SASSERT(axiom1); - assert_axiom(axiom1); - } - - { - expr_ref lhs(ctx.mk_eq_atom(ex, m_autil.mk_numeral(rational::zero(), true)), m); - expr_ref rhs(ctx.mk_eq_atom(S, mk_string("0")), m); - expr_ref axiom2(ctx.mk_eq_atom(lhs, rhs), m); - SASSERT(axiom2); - assert_axiom(axiom2); - } - - { - expr_ref premise(m_autil.mk_ge(ex, m_autil.mk_numeral(rational::one(), true)), m); - expr_ref hd(mk_str_var("hd"), m); - expr_ref tl(mk_str_var("tl"), m); - expr_ref conclusion1(ctx.mk_eq_atom(S, mk_concat(hd, tl)), m); - expr_ref conclusion2(ctx.mk_eq_atom(mk_strlen(hd), m_autil.mk_numeral(rational::one(), true)), m); - expr_ref conclusion3(m.mk_not(ctx.mk_eq_atom(hd, mk_string("0"))), m); - expr_ref conclusion(m.mk_and(conclusion1, conclusion2, conclusion3), m); - SASSERT(premise); - SASSERT(conclusion); - assert_implication(premise, conclusion); - } -} - -void theory_str::instantiate_axiom_int_to_str(enode * e) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - - app * ex = e->get_owner(); - if (axiomatized_terms.contains(ex)) { - TRACE("str", tout << "already set up str.from-int axiom for " << mk_pp(ex, m) << std::endl;); - return; - } - axiomatized_terms.insert(ex); - - TRACE("str", tout << "instantiate str.from-int axiom for " << mk_pp(ex, m) << std::endl;); - - // axiom 1: N < 0 <==> (str.from-int N) = "" - expr * N = ex->get_arg(0); - { - expr_ref axiom1_lhs(m.mk_not(m_autil.mk_ge(N, m_autil.mk_numeral(rational::zero(), true))), m); - expr_ref axiom1_rhs(ctx.mk_eq_atom(ex, mk_string("")), m); - expr_ref axiom1(ctx.mk_eq_atom(axiom1_lhs, axiom1_rhs), m); - SASSERT(axiom1); - assert_axiom(axiom1); - } -} - -expr * theory_str::mk_RegexIn(expr * str, expr * regexp) { - app * regexIn = u.re.mk_in_re(str, regexp); - // immediately force internalization so that axiom setup does not fail - get_context().internalize(regexIn, false); - set_up_axioms(regexIn); - return regexIn; -} - -static zstring str2RegexStr(zstring str) { - zstring res(""); - int len = str.length(); - for (int i = 0; i < len; i++) { - char nc = str[i]; - // 12 special chars - if (nc == '\\' || nc == '^' || nc == '$' || nc == '.' || nc == '|' || nc == '?' - || nc == '*' || nc == '+' || nc == '(' || nc == ')' || nc == '[' || nc == '{') { - res = res + zstring("\\"); - } - char tmp[2] = {(char)str[i], '\0'}; - res = res + zstring(tmp); - } - return res; -} - -zstring theory_str::get_std_regex_str(expr * regex) { - app * a_regex = to_app(regex); - if (u.re.is_to_re(a_regex)) { - expr * regAst = a_regex->get_arg(0); - zstring regAstVal; - u.str.is_string(regAst, regAstVal); - zstring regStr = str2RegexStr(regAstVal); - return regStr; - } else if (u.re.is_concat(a_regex)) { - expr * reg1Ast = a_regex->get_arg(0); - expr * reg2Ast = a_regex->get_arg(1); - zstring reg1Str = get_std_regex_str(reg1Ast); - zstring reg2Str = get_std_regex_str(reg2Ast); - return zstring("(") + reg1Str + zstring(")(") + reg2Str + zstring(")"); - } else if (u.re.is_union(a_regex)) { - expr * reg1Ast = a_regex->get_arg(0); - expr * reg2Ast = a_regex->get_arg(1); - zstring reg1Str = get_std_regex_str(reg1Ast); - zstring reg2Str = get_std_regex_str(reg2Ast); - return zstring("(") + reg1Str + zstring(")|(") + reg2Str + zstring(")"); - } else if (u.re.is_star(a_regex)) { - expr * reg1Ast = a_regex->get_arg(0); - zstring reg1Str = get_std_regex_str(reg1Ast); - return zstring("(") + reg1Str + zstring(")*"); - } else if (u.re.is_range(a_regex)) { - expr * range1 = a_regex->get_arg(0); - expr * range2 = a_regex->get_arg(1); - zstring range1val, range2val; - u.str.is_string(range1, range1val); - u.str.is_string(range2, range2val); - return zstring("[") + range1val + zstring("-") + range2val + zstring("]"); - } else { - TRACE("str", tout << "BUG: unrecognized regex term " << mk_pp(regex, get_manager()) << std::endl;); - UNREACHABLE(); return zstring(""); - } -} - -void theory_str::instantiate_axiom_RegexIn(enode * e) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - - app * ex = e->get_owner(); - if (axiomatized_terms.contains(ex)) { - TRACE("str", tout << "already set up RegexIn axiom for " << mk_pp(ex, m) << std::endl;); - return; - } - axiomatized_terms.insert(ex); - - TRACE("str", tout << "instantiate RegexIn axiom for " << mk_pp(ex, m) << std::endl;); - - { - zstring regexStr = get_std_regex_str(ex->get_arg(1)); - std::pair key1(ex->get_arg(0), regexStr); - // skip Z3str's map check, because we already check if we set up axioms on this term - regex_in_bool_map[key1] = ex; - regex_in_var_reg_str_map[ex->get_arg(0)].insert(regexStr); - } - - expr_ref str(ex->get_arg(0), m); - app * regex = to_app(ex->get_arg(1)); - - if (u.re.is_to_re(regex)) { - expr_ref rxStr(regex->get_arg(0), m); - // want to assert 'expr IFF (str == rxStr)' - expr_ref rhs(ctx.mk_eq_atom(str, rxStr), m); - expr_ref finalAxiom(m.mk_iff(ex, rhs), m); - SASSERT(finalAxiom); - assert_axiom(finalAxiom); - TRACE("str", tout << "set up Str2Reg: (RegexIn " << mk_pp(str, m) << " " << mk_pp(regex, m) << ")" << std::endl;); - } else if (u.re.is_concat(regex)) { - expr_ref var1(mk_regex_rep_var(), m); - expr_ref var2(mk_regex_rep_var(), m); - expr_ref rhs(mk_concat(var1, var2), m); - expr_ref rx1(regex->get_arg(0), m); - expr_ref rx2(regex->get_arg(1), m); - expr_ref var1InRegex1(mk_RegexIn(var1, rx1), m); - expr_ref var2InRegex2(mk_RegexIn(var2, rx2), m); - - expr_ref_vector items(m); - items.push_back(var1InRegex1); - items.push_back(var2InRegex2); - items.push_back(ctx.mk_eq_atom(ex, ctx.mk_eq_atom(str, rhs))); - - expr_ref finalAxiom(mk_and(items), m); - SASSERT(finalAxiom); - assert_axiom(finalAxiom); - } else if (u.re.is_union(regex)) { - expr_ref var1(mk_regex_rep_var(), m); - expr_ref var2(mk_regex_rep_var(), m); - expr_ref orVar(m.mk_or(ctx.mk_eq_atom(str, var1), ctx.mk_eq_atom(str, var2)), m); - expr_ref regex1(regex->get_arg(0), m); - expr_ref regex2(regex->get_arg(1), m); - expr_ref var1InRegex1(mk_RegexIn(var1, regex1), m); - expr_ref var2InRegex2(mk_RegexIn(var2, regex2), m); - expr_ref_vector items(m); - items.push_back(var1InRegex1); - items.push_back(var2InRegex2); - items.push_back(ctx.mk_eq_atom(ex, orVar)); - assert_axiom(mk_and(items)); - } else if (u.re.is_star(regex)) { - // slightly more complex due to the unrolling step. - expr_ref regex1(regex->get_arg(0), m); - expr_ref unrollCount(mk_unroll_bound_var(), m); - expr_ref unrollFunc(mk_unroll(regex1, unrollCount), m); - expr_ref_vector items(m); - items.push_back(ctx.mk_eq_atom(ex, ctx.mk_eq_atom(str, unrollFunc))); - items.push_back(ctx.mk_eq_atom(ctx.mk_eq_atom(unrollCount, mk_int(0)), ctx.mk_eq_atom(unrollFunc, mk_string("")))); - expr_ref finalAxiom(mk_and(items), m); - SASSERT(finalAxiom); - assert_axiom(finalAxiom); - } else if (u.re.is_range(regex)) { - // (re.range "A" "Z") unfolds to (re.union "A" "B" ... "Z"); - // we rewrite to expr IFF (str = "A" or str = "B" or ... or str = "Z") - expr_ref lo(regex->get_arg(0), m); - expr_ref hi(regex->get_arg(1), m); - zstring str_lo, str_hi; - SASSERT(u.str.is_string(lo)); - SASSERT(u.str.is_string(hi)); - u.str.is_string(lo, str_lo); - u.str.is_string(hi, str_hi); - SASSERT(str_lo.length() == 1); - SASSERT(str_hi.length() == 1); - unsigned int c1 = str_lo[0]; - unsigned int c2 = str_hi[0]; - if (c1 > c2) { - // exchange - unsigned int tmp = c1; - c1 = c2; - c2 = tmp; - } - expr_ref_vector range_cases(m); - for (unsigned int ch = c1; ch <= c2; ++ch) { - zstring s_ch(ch); - expr_ref rhs(ctx.mk_eq_atom(str, u.str.mk_string(s_ch)), m); - range_cases.push_back(rhs); - } - expr_ref rhs(mk_or(range_cases), m); - expr_ref finalAxiom(m.mk_iff(ex, rhs), m); + expr_ref finalAxiom(mk_and(items), m); SASSERT(finalAxiom); assert_axiom(finalAxiom); - } else { - TRACE("str", tout << "ERROR: unknown regex expression " << mk_pp(regex, m) << "!" << std::endl;); - NOT_IMPLEMENTED_YET(); - } -} - -void theory_str::attach_new_th_var(enode * n) { - context & ctx = get_context(); - theory_var v = mk_var(n); - ctx.attach_th_var(n, this, v); - TRACE("str", tout << "new theory var: " << mk_ismt2_pp(n->get_owner(), get_manager()) << " := v#" << v << std::endl;); -} - -void theory_str::reset_eh() { - TRACE("str", tout << "resetting" << std::endl;); - m_trail_stack.reset(); - - m_basicstr_axiom_todo.reset(); - m_str_eq_todo.reset(); - m_concat_axiom_todo.reset(); - pop_scope_eh(get_context().get_scope_level()); -} - -/* - * Check equality among equivalence class members of LHS and RHS - * to discover an incorrect LHS == RHS. - * For example, if we have y2 == "str3" - * and the equivalence classes are - * { y2, (Concat ce m2) } - * { "str3", (Concat abc x2) } - * then y2 can't be equal to "str3". - * Then add an assertion: (y2 == (Concat ce m2)) AND ("str3" == (Concat abc x2)) -> (y2 != "str3") - */ -bool theory_str::new_eq_check(expr * lhs, expr * rhs) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - - // skip this check if we defer consistency checking, as we can do it for every EQC in final check - if (!opt_DeferEQCConsistencyCheck) { - check_concat_len_in_eqc(lhs); - check_concat_len_in_eqc(rhs); + return unrollFunc; } - // Now we iterate over all pairs of terms across both EQCs - // and check whether we can show that any pair of distinct terms - // cannot possibly be equal. - // If that's the case, we assert an axiom to that effect and stop. + app * theory_str::mk_contains(expr * haystack, expr * needle) { + app * contains = u.str.mk_contains(haystack, needle); // TODO double-check semantics/argument order + m_trail.push_back(contains); + // immediately force internalization so that axiom setup does not fail + get_context().internalize(contains, false); + set_up_axioms(contains); + return contains; + } - expr * eqc_nn1 = lhs; - do { - expr * eqc_nn2 = rhs; - do { - TRACE("str", tout << "checking whether " << mk_pp(eqc_nn1, m) << " and " << mk_pp(eqc_nn2, m) << " can be equal" << std::endl;); - // inconsistency check: value - if (!can_two_nodes_eq(eqc_nn1, eqc_nn2)) { - TRACE("str", tout << "inconsistency detected: " << mk_pp(eqc_nn1, m) << " cannot be equal to " << mk_pp(eqc_nn2, m) << std::endl;); - expr_ref to_assert(m.mk_not(ctx.mk_eq_atom(eqc_nn1, eqc_nn2)), m); - assert_axiom(to_assert); - // this shouldn't use the integer theory at all, so we don't allow the option of quick-return - return false; - } - if (!check_length_consistency(eqc_nn1, eqc_nn2)) { - TRACE("str", tout << "inconsistency detected: " << mk_pp(eqc_nn1, m) << " and " << mk_pp(eqc_nn2, m) << " have inconsistent lengths" << std::endl;); - if (opt_NoQuickReturn_IntegerTheory){ - TRACE("str", tout << "continuing in new_eq_check() due to opt_NoQuickReturn_IntegerTheory" << std::endl;); - } else { - return false; - } - } - eqc_nn2 = get_eqc_next(eqc_nn2); - } while (eqc_nn2 != rhs); - eqc_nn1 = get_eqc_next(eqc_nn1); - } while (eqc_nn1 != lhs); - - if (!contains_map.empty()) { - check_contain_in_new_eq(lhs, rhs); - } - - if (!regex_in_bool_map.empty()) { - TRACE("str", tout << "checking regex consistency" << std::endl;); - check_regex_in(lhs, rhs); - } - - // okay, all checks here passed - return true; -} - -// support for user_smt_theory-style EQC handling - -app * theory_str::get_ast(theory_var i) { - return get_enode(i)->get_owner(); -} - -theory_var theory_str::get_var(expr * n) const { - if (!is_app(n)) { - return null_theory_var; - } - context & ctx = get_context(); - if (ctx.e_internalized(to_app(n))) { - enode * e = ctx.get_enode(to_app(n)); - return e->get_th_var(get_id()); - } - return null_theory_var; -} - -// simulate Z3_theory_get_eqc_next() -expr * theory_str::get_eqc_next(expr * n) { - theory_var v = get_var(n); - if (v != null_theory_var) { - theory_var r = m_find.next(v); - return get_ast(r); - } - return n; -} - -void theory_str::group_terms_by_eqc(expr * n, std::set & concats, std::set & vars, std::set & consts) { - context & ctx = get_context(); - expr * eqcNode = n; - do { - app * ast = to_app(eqcNode); - if (u.str.is_concat(ast)) { - expr * simConcat = simplify_concat(ast); - if (simConcat != ast) { - if (u.str.is_concat(to_app(simConcat))) { - concats.insert(simConcat); - } else { - if (u.str.is_string(simConcat)) { - consts.insert(simConcat); - } else { - vars.insert(simConcat); - } + app * theory_str::mk_indexof(expr * haystack, expr * needle) { + // TODO check meaning of the third argument here + app * indexof = u.str.mk_index(haystack, needle, mk_int(0)); + m_trail.push_back(indexof); + // immediately force internalization so that axiom setup does not fail + get_context().internalize(indexof, false); + set_up_axioms(indexof); + return indexof; + } + + app * theory_str::mk_strlen(expr * e) { + /*if (m_strutil.is_string(e)) {*/ if (false) { + zstring strval; + u.str.is_string(e, strval); + unsigned int len = strval.length(); + return m_autil.mk_numeral(rational(len), true); + } else { + if (false) { + // use cache + app * lenTerm = NULL; + if (!length_ast_map.find(e, lenTerm)) { + lenTerm = u.str.mk_length(e); + length_ast_map.insert(e, lenTerm); + m_trail.push_back(lenTerm); } + return lenTerm; } else { - concats.insert(simConcat); + // always regen + return u.str.mk_length(e); } - } else if (u.str.is_string(ast)) { - consts.insert(ast); - } else { - vars.insert(ast); - } - eqcNode = get_eqc_next(eqcNode); - } while (eqcNode != n); -} - -void theory_str::get_nodes_in_concat(expr * node, ptr_vector & nodeList) { - app * a_node = to_app(node); - if (!u.str.is_concat(a_node)) { - nodeList.push_back(node); - return; - } else { - SASSERT(a_node->get_num_args() == 2); - expr * leftArg = a_node->get_arg(0); - expr * rightArg = a_node->get_arg(1); - get_nodes_in_concat(leftArg, nodeList); - get_nodes_in_concat(rightArg, nodeList); - } -} - -// previously Concat() in strTheory.cpp -// Evaluates the concatenation (n1 . n2) with respect to -// the current equivalence classes of n1 and n2. -// Returns a constant string expression representing this concatenation -// if one can be determined, or NULL if this is not possible. -expr * theory_str::eval_concat(expr * n1, expr * n2) { - bool n1HasEqcValue = false; - bool n2HasEqcValue = false; - expr * v1 = get_eqc_value(n1, n1HasEqcValue); - expr * v2 = get_eqc_value(n2, n2HasEqcValue); - if (n1HasEqcValue && n2HasEqcValue) { - zstring n1_str, n2_str; - u.str.is_string(v1, n1_str); - u.str.is_string(v2, n2_str); - zstring result = n1_str + n2_str; - return mk_string(result); - } else if (n1HasEqcValue && !n2HasEqcValue) { - zstring v1_str; - u.str.is_string(v1, v1_str); - if (v1_str.empty()) { - return n2; - } - } else if (n2HasEqcValue && !n1HasEqcValue) { - zstring v2_str; - u.str.is_string(v2, v2_str); - if (v2_str.empty()) { - return n1; - } - } - // give up - return NULL; -} - -static inline std::string rational_to_string_if_exists(const rational & x, bool x_exists) { - if (x_exists) { - return x.to_string(); - } else { - return "?"; - } -} - -/* - * The inputs: - * ~ nn: non const node - * ~ eq_str: the equivalent constant string of nn - * Iterate the parent of all eqc nodes of nn, looking for: - * ~ concat node - * to see whether some concat nodes can be simplified. - */ -void theory_str::simplify_parent(expr * nn, expr * eq_str) { - ast_manager & m = get_manager(); - context & ctx = get_context(); - - TRACE("str", tout << "simplifying parents of " << mk_ismt2_pp(nn, m) - << " with respect to " << mk_ismt2_pp(eq_str, m) << std::endl;); - - ctx.internalize(nn, false); - - zstring eq_strValue; - u.str.is_string(eq_str, eq_strValue); - expr * n_eqNode = nn; - do { - enode * n_eq_enode = ctx.get_enode(n_eqNode); - TRACE("str", tout << "considering all parents of " << mk_ismt2_pp(n_eqNode, m) << std::endl - << "associated n_eq_enode has " << n_eq_enode->get_num_parents() << " parents" << std::endl;); - - // the goal of this next bit is to avoid dereferencing a bogus e_parent in the following loop. - // what I imagine is causing this bug is that, for example, we examine some parent, we add an axiom that involves it, - // and the parent_it iterator becomes invalidated, because we indirectly modified the container that we're iterating over. - - enode_vector current_parents; - for (enode_vector::const_iterator parent_it = n_eq_enode->begin_parents(); parent_it != n_eq_enode->end_parents(); parent_it++) { - current_parents.insert(*parent_it); - } - - for (enode_vector::iterator parent_it = current_parents.begin(); parent_it != current_parents.end(); ++parent_it) { - enode * e_parent = *parent_it; - SASSERT(e_parent != NULL); - - app * a_parent = e_parent->get_owner(); - TRACE("str", tout << "considering parent " << mk_ismt2_pp(a_parent, m) << std::endl;); - - if (u.str.is_concat(a_parent)) { - expr * arg0 = a_parent->get_arg(0); - expr * arg1 = a_parent->get_arg(1); - - rational parentLen; - bool parentLen_exists = get_len_value(a_parent, parentLen); - - if (arg0 == n_eq_enode->get_owner()) { - rational arg0Len, arg1Len; - bool arg0Len_exists = get_len_value(eq_str, arg0Len); - bool arg1Len_exists = get_len_value(arg1, arg1Len); - - TRACE("str", - tout << "simplify_parent #1:" << std::endl - << "* parent = " << mk_ismt2_pp(a_parent, m) << std::endl - << "* |parent| = " << rational_to_string_if_exists(parentLen, parentLen_exists) << std::endl - << "* |arg0| = " << rational_to_string_if_exists(arg0Len, arg0Len_exists) << std::endl - << "* |arg1| = " << rational_to_string_if_exists(arg1Len, arg1Len_exists) << std::endl; - ); - - if (parentLen_exists && !arg1Len_exists) { - TRACE("str", tout << "make up len for arg1" << std::endl;); - expr_ref implyL11(m.mk_and(ctx.mk_eq_atom(mk_strlen(a_parent), mk_int(parentLen)), - ctx.mk_eq_atom(mk_strlen(arg0), mk_int(arg0Len))), m); - rational makeUpLenArg1 = parentLen - arg0Len; - if (makeUpLenArg1.is_nonneg()) { - expr_ref implyR11(ctx.mk_eq_atom(mk_strlen(arg1), mk_int(makeUpLenArg1)), m); - assert_implication(implyL11, implyR11); - } else { - expr_ref neg(m.mk_not(implyL11), m); - assert_axiom(neg); - } - } - - // (Concat n_eqNode arg1) /\ arg1 has eq const - - expr * concatResult = eval_concat(eq_str, arg1); - if (concatResult != NULL) { - bool arg1HasEqcValue = false; - expr * arg1Value = get_eqc_value(arg1, arg1HasEqcValue); - expr_ref implyL(m); - if (arg1 != arg1Value) { - expr_ref eq_ast1(m); - eq_ast1 = ctx.mk_eq_atom(n_eqNode, eq_str); - SASSERT(eq_ast1); - - expr_ref eq_ast2(m); - eq_ast2 = ctx.mk_eq_atom(arg1, arg1Value); - SASSERT(eq_ast2); - implyL = m.mk_and(eq_ast1, eq_ast2); - } else { - implyL = ctx.mk_eq_atom(n_eqNode, eq_str); - } - - - if (!in_same_eqc(a_parent, concatResult)) { - expr_ref implyR(m); - implyR = ctx.mk_eq_atom(a_parent, concatResult); - SASSERT(implyR); - - assert_implication(implyL, implyR); - } - } else if (u.str.is_concat(to_app(n_eqNode))) { - expr_ref simpleConcat(m); - simpleConcat = mk_concat(eq_str, arg1); - if (!in_same_eqc(a_parent, simpleConcat)) { - expr_ref implyL(m); - implyL = ctx.mk_eq_atom(n_eqNode, eq_str); - SASSERT(implyL); - - expr_ref implyR(m); - implyR = ctx.mk_eq_atom(a_parent, simpleConcat); - SASSERT(implyR); - assert_implication(implyL, implyR); - } - } - } // if (arg0 == n_eq_enode->get_owner()) - - if (arg1 == n_eq_enode->get_owner()) { - rational arg0Len, arg1Len; - bool arg0Len_exists = get_len_value(arg0, arg0Len); - bool arg1Len_exists = get_len_value(eq_str, arg1Len); - - TRACE("str", - tout << "simplify_parent #2:" << std::endl - << "* parent = " << mk_ismt2_pp(a_parent, m) << std::endl - << "* |parent| = " << rational_to_string_if_exists(parentLen, parentLen_exists) << std::endl - << "* |arg0| = " << rational_to_string_if_exists(arg0Len, arg0Len_exists) << std::endl - << "* |arg1| = " << rational_to_string_if_exists(arg1Len, arg1Len_exists) << std::endl; - ); - if (parentLen_exists && !arg0Len_exists) { - TRACE("str", tout << "make up len for arg0" << std::endl;); - expr_ref implyL11(m.mk_and(ctx.mk_eq_atom(mk_strlen(a_parent), mk_int(parentLen)), - ctx.mk_eq_atom(mk_strlen(arg1), mk_int(arg1Len))), m); - rational makeUpLenArg0 = parentLen - arg1Len; - if (makeUpLenArg0.is_nonneg()) { - expr_ref implyR11(ctx.mk_eq_atom(mk_strlen(arg0), mk_int(makeUpLenArg0)), m); - assert_implication(implyL11, implyR11); - } else { - expr_ref neg(m.mk_not(implyL11), m); - assert_axiom(neg); - } - } - - // (Concat arg0 n_eqNode) /\ arg0 has eq const - - expr * concatResult = eval_concat(arg0, eq_str); - if (concatResult != NULL) { - bool arg0HasEqcValue = false; - expr * arg0Value = get_eqc_value(arg0, arg0HasEqcValue); - expr_ref implyL(m); - if (arg0 != arg0Value) { - expr_ref eq_ast1(m); - eq_ast1 = ctx.mk_eq_atom(n_eqNode, eq_str); - SASSERT(eq_ast1); - expr_ref eq_ast2(m); - eq_ast2 = ctx.mk_eq_atom(arg0, arg0Value); - SASSERT(eq_ast2); - - implyL = m.mk_and(eq_ast1, eq_ast2); - } else { - implyL = ctx.mk_eq_atom(n_eqNode, eq_str); - } - - if (!in_same_eqc(a_parent, concatResult)) { - expr_ref implyR(m); - implyR = ctx.mk_eq_atom(a_parent, concatResult); - SASSERT(implyR); - - assert_implication(implyL, implyR); - } - } else if (u.str.is_concat(to_app(n_eqNode))) { - expr_ref simpleConcat(m); - simpleConcat = mk_concat(arg0, eq_str); - if (!in_same_eqc(a_parent, simpleConcat)) { - expr_ref implyL(m); - implyL = ctx.mk_eq_atom(n_eqNode, eq_str); - SASSERT(implyL); - - expr_ref implyR(m); - implyR = ctx.mk_eq_atom(a_parent, simpleConcat); - SASSERT(implyR); - assert_implication(implyL, implyR); - } - } - } // if (arg1 == n_eq_enode->get_owner - - - //--------------------------------------------------------- - // Case (2-1) begin: (Concat n_eqNode (Concat str var)) - if (arg0 == n_eqNode && u.str.is_concat(to_app(arg1))) { - app * a_arg1 = to_app(arg1); - TRACE("str", tout << "simplify_parent #3" << std::endl;); - expr * r_concat_arg0 = a_arg1->get_arg(0); - if (u.str.is_string(r_concat_arg0)) { - expr * combined_str = eval_concat(eq_str, r_concat_arg0); - SASSERT(combined_str); - expr * r_concat_arg1 = a_arg1->get_arg(1); - expr_ref implyL(m); - implyL = ctx.mk_eq_atom(n_eqNode, eq_str); - expr * simplifiedAst = mk_concat(combined_str, r_concat_arg1); - if (!in_same_eqc(a_parent, simplifiedAst)) { - expr_ref implyR(m); - implyR = ctx.mk_eq_atom(a_parent, simplifiedAst); - assert_implication(implyL, implyR); - } - } - } - // Case (2-1) end: (Concat n_eqNode (Concat str var)) - //--------------------------------------------------------- - - - //--------------------------------------------------------- - // Case (2-2) begin: (Concat (Concat var str) n_eqNode) - if (u.str.is_concat(to_app(arg0)) && arg1 == n_eqNode) { - app * a_arg0 = to_app(arg0); - TRACE("str", tout << "simplify_parent #4" << std::endl;); - expr * l_concat_arg1 = a_arg0->get_arg(1); - if (u.str.is_string(l_concat_arg1)) { - expr * combined_str = eval_concat(l_concat_arg1, eq_str); - SASSERT(combined_str); - expr * l_concat_arg0 = a_arg0->get_arg(0); - expr_ref implyL(m); - implyL = ctx.mk_eq_atom(n_eqNode, eq_str); - expr * simplifiedAst = mk_concat(l_concat_arg0, combined_str); - if (!in_same_eqc(a_parent, simplifiedAst)) { - expr_ref implyR(m); - implyR = ctx.mk_eq_atom(a_parent, simplifiedAst); - assert_implication(implyL, implyR); - } - } - } - // Case (2-2) end: (Concat (Concat var str) n_eqNode) - //--------------------------------------------------------- - - // Have to look up one more layer: if the parent of the concat is another concat - //------------------------------------------------- - // Case (3-1) begin: (Concat (Concat var n_eqNode) str ) - if (arg1 == n_eqNode) { - for (enode_vector::iterator concat_parent_it = e_parent->begin_parents(); - concat_parent_it != e_parent->end_parents(); concat_parent_it++) { - enode * e_concat_parent = *concat_parent_it; - app * concat_parent = e_concat_parent->get_owner(); - if (u.str.is_concat(concat_parent)) { - expr * concat_parent_arg0 = concat_parent->get_arg(0); - expr * concat_parent_arg1 = concat_parent->get_arg(1); - if (concat_parent_arg0 == a_parent && u.str.is_string(concat_parent_arg1)) { - TRACE("str", tout << "simplify_parent #5" << std::endl;); - expr * combinedStr = eval_concat(eq_str, concat_parent_arg1); - SASSERT(combinedStr); - expr_ref implyL(m); - implyL = ctx.mk_eq_atom(n_eqNode, eq_str); - expr * simplifiedAst = mk_concat(arg0, combinedStr); - if (!in_same_eqc(concat_parent, simplifiedAst)) { - expr_ref implyR(m); - implyR = ctx.mk_eq_atom(concat_parent, simplifiedAst); - assert_implication(implyL, implyR); - } - } - } - } - } - // Case (3-1) end: (Concat (Concat var n_eqNode) str ) - // Case (3-2) begin: (Concat str (Concat n_eqNode var) ) - if (arg0 == n_eqNode) { - for (enode_vector::iterator concat_parent_it = e_parent->begin_parents(); - concat_parent_it != e_parent->end_parents(); concat_parent_it++) { - enode * e_concat_parent = *concat_parent_it; - app * concat_parent = e_concat_parent->get_owner(); - if (u.str.is_concat(concat_parent)) { - expr * concat_parent_arg0 = concat_parent->get_arg(0); - expr * concat_parent_arg1 = concat_parent->get_arg(1); - if (concat_parent_arg1 == a_parent && u.str.is_string(concat_parent_arg0)) { - TRACE("str", tout << "simplify_parent #6" << std::endl;); - expr * combinedStr = eval_concat(concat_parent_arg0, eq_str); - SASSERT(combinedStr); - expr_ref implyL(m); - implyL = ctx.mk_eq_atom(n_eqNode, eq_str); - expr * simplifiedAst = mk_concat(combinedStr, arg1); - if (!in_same_eqc(concat_parent, simplifiedAst)) { - expr_ref implyR(m); - implyR = ctx.mk_eq_atom(concat_parent, simplifiedAst); - assert_implication(implyL, implyR); - } - } - } - } - } - // Case (3-2) end: (Concat str (Concat n_eqNode var) ) - } // if is_concat(a_parent) - } // for parent_it : n_eq_enode->begin_parents() - - - // check next EQC member - n_eqNode = get_eqc_next(n_eqNode); - } while (n_eqNode != nn); -} - -expr * theory_str::simplify_concat(expr * node) { - ast_manager & m = get_manager(); - context & ctx = get_context(); - std::map resolvedMap; - ptr_vector argVec; - get_nodes_in_concat(node, argVec); - - for (unsigned i = 0; i < argVec.size(); ++i) { - bool vArgHasEqcValue = false; - expr * vArg = get_eqc_value(argVec[i], vArgHasEqcValue); - if (vArg != argVec[i]) { - resolvedMap[argVec[i]] = vArg; - } - } - - if (resolvedMap.size() == 0) { - // no simplification possible - return node; - } else { - expr * resultAst = mk_string(""); - for (unsigned i = 0; i < argVec.size(); ++i) { - bool vArgHasEqcValue = false; - expr * vArg = get_eqc_value(argVec[i], vArgHasEqcValue); - resultAst = mk_concat(resultAst, vArg); - } - TRACE("str", tout << mk_ismt2_pp(node, m) << " is simplified to " << mk_ismt2_pp(resultAst, m) << std::endl;); - - if (in_same_eqc(node, resultAst)) { - TRACE("str", tout << "SKIP: both concats are already in the same equivalence class" << std::endl;); - } else { - expr_ref_vector items(m); - int pos = 0; - std::map::iterator itor = resolvedMap.begin(); - for (; itor != resolvedMap.end(); ++itor) { - items.push_back(ctx.mk_eq_atom(itor->first, itor->second)); - pos += 1; - } - expr_ref premise(mk_and(items), m); - expr_ref conclusion(ctx.mk_eq_atom(node, resultAst), m); - assert_implication(premise, conclusion); - } - return resultAst; - } - -} - -// Modified signature of Z3str2's inferLenConcat(). -// Returns true iff nLen can be inferred by this method -// (i.e. the equivalent of a len_exists flag in get_len_value()). - -bool theory_str::infer_len_concat(expr * n, rational & nLen) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - expr * arg0 = to_app(n)->get_arg(0); - expr * arg1 = to_app(n)->get_arg(1); - - rational arg0_len, arg1_len; - bool arg0_len_exists = get_len_value(arg0, arg0_len); - bool arg1_len_exists = get_len_value(arg1, arg1_len); - rational tmp_len; - bool nLen_exists = get_len_value(n, tmp_len); - - if (arg0_len_exists && arg1_len_exists && !nLen_exists) { - expr_ref_vector l_items(m); - // if (mk_strlen(arg0) != mk_int(arg0_len)) { - { - l_items.push_back(ctx.mk_eq_atom(mk_strlen(arg0), mk_int(arg0_len))); - } - - // if (mk_strlen(arg1) != mk_int(arg1_len)) { - { - l_items.push_back(ctx.mk_eq_atom(mk_strlen(arg1), mk_int(arg1_len))); - } - - expr_ref axl(m.mk_and(l_items.size(), l_items.c_ptr()), m); - rational nnLen = arg0_len + arg1_len; - expr_ref axr(ctx.mk_eq_atom(mk_strlen(n), mk_int(nnLen)), m); - TRACE("str", tout << "inferred (Length " << mk_pp(n, m) << ") = " << nnLen << std::endl;); - assert_implication(axl, axr); - nLen = nnLen; - return true; - } else { - return false; - } -} - -void theory_str::infer_len_concat_arg(expr * n, rational len) { - if (len.is_neg()) { - return; - } - - context & ctx = get_context(); - ast_manager & m = get_manager(); - - expr * arg0 = to_app(n)->get_arg(0); - expr * arg1 = to_app(n)->get_arg(1); - rational arg0_len, arg1_len; - bool arg0_len_exists = get_len_value(arg0, arg0_len); - bool arg1_len_exists = get_len_value(arg1, arg1_len); - - expr_ref_vector l_items(m); - expr_ref axr(m); - axr.reset(); - - // if (mk_length(t, n) != mk_int(ctx, len)) { - { - l_items.push_back(ctx.mk_eq_atom(mk_strlen(n), mk_int(len))); - } - - if (!arg0_len_exists && arg1_len_exists) { - //if (mk_length(t, arg1) != mk_int(ctx, arg1_len)) { - { - l_items.push_back(ctx.mk_eq_atom(mk_strlen(arg1), mk_int(arg1_len))); - } - rational arg0Len = len - arg1_len; - if (arg0Len.is_nonneg()) { - axr = ctx.mk_eq_atom(mk_strlen(arg0), mk_int(arg0Len)); - } else { - // could negate - } - } else if (arg0_len_exists && !arg1_len_exists) { - //if (mk_length(t, arg0) != mk_int(ctx, arg0_len)) { - { - l_items.push_back(ctx.mk_eq_atom(mk_strlen(arg0), mk_int(arg0_len))); - } - rational arg1Len = len - arg0_len; - if (arg1Len.is_nonneg()) { - axr = ctx.mk_eq_atom(mk_strlen(arg1), mk_int(arg1Len)); - } else { - // could negate - } - } else { - - } - - if (axr) { - expr_ref axl(m.mk_and(l_items.size(), l_items.c_ptr()), m); - assert_implication(axl, axr); - } -} - -void theory_str::infer_len_concat_equality(expr * nn1, expr * nn2) { - rational nnLen; - bool nnLen_exists = get_len_value(nn1, nnLen); - if (!nnLen_exists) { - nnLen_exists = get_len_value(nn2, nnLen); - } - - // case 1: - // Known: a1_arg0 and a1_arg1 - // Unknown: nn1 - - if (u.str.is_concat(to_app(nn1))) { - rational nn1ConcatLen; - bool nn1ConcatLen_exists = infer_len_concat(nn1, nn1ConcatLen); - if (nnLen_exists && nn1ConcatLen_exists) { - nnLen = nn1ConcatLen; - } - } - - // case 2: - // Known: a1_arg0 and a1_arg1 - // Unknown: nn1 - - if (u.str.is_concat(to_app(nn2))) { - rational nn2ConcatLen; - bool nn2ConcatLen_exists = infer_len_concat(nn2, nn2ConcatLen); - if (nnLen_exists && nn2ConcatLen_exists) { - nnLen = nn2ConcatLen; - } - } - - if (nnLen_exists) { - if (u.str.is_concat(to_app(nn1))) { - infer_len_concat_arg(nn1, nnLen); - } - if (u.str.is_concat(to_app(nn2))) { - infer_len_concat_arg(nn2, nnLen); } } /* - if (isConcatFunc(t, nn2)) { - int nn2ConcatLen = inferLenConcat(t, nn2); - if (nnLen == -1 && nn2ConcatLen != -1) - nnLen = nn2ConcatLen; - } - - if (nnLen != -1) { - if (isConcatFunc(t, nn1)) { - inferLenConcatArg(t, nn1, nnLen); - } - if (isConcatFunc(t, nn2)) { - inferLenConcatArg(t, nn2, nnLen); - } - } - */ -} - -void theory_str::add_theory_aware_branching_info(expr * term, double priority, lbool phase) { - context & ctx = get_context(); - ctx.internalize(term, false); - bool_var v = ctx.get_bool_var(term); - ctx.add_theory_aware_branching_info(v, priority, phase); -} - -void theory_str::generate_mutual_exclusion(expr_ref_vector & terms) { - context & ctx = get_context(); - // pull each literal out of the arrangement disjunction - literal_vector ls; - for (unsigned i = 0; i < terms.size(); ++i) { - expr * e = terms.get(i); - literal l = ctx.get_literal(e); - ls.push_back(l); - } - ctx.mk_th_case_split(ls.size(), ls.c_ptr()); -} - -void theory_str::print_cut_var(expr * node, std::ofstream & xout) { - ast_manager & m = get_manager(); - xout << "Cut info of " << mk_pp(node, m) << std::endl; - if (cut_var_map.contains(node)) { - if (!cut_var_map[node].empty()) { - xout << "[" << cut_var_map[node].top()->level << "] "; - std::map::iterator itor = cut_var_map[node].top()->vars.begin(); - for (; itor != cut_var_map[node].top()->vars.end(); ++itor) { - xout << mk_pp(itor->first, m) << ", "; + * Returns the simplified concatenation of two expressions, + * where either both expressions are constant strings + * or one expression is the empty string. + * If this precondition does not hold, the function returns NULL. + * (note: this function was strTheory::Concat()) + */ + expr * theory_str::mk_concat_const_str(expr * n1, expr * n2) { + bool n1HasEqcValue = false; + bool n2HasEqcValue = false; + expr * v1 = get_eqc_value(n1, n1HasEqcValue); + expr * v2 = get_eqc_value(n2, n2HasEqcValue); + if (n1HasEqcValue && n2HasEqcValue) { + zstring n1_str; + u.str.is_string(v1, n1_str); + zstring n2_str; + u.str.is_string(v2, n2_str); + zstring result = n1_str + n2_str; + return mk_string(result); + } else if (n1HasEqcValue && !n2HasEqcValue) { + zstring n1_str; + u.str.is_string(v1, n1_str); + if (n1_str.empty()) { + return n2; + } + } else if (!n1HasEqcValue && n2HasEqcValue) { + zstring n2_str; + u.str.is_string(v2, n2_str); + if (n2_str.empty()) { + return n1; } - xout << std::endl; } - } -} - -/* - * Handle two equivalent Concats. - */ -void theory_str::simplify_concat_equality(expr * nn1, expr * nn2) { - ast_manager & m = get_manager(); - context & ctx = get_context(); - - app * a_nn1 = to_app(nn1); - SASSERT(a_nn1->get_num_args() == 2); - app * a_nn2 = to_app(nn2); - SASSERT(a_nn2->get_num_args() == 2); - - expr * a1_arg0 = a_nn1->get_arg(0); - expr * a1_arg1 = a_nn1->get_arg(1); - expr * a2_arg0 = a_nn2->get_arg(0); - expr * a2_arg1 = a_nn2->get_arg(1); - - rational a1_arg0_len, a1_arg1_len, a2_arg0_len, a2_arg1_len; - - bool a1_arg0_len_exists = get_len_value(a1_arg0, a1_arg0_len); - bool a1_arg1_len_exists = get_len_value(a1_arg1, a1_arg1_len); - bool a2_arg0_len_exists = get_len_value(a2_arg0, a2_arg0_len); - bool a2_arg1_len_exists = get_len_value(a2_arg1, a2_arg1_len); - - TRACE("str", tout << "nn1 = " << mk_ismt2_pp(nn1, m) << std::endl - << "nn2 = " << mk_ismt2_pp(nn2, m) << std::endl;); - - TRACE("str", tout - << "len(" << mk_pp(a1_arg0, m) << ") = " << (a1_arg0_len_exists ? a1_arg0_len.to_string() : "?") << std::endl - << "len(" << mk_pp(a1_arg1, m) << ") = " << (a1_arg1_len_exists ? a1_arg1_len.to_string() : "?") << std::endl - << "len(" << mk_pp(a2_arg0, m) << ") = " << (a2_arg0_len_exists ? a2_arg0_len.to_string() : "?") << std::endl - << "len(" << mk_pp(a2_arg1, m) << ") = " << (a2_arg1_len_exists ? a2_arg1_len.to_string() : "?") << std::endl - << std::endl;); - - infer_len_concat_equality(nn1, nn2); - - if (a1_arg0 == a2_arg0) { - if (!in_same_eqc(a1_arg1, a2_arg1)) { - expr_ref premise(ctx.mk_eq_atom(nn1, nn2), m); - expr_ref eq1(ctx.mk_eq_atom(a1_arg1, a2_arg1), m); - expr_ref eq2(ctx.mk_eq_atom(mk_strlen(a1_arg1), mk_strlen(a2_arg1)), m); - expr_ref conclusion(m.mk_and(eq1, eq2), m); - assert_implication(premise, conclusion); - } - TRACE("str", tout << "SKIP: a1_arg0 == a2_arg0" << std::endl;); - return; + return NULL; } - if (a1_arg1 == a2_arg1) { - if (!in_same_eqc(a1_arg0, a2_arg0)) { - expr_ref premise(ctx.mk_eq_atom(nn1, nn2), m); - expr_ref eq1(ctx.mk_eq_atom(a1_arg0, a2_arg0), m); - expr_ref eq2(ctx.mk_eq_atom(mk_strlen(a1_arg0), mk_strlen(a2_arg0)), m); - expr_ref conclusion(m.mk_and(eq1, eq2), m); - assert_implication(premise, conclusion); - } - TRACE("str", tout << "SKIP: a1_arg1 == a2_arg1" << std::endl;); - return; - } + expr * theory_str::mk_concat(expr * n1, expr * n2) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + ENSURE(n1 != NULL); + ENSURE(n2 != NULL); + bool n1HasEqcValue = false; + bool n2HasEqcValue = false; + n1 = get_eqc_value(n1, n1HasEqcValue); + n2 = get_eqc_value(n2, n2HasEqcValue); + if (n1HasEqcValue && n2HasEqcValue) { + return mk_concat_const_str(n1, n2); + } else if (n1HasEqcValue && !n2HasEqcValue) { + bool n2_isConcatFunc = u.str.is_concat(to_app(n2)); + zstring n1_str; + u.str.is_string(n1, n1_str); + if (n1_str.empty()) { + return n2; + } + if (n2_isConcatFunc) { + expr * n2_arg0 = to_app(n2)->get_arg(0); + expr * n2_arg1 = to_app(n2)->get_arg(1); + if (u.str.is_string(n2_arg0)) { + n1 = mk_concat_const_str(n1, n2_arg0); // n1 will be a constant + n2 = n2_arg1; + } + } + } else if (!n1HasEqcValue && n2HasEqcValue) { + zstring n2_str; + u.str.is_string(n2, n2_str); + if (n2_str.empty()) { + return n1; + } - // quick path - - if (in_same_eqc(a1_arg0, a2_arg0)) { - if (in_same_eqc(a1_arg1, a2_arg1)) { - TRACE("str", tout << "SKIP: a1_arg0 =~ a2_arg0 and a1_arg1 =~ a2_arg1" << std::endl;); - return; + if (u.str.is_concat(to_app(n1))) { + expr * n1_arg0 = to_app(n1)->get_arg(0); + expr * n1_arg1 = to_app(n1)->get_arg(1); + if (u.str.is_string(n1_arg1)) { + n1 = n1_arg0; + n2 = mk_concat_const_str(n1_arg1, n2); // n2 will be a constant + } + } } else { - TRACE("str", tout << "quick path 1-1: a1_arg0 =~ a2_arg0" << std::endl;); - expr_ref premise(m.mk_and(ctx.mk_eq_atom(nn1, nn2), ctx.mk_eq_atom(a1_arg0, a2_arg0)), m); - expr_ref conclusion(m.mk_and(ctx.mk_eq_atom(a1_arg1, a2_arg1), ctx.mk_eq_atom(mk_strlen(a1_arg1), mk_strlen(a2_arg1))), m); - assert_implication(premise, conclusion); - return; - } - } else { - if (in_same_eqc(a1_arg1, a2_arg1)) { - TRACE("str", tout << "quick path 1-2: a1_arg1 =~ a2_arg1" << std::endl;); - expr_ref premise(m.mk_and(ctx.mk_eq_atom(nn1, nn2), ctx.mk_eq_atom(a1_arg1, a2_arg1)), m); - expr_ref conclusion(m.mk_and(ctx.mk_eq_atom(a1_arg0, a2_arg0), ctx.mk_eq_atom(mk_strlen(a1_arg0), mk_strlen(a2_arg0))), m); - assert_implication(premise, conclusion); - return; - } - } - - // quick path 2-1 - if (a1_arg0_len_exists && a2_arg0_len_exists && a1_arg0_len == a2_arg0_len) { - if (!in_same_eqc(a1_arg0, a2_arg0)) { - TRACE("str", tout << "quick path 2-1: len(nn1.arg0) == len(nn2.arg0)" << std::endl;); - expr_ref ax_l1(ctx.mk_eq_atom(nn1, nn2), m); - expr_ref ax_l2(ctx.mk_eq_atom(mk_strlen(a1_arg0), mk_strlen(a2_arg0)), m); - expr_ref ax_r1(ctx.mk_eq_atom(a1_arg0, a2_arg0), m); - expr_ref ax_r2(ctx.mk_eq_atom(a1_arg1, a2_arg1), m); - - expr_ref premise(m.mk_and(ax_l1, ax_l2), m); - expr_ref conclusion(m.mk_and(ax_r1, ax_r2), m); - - assert_implication(premise, conclusion); - - if (opt_NoQuickReturn_IntegerTheory) { - TRACE("str", tout << "bypassing quick return from the end of this case" << std::endl;); - } else { - return; + if (u.str.is_concat(to_app(n1)) && u.str.is_concat(to_app(n2))) { + expr * n1_arg0 = to_app(n1)->get_arg(0); + expr * n1_arg1 = to_app(n1)->get_arg(1); + expr * n2_arg0 = to_app(n2)->get_arg(0); + expr * n2_arg1 = to_app(n2)->get_arg(1); + if (u.str.is_string(n1_arg1) && u.str.is_string(n2_arg0)) { + expr * tmpN1 = n1_arg0; + expr * tmpN2 = mk_concat_const_str(n1_arg1, n2_arg0); + n1 = mk_concat(tmpN1, tmpN2); + n2 = n2_arg1; + } } } + + //------------------------------------------------------ + // * expr * ast1 = mk_2_arg_app(ctx, td->Concat, n1, n2); + // * expr * ast2 = mk_2_arg_app(ctx, td->Concat, n1, n2); + // Z3 treats (ast1) and (ast2) as two different nodes. + //------------------------------------------------------- + + expr * concatAst = NULL; + + if (!concat_astNode_map.find(n1, n2, concatAst)) { + concatAst = u.str.mk_concat(n1, n2); + m_trail.push_back(concatAst); + concat_astNode_map.insert(n1, n2, concatAst); + + expr_ref concat_length(mk_strlen(concatAst), m); + + ptr_vector childrenVector; + get_nodes_in_concat(concatAst, childrenVector); + expr_ref_vector items(m); + for (unsigned int i = 0; i < childrenVector.size(); i++) { + items.push_back(mk_strlen(childrenVector.get(i))); + } + expr_ref lenAssert(ctx.mk_eq_atom(concat_length, m_autil.mk_add(items.size(), items.c_ptr())), m); + assert_axiom(lenAssert); + } + return concatAst; } - if (a1_arg1_len_exists && a2_arg1_len_exists && a1_arg1_len == a2_arg1_len) { - if (!in_same_eqc(a1_arg1, a2_arg1)) { - TRACE("str", tout << "quick path 2-2: len(nn1.arg1) == len(nn2.arg1)" << std::endl;); - expr_ref ax_l1(ctx.mk_eq_atom(nn1, nn2), m); - expr_ref ax_l2(ctx.mk_eq_atom(mk_strlen(a1_arg1), mk_strlen(a2_arg1)), m); - expr_ref ax_r1(ctx.mk_eq_atom(a1_arg0, a2_arg0), m); - expr_ref ax_r2(ctx.mk_eq_atom(a1_arg1, a2_arg1), m); + bool theory_str::can_propagate() { + return !m_basicstr_axiom_todo.empty() || !m_str_eq_todo.empty() + || !m_concat_axiom_todo.empty() || !m_concat_eval_todo.empty() + || !m_library_aware_axiom_todo.empty() + || !m_delayed_axiom_setup_terms.empty(); + ; + } - expr_ref premise(m.mk_and(ax_l1, ax_l2), m); - expr_ref conclusion(m.mk_and(ax_r1, ax_r2), m); + void theory_str::propagate() { + context & ctx = get_context(); + while (can_propagate()) { + TRACE("str", tout << "propagating..." << std::endl;); + for (unsigned i = 0; i < m_basicstr_axiom_todo.size(); ++i) { + instantiate_basic_string_axioms(m_basicstr_axiom_todo[i]); + } + m_basicstr_axiom_todo.reset(); + TRACE("str", tout << "reset m_basicstr_axiom_todo" << std::endl;); - assert_implication(premise, conclusion); - if (opt_NoQuickReturn_IntegerTheory) { - TRACE("str", tout << "bypassing quick return from the end of this case" << std::endl;); + for (unsigned i = 0; i < m_str_eq_todo.size(); ++i) { + std::pair pair = m_str_eq_todo[i]; + enode * lhs = pair.first; + enode * rhs = pair.second; + handle_equality(lhs->get_owner(), rhs->get_owner()); + } + m_str_eq_todo.reset(); + + for (unsigned i = 0; i < m_concat_axiom_todo.size(); ++i) { + instantiate_concat_axiom(m_concat_axiom_todo[i]); + } + m_concat_axiom_todo.reset(); + + for (unsigned i = 0; i < m_concat_eval_todo.size(); ++i) { + try_eval_concat(m_concat_eval_todo[i]); + } + m_concat_eval_todo.reset(); + + for (unsigned i = 0; i < m_library_aware_axiom_todo.size(); ++i) { + enode * e = m_library_aware_axiom_todo[i]; + app * a = e->get_owner(); + if (u.str.is_stoi(a)) { + instantiate_axiom_str_to_int(e); + } else if (u.str.is_itos(a)) { + instantiate_axiom_int_to_str(e); + } else if (u.str.is_at(a)) { + instantiate_axiom_CharAt(e); + } else if (u.str.is_prefix(a)) { + instantiate_axiom_prefixof(e); + } else if (u.str.is_suffix(a)) { + instantiate_axiom_suffixof(e); + } else if (u.str.is_contains(a)) { + instantiate_axiom_Contains(e); + } else if (u.str.is_index(a)) { + instantiate_axiom_Indexof(e); + /* TODO NEXT: Indexof2/Lastindexof rewrite? + } else if (is_Indexof2(e)) { + instantiate_axiom_Indexof2(e); + } else if (is_LastIndexof(e)) { + instantiate_axiom_LastIndexof(e); + */ + } else if (u.str.is_extract(a)) { + // TODO check semantics of substr vs. extract + instantiate_axiom_Substr(e); + } else if (u.str.is_replace(a)) { + instantiate_axiom_Replace(e); + } else if (u.str.is_in_re(a)) { + instantiate_axiom_RegexIn(e); + } else { + TRACE("str", tout << "BUG: unhandled library-aware term " << mk_pp(e->get_owner(), get_manager()) << std::endl;); + NOT_IMPLEMENTED_YET(); + } + } + m_library_aware_axiom_todo.reset(); + + for (unsigned i = 0; i < m_delayed_axiom_setup_terms.size(); ++i) { + // I think this is okay + ctx.internalize(m_delayed_axiom_setup_terms[i].get(), false); + set_up_axioms(m_delayed_axiom_setup_terms[i].get()); + } + m_delayed_axiom_setup_terms.reset(); + } + } + + /* + * Attempt to evaluate a concat over constant strings, + * and if this is possible, assert equality between the + * flattened string and the original term. + */ + + void theory_str::try_eval_concat(enode * cat) { + app * a_cat = cat->get_owner(); + SASSERT(u.str.is_concat(a_cat)); + + context & ctx = get_context(); + ast_manager & m = get_manager(); + + TRACE("str", tout << "attempting to flatten " << mk_pp(a_cat, m) << std::endl;); + + std::stack worklist; + zstring flattenedString(""); + bool constOK = true; + + { + app * arg0 = to_app(a_cat->get_arg(0)); + app * arg1 = to_app(a_cat->get_arg(1)); + + worklist.push(arg1); + worklist.push(arg0); + } + + while (constOK && !worklist.empty()) { + app * evalArg = worklist.top(); worklist.pop(); + zstring nextStr; + if (u.str.is_string(evalArg, nextStr)) { + flattenedString = flattenedString + nextStr; + } else if (u.str.is_concat(evalArg)) { + app * arg0 = to_app(evalArg->get_arg(0)); + app * arg1 = to_app(evalArg->get_arg(1)); + + worklist.push(arg1); + worklist.push(arg0); } else { - return; + TRACE("str", tout << "non-constant term in concat -- giving up." << std::endl;); + constOK = false; + break; } } + if (constOK) { + TRACE("str", tout << "flattened to \"" << flattenedString.encode().c_str() << "\"" << std::endl;); + expr_ref constStr(mk_string(flattenedString), m); + expr_ref axiom(ctx.mk_eq_atom(a_cat, constStr), m); + assert_axiom(axiom); + } } - expr_ref new_nn1(simplify_concat(nn1), m); - expr_ref new_nn2(simplify_concat(nn2), m); - app * a_new_nn1 = to_app(new_nn1); - app * a_new_nn2 = to_app(new_nn2); + /* + * Instantiate an axiom of the following form: + * Length(Concat(x, y)) = Length(x) + Length(y) + */ + void theory_str::instantiate_concat_axiom(enode * cat) { + app * a_cat = cat->get_owner(); + SASSERT(u.str.is_concat(a_cat)); - TRACE("str", tout << "new_nn1 = " << mk_ismt2_pp(new_nn1, m) << std::endl - << "new_nn2 = " << mk_ismt2_pp(new_nn2, m) << std::endl;); + ast_manager & m = get_manager(); - if (new_nn1 == new_nn2) { - TRACE("str", tout << "equal concats, return" << std::endl;); - return; + TRACE("str", tout << "instantiating concat axiom for " << mk_ismt2_pp(a_cat, m) << std::endl;); + + // build LHS + expr_ref len_xy(m); + len_xy = mk_strlen(a_cat); + SASSERT(len_xy); + + // build RHS: start by extracting x and y from Concat(x, y) + unsigned nArgs = a_cat->get_num_args(); + SASSERT(nArgs == 2); + app * a_x = to_app(a_cat->get_arg(0)); + app * a_y = to_app(a_cat->get_arg(1)); + + expr_ref len_x(m); + len_x = mk_strlen(a_x); + SASSERT(len_x); + + expr_ref len_y(m); + len_y = mk_strlen(a_y); + SASSERT(len_y); + + // now build len_x + len_y + expr_ref len_x_plus_len_y(m); + len_x_plus_len_y = m_autil.mk_add(len_x, len_y); + SASSERT(len_x_plus_len_y); + + // finally assert equality between the two subexpressions + app * eq = m.mk_eq(len_xy, len_x_plus_len_y); + SASSERT(eq); + assert_axiom(eq); } - if (!can_two_nodes_eq(new_nn1, new_nn2)) { - expr_ref detected(m.mk_not(ctx.mk_eq_atom(new_nn1, new_nn2)), m); - TRACE("str", tout << "inconsistency detected: " << mk_ismt2_pp(detected, m) << std::endl;); - assert_axiom(detected); - return; + /* + * Add axioms that are true for any string variable: + * 1. Length(x) >= 0 + * 2. Length(x) == 0 <=> x == "" + * If the term is a string constant, we can assert something stronger: + * Length(x) == strlen(x) + */ + void theory_str::instantiate_basic_string_axioms(enode * str) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + TRACE("str", tout << "set up basic string axioms on " << mk_pp(str->get_owner(), m) << std::endl;); + + // TESTING: attempt to avoid a crash here when a variable goes out of scope + if (str->get_iscope_lvl() > ctx.get_scope_level()) { + TRACE("str", tout << "WARNING: skipping axiom setup on out-of-scope string term" << std::endl;); + return; + } + + // generate a stronger axiom for constant strings + app * a_str = str->get_owner(); + if (u.str.is_string(a_str)) { + expr_ref len_str(m); + len_str = mk_strlen(a_str); + SASSERT(len_str); + + zstring strconst; + u.str.is_string(str->get_owner(), strconst); + TRACE("str", tout << "instantiating constant string axioms for \"" << strconst.encode().c_str() << "\"" << std::endl;); + unsigned int l = strconst.length(); + expr_ref len(m_autil.mk_numeral(rational(l), true), m); + + literal lit(mk_eq(len_str, len, false)); + ctx.mark_as_relevant(lit); + ctx.mk_th_axiom(get_id(), 1, &lit); + } else { + // build axiom 1: Length(a_str) >= 0 + { + // build LHS + expr_ref len_str(m); + len_str = mk_strlen(a_str); + SASSERT(len_str); + // build RHS + expr_ref zero(m); + zero = m_autil.mk_numeral(rational(0), true); + SASSERT(zero); + // build LHS >= RHS and assert + app * lhs_ge_rhs = m_autil.mk_ge(len_str, zero); + SASSERT(lhs_ge_rhs); + TRACE("str", tout << "string axiom 1: " << mk_ismt2_pp(lhs_ge_rhs, m) << std::endl;); + assert_axiom(lhs_ge_rhs); + } + + // build axiom 2: Length(a_str) == 0 <=> a_str == "" + { + // build LHS of iff + expr_ref len_str(m); + len_str = mk_strlen(a_str); + SASSERT(len_str); + expr_ref zero(m); + zero = m_autil.mk_numeral(rational(0), true); + SASSERT(zero); + expr_ref lhs(m); + lhs = ctx.mk_eq_atom(len_str, zero); + SASSERT(lhs); + // build RHS of iff + expr_ref empty_str(m); + empty_str = mk_string(""); + SASSERT(empty_str); + expr_ref rhs(m); + rhs = ctx.mk_eq_atom(a_str, empty_str); + SASSERT(rhs); + // build LHS <=> RHS and assert + TRACE("str", tout << "string axiom 2: " << mk_ismt2_pp(lhs, m) << " <=> " << mk_ismt2_pp(rhs, m) << std::endl;); + literal l(mk_eq(lhs, rhs, true)); + ctx.mark_as_relevant(l); + ctx.mk_th_axiom(get_id(), 1, &l); + } + + } } - // check whether new_nn1 and new_nn2 are still concats + /* + * Add an axiom of the form: + * (lhs == rhs) -> ( Length(lhs) == Length(rhs) ) + */ + void theory_str::instantiate_str_eq_length_axiom(enode * lhs, enode * rhs) { + context & ctx = get_context(); + ast_manager & m = get_manager(); - bool n1IsConcat = u.str.is_concat(a_new_nn1); - bool n2IsConcat = u.str.is_concat(a_new_nn2); - if (!n1IsConcat && n2IsConcat) { - TRACE("str", tout << "nn1_new is not a concat" << std::endl;); - if (u.str.is_string(a_new_nn1)) { - simplify_parent(new_nn2, new_nn1); - } - return; - } else if (n1IsConcat && !n2IsConcat) { - TRACE("str", tout << "nn2_new is not a concat" << std::endl;); - if (u.str.is_string(a_new_nn2)) { - simplify_parent(new_nn1, new_nn2); - } - return; - } else if (!n1IsConcat && !n2IsConcat) { - // normally this should never happen, because group_terms_by_eqc() should have pre-simplified - // as much as possible. however, we make a defensive check here just in case - TRACE("str", tout << "WARNING: nn1_new and nn2_new both simplify to non-concat terms" << std::endl;); - return; - } + app * a_lhs = lhs->get_owner(); + app * a_rhs = rhs->get_owner(); - expr * v1_arg0 = a_new_nn1->get_arg(0); - expr * v1_arg1 = a_new_nn1->get_arg(1); - expr * v2_arg0 = a_new_nn2->get_arg(0); - expr * v2_arg1 = a_new_nn2->get_arg(1); + // build premise: (lhs == rhs) + expr_ref premise(ctx.mk_eq_atom(a_lhs, a_rhs), m); - if (!in_same_eqc(new_nn1, new_nn2) && (nn1 != new_nn1 || nn2 != new_nn2)) { - int ii4 = 0; - expr* item[3]; - if (nn1 != new_nn1) { - item[ii4++] = ctx.mk_eq_atom(nn1, new_nn1); - } - if (nn2 != new_nn2) { - item[ii4++] = ctx.mk_eq_atom(nn2, new_nn2); - } - item[ii4++] = ctx.mk_eq_atom(nn1, nn2); - expr_ref premise(m.mk_and(ii4, item), m); - expr_ref conclusion(ctx.mk_eq_atom(new_nn1, new_nn2), m); + // build conclusion: ( Length(lhs) == Length(rhs) ) + expr_ref len_lhs(mk_strlen(a_lhs), m); + SASSERT(len_lhs); + expr_ref len_rhs(mk_strlen(a_rhs), m); + SASSERT(len_rhs); + expr_ref conclusion(ctx.mk_eq_atom(len_lhs, len_rhs), m); + + TRACE("str", tout << "string-eq length-eq axiom: " + << mk_ismt2_pp(premise, m) << " -> " << mk_ismt2_pp(conclusion, m) << std::endl;); assert_implication(premise, conclusion); } - // start to split both concats - check_and_init_cut_var(v1_arg0); - check_and_init_cut_var(v1_arg1); - check_and_init_cut_var(v2_arg0); - check_and_init_cut_var(v2_arg1); + void theory_str::instantiate_axiom_CharAt(enode * e) { + context & ctx = get_context(); + ast_manager & m = get_manager(); - //************************************************************* - // case 1: concat(x, y) = concat(m, n) - //************************************************************* - if (is_concat_eq_type1(new_nn1, new_nn2)) { - process_concat_eq_type1(new_nn1, new_nn2); - return; + app * expr = e->get_owner(); + if (axiomatized_terms.contains(expr)) { + TRACE("str", tout << "already set up CharAt axiom for " << mk_pp(expr, m) << std::endl;); + return; + } + axiomatized_terms.insert(expr); + + TRACE("str", tout << "instantiate CharAt axiom for " << mk_pp(expr, m) << std::endl;); + + expr_ref ts0(mk_str_var("ts0"), m); + expr_ref ts1(mk_str_var("ts1"), m); + expr_ref ts2(mk_str_var("ts2"), m); + + expr_ref cond(m.mk_and( + m_autil.mk_ge(expr->get_arg(1), mk_int(0)), + // REWRITE for arithmetic theory: + // m_autil.mk_lt(expr->get_arg(1), mk_strlen(expr->get_arg(0))) + m.mk_not(m_autil.mk_ge(m_autil.mk_add(expr->get_arg(1), m_autil.mk_mul(mk_int(-1), mk_strlen(expr->get_arg(0)))), mk_int(0))) + ), m); + + expr_ref_vector and_item(m); + and_item.push_back(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(ts0, mk_concat(ts1, ts2)))); + and_item.push_back(ctx.mk_eq_atom(expr->get_arg(1), mk_strlen(ts0))); + and_item.push_back(ctx.mk_eq_atom(mk_strlen(ts1), mk_int(1))); + + expr_ref thenBranch(m.mk_and(and_item.size(), and_item.c_ptr()), m); + expr_ref elseBranch(ctx.mk_eq_atom(ts1, mk_string("")), m); + + expr_ref axiom(m.mk_ite(cond, thenBranch, elseBranch), m); + expr_ref reductionVar(ctx.mk_eq_atom(expr, ts1), m); + + SASSERT(axiom); + SASSERT(reductionVar); + + expr_ref finalAxiom(m.mk_and(axiom, reductionVar), m); + SASSERT(finalAxiom); + assert_axiom(finalAxiom); } - //************************************************************* - // case 2: concat(x, y) = concat(m, "str") - //************************************************************* - if (is_concat_eq_type2(new_nn1, new_nn2)) { - process_concat_eq_type2(new_nn1, new_nn2); - return; + void theory_str::instantiate_axiom_prefixof(enode * e) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + app * expr = e->get_owner(); + if (axiomatized_terms.contains(expr)) { + TRACE("str", tout << "already set up prefixof axiom for " << mk_pp(expr, m) << std::endl;); + return; + } + axiomatized_terms.insert(expr); + + TRACE("str", tout << "instantiate prefixof axiom for " << mk_pp(expr, m) << std::endl;); + + expr_ref ts0(mk_str_var("ts0"), m); + expr_ref ts1(mk_str_var("ts1"), m); + + expr_ref_vector innerItems(m); + innerItems.push_back(ctx.mk_eq_atom(expr->get_arg(1), mk_concat(ts0, ts1))); + innerItems.push_back(ctx.mk_eq_atom(mk_strlen(ts0), mk_strlen(expr->get_arg(0)))); + innerItems.push_back(m.mk_ite(ctx.mk_eq_atom(ts0, expr->get_arg(0)), expr, m.mk_not(expr))); + expr_ref then1(m.mk_and(innerItems.size(), innerItems.c_ptr()), m); + SASSERT(then1); + + // the top-level condition is Length(arg0) >= Length(arg1) + expr_ref topLevelCond( + m_autil.mk_ge( + m_autil.mk_add( + mk_strlen(expr->get_arg(1)), m_autil.mk_mul(mk_int(-1), mk_strlen(expr->get_arg(0)))), + mk_int(0)) + , m); + SASSERT(topLevelCond); + + expr_ref finalAxiom(m.mk_ite(topLevelCond, then1, m.mk_not(expr)), m); + SASSERT(finalAxiom); + assert_axiom(finalAxiom); } - //************************************************************* - // case 3: concat(x, y) = concat("str", n) - //************************************************************* - if (is_concat_eq_type3(new_nn1, new_nn2)) { - process_concat_eq_type3(new_nn1, new_nn2); - return; + void theory_str::instantiate_axiom_suffixof(enode * e) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + app * expr = e->get_owner(); + if (axiomatized_terms.contains(expr)) { + TRACE("str", tout << "already set up suffixof axiom for " << mk_pp(expr, m) << std::endl;); + return; + } + axiomatized_terms.insert(expr); + + TRACE("str", tout << "instantiate suffixof axiom for " << mk_pp(expr, m) << std::endl;); + + expr_ref ts0(mk_str_var("ts0"), m); + expr_ref ts1(mk_str_var("ts1"), m); + + expr_ref_vector innerItems(m); + innerItems.push_back(ctx.mk_eq_atom(expr->get_arg(1), mk_concat(ts0, ts1))); + innerItems.push_back(ctx.mk_eq_atom(mk_strlen(ts1), mk_strlen(expr->get_arg(0)))); + innerItems.push_back(m.mk_ite(ctx.mk_eq_atom(ts1, expr->get_arg(0)), expr, m.mk_not(expr))); + expr_ref then1(m.mk_and(innerItems.size(), innerItems.c_ptr()), m); + SASSERT(then1); + + // the top-level condition is Length(arg0) >= Length(arg1) + expr_ref topLevelCond( + m_autil.mk_ge( + m_autil.mk_add( + mk_strlen(expr->get_arg(1)), m_autil.mk_mul(mk_int(-1), mk_strlen(expr->get_arg(0)))), + mk_int(0)) + , m); + SASSERT(topLevelCond); + + expr_ref finalAxiom(m.mk_ite(topLevelCond, then1, m.mk_not(expr)), m); + SASSERT(finalAxiom); + assert_axiom(finalAxiom); } - //************************************************************* - // case 4: concat("str1", y) = concat("str2", n) - //************************************************************* - if (is_concat_eq_type4(new_nn1, new_nn2)) { - process_concat_eq_type4(new_nn1, new_nn2); - return; + void theory_str::instantiate_axiom_Contains(enode * e) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + app * ex = e->get_owner(); + if (axiomatized_terms.contains(ex)) { + TRACE("str", tout << "already set up Contains axiom for " << mk_pp(ex, m) << std::endl;); + return; + } + axiomatized_terms.insert(ex); + + // quick path, because this is necessary due to rewriter behaviour + // at minimum it should fix z3str/concat-006.smt2 + zstring haystackStr, needleStr; + if (u.str.is_string(ex->get_arg(0), haystackStr) && u.str.is_string(ex->get_arg(1), needleStr)) { + TRACE("str", tout << "eval constant Contains term " << mk_pp(ex, m) << std::endl;); + if (haystackStr.contains(needleStr)) { + assert_axiom(ex); + } else { + assert_axiom(m.mk_not(ex)); + } + return; + } + + { // register Contains() + expr * str = ex->get_arg(0); + expr * substr = ex->get_arg(1); + contains_map.push_back(ex); + std::pair key = std::pair(str, substr); + contain_pair_bool_map.insert(str, substr, ex); + contain_pair_idx_map[str].insert(key); + contain_pair_idx_map[substr].insert(key); + } + + TRACE("str", tout << "instantiate Contains axiom for " << mk_pp(ex, m) << std::endl;); + + expr_ref ts0(mk_str_var("ts0"), m); + expr_ref ts1(mk_str_var("ts1"), m); + + expr_ref breakdownAssert(ctx.mk_eq_atom(ex, ctx.mk_eq_atom(ex->get_arg(0), mk_concat(ts0, mk_concat(ex->get_arg(1), ts1)))), m); + SASSERT(breakdownAssert); + assert_axiom(breakdownAssert); } - //************************************************************* - // case 5: concat(x, "str1") = concat(m, "str2") - //************************************************************* - if (is_concat_eq_type5(new_nn1, new_nn2)) { - process_concat_eq_type5(new_nn1, new_nn2); - return; - } - //************************************************************* - // case 6: concat("str1", y) = concat(m, "str2") - //************************************************************* - if (is_concat_eq_type6(new_nn1, new_nn2)) { - process_concat_eq_type6(new_nn1, new_nn2); - return; + void theory_str::instantiate_axiom_Indexof(enode * e) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + app * expr = e->get_owner(); + if (axiomatized_terms.contains(expr)) { + TRACE("str", tout << "already set up Indexof axiom for " << mk_pp(expr, m) << std::endl;); + return; + } + axiomatized_terms.insert(expr); + + TRACE("str", tout << "instantiate Indexof axiom for " << mk_pp(expr, m) << std::endl;); + + expr_ref x1(mk_str_var("x1"), m); + expr_ref x2(mk_str_var("x2"), m); + expr_ref indexAst(mk_int_var("index"), m); + + expr_ref condAst(mk_contains(expr->get_arg(0), expr->get_arg(1)), m); + SASSERT(condAst); + + // ----------------------- + // true branch + expr_ref_vector thenItems(m); + // args[0] = x1 . args[1] . x2 + thenItems.push_back(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(x1, mk_concat(expr->get_arg(1), x2)))); + // indexAst = |x1| + thenItems.push_back(ctx.mk_eq_atom(indexAst, mk_strlen(x1))); + // args[0] = x3 . x4 + // /\ |x3| = |x1| + |args[1]| - 1 + // /\ ! contains(x3, args[1]) + expr_ref x3(mk_str_var("x3"), m); + expr_ref x4(mk_str_var("x4"), m); + expr_ref tmpLen(m_autil.mk_add(indexAst, mk_strlen(expr->get_arg(1)), mk_int(-1)), m); + SASSERT(tmpLen); + thenItems.push_back(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(x3, x4))); + thenItems.push_back(ctx.mk_eq_atom(mk_strlen(x3), tmpLen)); + thenItems.push_back(m.mk_not(mk_contains(x3, expr->get_arg(1)))); + expr_ref thenBranch(m.mk_and(thenItems.size(), thenItems.c_ptr()), m); + SASSERT(thenBranch); + + // ----------------------- + // false branch + expr_ref elseBranch(ctx.mk_eq_atom(indexAst, mk_int(-1)), m); + SASSERT(elseBranch); + + expr_ref breakdownAssert(m.mk_ite(condAst, thenBranch, elseBranch), m); + SASSERT(breakdownAssert); + + expr_ref reduceToIndex(ctx.mk_eq_atom(expr, indexAst), m); + SASSERT(reduceToIndex); + + expr_ref finalAxiom(m.mk_and(breakdownAssert, reduceToIndex), m); + SASSERT(finalAxiom); + assert_axiom(finalAxiom); } -} + void theory_str::instantiate_axiom_Indexof2(enode * e) { + context & ctx = get_context(); + ast_manager & m = get_manager(); -/* - * Returns true if attempting to process a concat equality between lhs and rhs - * will result in overlapping variables (false otherwise). - */ -bool theory_str::will_result_in_overlap(expr * lhs, expr * rhs) { - ast_manager & m = get_manager(); + app * expr = e->get_owner(); + if (axiomatized_terms.contains(expr)) { + TRACE("str", tout << "already set up Indexof2 axiom for " << mk_pp(expr, m) << std::endl;); + return; + } + axiomatized_terms.insert(expr); - expr_ref new_nn1(simplify_concat(lhs), m); - expr_ref new_nn2(simplify_concat(rhs), m); - app * a_new_nn1 = to_app(new_nn1); - app * a_new_nn2 = to_app(new_nn2); + TRACE("str", tout << "instantiate Indexof2 axiom for " << mk_pp(expr, m) << std::endl;); - bool n1IsConcat = u.str.is_concat(a_new_nn1); - bool n2IsConcat = u.str.is_concat(a_new_nn2); - if (!n1IsConcat && !n2IsConcat) { - // we simplified both sides to non-concat expressions... - return false; + // ------------------------------------------------------------------------------- + // if (arg[2] >= length(arg[0])) // ite2 + // resAst = -1 + // else + // args[0] = prefix . suffix + // /\ indexAst = indexof(suffix, arg[1]) + // /\ args[2] = len(prefix) + // /\ if (indexAst == -1) resAst = indexAst // ite3 + // else resAst = args[2] + indexAst + // ------------------------------------------------------------------------------- + + expr_ref resAst(mk_int_var("res"), m); + expr_ref indexAst(mk_int_var("index"), m); + expr_ref prefix(mk_str_var("prefix"), m); + expr_ref suffix(mk_str_var("suffix"), m); + expr_ref prefixLen(mk_strlen(prefix), m); + expr_ref zeroAst(mk_int(0), m); + expr_ref negOneAst(mk_int(-1), m); + + expr_ref ite3(m.mk_ite( + ctx.mk_eq_atom(indexAst, negOneAst), + ctx.mk_eq_atom(resAst, negOneAst), + ctx.mk_eq_atom(resAst, m_autil.mk_add(expr->get_arg(2), indexAst)) + ),m); + + expr_ref_vector ite2ElseItems(m); + ite2ElseItems.push_back(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(prefix, suffix))); + ite2ElseItems.push_back(ctx.mk_eq_atom(indexAst, mk_indexof(suffix, expr->get_arg(1)))); + ite2ElseItems.push_back(ctx.mk_eq_atom(expr->get_arg(2), prefixLen)); + ite2ElseItems.push_back(ite3); + expr_ref ite2Else(m.mk_and(ite2ElseItems.size(), ite2ElseItems.c_ptr()), m); + SASSERT(ite2Else); + + expr_ref ite2(m.mk_ite( + //m_autil.mk_ge(expr->get_arg(2), mk_strlen(expr->get_arg(0))), + m_autil.mk_ge(m_autil.mk_add(expr->get_arg(2), m_autil.mk_mul(mk_int(-1), mk_strlen(expr->get_arg(0)))), zeroAst), + ctx.mk_eq_atom(resAst, negOneAst), + ite2Else + ), m); + SASSERT(ite2); + + expr_ref ite1(m.mk_ite( + //m_autil.mk_lt(expr->get_arg(2), zeroAst), + m.mk_not(m_autil.mk_ge(expr->get_arg(2), zeroAst)), + ctx.mk_eq_atom(resAst, mk_indexof(expr->get_arg(0), expr->get_arg(1))), + ite2 + ), m); + SASSERT(ite1); + assert_axiom(ite1); + + expr_ref reduceTerm(ctx.mk_eq_atom(expr, resAst), m); + SASSERT(reduceTerm); + assert_axiom(reduceTerm); } - expr * v1_arg0 = a_new_nn1->get_arg(0); - expr * v1_arg1 = a_new_nn1->get_arg(1); - expr * v2_arg0 = a_new_nn2->get_arg(0); - expr * v2_arg1 = a_new_nn2->get_arg(1); + void theory_str::instantiate_axiom_LastIndexof(enode * e) { + context & ctx = get_context(); + ast_manager & m = get_manager(); - TRACE("str", tout << "checking whether " << mk_pp(new_nn1, m) << " and " << mk_pp(new_nn1, m) << " might overlap." << std::endl;); + app * expr = e->get_owner(); + if (axiomatized_terms.contains(expr)) { + TRACE("str", tout << "already set up LastIndexof axiom for " << mk_pp(expr, m) << std::endl;); + return; + } + axiomatized_terms.insert(expr); - check_and_init_cut_var(v1_arg0); - check_and_init_cut_var(v1_arg1); - check_and_init_cut_var(v2_arg0); - check_and_init_cut_var(v2_arg1); + TRACE("str", tout << "instantiate LastIndexof axiom for " << mk_pp(expr, m) << std::endl;); - //************************************************************* - // case 1: concat(x, y) = concat(m, n) - //************************************************************* - if (is_concat_eq_type1(new_nn1, new_nn2)) { - TRACE("str", tout << "Type 1 check." << std::endl;); - expr * x = to_app(new_nn1)->get_arg(0); - expr * y = to_app(new_nn1)->get_arg(1); - expr * m = to_app(new_nn2)->get_arg(0); - expr * n = to_app(new_nn2)->get_arg(1); + expr_ref x1(mk_str_var("x1"), m); + expr_ref x2(mk_str_var("x2"), m); + expr_ref indexAst(mk_int_var("index"), m); + expr_ref_vector items(m); - if (has_self_cut(m, y)) { - TRACE("str", tout << "Possible overlap found" << std::endl; print_cut_var(m, tout); print_cut_var(y, tout);); - return true; - } else if (has_self_cut(x, n)) { - TRACE("str", tout << "Possible overlap found" << std::endl; print_cut_var(x, tout); print_cut_var(n, tout);); - return true; - } else { - return false; + // args[0] = x1 . args[1] . x2 + expr_ref eq1(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(x1, mk_concat(expr->get_arg(1), x2))), m); + expr_ref arg0HasArg1(mk_contains(expr->get_arg(0), expr->get_arg(1)), m); // arg0HasArg1 = Contains(args[0], args[1]) + items.push_back(ctx.mk_eq_atom(arg0HasArg1, eq1)); + + + expr_ref condAst(arg0HasArg1, m); + //---------------------------- + // true branch + expr_ref_vector thenItems(m); + thenItems.push_back(m_autil.mk_ge(indexAst, mk_int(0))); + // args[0] = x1 . args[1] . x2 + // x1 doesn't contain args[1] + thenItems.push_back(m.mk_not(mk_contains(x2, expr->get_arg(1)))); + thenItems.push_back(ctx.mk_eq_atom(indexAst, mk_strlen(x1))); + + bool canSkip = false; + zstring arg1Str; + if (u.str.is_string(expr->get_arg(1), arg1Str)) { + if (arg1Str.length() == 1) { + canSkip = true; + } + } + + if (!canSkip) { + // args[0] = x3 . x4 /\ |x3| = |x1| + 1 /\ ! contains(x4, args[1]) + expr_ref x3(mk_str_var("x3"), m); + expr_ref x4(mk_str_var("x4"), m); + expr_ref tmpLen(m_autil.mk_add(indexAst, mk_int(1)), m); + thenItems.push_back(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(x3, x4))); + thenItems.push_back(ctx.mk_eq_atom(mk_strlen(x3), tmpLen)); + thenItems.push_back(m.mk_not(mk_contains(x4, expr->get_arg(1)))); + } + //---------------------------- + // else branch + expr_ref_vector elseItems(m); + elseItems.push_back(ctx.mk_eq_atom(indexAst, mk_int(-1))); + + items.push_back(m.mk_ite(condAst, m.mk_and(thenItems.size(), thenItems.c_ptr()), m.mk_and(elseItems.size(), elseItems.c_ptr()))); + + expr_ref breakdownAssert(m.mk_and(items.size(), items.c_ptr()), m); + SASSERT(breakdownAssert); + + expr_ref reduceToIndex(ctx.mk_eq_atom(expr, indexAst), m); + SASSERT(reduceToIndex); + + expr_ref finalAxiom(m.mk_and(breakdownAssert, reduceToIndex), m); + SASSERT(finalAxiom); + assert_axiom(finalAxiom); + } + + void theory_str::instantiate_axiom_Substr(enode * e) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + app * expr = e->get_owner(); + if (axiomatized_terms.contains(expr)) { + TRACE("str", tout << "already set up Substr axiom for " << mk_pp(expr, m) << std::endl;); + return; + } + axiomatized_terms.insert(expr); + + TRACE("str", tout << "instantiate Substr axiom for " << mk_pp(expr, m) << std::endl;); + + expr_ref substrBase(expr->get_arg(0), m); + expr_ref substrPos(expr->get_arg(1), m); + expr_ref substrLen(expr->get_arg(2), m); + SASSERT(substrBase); + SASSERT(substrPos); + SASSERT(substrLen); + + expr_ref zero(m_autil.mk_numeral(rational::zero(), true), m); + expr_ref minusOne(m_autil.mk_numeral(rational::minus_one(), true), m); + SASSERT(zero); + SASSERT(minusOne); + + expr_ref_vector argumentsValid_terms(m); + // pos >= 0 + argumentsValid_terms.push_back(m_autil.mk_ge(substrPos, zero)); + // pos < strlen(base) + // --> pos + -1*strlen(base) < 0 + argumentsValid_terms.push_back(m.mk_not(m_autil.mk_ge( + m_autil.mk_add(substrPos, m_autil.mk_mul(minusOne, substrLen)), + zero))); + // len >= 0 + argumentsValid_terms.push_back(m_autil.mk_ge(substrLen, zero)); + + expr_ref argumentsValid(mk_and(argumentsValid_terms), m); + SASSERT(argumentsValid); + ctx.internalize(argumentsValid, false); + + // (pos+len) >= strlen(base) + // --> pos + len + -1*strlen(base) >= 0 + expr_ref lenOutOfBounds(m_autil.mk_ge( + m_autil.mk_add(substrPos, substrLen, m_autil.mk_mul(minusOne, mk_strlen(substrBase))), + zero), m); + SASSERT(lenOutOfBounds); + ctx.internalize(argumentsValid, false); + + // Case 1: pos < 0 or pos >= strlen(base) or len < 0 + // ==> (Substr ...) = "" + expr_ref case1_premise(m.mk_not(argumentsValid), m); + SASSERT(case1_premise); + ctx.internalize(case1_premise, false); + expr_ref case1_conclusion(ctx.mk_eq_atom(expr, mk_string("")), m); + SASSERT(case1_conclusion); + ctx.internalize(case1_conclusion, false); + expr_ref case1(rewrite_implication(case1_premise, case1_conclusion), m); + SASSERT(case1); + + // Case 2: (pos >= 0 and pos < strlen(base) and len >= 0) and (pos+len) >= strlen(base) + // ==> base = t0.t1 AND len(t0) = pos AND (Substr ...) = t1 + expr_ref t0(mk_str_var("t0"), m); + expr_ref t1(mk_str_var("t1"), m); + expr_ref case2_conclusion(m.mk_and( + ctx.mk_eq_atom(substrBase, mk_concat(t0,t1)), + ctx.mk_eq_atom(mk_strlen(t0), substrPos), + ctx.mk_eq_atom(expr, t1)), m); + expr_ref case2(rewrite_implication(m.mk_and(argumentsValid, lenOutOfBounds), case2_conclusion), m); + SASSERT(case2); + + // Case 3: (pos >= 0 and pos < strlen(base) and len >= 0) and (pos+len) < strlen(base) + // ==> base = t2.t3.t4 AND len(t2) = pos AND len(t3) = len AND (Substr ...) = t3 + expr_ref t2(mk_str_var("t2"), m); + expr_ref t3(mk_str_var("t3"), m); + expr_ref t4(mk_str_var("t4"), m); + expr_ref_vector case3_conclusion_terms(m); + case3_conclusion_terms.push_back(ctx.mk_eq_atom(substrBase, mk_concat(t2, mk_concat(t3, t4)))); + case3_conclusion_terms.push_back(ctx.mk_eq_atom(mk_strlen(t2), substrPos)); + case3_conclusion_terms.push_back(ctx.mk_eq_atom(mk_strlen(t3), substrLen)); + case3_conclusion_terms.push_back(ctx.mk_eq_atom(expr, t3)); + expr_ref case3_conclusion(mk_and(case3_conclusion_terms), m); + expr_ref case3(rewrite_implication(m.mk_and(argumentsValid, m.mk_not(lenOutOfBounds)), case3_conclusion), m); + SASSERT(case3); + + ctx.internalize(case1, false); + ctx.internalize(case2, false); + ctx.internalize(case3, false); + + expr_ref finalAxiom(m.mk_and(case1, case2, case3), m); + SASSERT(finalAxiom); + assert_axiom(finalAxiom); + } + + void theory_str::instantiate_axiom_Replace(enode * e) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + app * expr = e->get_owner(); + if (axiomatized_terms.contains(expr)) { + TRACE("str", tout << "already set up Replace axiom for " << mk_pp(expr, m) << std::endl;); + return; + } + axiomatized_terms.insert(expr); + + TRACE("str", tout << "instantiate Replace axiom for " << mk_pp(expr, m) << std::endl;); + + expr_ref x1(mk_str_var("x1"), m); + expr_ref x2(mk_str_var("x2"), m); + expr_ref i1(mk_int_var("i1"), m); + expr_ref result(mk_str_var("result"), m); + + // condAst = Contains(args[0], args[1]) + expr_ref condAst(mk_contains(expr->get_arg(0), expr->get_arg(1)), m); + // ----------------------- + // true branch + expr_ref_vector thenItems(m); + // args[0] = x1 . args[1] . x2 + thenItems.push_back(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(x1, mk_concat(expr->get_arg(1), x2)))); + // i1 = |x1| + thenItems.push_back(ctx.mk_eq_atom(i1, mk_strlen(x1))); + // args[0] = x3 . x4 /\ |x3| = |x1| + |args[1]| - 1 /\ ! contains(x3, args[1]) + expr_ref x3(mk_str_var("x3"), m); + expr_ref x4(mk_str_var("x4"), m); + expr_ref tmpLen(m_autil.mk_add(i1, mk_strlen(expr->get_arg(1)), mk_int(-1)), m); + thenItems.push_back(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(x3, x4))); + thenItems.push_back(ctx.mk_eq_atom(mk_strlen(x3), tmpLen)); + thenItems.push_back(m.mk_not(mk_contains(x3, expr->get_arg(1)))); + thenItems.push_back(ctx.mk_eq_atom(result, mk_concat(x1, mk_concat(expr->get_arg(2), x2)))); + // ----------------------- + // false branch + expr_ref elseBranch(ctx.mk_eq_atom(result, expr->get_arg(0)), m); + + expr_ref breakdownAssert(m.mk_ite(condAst, m.mk_and(thenItems.size(), thenItems.c_ptr()), elseBranch), m); + SASSERT(breakdownAssert); + + expr_ref reduceToResult(ctx.mk_eq_atom(expr, result), m); + SASSERT(reduceToResult); + + expr_ref finalAxiom(m.mk_and(breakdownAssert, reduceToResult), m); + SASSERT(finalAxiom); + assert_axiom(finalAxiom); + } + + void theory_str::instantiate_axiom_str_to_int(enode * e) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + app * ex = e->get_owner(); + if (axiomatized_terms.contains(ex)) { + TRACE("str", tout << "already set up str.to-int axiom for " << mk_pp(ex, m) << std::endl;); + return; + } + axiomatized_terms.insert(ex); + + TRACE("str", tout << "instantiate str.to-int axiom for " << mk_pp(ex, m) << std::endl;); + + // let expr = (str.to-int S) + // axiom 1: expr >= -1 + // axiom 2: expr = 0 <==> S = "0" + // axiom 3: expr >= 1 ==> len(S) > 0 AND S[0] != "0" + + expr * S = ex->get_arg(0); + { + expr_ref axiom1(m_autil.mk_ge(ex, m_autil.mk_numeral(rational::minus_one(), true)), m); + SASSERT(axiom1); + assert_axiom(axiom1); + } + + { + expr_ref lhs(ctx.mk_eq_atom(ex, m_autil.mk_numeral(rational::zero(), true)), m); + expr_ref rhs(ctx.mk_eq_atom(S, mk_string("0")), m); + expr_ref axiom2(ctx.mk_eq_atom(lhs, rhs), m); + SASSERT(axiom2); + assert_axiom(axiom2); + } + + { + expr_ref premise(m_autil.mk_ge(ex, m_autil.mk_numeral(rational::one(), true)), m); + expr_ref hd(mk_str_var("hd"), m); + expr_ref tl(mk_str_var("tl"), m); + expr_ref conclusion1(ctx.mk_eq_atom(S, mk_concat(hd, tl)), m); + expr_ref conclusion2(ctx.mk_eq_atom(mk_strlen(hd), m_autil.mk_numeral(rational::one(), true)), m); + expr_ref conclusion3(m.mk_not(ctx.mk_eq_atom(hd, mk_string("0"))), m); + expr_ref conclusion(m.mk_and(conclusion1, conclusion2, conclusion3), m); + SASSERT(premise); + SASSERT(conclusion); + assert_implication(premise, conclusion); } } - //************************************************************* - // case 2: concat(x, y) = concat(m, "str") - //************************************************************* - if (is_concat_eq_type2(new_nn1, new_nn2)) { + void theory_str::instantiate_axiom_int_to_str(enode * e) { + context & ctx = get_context(); + ast_manager & m = get_manager(); - expr * y = NULL; - expr * m = NULL; - expr * v1_arg0 = to_app(new_nn1)->get_arg(0); - expr * v1_arg1 = to_app(new_nn1)->get_arg(1); - expr * v2_arg0 = to_app(new_nn2)->get_arg(0); - expr * v2_arg1 = to_app(new_nn2)->get_arg(1); - - if (u.str.is_string(v1_arg1) && !u.str.is_string(v2_arg1)) { - m = v1_arg0; - y = v2_arg1; - } else { - m = v2_arg0; - y = v1_arg1; + app * ex = e->get_owner(); + if (axiomatized_terms.contains(ex)) { + TRACE("str", tout << "already set up str.from-int axiom for " << mk_pp(ex, m) << std::endl;); + return; } + axiomatized_terms.insert(ex); - if (has_self_cut(m, y)) { - TRACE("str", tout << "Possible overlap found" << std::endl; print_cut_var(m, tout); print_cut_var(y, tout);); - return true; - } else { - return false; + TRACE("str", tout << "instantiate str.from-int axiom for " << mk_pp(ex, m) << std::endl;); + + // axiom 1: N < 0 <==> (str.from-int N) = "" + expr * N = ex->get_arg(0); + { + expr_ref axiom1_lhs(m.mk_not(m_autil.mk_ge(N, m_autil.mk_numeral(rational::zero(), true))), m); + expr_ref axiom1_rhs(ctx.mk_eq_atom(ex, mk_string("")), m); + expr_ref axiom1(ctx.mk_eq_atom(axiom1_lhs, axiom1_rhs), m); + SASSERT(axiom1); + assert_axiom(axiom1); } } - //************************************************************* - // case 3: concat(x, y) = concat("str", n) - //************************************************************* - if (is_concat_eq_type3(new_nn1, new_nn2)) { - expr * v1_arg0 = to_app(new_nn1)->get_arg(0); - expr * v1_arg1 = to_app(new_nn1)->get_arg(1); - expr * v2_arg0 = to_app(new_nn2)->get_arg(0); - expr * v2_arg1 = to_app(new_nn2)->get_arg(1); + expr * theory_str::mk_RegexIn(expr * str, expr * regexp) { + app * regexIn = u.re.mk_in_re(str, regexp); + // immediately force internalization so that axiom setup does not fail + get_context().internalize(regexIn, false); + set_up_axioms(regexIn); + return regexIn; + } - expr * x = NULL; - expr * n = NULL; - - if (u.str.is_string(v1_arg0) && !u.str.is_string(v2_arg0)) { - n = v1_arg1; - x = v2_arg0; - } else { - n = v2_arg1; - x = v1_arg0; + static zstring str2RegexStr(zstring str) { + zstring res(""); + int len = str.length(); + for (int i = 0; i < len; i++) { + char nc = str[i]; + // 12 special chars + if (nc == '\\' || nc == '^' || nc == '$' || nc == '.' || nc == '|' || nc == '?' + || nc == '*' || nc == '+' || nc == '(' || nc == ')' || nc == '[' || nc == '{') { + res = res + zstring("\\"); + } + char tmp[2] = {(char)str[i], '\0'}; + res = res + zstring(tmp); } - if (has_self_cut(x, n)) { - TRACE("str", tout << "Possible overlap found" << std::endl; print_cut_var(x, tout); print_cut_var(n, tout);); - return true; + return res; + } + + zstring theory_str::get_std_regex_str(expr * regex) { + app * a_regex = to_app(regex); + if (u.re.is_to_re(a_regex)) { + expr * regAst = a_regex->get_arg(0); + zstring regAstVal; + u.str.is_string(regAst, regAstVal); + zstring regStr = str2RegexStr(regAstVal); + return regStr; + } else if (u.re.is_concat(a_regex)) { + expr * reg1Ast = a_regex->get_arg(0); + expr * reg2Ast = a_regex->get_arg(1); + zstring reg1Str = get_std_regex_str(reg1Ast); + zstring reg2Str = get_std_regex_str(reg2Ast); + return zstring("(") + reg1Str + zstring(")(") + reg2Str + zstring(")"); + } else if (u.re.is_union(a_regex)) { + expr * reg1Ast = a_regex->get_arg(0); + expr * reg2Ast = a_regex->get_arg(1); + zstring reg1Str = get_std_regex_str(reg1Ast); + zstring reg2Str = get_std_regex_str(reg2Ast); + return zstring("(") + reg1Str + zstring(")|(") + reg2Str + zstring(")"); + } else if (u.re.is_star(a_regex)) { + expr * reg1Ast = a_regex->get_arg(0); + zstring reg1Str = get_std_regex_str(reg1Ast); + return zstring("(") + reg1Str + zstring(")*"); + } else if (u.re.is_range(a_regex)) { + expr * range1 = a_regex->get_arg(0); + expr * range2 = a_regex->get_arg(1); + zstring range1val, range2val; + u.str.is_string(range1, range1val); + u.str.is_string(range2, range2val); + return zstring("[") + range1val + zstring("-") + range2val + zstring("]"); } else { - return false; + TRACE("str", tout << "BUG: unrecognized regex term " << mk_pp(regex, get_manager()) << std::endl;); + UNREACHABLE(); return zstring(""); } } - //************************************************************* - // case 4: concat("str1", y) = concat("str2", n) - //************************************************************* - if (is_concat_eq_type4(new_nn1, new_nn2)) { - // This case can never result in an overlap. - return false; - } + void theory_str::instantiate_axiom_RegexIn(enode * e) { + context & ctx = get_context(); + ast_manager & m = get_manager(); - //************************************************************* - // case 5: concat(x, "str1") = concat(m, "str2") - //************************************************************* - if (is_concat_eq_type5(new_nn1, new_nn2)) { - // This case can never result in an overlap. - return false; - } - //************************************************************* - // case 6: concat("str1", y) = concat(m, "str2") - //************************************************************* - if (is_concat_eq_type6(new_nn1, new_nn2)) { - expr * v1_arg0 = to_app(new_nn1)->get_arg(0); - expr * v1_arg1 = to_app(new_nn1)->get_arg(1); - expr * v2_arg0 = to_app(new_nn2)->get_arg(0); - expr * v2_arg1 = to_app(new_nn2)->get_arg(1); - - expr * y = NULL; - expr * m = NULL; - - if (u.str.is_string(v1_arg0)) { - y = v1_arg1; - m = v2_arg0; - } else { - y = v2_arg1; - m = v1_arg0; + app * ex = e->get_owner(); + if (axiomatized_terms.contains(ex)) { + TRACE("str", tout << "already set up RegexIn axiom for " << mk_pp(ex, m) << std::endl;); + return; } - if (has_self_cut(m, y)) { - TRACE("str", tout << "Possible overlap found" << std::endl; print_cut_var(m, tout); print_cut_var(y, tout);); - return true; + axiomatized_terms.insert(ex); + + TRACE("str", tout << "instantiate RegexIn axiom for " << mk_pp(ex, m) << std::endl;); + + { + zstring regexStr = get_std_regex_str(ex->get_arg(1)); + std::pair key1(ex->get_arg(0), regexStr); + // skip Z3str's map check, because we already check if we set up axioms on this term + regex_in_bool_map[key1] = ex; + regex_in_var_reg_str_map[ex->get_arg(0)].insert(regexStr); + } + + expr_ref str(ex->get_arg(0), m); + app * regex = to_app(ex->get_arg(1)); + + if (u.re.is_to_re(regex)) { + expr_ref rxStr(regex->get_arg(0), m); + // want to assert 'expr IFF (str == rxStr)' + expr_ref rhs(ctx.mk_eq_atom(str, rxStr), m); + expr_ref finalAxiom(m.mk_iff(ex, rhs), m); + SASSERT(finalAxiom); + assert_axiom(finalAxiom); + TRACE("str", tout << "set up Str2Reg: (RegexIn " << mk_pp(str, m) << " " << mk_pp(regex, m) << ")" << std::endl;); + } else if (u.re.is_concat(regex)) { + expr_ref var1(mk_regex_rep_var(), m); + expr_ref var2(mk_regex_rep_var(), m); + expr_ref rhs(mk_concat(var1, var2), m); + expr_ref rx1(regex->get_arg(0), m); + expr_ref rx2(regex->get_arg(1), m); + expr_ref var1InRegex1(mk_RegexIn(var1, rx1), m); + expr_ref var2InRegex2(mk_RegexIn(var2, rx2), m); + + expr_ref_vector items(m); + items.push_back(var1InRegex1); + items.push_back(var2InRegex2); + items.push_back(ctx.mk_eq_atom(ex, ctx.mk_eq_atom(str, rhs))); + + expr_ref finalAxiom(mk_and(items), m); + SASSERT(finalAxiom); + assert_axiom(finalAxiom); + } else if (u.re.is_union(regex)) { + expr_ref var1(mk_regex_rep_var(), m); + expr_ref var2(mk_regex_rep_var(), m); + expr_ref orVar(m.mk_or(ctx.mk_eq_atom(str, var1), ctx.mk_eq_atom(str, var2)), m); + expr_ref regex1(regex->get_arg(0), m); + expr_ref regex2(regex->get_arg(1), m); + expr_ref var1InRegex1(mk_RegexIn(var1, regex1), m); + expr_ref var2InRegex2(mk_RegexIn(var2, regex2), m); + expr_ref_vector items(m); + items.push_back(var1InRegex1); + items.push_back(var2InRegex2); + items.push_back(ctx.mk_eq_atom(ex, orVar)); + assert_axiom(mk_and(items)); + } else if (u.re.is_star(regex)) { + // slightly more complex due to the unrolling step. + expr_ref regex1(regex->get_arg(0), m); + expr_ref unrollCount(mk_unroll_bound_var(), m); + expr_ref unrollFunc(mk_unroll(regex1, unrollCount), m); + expr_ref_vector items(m); + items.push_back(ctx.mk_eq_atom(ex, ctx.mk_eq_atom(str, unrollFunc))); + items.push_back(ctx.mk_eq_atom(ctx.mk_eq_atom(unrollCount, mk_int(0)), ctx.mk_eq_atom(unrollFunc, mk_string("")))); + expr_ref finalAxiom(mk_and(items), m); + SASSERT(finalAxiom); + assert_axiom(finalAxiom); + } else if (u.re.is_range(regex)) { + // (re.range "A" "Z") unfolds to (re.union "A" "B" ... "Z"); + // we rewrite to expr IFF (str = "A" or str = "B" or ... or str = "Z") + expr_ref lo(regex->get_arg(0), m); + expr_ref hi(regex->get_arg(1), m); + zstring str_lo, str_hi; + SASSERT(u.str.is_string(lo)); + SASSERT(u.str.is_string(hi)); + u.str.is_string(lo, str_lo); + u.str.is_string(hi, str_hi); + SASSERT(str_lo.length() == 1); + SASSERT(str_hi.length() == 1); + unsigned int c1 = str_lo[0]; + unsigned int c2 = str_hi[0]; + if (c1 > c2) { + // exchange + unsigned int tmp = c1; + c1 = c2; + c2 = tmp; + } + expr_ref_vector range_cases(m); + for (unsigned int ch = c1; ch <= c2; ++ch) { + zstring s_ch(ch); + expr_ref rhs(ctx.mk_eq_atom(str, u.str.mk_string(s_ch)), m); + range_cases.push_back(rhs); + } + expr_ref rhs(mk_or(range_cases), m); + expr_ref finalAxiom(m.mk_iff(ex, rhs), m); + SASSERT(finalAxiom); + assert_axiom(finalAxiom); } else { - return false; + TRACE("str", tout << "ERROR: unknown regex expression " << mk_pp(regex, m) << "!" << std::endl;); + NOT_IMPLEMENTED_YET(); } } - TRACE("str", tout << "warning: unrecognized concat case" << std::endl;); - return false; -} + void theory_str::attach_new_th_var(enode * n) { + context & ctx = get_context(); + theory_var v = mk_var(n); + ctx.attach_th_var(n, this, v); + TRACE("str", tout << "new theory var: " << mk_ismt2_pp(n->get_owner(), get_manager()) << " := v#" << v << std::endl;); + } -/************************************************************* - * Type 1: concat(x, y) = concat(m, n) - * x, y, m and n all variables - *************************************************************/ -bool theory_str::is_concat_eq_type1(expr * concatAst1, expr * concatAst2) { - expr * x = to_app(concatAst1)->get_arg(0); - expr * y = to_app(concatAst1)->get_arg(1); - expr * m = to_app(concatAst2)->get_arg(0); - expr * n = to_app(concatAst2)->get_arg(1); + void theory_str::reset_eh() { + TRACE("str", tout << "resetting" << std::endl;); + m_trail_stack.reset(); - if (!u.str.is_string(x) && !u.str.is_string(y) && !u.str.is_string(m) && !u.str.is_string(n)) { + m_basicstr_axiom_todo.reset(); + m_str_eq_todo.reset(); + m_concat_axiom_todo.reset(); + pop_scope_eh(get_context().get_scope_level()); + } + + /* + * Check equality among equivalence class members of LHS and RHS + * to discover an incorrect LHS == RHS. + * For example, if we have y2 == "str3" + * and the equivalence classes are + * { y2, (Concat ce m2) } + * { "str3", (Concat abc x2) } + * then y2 can't be equal to "str3". + * Then add an assertion: (y2 == (Concat ce m2)) AND ("str3" == (Concat abc x2)) -> (y2 != "str3") + */ + bool theory_str::new_eq_check(expr * lhs, expr * rhs) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + // skip this check if we defer consistency checking, as we can do it for every EQC in final check + if (!opt_DeferEQCConsistencyCheck) { + check_concat_len_in_eqc(lhs); + check_concat_len_in_eqc(rhs); + } + + // Now we iterate over all pairs of terms across both EQCs + // and check whether we can show that any pair of distinct terms + // cannot possibly be equal. + // If that's the case, we assert an axiom to that effect and stop. + + expr * eqc_nn1 = lhs; + do { + expr * eqc_nn2 = rhs; + do { + TRACE("str", tout << "checking whether " << mk_pp(eqc_nn1, m) << " and " << mk_pp(eqc_nn2, m) << " can be equal" << std::endl;); + // inconsistency check: value + if (!can_two_nodes_eq(eqc_nn1, eqc_nn2)) { + TRACE("str", tout << "inconsistency detected: " << mk_pp(eqc_nn1, m) << " cannot be equal to " << mk_pp(eqc_nn2, m) << std::endl;); + expr_ref to_assert(m.mk_not(ctx.mk_eq_atom(eqc_nn1, eqc_nn2)), m); + assert_axiom(to_assert); + // this shouldn't use the integer theory at all, so we don't allow the option of quick-return + return false; + } + if (!check_length_consistency(eqc_nn1, eqc_nn2)) { + TRACE("str", tout << "inconsistency detected: " << mk_pp(eqc_nn1, m) << " and " << mk_pp(eqc_nn2, m) << " have inconsistent lengths" << std::endl;); + if (opt_NoQuickReturn_IntegerTheory){ + TRACE("str", tout << "continuing in new_eq_check() due to opt_NoQuickReturn_IntegerTheory" << std::endl;); + } else { + return false; + } + } + eqc_nn2 = get_eqc_next(eqc_nn2); + } while (eqc_nn2 != rhs); + eqc_nn1 = get_eqc_next(eqc_nn1); + } while (eqc_nn1 != lhs); + + if (!contains_map.empty()) { + check_contain_in_new_eq(lhs, rhs); + } + + if (!regex_in_bool_map.empty()) { + TRACE("str", tout << "checking regex consistency" << std::endl;); + check_regex_in(lhs, rhs); + } + + // okay, all checks here passed return true; - } else { + } + + // support for user_smt_theory-style EQC handling + + app * theory_str::get_ast(theory_var i) { + return get_enode(i)->get_owner(); + } + + theory_var theory_str::get_var(expr * n) const { + if (!is_app(n)) { + return null_theory_var; + } + context & ctx = get_context(); + if (ctx.e_internalized(to_app(n))) { + enode * e = ctx.get_enode(to_app(n)); + return e->get_th_var(get_id()); + } + return null_theory_var; + } + + // simulate Z3_theory_get_eqc_next() + expr * theory_str::get_eqc_next(expr * n) { + theory_var v = get_var(n); + if (v != null_theory_var) { + theory_var r = m_find.next(v); + return get_ast(r); + } + return n; + } + + void theory_str::group_terms_by_eqc(expr * n, std::set & concats, std::set & vars, std::set & consts) { + context & ctx = get_context(); + expr * eqcNode = n; + do { + app * ast = to_app(eqcNode); + if (u.str.is_concat(ast)) { + expr * simConcat = simplify_concat(ast); + if (simConcat != ast) { + if (u.str.is_concat(to_app(simConcat))) { + concats.insert(simConcat); + } else { + if (u.str.is_string(simConcat)) { + consts.insert(simConcat); + } else { + vars.insert(simConcat); + } + } + } else { + concats.insert(simConcat); + } + } else if (u.str.is_string(ast)) { + consts.insert(ast); + } else { + vars.insert(ast); + } + eqcNode = get_eqc_next(eqcNode); + } while (eqcNode != n); + } + + void theory_str::get_nodes_in_concat(expr * node, ptr_vector & nodeList) { + app * a_node = to_app(node); + if (!u.str.is_concat(a_node)) { + nodeList.push_back(node); + return; + } else { + SASSERT(a_node->get_num_args() == 2); + expr * leftArg = a_node->get_arg(0); + expr * rightArg = a_node->get_arg(1); + get_nodes_in_concat(leftArg, nodeList); + get_nodes_in_concat(rightArg, nodeList); + } + } + + // previously Concat() in strTheory.cpp + // Evaluates the concatenation (n1 . n2) with respect to + // the current equivalence classes of n1 and n2. + // Returns a constant string expression representing this concatenation + // if one can be determined, or NULL if this is not possible. + expr * theory_str::eval_concat(expr * n1, expr * n2) { + bool n1HasEqcValue = false; + bool n2HasEqcValue = false; + expr * v1 = get_eqc_value(n1, n1HasEqcValue); + expr * v2 = get_eqc_value(n2, n2HasEqcValue); + if (n1HasEqcValue && n2HasEqcValue) { + zstring n1_str, n2_str; + u.str.is_string(v1, n1_str); + u.str.is_string(v2, n2_str); + zstring result = n1_str + n2_str; + return mk_string(result); + } else if (n1HasEqcValue && !n2HasEqcValue) { + zstring v1_str; + u.str.is_string(v1, v1_str); + if (v1_str.empty()) { + return n2; + } + } else if (n2HasEqcValue && !n1HasEqcValue) { + zstring v2_str; + u.str.is_string(v2, v2_str); + if (v2_str.empty()) { + return n1; + } + } + // give up + return NULL; + } + + static inline std::string rational_to_string_if_exists(const rational & x, bool x_exists) { + if (x_exists) { + return x.to_string(); + } else { + return "?"; + } + } + + /* + * The inputs: + * ~ nn: non const node + * ~ eq_str: the equivalent constant string of nn + * Iterate the parent of all eqc nodes of nn, looking for: + * ~ concat node + * to see whether some concat nodes can be simplified. + */ + void theory_str::simplify_parent(expr * nn, expr * eq_str) { + ast_manager & m = get_manager(); + context & ctx = get_context(); + + TRACE("str", tout << "simplifying parents of " << mk_ismt2_pp(nn, m) + << " with respect to " << mk_ismt2_pp(eq_str, m) << std::endl;); + + ctx.internalize(nn, false); + + zstring eq_strValue; + u.str.is_string(eq_str, eq_strValue); + expr * n_eqNode = nn; + do { + enode * n_eq_enode = ctx.get_enode(n_eqNode); + TRACE("str", tout << "considering all parents of " << mk_ismt2_pp(n_eqNode, m) << std::endl + << "associated n_eq_enode has " << n_eq_enode->get_num_parents() << " parents" << std::endl;); + + // the goal of this next bit is to avoid dereferencing a bogus e_parent in the following loop. + // what I imagine is causing this bug is that, for example, we examine some parent, we add an axiom that involves it, + // and the parent_it iterator becomes invalidated, because we indirectly modified the container that we're iterating over. + + enode_vector current_parents; + for (enode_vector::const_iterator parent_it = n_eq_enode->begin_parents(); parent_it != n_eq_enode->end_parents(); parent_it++) { + current_parents.insert(*parent_it); + } + + for (enode_vector::iterator parent_it = current_parents.begin(); parent_it != current_parents.end(); ++parent_it) { + enode * e_parent = *parent_it; + SASSERT(e_parent != NULL); + + app * a_parent = e_parent->get_owner(); + TRACE("str", tout << "considering parent " << mk_ismt2_pp(a_parent, m) << std::endl;); + + if (u.str.is_concat(a_parent)) { + expr * arg0 = a_parent->get_arg(0); + expr * arg1 = a_parent->get_arg(1); + + rational parentLen; + bool parentLen_exists = get_len_value(a_parent, parentLen); + + if (arg0 == n_eq_enode->get_owner()) { + rational arg0Len, arg1Len; + bool arg0Len_exists = get_len_value(eq_str, arg0Len); + bool arg1Len_exists = get_len_value(arg1, arg1Len); + + TRACE("str", + tout << "simplify_parent #1:" << std::endl + << "* parent = " << mk_ismt2_pp(a_parent, m) << std::endl + << "* |parent| = " << rational_to_string_if_exists(parentLen, parentLen_exists) << std::endl + << "* |arg0| = " << rational_to_string_if_exists(arg0Len, arg0Len_exists) << std::endl + << "* |arg1| = " << rational_to_string_if_exists(arg1Len, arg1Len_exists) << std::endl; + ); + + if (parentLen_exists && !arg1Len_exists) { + TRACE("str", tout << "make up len for arg1" << std::endl;); + expr_ref implyL11(m.mk_and(ctx.mk_eq_atom(mk_strlen(a_parent), mk_int(parentLen)), + ctx.mk_eq_atom(mk_strlen(arg0), mk_int(arg0Len))), m); + rational makeUpLenArg1 = parentLen - arg0Len; + if (makeUpLenArg1.is_nonneg()) { + expr_ref implyR11(ctx.mk_eq_atom(mk_strlen(arg1), mk_int(makeUpLenArg1)), m); + assert_implication(implyL11, implyR11); + } else { + expr_ref neg(m.mk_not(implyL11), m); + assert_axiom(neg); + } + } + + // (Concat n_eqNode arg1) /\ arg1 has eq const + + expr * concatResult = eval_concat(eq_str, arg1); + if (concatResult != NULL) { + bool arg1HasEqcValue = false; + expr * arg1Value = get_eqc_value(arg1, arg1HasEqcValue); + expr_ref implyL(m); + if (arg1 != arg1Value) { + expr_ref eq_ast1(m); + eq_ast1 = ctx.mk_eq_atom(n_eqNode, eq_str); + SASSERT(eq_ast1); + + expr_ref eq_ast2(m); + eq_ast2 = ctx.mk_eq_atom(arg1, arg1Value); + SASSERT(eq_ast2); + implyL = m.mk_and(eq_ast1, eq_ast2); + } else { + implyL = ctx.mk_eq_atom(n_eqNode, eq_str); + } + + + if (!in_same_eqc(a_parent, concatResult)) { + expr_ref implyR(m); + implyR = ctx.mk_eq_atom(a_parent, concatResult); + SASSERT(implyR); + + assert_implication(implyL, implyR); + } + } else if (u.str.is_concat(to_app(n_eqNode))) { + expr_ref simpleConcat(m); + simpleConcat = mk_concat(eq_str, arg1); + if (!in_same_eqc(a_parent, simpleConcat)) { + expr_ref implyL(m); + implyL = ctx.mk_eq_atom(n_eqNode, eq_str); + SASSERT(implyL); + + expr_ref implyR(m); + implyR = ctx.mk_eq_atom(a_parent, simpleConcat); + SASSERT(implyR); + assert_implication(implyL, implyR); + } + } + } // if (arg0 == n_eq_enode->get_owner()) + + if (arg1 == n_eq_enode->get_owner()) { + rational arg0Len, arg1Len; + bool arg0Len_exists = get_len_value(arg0, arg0Len); + bool arg1Len_exists = get_len_value(eq_str, arg1Len); + + TRACE("str", + tout << "simplify_parent #2:" << std::endl + << "* parent = " << mk_ismt2_pp(a_parent, m) << std::endl + << "* |parent| = " << rational_to_string_if_exists(parentLen, parentLen_exists) << std::endl + << "* |arg0| = " << rational_to_string_if_exists(arg0Len, arg0Len_exists) << std::endl + << "* |arg1| = " << rational_to_string_if_exists(arg1Len, arg1Len_exists) << std::endl; + ); + if (parentLen_exists && !arg0Len_exists) { + TRACE("str", tout << "make up len for arg0" << std::endl;); + expr_ref implyL11(m.mk_and(ctx.mk_eq_atom(mk_strlen(a_parent), mk_int(parentLen)), + ctx.mk_eq_atom(mk_strlen(arg1), mk_int(arg1Len))), m); + rational makeUpLenArg0 = parentLen - arg1Len; + if (makeUpLenArg0.is_nonneg()) { + expr_ref implyR11(ctx.mk_eq_atom(mk_strlen(arg0), mk_int(makeUpLenArg0)), m); + assert_implication(implyL11, implyR11); + } else { + expr_ref neg(m.mk_not(implyL11), m); + assert_axiom(neg); + } + } + + // (Concat arg0 n_eqNode) /\ arg0 has eq const + + expr * concatResult = eval_concat(arg0, eq_str); + if (concatResult != NULL) { + bool arg0HasEqcValue = false; + expr * arg0Value = get_eqc_value(arg0, arg0HasEqcValue); + expr_ref implyL(m); + if (arg0 != arg0Value) { + expr_ref eq_ast1(m); + eq_ast1 = ctx.mk_eq_atom(n_eqNode, eq_str); + SASSERT(eq_ast1); + expr_ref eq_ast2(m); + eq_ast2 = ctx.mk_eq_atom(arg0, arg0Value); + SASSERT(eq_ast2); + + implyL = m.mk_and(eq_ast1, eq_ast2); + } else { + implyL = ctx.mk_eq_atom(n_eqNode, eq_str); + } + + if (!in_same_eqc(a_parent, concatResult)) { + expr_ref implyR(m); + implyR = ctx.mk_eq_atom(a_parent, concatResult); + SASSERT(implyR); + + assert_implication(implyL, implyR); + } + } else if (u.str.is_concat(to_app(n_eqNode))) { + expr_ref simpleConcat(m); + simpleConcat = mk_concat(arg0, eq_str); + if (!in_same_eqc(a_parent, simpleConcat)) { + expr_ref implyL(m); + implyL = ctx.mk_eq_atom(n_eqNode, eq_str); + SASSERT(implyL); + + expr_ref implyR(m); + implyR = ctx.mk_eq_atom(a_parent, simpleConcat); + SASSERT(implyR); + assert_implication(implyL, implyR); + } + } + } // if (arg1 == n_eq_enode->get_owner + + + //--------------------------------------------------------- + // Case (2-1) begin: (Concat n_eqNode (Concat str var)) + if (arg0 == n_eqNode && u.str.is_concat(to_app(arg1))) { + app * a_arg1 = to_app(arg1); + TRACE("str", tout << "simplify_parent #3" << std::endl;); + expr * r_concat_arg0 = a_arg1->get_arg(0); + if (u.str.is_string(r_concat_arg0)) { + expr * combined_str = eval_concat(eq_str, r_concat_arg0); + SASSERT(combined_str); + expr * r_concat_arg1 = a_arg1->get_arg(1); + expr_ref implyL(m); + implyL = ctx.mk_eq_atom(n_eqNode, eq_str); + expr * simplifiedAst = mk_concat(combined_str, r_concat_arg1); + if (!in_same_eqc(a_parent, simplifiedAst)) { + expr_ref implyR(m); + implyR = ctx.mk_eq_atom(a_parent, simplifiedAst); + assert_implication(implyL, implyR); + } + } + } + // Case (2-1) end: (Concat n_eqNode (Concat str var)) + //--------------------------------------------------------- + + + //--------------------------------------------------------- + // Case (2-2) begin: (Concat (Concat var str) n_eqNode) + if (u.str.is_concat(to_app(arg0)) && arg1 == n_eqNode) { + app * a_arg0 = to_app(arg0); + TRACE("str", tout << "simplify_parent #4" << std::endl;); + expr * l_concat_arg1 = a_arg0->get_arg(1); + if (u.str.is_string(l_concat_arg1)) { + expr * combined_str = eval_concat(l_concat_arg1, eq_str); + SASSERT(combined_str); + expr * l_concat_arg0 = a_arg0->get_arg(0); + expr_ref implyL(m); + implyL = ctx.mk_eq_atom(n_eqNode, eq_str); + expr * simplifiedAst = mk_concat(l_concat_arg0, combined_str); + if (!in_same_eqc(a_parent, simplifiedAst)) { + expr_ref implyR(m); + implyR = ctx.mk_eq_atom(a_parent, simplifiedAst); + assert_implication(implyL, implyR); + } + } + } + // Case (2-2) end: (Concat (Concat var str) n_eqNode) + //--------------------------------------------------------- + + // Have to look up one more layer: if the parent of the concat is another concat + //------------------------------------------------- + // Case (3-1) begin: (Concat (Concat var n_eqNode) str ) + if (arg1 == n_eqNode) { + for (enode_vector::iterator concat_parent_it = e_parent->begin_parents(); + concat_parent_it != e_parent->end_parents(); concat_parent_it++) { + enode * e_concat_parent = *concat_parent_it; + app * concat_parent = e_concat_parent->get_owner(); + if (u.str.is_concat(concat_parent)) { + expr * concat_parent_arg0 = concat_parent->get_arg(0); + expr * concat_parent_arg1 = concat_parent->get_arg(1); + if (concat_parent_arg0 == a_parent && u.str.is_string(concat_parent_arg1)) { + TRACE("str", tout << "simplify_parent #5" << std::endl;); + expr * combinedStr = eval_concat(eq_str, concat_parent_arg1); + SASSERT(combinedStr); + expr_ref implyL(m); + implyL = ctx.mk_eq_atom(n_eqNode, eq_str); + expr * simplifiedAst = mk_concat(arg0, combinedStr); + if (!in_same_eqc(concat_parent, simplifiedAst)) { + expr_ref implyR(m); + implyR = ctx.mk_eq_atom(concat_parent, simplifiedAst); + assert_implication(implyL, implyR); + } + } + } + } + } + // Case (3-1) end: (Concat (Concat var n_eqNode) str ) + // Case (3-2) begin: (Concat str (Concat n_eqNode var) ) + if (arg0 == n_eqNode) { + for (enode_vector::iterator concat_parent_it = e_parent->begin_parents(); + concat_parent_it != e_parent->end_parents(); concat_parent_it++) { + enode * e_concat_parent = *concat_parent_it; + app * concat_parent = e_concat_parent->get_owner(); + if (u.str.is_concat(concat_parent)) { + expr * concat_parent_arg0 = concat_parent->get_arg(0); + expr * concat_parent_arg1 = concat_parent->get_arg(1); + if (concat_parent_arg1 == a_parent && u.str.is_string(concat_parent_arg0)) { + TRACE("str", tout << "simplify_parent #6" << std::endl;); + expr * combinedStr = eval_concat(concat_parent_arg0, eq_str); + SASSERT(combinedStr); + expr_ref implyL(m); + implyL = ctx.mk_eq_atom(n_eqNode, eq_str); + expr * simplifiedAst = mk_concat(combinedStr, arg1); + if (!in_same_eqc(concat_parent, simplifiedAst)) { + expr_ref implyR(m); + implyR = ctx.mk_eq_atom(concat_parent, simplifiedAst); + assert_implication(implyL, implyR); + } + } + } + } + } + // Case (3-2) end: (Concat str (Concat n_eqNode var) ) + } // if is_concat(a_parent) + } // for parent_it : n_eq_enode->begin_parents() + + + // check next EQC member + n_eqNode = get_eqc_next(n_eqNode); + } while (n_eqNode != nn); + } + + expr * theory_str::simplify_concat(expr * node) { + ast_manager & m = get_manager(); + context & ctx = get_context(); + std::map resolvedMap; + ptr_vector argVec; + get_nodes_in_concat(node, argVec); + + for (unsigned i = 0; i < argVec.size(); ++i) { + bool vArgHasEqcValue = false; + expr * vArg = get_eqc_value(argVec[i], vArgHasEqcValue); + if (vArg != argVec[i]) { + resolvedMap[argVec[i]] = vArg; + } + } + + if (resolvedMap.size() == 0) { + // no simplification possible + return node; + } else { + expr * resultAst = mk_string(""); + for (unsigned i = 0; i < argVec.size(); ++i) { + bool vArgHasEqcValue = false; + expr * vArg = get_eqc_value(argVec[i], vArgHasEqcValue); + resultAst = mk_concat(resultAst, vArg); + } + TRACE("str", tout << mk_ismt2_pp(node, m) << " is simplified to " << mk_ismt2_pp(resultAst, m) << std::endl;); + + if (in_same_eqc(node, resultAst)) { + TRACE("str", tout << "SKIP: both concats are already in the same equivalence class" << std::endl;); + } else { + expr_ref_vector items(m); + int pos = 0; + std::map::iterator itor = resolvedMap.begin(); + for (; itor != resolvedMap.end(); ++itor) { + items.push_back(ctx.mk_eq_atom(itor->first, itor->second)); + pos += 1; + } + expr_ref premise(mk_and(items), m); + expr_ref conclusion(ctx.mk_eq_atom(node, resultAst), m); + assert_implication(premise, conclusion); + } + return resultAst; + } + + } + + // Modified signature of Z3str2's inferLenConcat(). + // Returns true iff nLen can be inferred by this method + // (i.e. the equivalent of a len_exists flag in get_len_value()). + + bool theory_str::infer_len_concat(expr * n, rational & nLen) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + expr * arg0 = to_app(n)->get_arg(0); + expr * arg1 = to_app(n)->get_arg(1); + + rational arg0_len, arg1_len; + bool arg0_len_exists = get_len_value(arg0, arg0_len); + bool arg1_len_exists = get_len_value(arg1, arg1_len); + rational tmp_len; + bool nLen_exists = get_len_value(n, tmp_len); + + if (arg0_len_exists && arg1_len_exists && !nLen_exists) { + expr_ref_vector l_items(m); + // if (mk_strlen(arg0) != mk_int(arg0_len)) { + { + l_items.push_back(ctx.mk_eq_atom(mk_strlen(arg0), mk_int(arg0_len))); + } + + // if (mk_strlen(arg1) != mk_int(arg1_len)) { + { + l_items.push_back(ctx.mk_eq_atom(mk_strlen(arg1), mk_int(arg1_len))); + } + + expr_ref axl(m.mk_and(l_items.size(), l_items.c_ptr()), m); + rational nnLen = arg0_len + arg1_len; + expr_ref axr(ctx.mk_eq_atom(mk_strlen(n), mk_int(nnLen)), m); + TRACE("str", tout << "inferred (Length " << mk_pp(n, m) << ") = " << nnLen << std::endl;); + assert_implication(axl, axr); + nLen = nnLen; + return true; + } else { + return false; + } + } + + void theory_str::infer_len_concat_arg(expr * n, rational len) { + if (len.is_neg()) { + return; + } + + context & ctx = get_context(); + ast_manager & m = get_manager(); + + expr * arg0 = to_app(n)->get_arg(0); + expr * arg1 = to_app(n)->get_arg(1); + rational arg0_len, arg1_len; + bool arg0_len_exists = get_len_value(arg0, arg0_len); + bool arg1_len_exists = get_len_value(arg1, arg1_len); + + expr_ref_vector l_items(m); + expr_ref axr(m); + axr.reset(); + + // if (mk_length(t, n) != mk_int(ctx, len)) { + { + l_items.push_back(ctx.mk_eq_atom(mk_strlen(n), mk_int(len))); + } + + if (!arg0_len_exists && arg1_len_exists) { + //if (mk_length(t, arg1) != mk_int(ctx, arg1_len)) { + { + l_items.push_back(ctx.mk_eq_atom(mk_strlen(arg1), mk_int(arg1_len))); + } + rational arg0Len = len - arg1_len; + if (arg0Len.is_nonneg()) { + axr = ctx.mk_eq_atom(mk_strlen(arg0), mk_int(arg0Len)); + } else { + // could negate + } + } else if (arg0_len_exists && !arg1_len_exists) { + //if (mk_length(t, arg0) != mk_int(ctx, arg0_len)) { + { + l_items.push_back(ctx.mk_eq_atom(mk_strlen(arg0), mk_int(arg0_len))); + } + rational arg1Len = len - arg0_len; + if (arg1Len.is_nonneg()) { + axr = ctx.mk_eq_atom(mk_strlen(arg1), mk_int(arg1Len)); + } else { + // could negate + } + } else { + + } + + if (axr) { + expr_ref axl(m.mk_and(l_items.size(), l_items.c_ptr()), m); + assert_implication(axl, axr); + } + } + + void theory_str::infer_len_concat_equality(expr * nn1, expr * nn2) { + rational nnLen; + bool nnLen_exists = get_len_value(nn1, nnLen); + if (!nnLen_exists) { + nnLen_exists = get_len_value(nn2, nnLen); + } + + // case 1: + // Known: a1_arg0 and a1_arg1 + // Unknown: nn1 + + if (u.str.is_concat(to_app(nn1))) { + rational nn1ConcatLen; + bool nn1ConcatLen_exists = infer_len_concat(nn1, nn1ConcatLen); + if (nnLen_exists && nn1ConcatLen_exists) { + nnLen = nn1ConcatLen; + } + } + + // case 2: + // Known: a1_arg0 and a1_arg1 + // Unknown: nn1 + + if (u.str.is_concat(to_app(nn2))) { + rational nn2ConcatLen; + bool nn2ConcatLen_exists = infer_len_concat(nn2, nn2ConcatLen); + if (nnLen_exists && nn2ConcatLen_exists) { + nnLen = nn2ConcatLen; + } + } + + if (nnLen_exists) { + if (u.str.is_concat(to_app(nn1))) { + infer_len_concat_arg(nn1, nnLen); + } + if (u.str.is_concat(to_app(nn2))) { + infer_len_concat_arg(nn2, nnLen); + } + } + + /* + if (isConcatFunc(t, nn2)) { + int nn2ConcatLen = inferLenConcat(t, nn2); + if (nnLen == -1 && nn2ConcatLen != -1) + nnLen = nn2ConcatLen; + } + + if (nnLen != -1) { + if (isConcatFunc(t, nn1)) { + inferLenConcatArg(t, nn1, nnLen); + } + if (isConcatFunc(t, nn2)) { + inferLenConcatArg(t, nn2, nnLen); + } + } + */ + } + + void theory_str::add_theory_aware_branching_info(expr * term, double priority, lbool phase) { + context & ctx = get_context(); + ctx.internalize(term, false); + bool_var v = ctx.get_bool_var(term); + ctx.add_theory_aware_branching_info(v, priority, phase); + } + + void theory_str::generate_mutual_exclusion(expr_ref_vector & terms) { + context & ctx = get_context(); + // pull each literal out of the arrangement disjunction + literal_vector ls; + for (unsigned i = 0; i < terms.size(); ++i) { + expr * e = terms.get(i); + literal l = ctx.get_literal(e); + ls.push_back(l); + } + ctx.mk_th_case_split(ls.size(), ls.c_ptr()); + } + + void theory_str::print_cut_var(expr * node, std::ofstream & xout) { + ast_manager & m = get_manager(); + xout << "Cut info of " << mk_pp(node, m) << std::endl; + if (cut_var_map.contains(node)) { + if (!cut_var_map[node].empty()) { + xout << "[" << cut_var_map[node].top()->level << "] "; + std::map::iterator itor = cut_var_map[node].top()->vars.begin(); + for (; itor != cut_var_map[node].top()->vars.end(); ++itor) { + xout << mk_pp(itor->first, m) << ", "; + } + xout << std::endl; + } + } + } + + /* + * Handle two equivalent Concats. + */ + void theory_str::simplify_concat_equality(expr * nn1, expr * nn2) { + ast_manager & m = get_manager(); + context & ctx = get_context(); + + app * a_nn1 = to_app(nn1); + SASSERT(a_nn1->get_num_args() == 2); + app * a_nn2 = to_app(nn2); + SASSERT(a_nn2->get_num_args() == 2); + + expr * a1_arg0 = a_nn1->get_arg(0); + expr * a1_arg1 = a_nn1->get_arg(1); + expr * a2_arg0 = a_nn2->get_arg(0); + expr * a2_arg1 = a_nn2->get_arg(1); + + rational a1_arg0_len, a1_arg1_len, a2_arg0_len, a2_arg1_len; + + bool a1_arg0_len_exists = get_len_value(a1_arg0, a1_arg0_len); + bool a1_arg1_len_exists = get_len_value(a1_arg1, a1_arg1_len); + bool a2_arg0_len_exists = get_len_value(a2_arg0, a2_arg0_len); + bool a2_arg1_len_exists = get_len_value(a2_arg1, a2_arg1_len); + + TRACE("str", tout << "nn1 = " << mk_ismt2_pp(nn1, m) << std::endl + << "nn2 = " << mk_ismt2_pp(nn2, m) << std::endl;); + + TRACE("str", tout + << "len(" << mk_pp(a1_arg0, m) << ") = " << (a1_arg0_len_exists ? a1_arg0_len.to_string() : "?") << std::endl + << "len(" << mk_pp(a1_arg1, m) << ") = " << (a1_arg1_len_exists ? a1_arg1_len.to_string() : "?") << std::endl + << "len(" << mk_pp(a2_arg0, m) << ") = " << (a2_arg0_len_exists ? a2_arg0_len.to_string() : "?") << std::endl + << "len(" << mk_pp(a2_arg1, m) << ") = " << (a2_arg1_len_exists ? a2_arg1_len.to_string() : "?") << std::endl + << std::endl;); + + infer_len_concat_equality(nn1, nn2); + + if (a1_arg0 == a2_arg0) { + if (!in_same_eqc(a1_arg1, a2_arg1)) { + expr_ref premise(ctx.mk_eq_atom(nn1, nn2), m); + expr_ref eq1(ctx.mk_eq_atom(a1_arg1, a2_arg1), m); + expr_ref eq2(ctx.mk_eq_atom(mk_strlen(a1_arg1), mk_strlen(a2_arg1)), m); + expr_ref conclusion(m.mk_and(eq1, eq2), m); + assert_implication(premise, conclusion); + } + TRACE("str", tout << "SKIP: a1_arg0 == a2_arg0" << std::endl;); + return; + } + + if (a1_arg1 == a2_arg1) { + if (!in_same_eqc(a1_arg0, a2_arg0)) { + expr_ref premise(ctx.mk_eq_atom(nn1, nn2), m); + expr_ref eq1(ctx.mk_eq_atom(a1_arg0, a2_arg0), m); + expr_ref eq2(ctx.mk_eq_atom(mk_strlen(a1_arg0), mk_strlen(a2_arg0)), m); + expr_ref conclusion(m.mk_and(eq1, eq2), m); + assert_implication(premise, conclusion); + } + TRACE("str", tout << "SKIP: a1_arg1 == a2_arg1" << std::endl;); + return; + } + + // quick path + + if (in_same_eqc(a1_arg0, a2_arg0)) { + if (in_same_eqc(a1_arg1, a2_arg1)) { + TRACE("str", tout << "SKIP: a1_arg0 =~ a2_arg0 and a1_arg1 =~ a2_arg1" << std::endl;); + return; + } else { + TRACE("str", tout << "quick path 1-1: a1_arg0 =~ a2_arg0" << std::endl;); + expr_ref premise(m.mk_and(ctx.mk_eq_atom(nn1, nn2), ctx.mk_eq_atom(a1_arg0, a2_arg0)), m); + expr_ref conclusion(m.mk_and(ctx.mk_eq_atom(a1_arg1, a2_arg1), ctx.mk_eq_atom(mk_strlen(a1_arg1), mk_strlen(a2_arg1))), m); + assert_implication(premise, conclusion); + return; + } + } else { + if (in_same_eqc(a1_arg1, a2_arg1)) { + TRACE("str", tout << "quick path 1-2: a1_arg1 =~ a2_arg1" << std::endl;); + expr_ref premise(m.mk_and(ctx.mk_eq_atom(nn1, nn2), ctx.mk_eq_atom(a1_arg1, a2_arg1)), m); + expr_ref conclusion(m.mk_and(ctx.mk_eq_atom(a1_arg0, a2_arg0), ctx.mk_eq_atom(mk_strlen(a1_arg0), mk_strlen(a2_arg0))), m); + assert_implication(premise, conclusion); + return; + } + } + + // quick path 2-1 + if (a1_arg0_len_exists && a2_arg0_len_exists && a1_arg0_len == a2_arg0_len) { + if (!in_same_eqc(a1_arg0, a2_arg0)) { + TRACE("str", tout << "quick path 2-1: len(nn1.arg0) == len(nn2.arg0)" << std::endl;); + expr_ref ax_l1(ctx.mk_eq_atom(nn1, nn2), m); + expr_ref ax_l2(ctx.mk_eq_atom(mk_strlen(a1_arg0), mk_strlen(a2_arg0)), m); + expr_ref ax_r1(ctx.mk_eq_atom(a1_arg0, a2_arg0), m); + expr_ref ax_r2(ctx.mk_eq_atom(a1_arg1, a2_arg1), m); + + expr_ref premise(m.mk_and(ax_l1, ax_l2), m); + expr_ref conclusion(m.mk_and(ax_r1, ax_r2), m); + + assert_implication(premise, conclusion); + + if (opt_NoQuickReturn_IntegerTheory) { + TRACE("str", tout << "bypassing quick return from the end of this case" << std::endl;); + } else { + return; + } + } + } + + if (a1_arg1_len_exists && a2_arg1_len_exists && a1_arg1_len == a2_arg1_len) { + if (!in_same_eqc(a1_arg1, a2_arg1)) { + TRACE("str", tout << "quick path 2-2: len(nn1.arg1) == len(nn2.arg1)" << std::endl;); + expr_ref ax_l1(ctx.mk_eq_atom(nn1, nn2), m); + expr_ref ax_l2(ctx.mk_eq_atom(mk_strlen(a1_arg1), mk_strlen(a2_arg1)), m); + expr_ref ax_r1(ctx.mk_eq_atom(a1_arg0, a2_arg0), m); + expr_ref ax_r2(ctx.mk_eq_atom(a1_arg1, a2_arg1), m); + + expr_ref premise(m.mk_and(ax_l1, ax_l2), m); + expr_ref conclusion(m.mk_and(ax_r1, ax_r2), m); + + assert_implication(premise, conclusion); + if (opt_NoQuickReturn_IntegerTheory) { + TRACE("str", tout << "bypassing quick return from the end of this case" << std::endl;); + } else { + return; + } + } + } + + expr_ref new_nn1(simplify_concat(nn1), m); + expr_ref new_nn2(simplify_concat(nn2), m); + app * a_new_nn1 = to_app(new_nn1); + app * a_new_nn2 = to_app(new_nn2); + + TRACE("str", tout << "new_nn1 = " << mk_ismt2_pp(new_nn1, m) << std::endl + << "new_nn2 = " << mk_ismt2_pp(new_nn2, m) << std::endl;); + + if (new_nn1 == new_nn2) { + TRACE("str", tout << "equal concats, return" << std::endl;); + return; + } + + if (!can_two_nodes_eq(new_nn1, new_nn2)) { + expr_ref detected(m.mk_not(ctx.mk_eq_atom(new_nn1, new_nn2)), m); + TRACE("str", tout << "inconsistency detected: " << mk_ismt2_pp(detected, m) << std::endl;); + assert_axiom(detected); + return; + } + + // check whether new_nn1 and new_nn2 are still concats + + bool n1IsConcat = u.str.is_concat(a_new_nn1); + bool n2IsConcat = u.str.is_concat(a_new_nn2); + if (!n1IsConcat && n2IsConcat) { + TRACE("str", tout << "nn1_new is not a concat" << std::endl;); + if (u.str.is_string(a_new_nn1)) { + simplify_parent(new_nn2, new_nn1); + } + return; + } else if (n1IsConcat && !n2IsConcat) { + TRACE("str", tout << "nn2_new is not a concat" << std::endl;); + if (u.str.is_string(a_new_nn2)) { + simplify_parent(new_nn1, new_nn2); + } + return; + } else if (!n1IsConcat && !n2IsConcat) { + // normally this should never happen, because group_terms_by_eqc() should have pre-simplified + // as much as possible. however, we make a defensive check here just in case + TRACE("str", tout << "WARNING: nn1_new and nn2_new both simplify to non-concat terms" << std::endl;); + return; + } + + expr * v1_arg0 = a_new_nn1->get_arg(0); + expr * v1_arg1 = a_new_nn1->get_arg(1); + expr * v2_arg0 = a_new_nn2->get_arg(0); + expr * v2_arg1 = a_new_nn2->get_arg(1); + + if (!in_same_eqc(new_nn1, new_nn2) && (nn1 != new_nn1 || nn2 != new_nn2)) { + int ii4 = 0; + expr* item[3]; + if (nn1 != new_nn1) { + item[ii4++] = ctx.mk_eq_atom(nn1, new_nn1); + } + if (nn2 != new_nn2) { + item[ii4++] = ctx.mk_eq_atom(nn2, new_nn2); + } + item[ii4++] = ctx.mk_eq_atom(nn1, nn2); + expr_ref premise(m.mk_and(ii4, item), m); + expr_ref conclusion(ctx.mk_eq_atom(new_nn1, new_nn2), m); + assert_implication(premise, conclusion); + } + + // start to split both concats + check_and_init_cut_var(v1_arg0); + check_and_init_cut_var(v1_arg1); + check_and_init_cut_var(v2_arg0); + check_and_init_cut_var(v2_arg1); + + //************************************************************* + // case 1: concat(x, y) = concat(m, n) + //************************************************************* + if (is_concat_eq_type1(new_nn1, new_nn2)) { + process_concat_eq_type1(new_nn1, new_nn2); + return; + } + + //************************************************************* + // case 2: concat(x, y) = concat(m, "str") + //************************************************************* + if (is_concat_eq_type2(new_nn1, new_nn2)) { + process_concat_eq_type2(new_nn1, new_nn2); + return; + } + + //************************************************************* + // case 3: concat(x, y) = concat("str", n) + //************************************************************* + if (is_concat_eq_type3(new_nn1, new_nn2)) { + process_concat_eq_type3(new_nn1, new_nn2); + return; + } + + //************************************************************* + // case 4: concat("str1", y) = concat("str2", n) + //************************************************************* + if (is_concat_eq_type4(new_nn1, new_nn2)) { + process_concat_eq_type4(new_nn1, new_nn2); + return; + } + + //************************************************************* + // case 5: concat(x, "str1") = concat(m, "str2") + //************************************************************* + if (is_concat_eq_type5(new_nn1, new_nn2)) { + process_concat_eq_type5(new_nn1, new_nn2); + return; + } + //************************************************************* + // case 6: concat("str1", y) = concat(m, "str2") + //************************************************************* + if (is_concat_eq_type6(new_nn1, new_nn2)) { + process_concat_eq_type6(new_nn1, new_nn2); + return; + } + + } + + /* + * Returns true if attempting to process a concat equality between lhs and rhs + * will result in overlapping variables (false otherwise). + */ + bool theory_str::will_result_in_overlap(expr * lhs, expr * rhs) { + ast_manager & m = get_manager(); + + expr_ref new_nn1(simplify_concat(lhs), m); + expr_ref new_nn2(simplify_concat(rhs), m); + app * a_new_nn1 = to_app(new_nn1); + app * a_new_nn2 = to_app(new_nn2); + + bool n1IsConcat = u.str.is_concat(a_new_nn1); + bool n2IsConcat = u.str.is_concat(a_new_nn2); + if (!n1IsConcat && !n2IsConcat) { + // we simplified both sides to non-concat expressions... + return false; + } + + expr * v1_arg0 = a_new_nn1->get_arg(0); + expr * v1_arg1 = a_new_nn1->get_arg(1); + expr * v2_arg0 = a_new_nn2->get_arg(0); + expr * v2_arg1 = a_new_nn2->get_arg(1); + + TRACE("str", tout << "checking whether " << mk_pp(new_nn1, m) << " and " << mk_pp(new_nn1, m) << " might overlap." << std::endl;); + + check_and_init_cut_var(v1_arg0); + check_and_init_cut_var(v1_arg1); + check_and_init_cut_var(v2_arg0); + check_and_init_cut_var(v2_arg1); + + //************************************************************* + // case 1: concat(x, y) = concat(m, n) + //************************************************************* + if (is_concat_eq_type1(new_nn1, new_nn2)) { + TRACE("str", tout << "Type 1 check." << std::endl;); + expr * x = to_app(new_nn1)->get_arg(0); + expr * y = to_app(new_nn1)->get_arg(1); + expr * m = to_app(new_nn2)->get_arg(0); + expr * n = to_app(new_nn2)->get_arg(1); + + if (has_self_cut(m, y)) { + TRACE("str", tout << "Possible overlap found" << std::endl; print_cut_var(m, tout); print_cut_var(y, tout);); + return true; + } else if (has_self_cut(x, n)) { + TRACE("str", tout << "Possible overlap found" << std::endl; print_cut_var(x, tout); print_cut_var(n, tout);); + return true; + } else { + return false; + } + } + + //************************************************************* + // case 2: concat(x, y) = concat(m, "str") + //************************************************************* + if (is_concat_eq_type2(new_nn1, new_nn2)) { + + expr * y = NULL; + expr * m = NULL; + expr * v1_arg0 = to_app(new_nn1)->get_arg(0); + expr * v1_arg1 = to_app(new_nn1)->get_arg(1); + expr * v2_arg0 = to_app(new_nn2)->get_arg(0); + expr * v2_arg1 = to_app(new_nn2)->get_arg(1); + + if (u.str.is_string(v1_arg1) && !u.str.is_string(v2_arg1)) { + m = v1_arg0; + y = v2_arg1; + } else { + m = v2_arg0; + y = v1_arg1; + } + + if (has_self_cut(m, y)) { + TRACE("str", tout << "Possible overlap found" << std::endl; print_cut_var(m, tout); print_cut_var(y, tout);); + return true; + } else { + return false; + } + } + + //************************************************************* + // case 3: concat(x, y) = concat("str", n) + //************************************************************* + if (is_concat_eq_type3(new_nn1, new_nn2)) { + expr * v1_arg0 = to_app(new_nn1)->get_arg(0); + expr * v1_arg1 = to_app(new_nn1)->get_arg(1); + expr * v2_arg0 = to_app(new_nn2)->get_arg(0); + expr * v2_arg1 = to_app(new_nn2)->get_arg(1); + + expr * x = NULL; + expr * n = NULL; + + if (u.str.is_string(v1_arg0) && !u.str.is_string(v2_arg0)) { + n = v1_arg1; + x = v2_arg0; + } else { + n = v2_arg1; + x = v1_arg0; + } + if (has_self_cut(x, n)) { + TRACE("str", tout << "Possible overlap found" << std::endl; print_cut_var(x, tout); print_cut_var(n, tout);); + return true; + } else { + return false; + } + } + + //************************************************************* + // case 4: concat("str1", y) = concat("str2", n) + //************************************************************* + if (is_concat_eq_type4(new_nn1, new_nn2)) { + // This case can never result in an overlap. + return false; + } + + //************************************************************* + // case 5: concat(x, "str1") = concat(m, "str2") + //************************************************************* + if (is_concat_eq_type5(new_nn1, new_nn2)) { + // This case can never result in an overlap. + return false; + } + //************************************************************* + // case 6: concat("str1", y) = concat(m, "str2") + //************************************************************* + if (is_concat_eq_type6(new_nn1, new_nn2)) { + expr * v1_arg0 = to_app(new_nn1)->get_arg(0); + expr * v1_arg1 = to_app(new_nn1)->get_arg(1); + expr * v2_arg0 = to_app(new_nn2)->get_arg(0); + expr * v2_arg1 = to_app(new_nn2)->get_arg(1); + + expr * y = NULL; + expr * m = NULL; + + if (u.str.is_string(v1_arg0)) { + y = v1_arg1; + m = v2_arg0; + } else { + y = v2_arg1; + m = v1_arg0; + } + if (has_self_cut(m, y)) { + TRACE("str", tout << "Possible overlap found" << std::endl; print_cut_var(m, tout); print_cut_var(y, tout);); + return true; + } else { + return false; + } + } + + TRACE("str", tout << "warning: unrecognized concat case" << std::endl;); return false; } -} -void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { - ast_manager & mgr = get_manager(); - context & ctx = get_context(); + /************************************************************* + * Type 1: concat(x, y) = concat(m, n) + * x, y, m and n all variables + *************************************************************/ + bool theory_str::is_concat_eq_type1(expr * concatAst1, expr * concatAst2) { + expr * x = to_app(concatAst1)->get_arg(0); + expr * y = to_app(concatAst1)->get_arg(1); + expr * m = to_app(concatAst2)->get_arg(0); + expr * n = to_app(concatAst2)->get_arg(1); - bool overlapAssumptionUsed = false; - - TRACE("str", tout << "process_concat_eq TYPE 1" << std::endl - << "concatAst1 = " << mk_ismt2_pp(concatAst1, mgr) << std::endl - << "concatAst2 = " << mk_ismt2_pp(concatAst2, mgr) << std::endl; - ); - - if (!u.str.is_concat(to_app(concatAst1))) { - TRACE("str", tout << "concatAst1 is not a concat function" << std::endl;); - return; - } - if (!u.str.is_concat(to_app(concatAst2))) { - TRACE("str", tout << "concatAst2 is not a concat function" << std::endl;); - return; - } - expr * x = to_app(concatAst1)->get_arg(0); - expr * y = to_app(concatAst1)->get_arg(1); - expr * m = to_app(concatAst2)->get_arg(0); - expr * n = to_app(concatAst2)->get_arg(1); - - rational x_len, y_len, m_len, n_len; - bool x_len_exists = get_len_value(x, x_len); - bool y_len_exists = get_len_value(y, y_len); - bool m_len_exists = get_len_value(m, m_len); - bool n_len_exists = get_len_value(n, n_len); - - int splitType = -1; - if (x_len_exists && m_len_exists) { - TRACE("str", tout << "length values found: x/m" << std::endl;); - if (x_len < m_len) { - splitType = 0; - } else if (x_len == m_len) { - splitType = 1; + if (!u.str.is_string(x) && !u.str.is_string(y) && !u.str.is_string(m) && !u.str.is_string(n)) { + return true; } else { - splitType = 2; + return false; } } - if (splitType == -1 && y_len_exists && n_len_exists) { - TRACE("str", tout << "length values found: y/n" << std::endl;); - if (y_len > n_len) { - splitType = 0; - } else if (y_len == n_len) { - splitType = 1; - } else { - splitType = 2; + void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { + ast_manager & mgr = get_manager(); + context & ctx = get_context(); + + bool overlapAssumptionUsed = false; + + TRACE("str", tout << "process_concat_eq TYPE 1" << std::endl + << "concatAst1 = " << mk_ismt2_pp(concatAst1, mgr) << std::endl + << "concatAst2 = " << mk_ismt2_pp(concatAst2, mgr) << std::endl; + ); + + if (!u.str.is_concat(to_app(concatAst1))) { + TRACE("str", tout << "concatAst1 is not a concat function" << std::endl;); + return; } - } + if (!u.str.is_concat(to_app(concatAst2))) { + TRACE("str", tout << "concatAst2 is not a concat function" << std::endl;); + return; + } + expr * x = to_app(concatAst1)->get_arg(0); + expr * y = to_app(concatAst1)->get_arg(1); + expr * m = to_app(concatAst2)->get_arg(0); + expr * n = to_app(concatAst2)->get_arg(1); - TRACE("str", tout - << "len(x) = " << (x_len_exists ? x_len.to_string() : "?") << std::endl - << "len(y) = " << (y_len_exists ? y_len.to_string() : "?") << std::endl - << "len(m) = " << (m_len_exists ? m_len.to_string() : "?") << std::endl - << "len(n) = " << (n_len_exists ? n_len.to_string() : "?") << std::endl - << "split type " << splitType << std::endl; - ); + rational x_len, y_len, m_len, n_len; + bool x_len_exists = get_len_value(x, x_len); + bool y_len_exists = get_len_value(y, y_len); + bool m_len_exists = get_len_value(m, m_len); + bool n_len_exists = get_len_value(n, n_len); - expr * t1 = NULL; - expr * t2 = NULL; - expr * xorFlag = NULL; + int splitType = -1; + if (x_len_exists && m_len_exists) { + TRACE("str", tout << "length values found: x/m" << std::endl;); + if (x_len < m_len) { + splitType = 0; + } else if (x_len == m_len) { + splitType = 1; + } else { + splitType = 2; + } + } - std::pair key1(concatAst1, concatAst2); - std::pair key2(concatAst2, concatAst1); + if (splitType == -1 && y_len_exists && n_len_exists) { + TRACE("str", tout << "length values found: y/n" << std::endl;); + if (y_len > n_len) { + splitType = 0; + } else if (y_len == n_len) { + splitType = 1; + } else { + splitType = 2; + } + } - // check the entries in this map to make sure they're still in scope - // before we use them. + TRACE("str", tout + << "len(x) = " << (x_len_exists ? x_len.to_string() : "?") << std::endl + << "len(y) = " << (y_len_exists ? y_len.to_string() : "?") << std::endl + << "len(m) = " << (m_len_exists ? m_len.to_string() : "?") << std::endl + << "len(n) = " << (n_len_exists ? n_len.to_string() : "?") << std::endl + << "split type " << splitType << std::endl; + ); - std::map, std::map >::iterator entry1 = varForBreakConcat.find(key1); - std::map, std::map >::iterator entry2 = varForBreakConcat.find(key2); + expr * t1 = NULL; + expr * t2 = NULL; + expr * xorFlag = NULL; - bool entry1InScope; - if (entry1 == varForBreakConcat.end()) { - entry1InScope = false; - } else { - if (internal_variable_set.find((entry1->second)[0]) == internal_variable_set.end() - || internal_variable_set.find((entry1->second)[1]) == internal_variable_set.end() - /*|| internal_variable_set.find((entry1->second)[2]) == internal_variable_set.end() */) { + std::pair key1(concatAst1, concatAst2); + std::pair key2(concatAst2, concatAst1); + + // check the entries in this map to make sure they're still in scope + // before we use them. + + std::map, std::map >::iterator entry1 = varForBreakConcat.find(key1); + std::map, std::map >::iterator entry2 = varForBreakConcat.find(key2); + + bool entry1InScope; + if (entry1 == varForBreakConcat.end()) { entry1InScope = false; } else { - entry1InScope = true; + if (internal_variable_set.find((entry1->second)[0]) == internal_variable_set.end() + || internal_variable_set.find((entry1->second)[1]) == internal_variable_set.end() + /*|| internal_variable_set.find((entry1->second)[2]) == internal_variable_set.end() */) { + entry1InScope = false; + } else { + entry1InScope = true; + } } - } - bool entry2InScope; - if (entry2 == varForBreakConcat.end()) { - entry2InScope = false; - } else { - if (internal_variable_set.find((entry2->second)[0]) == internal_variable_set.end() - || internal_variable_set.find((entry2->second)[1]) == internal_variable_set.end() - /* || internal_variable_set.find((entry2->second)[2]) == internal_variable_set.end() */) { + bool entry2InScope; + if (entry2 == varForBreakConcat.end()) { entry2InScope = false; } else { - entry2InScope = true; - } - } - - TRACE("str", tout << "entry 1 " << (entry1InScope ? "in scope" : "not in scope") << std::endl - << "entry 2 " << (entry2InScope ? "in scope" : "not in scope") << std::endl;); - - if (!entry1InScope && !entry2InScope) { - t1 = mk_nonempty_str_var(); - t2 = mk_nonempty_str_var(); - xorFlag = mk_internal_xor_var(); - check_and_init_cut_var(t1); - check_and_init_cut_var(t2); - varForBreakConcat[key1][0] = t1; - varForBreakConcat[key1][1] = t2; - varForBreakConcat[key1][2] = xorFlag; - } else { - // match found - if (entry1InScope) { - t1 = varForBreakConcat[key1][0]; - t2 = varForBreakConcat[key1][1]; - xorFlag = varForBreakConcat[key1][2]; - } else { - t1 = varForBreakConcat[key2][0]; - t2 = varForBreakConcat[key2][1]; - xorFlag = varForBreakConcat[key2][2]; - } - refresh_theory_var(t1); - add_nonempty_constraint(t1); - refresh_theory_var(t2); - add_nonempty_constraint(t2); - } - - // For split types 0 through 2, we can get away with providing - // fewer split options since more length information is available. - if (splitType == 0) { - //-------------------------------------- - // Type 0: M cuts Y. - // len(x) < len(m) || len(y) > len(n) - //-------------------------------------- - expr_ref_vector ax_l_items(mgr); - expr_ref_vector ax_r_items(mgr); - - ax_l_items.push_back(ctx.mk_eq_atom(concatAst1, concatAst2)); - - expr_ref x_t1(mk_concat(x, t1), mgr); - expr_ref t1_n(mk_concat(t1, n), mgr); - - ax_r_items.push_back(ctx.mk_eq_atom(m, x_t1)); - ax_r_items.push_back(ctx.mk_eq_atom(y, t1_n)); - - if (m_len_exists && x_len_exists) { - ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_int(x_len))); - ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(m), mk_int(m_len))); - rational m_sub_x = m_len - x_len; - ax_r_items.push_back(ctx.mk_eq_atom(mk_strlen(t1), mk_int(m_sub_x))); - } else { - ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_int(y_len))); - ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(n), mk_int(n_len))); - rational y_sub_n = y_len - n_len; - ax_r_items.push_back(ctx.mk_eq_atom(mk_strlen(t1), mk_int(y_sub_n))); - } - - expr_ref ax_l(mk_and(ax_l_items), mgr); - expr_ref ax_r(mk_and(ax_r_items), mgr); - - if (!has_self_cut(m, y)) { - // Cut Info - add_cut_info_merge(t1, sLevel, m); - add_cut_info_merge(t1, sLevel, y); - - if (m_params.m_StrongArrangements) { - expr_ref ax_strong(ctx.mk_eq_atom(ax_l, ax_r), mgr); - assert_axiom(ax_strong); + if (internal_variable_set.find((entry2->second)[0]) == internal_variable_set.end() + || internal_variable_set.find((entry2->second)[1]) == internal_variable_set.end() + /* || internal_variable_set.find((entry2->second)[2]) == internal_variable_set.end() */) { + entry2InScope = false; } else { - assert_implication(ax_l, ax_r); - } - } else { - loopDetected = true; - if (m_params.m_FiniteOverlapModels) { - expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2); - assert_implication(ax_l, tester); - add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true); - } else { - TRACE("str", tout << "AVOID LOOP: SKIPPED" << std::endl;); - TRACE("str", {print_cut_var(m, tout); print_cut_var(y, tout);}); - - if (!overlapAssumptionUsed) { - overlapAssumptionUsed = true; - assert_implication(ax_l, m_theoryStrOverlapAssumption_term); - } + entry2InScope = true; } } - } else if (splitType == 1) { - // Type 1: - // len(x) = len(m) || len(y) = len(n) - expr_ref ax_l1(ctx.mk_eq_atom(concatAst1, concatAst2), mgr); - expr_ref ax_l2(mgr.mk_or(ctx.mk_eq_atom(mk_strlen(x), mk_strlen(m)), ctx.mk_eq_atom(mk_strlen(y), mk_strlen(n))), mgr); - expr_ref ax_l(mgr.mk_and(ax_l1, ax_l2), mgr); - expr_ref ax_r(mgr.mk_and(ctx.mk_eq_atom(x,m), ctx.mk_eq_atom(y,n)), mgr); - assert_implication(ax_l, ax_r); - } else if (splitType == 2) { - // Type 2: X cuts N. - // len(x) > len(m) || len(y) < len(n) - expr_ref m_t2(mk_concat(m, t2), mgr); - expr_ref t2_y(mk_concat(t2, y), mgr); - expr_ref_vector ax_l_items(mgr); - ax_l_items.push_back(ctx.mk_eq_atom(concatAst1, concatAst2)); + TRACE("str", tout << "entry 1 " << (entry1InScope ? "in scope" : "not in scope") << std::endl + << "entry 2 " << (entry2InScope ? "in scope" : "not in scope") << std::endl;); - expr_ref_vector ax_r_items(mgr); - ax_r_items.push_back(ctx.mk_eq_atom(x, m_t2)); - ax_r_items.push_back(ctx.mk_eq_atom(t2_y, n)); - - if (m_len_exists && x_len_exists) { - ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_int(x_len))); - ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(m), mk_int(m_len))); - rational x_sub_m = x_len - m_len; - ax_r_items.push_back(ctx.mk_eq_atom(mk_strlen(t2), mk_int(x_sub_m))); + if (!entry1InScope && !entry2InScope) { + t1 = mk_nonempty_str_var(); + t2 = mk_nonempty_str_var(); + xorFlag = mk_internal_xor_var(); + check_and_init_cut_var(t1); + check_and_init_cut_var(t2); + varForBreakConcat[key1][0] = t1; + varForBreakConcat[key1][1] = t2; + varForBreakConcat[key1][2] = xorFlag; } else { - ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_int(y_len))); - ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(n), mk_int(n_len))); - rational n_sub_y = n_len - y_len; - ax_r_items.push_back(ctx.mk_eq_atom(mk_strlen(t2), mk_int(n_sub_y))); + // match found + if (entry1InScope) { + t1 = varForBreakConcat[key1][0]; + t2 = varForBreakConcat[key1][1]; + xorFlag = varForBreakConcat[key1][2]; + } else { + t1 = varForBreakConcat[key2][0]; + t2 = varForBreakConcat[key2][1]; + xorFlag = varForBreakConcat[key2][2]; + } + refresh_theory_var(t1); + add_nonempty_constraint(t1); + refresh_theory_var(t2); + add_nonempty_constraint(t2); } - expr_ref ax_l(mk_and(ax_l_items), mgr); - expr_ref ax_r(mk_and(ax_r_items), mgr); + // For split types 0 through 2, we can get away with providing + // fewer split options since more length information is available. + if (splitType == 0) { + //-------------------------------------- + // Type 0: M cuts Y. + // len(x) < len(m) || len(y) > len(n) + //-------------------------------------- + expr_ref_vector ax_l_items(mgr); + expr_ref_vector ax_r_items(mgr); - if (!has_self_cut(x, n)) { - // Cut Info - add_cut_info_merge(t2, sLevel, x); - add_cut_info_merge(t2, sLevel, n); + ax_l_items.push_back(ctx.mk_eq_atom(concatAst1, concatAst2)); - if (m_params.m_StrongArrangements) { - expr_ref ax_strong(ctx.mk_eq_atom(ax_l, ax_r), mgr); - assert_axiom(ax_strong); - } else { - assert_implication(ax_l, ax_r); - } - } else { - loopDetected = true; - if (m_params.m_FiniteOverlapModels) { - expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2); - assert_implication(ax_l, tester); - add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true); - } else { - TRACE("str", tout << "AVOID LOOP: SKIPPED" << std::endl;); - TRACE("str", {print_cut_var(m, tout); print_cut_var(y, tout);}); - - if (!overlapAssumptionUsed) { - overlapAssumptionUsed = true; - assert_implication(ax_l, m_theoryStrOverlapAssumption_term); - } - } - } - } else if (splitType == -1) { - // Here we don't really have a choice. We have no length information at all... - - // This vector will eventually contain one term for each possible arrangement we explore. - expr_ref_vector arrangement_disjunction(mgr); - - // break option 1: m cuts y - // len(x) < len(m) || len(y) > len(n) - if (!avoidLoopCut || !has_self_cut(m, y)) { - expr_ref_vector and_item(mgr); - // break down option 1-1 expr_ref x_t1(mk_concat(x, t1), mgr); expr_ref t1_n(mk_concat(t1, n), mgr); - and_item.push_back(ctx.mk_eq_atom(m, x_t1)); - and_item.push_back(ctx.mk_eq_atom(y, t1_n)); + ax_r_items.push_back(ctx.mk_eq_atom(m, x_t1)); + ax_r_items.push_back(ctx.mk_eq_atom(y, t1_n)); - expr_ref x_plus_t1(m_autil.mk_add(mk_strlen(x), mk_strlen(t1)), mgr); - and_item.push_back(ctx.mk_eq_atom(mk_strlen(m), x_plus_t1)); - // These were crashing the solver because the integer theory - // expects a constant on the right-hand side. - // The things we want to assert here are len(m) > len(x) and len(y) > len(n). - // We rewrite A > B as A-B > 0 and then as not(A-B <= 0), - // and then, *because we aren't allowed to use subtraction*, - // as not(A + -1*B <= 0) - and_item.push_back( - mgr.mk_not(m_autil.mk_le( - m_autil.mk_add(mk_strlen(m), m_autil.mk_mul(mk_int(-1), mk_strlen(x))), - mk_int(0))) ); - and_item.push_back( - mgr.mk_not(m_autil.mk_le( - m_autil.mk_add(mk_strlen(y),m_autil.mk_mul(mk_int(-1), mk_strlen(n))), - mk_int(0))) ); - - expr_ref option1(mk_and(and_item), mgr); - arrangement_disjunction.push_back(option1); - add_theory_aware_branching_info(option1, 0.1, l_true); - - add_cut_info_merge(t1, ctx.get_scope_level(), m); - add_cut_info_merge(t1, ctx.get_scope_level(), y); - } else { - loopDetected = true; - if (m_params.m_FiniteOverlapModels) { - expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2); - arrangement_disjunction.push_back(tester); - add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true); + if (m_len_exists && x_len_exists) { + ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_int(x_len))); + ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(m), mk_int(m_len))); + rational m_sub_x = m_len - x_len; + ax_r_items.push_back(ctx.mk_eq_atom(mk_strlen(t1), mk_int(m_sub_x))); } else { - TRACE("str", tout << "AVOID LOOP: SKIPPED" << std::endl;); - TRACE("str", {print_cut_var(m, tout); print_cut_var(y, tout);}); - - if (!overlapAssumptionUsed) { - overlapAssumptionUsed = true; - arrangement_disjunction.push_back(m_theoryStrOverlapAssumption_term); - } - } - } - - // break option 2: - // x = m . t2 - // n = t2 . y - if (!avoidLoopCut || !has_self_cut(x, n)) { - expr_ref_vector and_item(mgr); - // break down option 1-2 - expr_ref m_t2(mk_concat(m, t2), mgr); - expr_ref t2_y(mk_concat(t2, y), mgr); - - and_item.push_back(ctx.mk_eq_atom(x, m_t2)); - and_item.push_back(ctx.mk_eq_atom(n, t2_y)); - - - expr_ref m_plus_t2(m_autil.mk_add(mk_strlen(m), mk_strlen(t2)), mgr); - - and_item.push_back(ctx.mk_eq_atom(mk_strlen(x), m_plus_t2)); - // want len(x) > len(m) and len(n) > len(y) - and_item.push_back( - mgr.mk_not(m_autil.mk_le( - m_autil.mk_add(mk_strlen(x), m_autil.mk_mul(mk_int(-1), mk_strlen(m))), - mk_int(0))) ); - and_item.push_back( - mgr.mk_not(m_autil.mk_le( - m_autil.mk_add(mk_strlen(n), m_autil.mk_mul(mk_int(-1), mk_strlen(y))), - mk_int(0))) ); - - expr_ref option2(mk_and(and_item), mgr); - arrangement_disjunction.push_back(option2); - add_theory_aware_branching_info(option2, 0.1, l_true); - - add_cut_info_merge(t2, ctx.get_scope_level(), x); - add_cut_info_merge(t2, ctx.get_scope_level(), n); - } else { - loopDetected = true; - if (m_params.m_FiniteOverlapModels) { - expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2); - arrangement_disjunction.push_back(tester); - add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true); - } else { - TRACE("str", tout << "AVOID LOOP: SKIPPED" << std::endl;); - TRACE("str", {print_cut_var(x, tout); print_cut_var(n, tout);}); - - if (!overlapAssumptionUsed) { - overlapAssumptionUsed = true; - arrangement_disjunction.push_back(m_theoryStrOverlapAssumption_term); - } - } - } - - // option 3: - // x = m, y = n - if (can_two_nodes_eq(x, m) && can_two_nodes_eq(y, n)) { - expr_ref_vector and_item(mgr); - - and_item.push_back(ctx.mk_eq_atom(x, m)); - and_item.push_back(ctx.mk_eq_atom(y, n)); - and_item.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_strlen(m))); - and_item.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_strlen(n))); - - expr_ref option3(mk_and(and_item), mgr); - arrangement_disjunction.push_back(option3); - // prioritize this case, it is easier - add_theory_aware_branching_info(option3, 0.5, l_true); - } - - if (!arrangement_disjunction.empty()) { - expr_ref premise(ctx.mk_eq_atom(concatAst1, concatAst2), mgr); - expr_ref conclusion(mk_or(arrangement_disjunction), mgr); - if (m_params.m_StrongArrangements) { - expr_ref ax_strong(ctx.mk_eq_atom(premise, conclusion), mgr); - assert_axiom(ax_strong); - } else { - assert_implication(premise, conclusion); - } - // assert mutual exclusion between each branch of the arrangement - generate_mutual_exclusion(arrangement_disjunction); - } else { - TRACE("str", tout << "STOP: no split option found for two EQ concats." << std::endl;); - } - } // (splitType == -1) -} - -/************************************************************* - * Type 2: concat(x, y) = concat(m, "str") - *************************************************************/ -bool theory_str::is_concat_eq_type2(expr * concatAst1, expr * concatAst2) { - expr * v1_arg0 = to_app(concatAst1)->get_arg(0); - expr * v1_arg1 = to_app(concatAst1)->get_arg(1); - expr * v2_arg0 = to_app(concatAst2)->get_arg(0); - expr * v2_arg1 = to_app(concatAst2)->get_arg(1); - - if ((!u.str.is_string(v1_arg0)) && u.str.is_string(v1_arg1) - && (!u.str.is_string(v2_arg0)) && (!u.str.is_string(v2_arg1))) { - return true; - } else if ((!u.str.is_string(v2_arg0)) && u.str.is_string(v2_arg1) - && (!u.str.is_string(v1_arg0)) && (!u.str.is_string(v1_arg1))) { - return true; - } else { - return false; - } -} - -void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { - ast_manager & mgr = get_manager(); - context & ctx = get_context(); - - bool overlapAssumptionUsed = false; - - TRACE("str", tout << "process_concat_eq TYPE 2" << std::endl - << "concatAst1 = " << mk_ismt2_pp(concatAst1, mgr) << std::endl - << "concatAst2 = " << mk_ismt2_pp(concatAst2, mgr) << std::endl; - ); - - if (!u.str.is_concat(to_app(concatAst1))) { - TRACE("str", tout << "concatAst1 is not a concat function" << std::endl;); - return; - } - if (!u.str.is_concat(to_app(concatAst2))) { - TRACE("str", tout << "concatAst2 is not a concat function" << std::endl;); - return; - } - - expr * x = NULL; - expr * y = NULL; - expr * strAst = NULL; - expr * m = NULL; - - expr * v1_arg0 = to_app(concatAst1)->get_arg(0); - expr * v1_arg1 = to_app(concatAst1)->get_arg(1); - expr * v2_arg0 = to_app(concatAst2)->get_arg(0); - expr * v2_arg1 = to_app(concatAst2)->get_arg(1); - - if (u.str.is_string(v1_arg1) && !u.str.is_string(v2_arg1)) { - m = v1_arg0; - strAst = v1_arg1; - x = v2_arg0; - y = v2_arg1; - } else { - m = v2_arg0; - strAst = v2_arg1; - x = v1_arg0; - y = v1_arg1; - } - - zstring strValue; - u.str.is_string(strAst, strValue); - - rational x_len, y_len, m_len, str_len; - bool x_len_exists = get_len_value(x, x_len); - bool y_len_exists = get_len_value(y, y_len); - bool m_len_exists = get_len_value(m, m_len); - bool str_len_exists = true; - str_len = rational(strValue.length()); - - // setup - - expr * xorFlag = NULL; - expr * temp1 = NULL; - std::pair key1(concatAst1, concatAst2); - std::pair key2(concatAst2, concatAst1); - - // check the entries in this map to make sure they're still in scope - // before we use them. - - std::map, std::map >::iterator entry1 = varForBreakConcat.find(key1); - std::map, std::map >::iterator entry2 = varForBreakConcat.find(key2); - - // prevent checking scope for the XOR term, as it's always in the same scope as the split var - - bool entry1InScope; - if (entry1 == varForBreakConcat.end()) { - entry1InScope = false; - } else { - if (internal_variable_set.find((entry1->second)[0]) == internal_variable_set.end() - /*|| internal_variable_set.find((entry1->second)[1]) == internal_variable_set.end()*/ - ) { - entry1InScope = false; - } else { - entry1InScope = true; - } - } - - bool entry2InScope; - if (entry2 == varForBreakConcat.end()) { - entry2InScope = false; - } else { - if (internal_variable_set.find((entry2->second)[0]) == internal_variable_set.end() - /*|| internal_variable_set.find((entry2->second)[1]) == internal_variable_set.end()*/ - ) { - entry2InScope = false; - } else { - entry2InScope = true; - } - } - - TRACE("str", tout << "entry 1 " << (entry1InScope ? "in scope" : "not in scope") << std::endl - << "entry 2 " << (entry2InScope ? "in scope" : "not in scope") << std::endl;); - - - if (!entry1InScope && !entry2InScope) { - temp1 = mk_nonempty_str_var(); - xorFlag = mk_internal_xor_var(); - varForBreakConcat[key1][0] = temp1; - varForBreakConcat[key1][1] = xorFlag; - } else { - if (entry1InScope) { - temp1 = varForBreakConcat[key1][0]; - xorFlag = varForBreakConcat[key1][1]; - } else if (entry2InScope) { - temp1 = varForBreakConcat[key2][0]; - xorFlag = varForBreakConcat[key2][1]; - } - refresh_theory_var(temp1); - add_nonempty_constraint(temp1); - } - - int splitType = -1; - if (x_len_exists && m_len_exists) { - if (x_len < m_len) - splitType = 0; - else if (x_len == m_len) - splitType = 1; - else - splitType = 2; - } - if (splitType == -1 && y_len_exists && str_len_exists) { - if (y_len > str_len) - splitType = 0; - else if (y_len == str_len) - splitType = 1; - else - splitType = 2; - } - - TRACE("str", tout << "Split type " << splitType << std::endl;); - - // Provide fewer split options when length information is available. - - if (splitType == 0) { - // M cuts Y - // | x | y | - // | m | str | - expr_ref temp1_strAst(mk_concat(temp1, strAst), mgr); - if (can_two_nodes_eq(y, temp1_strAst)) { - expr_ref_vector l_items(mgr); - l_items.push_back(ctx.mk_eq_atom(concatAst1, concatAst2)); - - expr_ref_vector r_items(mgr); - expr_ref x_temp1(mk_concat(x, temp1), mgr); - r_items.push_back(ctx.mk_eq_atom(m, x_temp1)); - r_items.push_back(ctx.mk_eq_atom(y, temp1_strAst)); - - if (x_len_exists && m_len_exists) { - l_items.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_int(x_len))); - l_items.push_back(ctx.mk_eq_atom(mk_strlen(m), mk_int(m_len))); - rational m_sub_x = (m_len - x_len); - r_items.push_back(ctx.mk_eq_atom(mk_strlen(temp1), mk_int(m_sub_x))); - } else { - l_items.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_int(y_len))); - l_items.push_back(ctx.mk_eq_atom(mk_strlen(strAst), mk_int(str_len))); - rational y_sub_str = (y_len - str_len); - r_items.push_back(ctx.mk_eq_atom(mk_strlen(temp1), mk_int(y_sub_str))); + ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_int(y_len))); + ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(n), mk_int(n_len))); + rational y_sub_n = y_len - n_len; + ax_r_items.push_back(ctx.mk_eq_atom(mk_strlen(t1), mk_int(y_sub_n))); } - expr_ref ax_l(mk_and(l_items), mgr); - expr_ref ax_r(mk_and(r_items), mgr); + expr_ref ax_l(mk_and(ax_l_items), mgr); + expr_ref ax_r(mk_and(ax_r_items), mgr); - if (!avoidLoopCut || !(has_self_cut(m, y))) { - // break down option 2-1 - add_cut_info_merge(temp1, sLevel, y); - add_cut_info_merge(temp1, sLevel, m); - - if (m_params.m_StrongArrangements) { - expr_ref ax_strong(ctx.mk_eq_atom(ax_l, ax_r), mgr); - assert_axiom(ax_strong); - } else { - assert_implication(ax_l, ax_r); - } - } else { - loopDetected = true; - - if (m_params.m_FiniteOverlapModels) { - expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2); - assert_implication(ax_l, tester); - add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true); - } else { - TRACE("str", tout << "AVOID LOOP: SKIP" << std::endl;); - TRACE("str", {print_cut_var(m, tout); print_cut_var(y, tout);}); - - if (!overlapAssumptionUsed) { - overlapAssumptionUsed = true; - assert_implication(ax_l, m_theoryStrOverlapAssumption_term); - } - } - } - } - } else if (splitType == 1) { - // | x | y | - // | m | str | - expr_ref ax_l1(ctx.mk_eq_atom(concatAst1, concatAst2), mgr); - expr_ref ax_l2(mgr.mk_or( - ctx.mk_eq_atom(mk_strlen(x), mk_strlen(m)), - ctx.mk_eq_atom(mk_strlen(y), mk_strlen(strAst))), mgr); - expr_ref ax_l(mgr.mk_and(ax_l1, ax_l2), mgr); - expr_ref ax_r(mgr.mk_and(ctx.mk_eq_atom(x, m), ctx.mk_eq_atom(y, strAst)), mgr); - assert_implication(ax_l, ax_r); - } else if (splitType == 2) { - // m cut y, - // | x | y | - // | m | str | - rational lenDelta; - expr_ref_vector l_items(mgr); - l_items.push_back(ctx.mk_eq_atom(concatAst1, concatAst2)); - if (x_len_exists && m_len_exists) { - l_items.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_int(x_len))); - l_items.push_back(ctx.mk_eq_atom(mk_strlen(m), mk_int(m_len))); - lenDelta = x_len - m_len; - } else { - l_items.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_int(y_len))); - lenDelta = str_len - y_len; - } - TRACE("str", - tout - << "xLen? " << (x_len_exists ? "yes" : "no") << std::endl - << "mLen? " << (m_len_exists ? "yes" : "no") << std::endl - << "yLen? " << (y_len_exists ? "yes" : "no") << std::endl - << "xLen = " << x_len.to_string() << std::endl - << "yLen = " << y_len.to_string() << std::endl - << "mLen = " << m_len.to_string() << std::endl - << "strLen = " << str_len.to_string() << std::endl - << "lenDelta = " << lenDelta.to_string() << std::endl - << "strValue = \"" << strValue << "\" (len=" << strValue.length() << ")" << "\n" - ; - ); - - zstring part1Str = strValue.extract(0, lenDelta.get_unsigned()); - zstring part2Str = strValue.extract(lenDelta.get_unsigned(), strValue.length() - lenDelta.get_unsigned()); - - expr_ref prefixStr(mk_string(part1Str), mgr); - expr_ref x_concat(mk_concat(m, prefixStr), mgr); - expr_ref cropStr(mk_string(part2Str), mgr); - - if (can_two_nodes_eq(x, x_concat) && can_two_nodes_eq(y, cropStr)) { - expr_ref_vector r_items(mgr); - r_items.push_back(ctx.mk_eq_atom(x, x_concat)); - r_items.push_back(ctx.mk_eq_atom(y, cropStr)); - expr_ref ax_l(mk_and(l_items), mgr); - expr_ref ax_r(mk_and(r_items), mgr); - - if (m_params.m_StrongArrangements) { - expr_ref ax_strong(ctx.mk_eq_atom(ax_l, ax_r), mgr); - assert_axiom(ax_strong); - } else { - assert_implication(ax_l, ax_r); - } - } else { - // negate! It's impossible to split str with these lengths - TRACE("str", tout << "CONFLICT: Impossible to split str with these lengths." << std::endl;); - expr_ref ax_l(mk_and(l_items), mgr); - assert_axiom(mgr.mk_not(ax_l)); - } - } else { - // Split type -1: no idea about the length... - expr_ref_vector arrangement_disjunction(mgr); - - expr_ref temp1_strAst(mk_concat(temp1, strAst), mgr); - - // m cuts y - if (can_two_nodes_eq(y, temp1_strAst)) { - if (!avoidLoopCut || !has_self_cut(m, y)) { - // break down option 2-1 - expr_ref_vector and_item(mgr); - - expr_ref x_temp1(mk_concat(x, temp1), mgr); - and_item.push_back(ctx.mk_eq_atom(m, x_temp1)); - and_item.push_back(ctx.mk_eq_atom(y, temp1_strAst)); - - and_item.push_back(ctx.mk_eq_atom(mk_strlen(m), - m_autil.mk_add(mk_strlen(x), mk_strlen(temp1)))); - - expr_ref option1(mk_and(and_item), mgr); - arrangement_disjunction.push_back(option1); - add_theory_aware_branching_info(option1, 0.1, l_true); - add_cut_info_merge(temp1, ctx.get_scope_level(), y); - add_cut_info_merge(temp1, ctx.get_scope_level(), m); - } else { - loopDetected = true; - if (m_params.m_FiniteOverlapModels) { - expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2); - arrangement_disjunction.push_back(tester); - add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true); - } else { - TRACE("str", tout << "AVOID LOOP: SKIPPED" << std::endl;); - TRACE("str", {print_cut_var(m, tout); print_cut_var(y, tout);}); - - if (!overlapAssumptionUsed) { - overlapAssumptionUsed = true; - arrangement_disjunction.push_back(m_theoryStrOverlapAssumption_term); - } - } - } - } - - for (unsigned int i = 0; i <= strValue.length(); ++i) { - zstring part1Str = strValue.extract(0, i); - zstring part2Str = strValue.extract(i, strValue.length() - i); - expr_ref prefixStr(mk_string(part1Str), mgr); - expr_ref x_concat(mk_concat(m, prefixStr), mgr); - expr_ref cropStr(mk_string(part2Str), mgr); - if (can_two_nodes_eq(x, x_concat) && can_two_nodes_eq(y, cropStr)) { - // break down option 2-2 - expr_ref_vector and_item(mgr); - and_item.push_back(ctx.mk_eq_atom(x, x_concat)); - and_item.push_back(ctx.mk_eq_atom(y, cropStr)); - and_item.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_int(part2Str.length()))); - expr_ref option2(mk_and(and_item), mgr); - arrangement_disjunction.push_back(option2); - double priority; - // prioritize the option where y is equal to the original string - if (i == 0) { - priority = 0.5; - } else { - priority = 0.1; - } - add_theory_aware_branching_info(option2, priority, l_true); - } - } - - if (!arrangement_disjunction.empty()) { - expr_ref implyR(mk_or(arrangement_disjunction), mgr); - - if (m_params.m_StrongArrangements) { - expr_ref implyLHS(ctx.mk_eq_atom(concatAst1, concatAst2), mgr); - expr_ref ax_strong(ctx.mk_eq_atom(implyLHS, implyR), mgr); - assert_axiom(ax_strong); - } else { - assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); - } - generate_mutual_exclusion(arrangement_disjunction); - } else { - TRACE("str", tout << "STOP: Should not split two EQ concats." << std::endl;); - } - } // (splitType == -1) -} - -/************************************************************* - * Type 3: concat(x, y) = concat("str", n) - *************************************************************/ -bool theory_str::is_concat_eq_type3(expr * concatAst1, expr * concatAst2) { - expr * v1_arg0 = to_app(concatAst1)->get_arg(0); - expr * v1_arg1 = to_app(concatAst1)->get_arg(1); - expr * v2_arg0 = to_app(concatAst2)->get_arg(0); - expr * v2_arg1 = to_app(concatAst2)->get_arg(1); - - if (u.str.is_string(v1_arg0) && (!u.str.is_string(v1_arg1)) - && (!u.str.is_string(v2_arg0)) && (!u.str.is_string(v2_arg1))) { - return true; - } else if (u.str.is_string(v2_arg0) && (!u.str.is_string(v2_arg1)) - && (!u.str.is_string(v1_arg0)) && (!u.str.is_string(v1_arg1))) { - return true; - } else { - return false; - } -} - -void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { - ast_manager & mgr = get_manager(); - context & ctx = get_context(); - - bool overlapAssumptionUsed = false; - - TRACE("str", tout << "process_concat_eq TYPE 3" << std::endl - << "concatAst1 = " << mk_ismt2_pp(concatAst1, mgr) << std::endl - << "concatAst2 = " << mk_ismt2_pp(concatAst2, mgr) << std::endl; - ); - - if (!u.str.is_concat(to_app(concatAst1))) { - TRACE("str", tout << "concatAst1 is not a concat function" << std::endl;); - return; - } - if (!u.str.is_concat(to_app(concatAst2))) { - TRACE("str", tout << "concatAst2 is not a concat function" << std::endl;); - return; - } - - expr * v1_arg0 = to_app(concatAst1)->get_arg(0); - expr * v1_arg1 = to_app(concatAst1)->get_arg(1); - expr * v2_arg0 = to_app(concatAst2)->get_arg(0); - expr * v2_arg1 = to_app(concatAst2)->get_arg(1); - - expr * x = NULL; - expr * y = NULL; - expr * strAst = NULL; - expr * n = NULL; - - if (u.str.is_string(v1_arg0) && !u.str.is_string(v2_arg0)) { - strAst = v1_arg0; - n = v1_arg1; - x = v2_arg0; - y = v2_arg1; - } else { - strAst = v2_arg0; - n = v2_arg1; - x = v1_arg0; - y = v1_arg1; - } - - zstring strValue; - u.str.is_string(strAst, strValue); - - rational x_len, y_len, str_len, n_len; - bool x_len_exists = get_len_value(x, x_len); - bool y_len_exists = get_len_value(y, y_len); - str_len = rational((unsigned)(strValue.length())); - bool n_len_exists = get_len_value(n, n_len); - - expr_ref xorFlag(mgr); - expr_ref temp1(mgr); - std::pair key1(concatAst1, concatAst2); - std::pair key2(concatAst2, concatAst1); - - // check the entries in this map to make sure they're still in scope - // before we use them. - - std::map, std::map >::iterator entry1 = varForBreakConcat.find(key1); - std::map, std::map >::iterator entry2 = varForBreakConcat.find(key2); - - bool entry1InScope; - if (entry1 == varForBreakConcat.end()) { - entry1InScope = false; - } else { - if (internal_variable_set.find((entry1->second)[0]) == internal_variable_set.end() - /* || internal_variable_set.find((entry1->second)[1]) == internal_variable_set.end() */) { - entry1InScope = false; - } else { - entry1InScope = true; - } - } - - bool entry2InScope; - if (entry2 == varForBreakConcat.end()) { - entry2InScope = false; - } else { - if (internal_variable_set.find((entry2->second)[0]) == internal_variable_set.end() - /* || internal_variable_set.find((entry2->second)[1]) == internal_variable_set.end() */) { - entry2InScope = false; - } else { - entry2InScope = true; - } - } - - TRACE("str", tout << "entry 1 " << (entry1InScope ? "in scope" : "not in scope") << std::endl - << "entry 2 " << (entry2InScope ? "in scope" : "not in scope") << std::endl;); - - - if (!entry1InScope && !entry2InScope) { - temp1 = mk_nonempty_str_var(); - xorFlag = mk_internal_xor_var(); - - varForBreakConcat[key1][0] = temp1; - varForBreakConcat[key1][1] = xorFlag; - } else { - if (entry1InScope) { - temp1 = varForBreakConcat[key1][0]; - xorFlag = varForBreakConcat[key1][1]; - } else if (varForBreakConcat.find(key2) != varForBreakConcat.end()) { - temp1 = varForBreakConcat[key2][0]; - xorFlag = varForBreakConcat[key2][1]; - } - refresh_theory_var(temp1); - add_nonempty_constraint(temp1); - } - - - - int splitType = -1; - if (x_len_exists) { - if (x_len < str_len) - splitType = 0; - else if (x_len == str_len) - splitType = 1; - else - splitType = 2; - } - if (splitType == -1 && y_len_exists && n_len_exists) { - if (y_len > n_len) - splitType = 0; - else if (y_len == n_len) - splitType = 1; - else - splitType = 2; - } - - TRACE("str", tout << "Split type " << splitType << std::endl;); - - // Provide fewer split options when length information is available. - if (splitType == 0) { - // | x | y | - // | str | n | - expr_ref_vector litems(mgr); - litems.push_back(ctx.mk_eq_atom(concatAst1, concatAst2)); - rational prefixLen; - if (!x_len_exists) { - prefixLen = str_len - (y_len - n_len); - litems.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_int(y_len))); - litems.push_back(ctx.mk_eq_atom(mk_strlen(n), mk_int(n_len))); - } else { - prefixLen = x_len; - litems.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_int(x_len))); - } - zstring prefixStr = strValue.extract(0, prefixLen.get_unsigned()); - rational str_sub_prefix = str_len - prefixLen; - zstring suffixStr = strValue.extract(prefixLen.get_unsigned(), str_sub_prefix.get_unsigned()); - expr_ref prefixAst(mk_string(prefixStr), mgr); - expr_ref suffixAst(mk_string(suffixStr), mgr); - expr_ref ax_l(mgr.mk_and(litems.size(), litems.c_ptr()), mgr); - - expr_ref suf_n_concat(mk_concat(suffixAst, n), mgr); - if (can_two_nodes_eq(x, prefixAst) && can_two_nodes_eq(y, suf_n_concat)) { - expr_ref_vector r_items(mgr); - r_items.push_back(ctx.mk_eq_atom(x, prefixAst)); - r_items.push_back(ctx.mk_eq_atom(y, suf_n_concat)); - - if (m_params.m_StrongArrangements) { - expr_ref ax_strong(ctx.mk_eq_atom(ax_l, mk_and(r_items)), mgr); - assert_axiom(ax_strong); - } else { - assert_implication(ax_l, mk_and(r_items)); - } - } else { - // negate! It's impossible to split str with these lengths - TRACE("str", tout << "CONFLICT: Impossible to split str with these lengths." << std::endl;); - assert_axiom(mgr.mk_not(ax_l)); - } - } - else if (splitType == 1) { - expr_ref ax_l1(ctx.mk_eq_atom(concatAst1, concatAst2), mgr); - expr_ref ax_l2(mgr.mk_or( - ctx.mk_eq_atom(mk_strlen(x), mk_strlen(strAst)), - ctx.mk_eq_atom(mk_strlen(y), mk_strlen(n))), mgr); - expr_ref ax_l(mgr.mk_and(ax_l1, ax_l2), mgr); - expr_ref ax_r(mgr.mk_and(ctx.mk_eq_atom(x, strAst), ctx.mk_eq_atom(y, n)), mgr); - - if (m_params.m_StrongArrangements) { - expr_ref ax_strong(ctx.mk_eq_atom(ax_l, ax_r), mgr); - assert_axiom(ax_strong); - } else { - assert_implication(ax_l, ax_r); - } - } - else if (splitType == 2) { - // | x | y | - // | str | n | - expr_ref_vector litems(mgr); - litems.push_back(ctx.mk_eq_atom(concatAst1, concatAst2)); - rational tmpLen; - if (!x_len_exists) { - tmpLen = n_len - y_len; - litems.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_int(y_len))); - litems.push_back(ctx.mk_eq_atom(mk_strlen(n), mk_int(n_len))); - } else { - tmpLen = x_len - str_len; - litems.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_int(x_len))); - } - expr_ref ax_l(mgr.mk_and(litems.size(), litems.c_ptr()), mgr); - - expr_ref str_temp1(mk_concat(strAst, temp1), mgr); - expr_ref temp1_y(mk_concat(temp1, y), mgr); - - if (can_two_nodes_eq(x, str_temp1)) { - if (!avoidLoopCut || !(has_self_cut(x, n))) { - expr_ref_vector r_items(mgr); - r_items.push_back(ctx.mk_eq_atom(x, str_temp1)); - r_items.push_back(ctx.mk_eq_atom(n, temp1_y)); - r_items.push_back(ctx.mk_eq_atom(mk_strlen(temp1), mk_int(tmpLen))); - expr_ref ax_r(mk_and(r_items), mgr); - - //Cut Info - add_cut_info_merge(temp1, sLevel, x); - add_cut_info_merge(temp1, sLevel, n); + if (!has_self_cut(m, y)) { + // Cut Info + add_cut_info_merge(t1, sLevel, m); + add_cut_info_merge(t1, sLevel, y); if (m_params.m_StrongArrangements) { expr_ref ax_strong(ctx.mk_eq_atom(ax_l, ax_r), mgr); @@ -3937,82 +3112,117 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true); } else { TRACE("str", tout << "AVOID LOOP: SKIPPED" << std::endl;); - TRACE("str", {print_cut_var(x, tout); print_cut_var(n, tout);}); + TRACE("str", {print_cut_var(m, tout); print_cut_var(y, tout);}); if (!overlapAssumptionUsed) { - overlapAssumptionUsed = true; - assert_implication(ax_l, m_theoryStrOverlapAssumption_term); + overlapAssumptionUsed = true; + assert_implication(ax_l, m_theoryStrOverlapAssumption_term); } } } - } - // else { - // // negate! It's impossible to split str with these lengths - // __debugPrint(logFile, "[Conflict] Negate! It's impossible to split str with these lengths @ %d.\n", __LINE__); - // addAxiom(t, Z3_mk_not(ctx, ax_l), __LINE__); - // } - } - else { - // Split type -1. We know nothing about the length... + } else if (splitType == 1) { + // Type 1: + // len(x) = len(m) || len(y) = len(n) + expr_ref ax_l1(ctx.mk_eq_atom(concatAst1, concatAst2), mgr); + expr_ref ax_l2(mgr.mk_or(ctx.mk_eq_atom(mk_strlen(x), mk_strlen(m)), ctx.mk_eq_atom(mk_strlen(y), mk_strlen(n))), mgr); + expr_ref ax_l(mgr.mk_and(ax_l1, ax_l2), mgr); + expr_ref ax_r(mgr.mk_and(ctx.mk_eq_atom(x,m), ctx.mk_eq_atom(y,n)), mgr); + assert_implication(ax_l, ax_r); + } else if (splitType == 2) { + // Type 2: X cuts N. + // len(x) > len(m) || len(y) < len(n) + expr_ref m_t2(mk_concat(m, t2), mgr); + expr_ref t2_y(mk_concat(t2, y), mgr); - expr_ref_vector arrangement_disjunction(mgr); + expr_ref_vector ax_l_items(mgr); + ax_l_items.push_back(ctx.mk_eq_atom(concatAst1, concatAst2)); - int pos = 1; - for (unsigned int i = 0; i <= strValue.length(); i++) { - zstring part1Str = strValue.extract(0, i); - zstring part2Str = strValue.extract(i, strValue.length() - i); - expr_ref cropStr(mk_string(part1Str), mgr); - expr_ref suffixStr(mk_string(part2Str), mgr); - expr_ref y_concat(mk_concat(suffixStr, n), mgr); + expr_ref_vector ax_r_items(mgr); + ax_r_items.push_back(ctx.mk_eq_atom(x, m_t2)); + ax_r_items.push_back(ctx.mk_eq_atom(t2_y, n)); - if (can_two_nodes_eq(x, cropStr) && can_two_nodes_eq(y, y_concat)) { + if (m_len_exists && x_len_exists) { + ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_int(x_len))); + ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(m), mk_int(m_len))); + rational x_sub_m = x_len - m_len; + ax_r_items.push_back(ctx.mk_eq_atom(mk_strlen(t2), mk_int(x_sub_m))); + } else { + ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_int(y_len))); + ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(n), mk_int(n_len))); + rational n_sub_y = n_len - y_len; + ax_r_items.push_back(ctx.mk_eq_atom(mk_strlen(t2), mk_int(n_sub_y))); + } + + expr_ref ax_l(mk_and(ax_l_items), mgr); + expr_ref ax_r(mk_and(ax_r_items), mgr); + + if (!has_self_cut(x, n)) { + // Cut Info + add_cut_info_merge(t2, sLevel, x); + add_cut_info_merge(t2, sLevel, n); + + if (m_params.m_StrongArrangements) { + expr_ref ax_strong(ctx.mk_eq_atom(ax_l, ax_r), mgr); + assert_axiom(ax_strong); + } else { + assert_implication(ax_l, ax_r); + } + } else { + loopDetected = true; + if (m_params.m_FiniteOverlapModels) { + expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2); + assert_implication(ax_l, tester); + add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true); + } else { + TRACE("str", tout << "AVOID LOOP: SKIPPED" << std::endl;); + TRACE("str", {print_cut_var(m, tout); print_cut_var(y, tout);}); + + if (!overlapAssumptionUsed) { + overlapAssumptionUsed = true; + assert_implication(ax_l, m_theoryStrOverlapAssumption_term); + } + } + } + } else if (splitType == -1) { + // Here we don't really have a choice. We have no length information at all... + + // This vector will eventually contain one term for each possible arrangement we explore. + expr_ref_vector arrangement_disjunction(mgr); + + // break option 1: m cuts y + // len(x) < len(m) || len(y) > len(n) + if (!avoidLoopCut || !has_self_cut(m, y)) { expr_ref_vector and_item(mgr); - // break down option 3-1 - expr_ref x_eq_str(ctx.mk_eq_atom(x, cropStr), mgr); + // break down option 1-1 + expr_ref x_t1(mk_concat(x, t1), mgr); + expr_ref t1_n(mk_concat(t1, n), mgr); - and_item.push_back(x_eq_str); ++pos; - and_item.push_back(ctx.mk_eq_atom(y, y_concat)); - and_item.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_strlen(cropStr))); ++pos; + and_item.push_back(ctx.mk_eq_atom(m, x_t1)); + and_item.push_back(ctx.mk_eq_atom(y, t1_n)); - // and_item[pos++] = Z3_mk_eq(ctx, or_item[option], Z3_mk_eq(ctx, mk_length(t, y), mk_length(t, y_concat))); - // adding length constraint for _ = constStr seems slowing things down. + expr_ref x_plus_t1(m_autil.mk_add(mk_strlen(x), mk_strlen(t1)), mgr); + and_item.push_back(ctx.mk_eq_atom(mk_strlen(m), x_plus_t1)); + // These were crashing the solver because the integer theory + // expects a constant on the right-hand side. + // The things we want to assert here are len(m) > len(x) and len(y) > len(n). + // We rewrite A > B as A-B > 0 and then as not(A-B <= 0), + // and then, *because we aren't allowed to use subtraction*, + // as not(A + -1*B <= 0) + and_item.push_back( + mgr.mk_not(m_autil.mk_le( + m_autil.mk_add(mk_strlen(m), m_autil.mk_mul(mk_int(-1), mk_strlen(x))), + mk_int(0))) ); + and_item.push_back( + mgr.mk_not(m_autil.mk_le( + m_autil.mk_add(mk_strlen(y),m_autil.mk_mul(mk_int(-1), mk_strlen(n))), + mk_int(0))) ); expr_ref option1(mk_and(and_item), mgr); arrangement_disjunction.push_back(option1); - double priority; - if (i == strValue.length()) { - priority = 0.5; - } else { - priority = 0.1; - } - add_theory_aware_branching_info(option1, priority, l_true); - } - } + add_theory_aware_branching_info(option1, 0.1, l_true); - expr_ref strAst_temp1(mk_concat(strAst, temp1), mgr); - - - //-------------------------------------------------------- - // x cut n - //-------------------------------------------------------- - if (can_two_nodes_eq(x, strAst_temp1)) { - if (!avoidLoopCut || !(has_self_cut(x, n))) { - // break down option 3-2 - expr_ref_vector and_item(mgr); - - expr_ref temp1_y(mk_concat(temp1, y), mgr); - and_item.push_back(ctx.mk_eq_atom(x, strAst_temp1)); ++pos; - and_item.push_back(ctx.mk_eq_atom(n, temp1_y)); ++pos; - - and_item.push_back(ctx.mk_eq_atom(mk_strlen(x), - m_autil.mk_add(mk_strlen(strAst), mk_strlen(temp1)) ) ); ++pos; - - expr_ref option2(mk_and(and_item), mgr); - arrangement_disjunction.push_back(option2); - add_theory_aware_branching_info(option2, 0.1, l_true); - - add_cut_info_merge(temp1, sLevel, x); - add_cut_info_merge(temp1, sLevel, n); + add_cut_info_merge(t1, ctx.get_scope_level(), m); + add_cut_info_merge(t1, ctx.get_scope_level(), y); } else { loopDetected = true; if (m_params.m_FiniteOverlapModels) { @@ -4020,6259 +3230,3742 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { arrangement_disjunction.push_back(tester); add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true); } else { - TRACE("str", tout << "AVOID LOOP: SKIPPED." << std::endl;); + TRACE("str", tout << "AVOID LOOP: SKIPPED" << std::endl;); + TRACE("str", {print_cut_var(m, tout); print_cut_var(y, tout);}); + + if (!overlapAssumptionUsed) { + overlapAssumptionUsed = true; + arrangement_disjunction.push_back(m_theoryStrOverlapAssumption_term); + } + } + } + + // break option 2: + // x = m . t2 + // n = t2 . y + if (!avoidLoopCut || !has_self_cut(x, n)) { + expr_ref_vector and_item(mgr); + // break down option 1-2 + expr_ref m_t2(mk_concat(m, t2), mgr); + expr_ref t2_y(mk_concat(t2, y), mgr); + + and_item.push_back(ctx.mk_eq_atom(x, m_t2)); + and_item.push_back(ctx.mk_eq_atom(n, t2_y)); + + + expr_ref m_plus_t2(m_autil.mk_add(mk_strlen(m), mk_strlen(t2)), mgr); + + and_item.push_back(ctx.mk_eq_atom(mk_strlen(x), m_plus_t2)); + // want len(x) > len(m) and len(n) > len(y) + and_item.push_back( + mgr.mk_not(m_autil.mk_le( + m_autil.mk_add(mk_strlen(x), m_autil.mk_mul(mk_int(-1), mk_strlen(m))), + mk_int(0))) ); + and_item.push_back( + mgr.mk_not(m_autil.mk_le( + m_autil.mk_add(mk_strlen(n), m_autil.mk_mul(mk_int(-1), mk_strlen(y))), + mk_int(0))) ); + + expr_ref option2(mk_and(and_item), mgr); + arrangement_disjunction.push_back(option2); + add_theory_aware_branching_info(option2, 0.1, l_true); + + add_cut_info_merge(t2, ctx.get_scope_level(), x); + add_cut_info_merge(t2, ctx.get_scope_level(), n); + } else { + loopDetected = true; + if (m_params.m_FiniteOverlapModels) { + expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2); + arrangement_disjunction.push_back(tester); + add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true); + } else { + TRACE("str", tout << "AVOID LOOP: SKIPPED" << std::endl;); TRACE("str", {print_cut_var(x, tout); print_cut_var(n, tout);}); if (!overlapAssumptionUsed) { - overlapAssumptionUsed = true; - arrangement_disjunction.push_back(m_theoryStrOverlapAssumption_term); + overlapAssumptionUsed = true; + arrangement_disjunction.push_back(m_theoryStrOverlapAssumption_term); } } } - } + // option 3: + // x = m, y = n + if (can_two_nodes_eq(x, m) && can_two_nodes_eq(y, n)) { + expr_ref_vector and_item(mgr); - if (!arrangement_disjunction.empty()) { - expr_ref implyR(mk_or(arrangement_disjunction), mgr); + and_item.push_back(ctx.mk_eq_atom(x, m)); + and_item.push_back(ctx.mk_eq_atom(y, n)); + and_item.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_strlen(m))); + and_item.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_strlen(n))); - if (m_params.m_StrongArrangements) { - expr_ref ax_lhs(ctx.mk_eq_atom(concatAst1, concatAst2), mgr); - expr_ref ax_strong(ctx.mk_eq_atom(ax_lhs, implyR), mgr); - assert_axiom(ax_strong); + expr_ref option3(mk_and(and_item), mgr); + arrangement_disjunction.push_back(option3); + // prioritize this case, it is easier + add_theory_aware_branching_info(option3, 0.5, l_true); + } + + if (!arrangement_disjunction.empty()) { + expr_ref premise(ctx.mk_eq_atom(concatAst1, concatAst2), mgr); + expr_ref conclusion(mk_or(arrangement_disjunction), mgr); + if (m_params.m_StrongArrangements) { + expr_ref ax_strong(ctx.mk_eq_atom(premise, conclusion), mgr); + assert_axiom(ax_strong); + } else { + assert_implication(premise, conclusion); + } + // assert mutual exclusion between each branch of the arrangement + generate_mutual_exclusion(arrangement_disjunction); } else { - assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); + TRACE("str", tout << "STOP: no split option found for two EQ concats." << std::endl;); } - generate_mutual_exclusion(arrangement_disjunction); + } // (splitType == -1) + } + + /************************************************************* + * Type 2: concat(x, y) = concat(m, "str") + *************************************************************/ + bool theory_str::is_concat_eq_type2(expr * concatAst1, expr * concatAst2) { + expr * v1_arg0 = to_app(concatAst1)->get_arg(0); + expr * v1_arg1 = to_app(concatAst1)->get_arg(1); + expr * v2_arg0 = to_app(concatAst2)->get_arg(0); + expr * v2_arg1 = to_app(concatAst2)->get_arg(1); + + if ((!u.str.is_string(v1_arg0)) && u.str.is_string(v1_arg1) + && (!u.str.is_string(v2_arg0)) && (!u.str.is_string(v2_arg1))) { + return true; + } else if ((!u.str.is_string(v2_arg0)) && u.str.is_string(v2_arg1) + && (!u.str.is_string(v1_arg0)) && (!u.str.is_string(v1_arg1))) { + return true; } else { - TRACE("str", tout << "STOP: should not split two eq. concats" << std::endl;); + return false; } } -} + void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { + ast_manager & mgr = get_manager(); + context & ctx = get_context(); -/************************************************************* - * Type 4: concat("str1", y) = concat("str2", n) - *************************************************************/ -bool theory_str::is_concat_eq_type4(expr * concatAst1, expr * concatAst2) { - expr * v1_arg0 = to_app(concatAst1)->get_arg(0); - expr * v1_arg1 = to_app(concatAst1)->get_arg(1); - expr * v2_arg0 = to_app(concatAst2)->get_arg(0); - expr * v2_arg1 = to_app(concatAst2)->get_arg(1); + bool overlapAssumptionUsed = false; - if (u.str.is_string(v1_arg0) && (!u.str.is_string(v1_arg1)) - && u.str.is_string(v2_arg0) && (!u.str.is_string(v2_arg1))) { - return true; - } else { - return false; - } -} + TRACE("str", tout << "process_concat_eq TYPE 2" << std::endl + << "concatAst1 = " << mk_ismt2_pp(concatAst1, mgr) << std::endl + << "concatAst2 = " << mk_ismt2_pp(concatAst2, mgr) << std::endl; + ); -void theory_str::process_concat_eq_type4(expr * concatAst1, expr * concatAst2) { - ast_manager & mgr = get_manager(); - context & ctx = get_context(); - TRACE("str", tout << "process_concat_eq TYPE 4" << std::endl - << "concatAst1 = " << mk_ismt2_pp(concatAst1, mgr) << std::endl - << "concatAst2 = " << mk_ismt2_pp(concatAst2, mgr) << std::endl; - ); - - if (!u.str.is_concat(to_app(concatAst1))) { - TRACE("str", tout << "concatAst1 is not a concat function" << std::endl;); - return; - } - if (!u.str.is_concat(to_app(concatAst2))) { - TRACE("str", tout << "concatAst2 is not a concat function" << std::endl;); - return; - } - - expr * v1_arg0 = to_app(concatAst1)->get_arg(0); - expr * v1_arg1 = to_app(concatAst1)->get_arg(1); - expr * v2_arg0 = to_app(concatAst2)->get_arg(0); - expr * v2_arg1 = to_app(concatAst2)->get_arg(1); - - expr * str1Ast = v1_arg0; - expr * y = v1_arg1; - expr * str2Ast = v2_arg0; - expr * n = v2_arg1; - - zstring str1Value, str2Value; - u.str.is_string(str1Ast, str1Value); - u.str.is_string(str2Ast, str2Value); - - unsigned int str1Len = str1Value.length(); - unsigned int str2Len = str2Value.length(); - - int commonLen = (str1Len > str2Len) ? str2Len : str1Len; - if (str1Value.extract(0, commonLen) != str2Value.extract(0, commonLen)) { - TRACE("str", tout << "Conflict: " << mk_ismt2_pp(concatAst1, mgr) - << " has no common prefix with " << mk_ismt2_pp(concatAst2, mgr) << std::endl;); - expr_ref toNegate(mgr.mk_not(ctx.mk_eq_atom(concatAst1, concatAst2)), mgr); - assert_axiom(toNegate); - return; - } else { - if (str1Len > str2Len) { - zstring deltaStr = str1Value.extract(str2Len, str1Len - str2Len); - expr_ref tmpAst(mk_concat(mk_string(deltaStr), y), mgr); - if (!in_same_eqc(tmpAst, n)) { - // break down option 4-1 - expr_ref implyR(ctx.mk_eq_atom(n, tmpAst), mgr); - if (m_params.m_StrongArrangements) { - expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr); - assert_axiom(ax_strong); - } else { - assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); - } - } - } else if (str1Len == str2Len) { - if (!in_same_eqc(n, y)) { - //break down option 4-2 - expr_ref implyR(ctx.mk_eq_atom(n, y), mgr); - - if (m_params.m_StrongArrangements) { - expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr); - assert_axiom(ax_strong); - } else { - assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); - } - } - } else { - zstring deltaStr = str2Value.extract(str1Len, str2Len - str1Len); - expr_ref tmpAst(mk_concat(mk_string(deltaStr), n), mgr); - if (!in_same_eqc(y, tmpAst)) { - //break down option 4-3 - expr_ref implyR(ctx.mk_eq_atom(y, tmpAst), mgr); - if (m_params.m_StrongArrangements) { - expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr); - assert_axiom(ax_strong); - } else { - assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); - } - } + if (!u.str.is_concat(to_app(concatAst1))) { + TRACE("str", tout << "concatAst1 is not a concat function" << std::endl;); + return; } - } -} - -/************************************************************* - * case 5: concat(x, "str1") = concat(m, "str2") - *************************************************************/ -bool theory_str::is_concat_eq_type5(expr * concatAst1, expr * concatAst2) { - expr * v1_arg0 = to_app(concatAst1)->get_arg(0); - expr * v1_arg1 = to_app(concatAst1)->get_arg(1); - expr * v2_arg0 = to_app(concatAst2)->get_arg(0); - expr * v2_arg1 = to_app(concatAst2)->get_arg(1); - - if ((!u.str.is_string(v1_arg0)) && u.str.is_string(v1_arg1) - && (!u.str.is_string(v2_arg0)) && u.str.is_string(v2_arg1)) { - return true; - } else { - return false; - } -} - -void theory_str::process_concat_eq_type5(expr * concatAst1, expr * concatAst2) { - ast_manager & mgr = get_manager(); - context & ctx = get_context(); - TRACE("str", tout << "process_concat_eq TYPE 5" << std::endl - << "concatAst1 = " << mk_ismt2_pp(concatAst1, mgr) << std::endl - << "concatAst2 = " << mk_ismt2_pp(concatAst2, mgr) << std::endl; - ); - - if (!u.str.is_concat(to_app(concatAst1))) { - TRACE("str", tout << "concatAst1 is not a concat function" << std::endl;); - return; - } - if (!u.str.is_concat(to_app(concatAst2))) { - TRACE("str", tout << "concatAst2 is not a concat function" << std::endl;); - return; - } - - expr * v1_arg0 = to_app(concatAst1)->get_arg(0); - expr * v1_arg1 = to_app(concatAst1)->get_arg(1); - expr * v2_arg0 = to_app(concatAst2)->get_arg(0); - expr * v2_arg1 = to_app(concatAst2)->get_arg(1); - - expr * x = v1_arg0; - expr * str1Ast = v1_arg1; - expr * m = v2_arg0; - expr * str2Ast = v2_arg1; - - zstring str1Value, str2Value; - u.str.is_string(str1Ast, str1Value); - u.str.is_string(str2Ast, str2Value); - - unsigned int str1Len = str1Value.length(); - unsigned int str2Len = str2Value.length(); - - int cLen = (str1Len > str2Len) ? str2Len : str1Len; - if (str1Value.extract(str1Len - cLen, cLen) != str2Value.extract(str2Len - cLen, cLen)) { - TRACE("str", tout << "Conflict: " << mk_ismt2_pp(concatAst1, mgr) - << " has no common suffix with " << mk_ismt2_pp(concatAst2, mgr) << std::endl;); - expr_ref toNegate(mgr.mk_not(ctx.mk_eq_atom(concatAst1, concatAst2)), mgr); - assert_axiom(toNegate); - return; - } else { - if (str1Len > str2Len) { - zstring deltaStr = str1Value.extract(0, str1Len - str2Len); - expr_ref x_deltaStr(mk_concat(x, mk_string(deltaStr)), mgr); - if (!in_same_eqc(m, x_deltaStr)) { - expr_ref implyR(ctx.mk_eq_atom(m, x_deltaStr), mgr); - if (m_params.m_StrongArrangements) { - expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr); - assert_axiom(ax_strong); - } else { - assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); - } - } - } else if (str1Len == str2Len) { - // test - if (!in_same_eqc(x, m)) { - expr_ref implyR(ctx.mk_eq_atom(x, m), mgr); - if (m_params.m_StrongArrangements) { - expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr); - assert_axiom(ax_strong); - } else { - assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); - } - } - } else { - zstring deltaStr = str2Value.extract(0, str2Len - str1Len); - expr_ref m_deltaStr(mk_concat(m, mk_string(deltaStr)), mgr); - if (!in_same_eqc(x, m_deltaStr)) { - expr_ref implyR(ctx.mk_eq_atom(x, m_deltaStr), mgr); - if (m_params.m_StrongArrangements) { - expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr); - assert_axiom(ax_strong); - } else { - assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); - } - } + if (!u.str.is_concat(to_app(concatAst2))) { + TRACE("str", tout << "concatAst2 is not a concat function" << std::endl;); + return; } - } -} -/************************************************************* - * case 6: concat("str1", y) = concat(m, "str2") - *************************************************************/ -bool theory_str::is_concat_eq_type6(expr * concatAst1, expr * concatAst2) { - expr * v1_arg0 = to_app(concatAst1)->get_arg(0); - expr * v1_arg1 = to_app(concatAst1)->get_arg(1); - expr * v2_arg0 = to_app(concatAst2)->get_arg(0); - expr * v2_arg1 = to_app(concatAst2)->get_arg(1); + expr * x = NULL; + expr * y = NULL; + expr * strAst = NULL; + expr * m = NULL; - if (u.str.is_string(v1_arg0) && (!u.str.is_string(v1_arg1)) - && (!u.str.is_string(v2_arg0)) && u.str.is_string(v2_arg1)) { - return true; - } else if (u.str.is_string(v2_arg0) && (!u.str.is_string(v2_arg1)) - && (!u.str.is_string(v1_arg0)) && u.str.is_string(v1_arg1)) { - return true; - } else { - return false; - } -} + expr * v1_arg0 = to_app(concatAst1)->get_arg(0); + expr * v1_arg1 = to_app(concatAst1)->get_arg(1); + expr * v2_arg0 = to_app(concatAst2)->get_arg(0); + expr * v2_arg1 = to_app(concatAst2)->get_arg(1); -void theory_str::process_concat_eq_type6(expr * concatAst1, expr * concatAst2) { - ast_manager & mgr = get_manager(); - context & ctx = get_context(); - TRACE("str", tout << "process_concat_eq TYPE 6" << std::endl - << "concatAst1 = " << mk_ismt2_pp(concatAst1, mgr) << std::endl - << "concatAst2 = " << mk_ismt2_pp(concatAst2, mgr) << std::endl; - ); + if (u.str.is_string(v1_arg1) && !u.str.is_string(v2_arg1)) { + m = v1_arg0; + strAst = v1_arg1; + x = v2_arg0; + y = v2_arg1; + } else { + m = v2_arg0; + strAst = v2_arg1; + x = v1_arg0; + y = v1_arg1; + } - if (!u.str.is_concat(to_app(concatAst1))) { - TRACE("str", tout << "concatAst1 is not a concat function" << std::endl;); - return; - } - if (!u.str.is_concat(to_app(concatAst2))) { - TRACE("str", tout << "concatAst2 is not a concat function" << std::endl;); - return; - } + zstring strValue; + u.str.is_string(strAst, strValue); - expr * v1_arg0 = to_app(concatAst1)->get_arg(0); - expr * v1_arg1 = to_app(concatAst1)->get_arg(1); - expr * v2_arg0 = to_app(concatAst2)->get_arg(0); - expr * v2_arg1 = to_app(concatAst2)->get_arg(1); + rational x_len, y_len, m_len, str_len; + bool x_len_exists = get_len_value(x, x_len); + bool y_len_exists = get_len_value(y, y_len); + bool m_len_exists = get_len_value(m, m_len); + bool str_len_exists = true; + str_len = rational(strValue.length()); + // setup - expr * str1Ast = NULL; - expr * y = NULL; - expr * m = NULL; - expr * str2Ast = NULL; + expr * xorFlag = NULL; + expr * temp1 = NULL; + std::pair key1(concatAst1, concatAst2); + std::pair key2(concatAst2, concatAst1); - if (u.str.is_string(v1_arg0)) { - str1Ast = v1_arg0; - y = v1_arg1; - m = v2_arg0; - str2Ast = v2_arg1; - } else { - str1Ast = v2_arg0; - y = v2_arg1; - m = v1_arg0; - str2Ast = v1_arg1; - } + // check the entries in this map to make sure they're still in scope + // before we use them. - zstring str1Value, str2Value; - u.str.is_string(str1Ast, str1Value); - u.str.is_string(str2Ast, str2Value); + std::map, std::map >::iterator entry1 = varForBreakConcat.find(key1); + std::map, std::map >::iterator entry2 = varForBreakConcat.find(key2); - unsigned int str1Len = str1Value.length(); - unsigned int str2Len = str2Value.length(); + // prevent checking scope for the XOR term, as it's always in the same scope as the split var - //---------------------------------------- - //(a) |---str1---|----y----| - // |--m--|-----str2-----| - // - //(b) |---str1---|----y----| - // |-----m----|--str2---| - // - //(c) |---str1---|----y----| - // |------m------|-str2-| - //---------------------------------------- - - std::list overlapLen; - overlapLen.push_back(0); - - for (unsigned int i = 1; i <= str1Len && i <= str2Len; i++) { - if (str1Value.extract(str1Len - i, i) == str2Value.extract(0, i)) - overlapLen.push_back(i); - } - - //---------------------------------------------------------------- - expr * commonVar = NULL; - expr * xorFlag = NULL; - std::pair key1(concatAst1, concatAst2); - std::pair key2(concatAst2, concatAst1); - - // check the entries in this map to make sure they're still in scope - // before we use them. - - std::map, std::map >::iterator entry1 = varForBreakConcat.find(key1); - std::map, std::map >::iterator entry2 = varForBreakConcat.find(key2); - - bool entry1InScope; - if (entry1 == varForBreakConcat.end()) { - entry1InScope = false; - } else { - if (internal_variable_set.find((entry1->second)[0]) == internal_variable_set.end() - /* || internal_variable_set.find((entry1->second)[1]) == internal_variable_set.end() */) { + bool entry1InScope; + if (entry1 == varForBreakConcat.end()) { entry1InScope = false; } else { - entry1InScope = true; + if (internal_variable_set.find((entry1->second)[0]) == internal_variable_set.end() + /*|| internal_variable_set.find((entry1->second)[1]) == internal_variable_set.end()*/ + ) { + entry1InScope = false; + } else { + entry1InScope = true; + } } - } - bool entry2InScope; - if (entry2 == varForBreakConcat.end()) { - entry2InScope = false; - } else { - if (internal_variable_set.find((entry2->second)[0]) == internal_variable_set.end() - /* || internal_variable_set.find((entry2->second)[1]) == internal_variable_set.end() */) { + bool entry2InScope; + if (entry2 == varForBreakConcat.end()) { entry2InScope = false; } else { - entry2InScope = true; + if (internal_variable_set.find((entry2->second)[0]) == internal_variable_set.end() + /*|| internal_variable_set.find((entry2->second)[1]) == internal_variable_set.end()*/ + ) { + entry2InScope = false; + } else { + entry2InScope = true; + } } - } - TRACE("str", tout << "entry 1 " << (entry1InScope ? "in scope" : "not in scope") << std::endl - << "entry 2 " << (entry2InScope ? "in scope" : "not in scope") << std::endl;); + TRACE("str", tout << "entry 1 " << (entry1InScope ? "in scope" : "not in scope") << std::endl + << "entry 2 " << (entry2InScope ? "in scope" : "not in scope") << std::endl;); - if (!entry1InScope && !entry2InScope) { - commonVar = mk_nonempty_str_var(); - xorFlag = mk_internal_xor_var(); - varForBreakConcat[key1][0] = commonVar; - varForBreakConcat[key1][1] = xorFlag; - } else { - if (entry1InScope) { - commonVar = (entry1->second)[0]; - xorFlag = (entry1->second)[1]; + + if (!entry1InScope && !entry2InScope) { + temp1 = mk_nonempty_str_var(); + xorFlag = mk_internal_xor_var(); + varForBreakConcat[key1][0] = temp1; + varForBreakConcat[key1][1] = xorFlag; } else { - commonVar = (entry2->second)[0]; - xorFlag = (entry2->second)[1]; - } - refresh_theory_var(commonVar); - add_nonempty_constraint(commonVar); - } - - bool overlapAssumptionUsed = false; - - expr_ref_vector arrangement_disjunction(mgr); - int pos = 1; - - if (!avoidLoopCut || !has_self_cut(m, y)) { - expr_ref_vector and_item(mgr); - - expr_ref str1_commonVar(mk_concat(str1Ast, commonVar), mgr); - and_item.push_back(ctx.mk_eq_atom(m, str1_commonVar)); - pos += 1; - - expr_ref commonVar_str2(mk_concat(commonVar, str2Ast), mgr); - and_item.push_back(ctx.mk_eq_atom(y, commonVar_str2)); - pos += 1; - - and_item.push_back(ctx.mk_eq_atom(mk_strlen(m), - m_autil.mk_add(mk_strlen(str1Ast), mk_strlen(commonVar)) )); - pos += 1; - - // addItems[0] = mk_length(t, commonVar); - // addItems[1] = mk_length(t, str2Ast); - // and_item[pos++] = Z3_mk_eq(ctx, or_item[option], Z3_mk_eq(ctx, mk_length(t, y), Z3_mk_add(ctx, 2, addItems))); - - expr_ref option1(mk_and(and_item), mgr); - arrangement_disjunction.push_back(option1); - add_theory_aware_branching_info(option1, 0.1, l_true); - } else { - loopDetected = true; - - if (m_params.m_FiniteOverlapModels) { - expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2); - arrangement_disjunction.push_back(tester); - add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true); - } else { - TRACE("str", tout << "AVOID LOOP: SKIPPED." << std::endl;); - TRACE("str", print_cut_var(m, tout); print_cut_var(y, tout);); - - // only add the overlap assumption one time - if (!overlapAssumptionUsed) { - arrangement_disjunction.push_back(m_theoryStrOverlapAssumption_term); - overlapAssumptionUsed = true; + if (entry1InScope) { + temp1 = varForBreakConcat[key1][0]; + xorFlag = varForBreakConcat[key1][1]; + } else if (entry2InScope) { + temp1 = varForBreakConcat[key2][0]; + xorFlag = varForBreakConcat[key2][1]; } + refresh_theory_var(temp1); + add_nonempty_constraint(temp1); } - } - for (std::list::iterator itor = overlapLen.begin(); itor != overlapLen.end(); itor++) { - unsigned int overLen = *itor; - zstring prefix = str1Value.extract(0, str1Len - overLen); - zstring suffix = str2Value.extract(overLen, str2Len - overLen); - - expr_ref_vector and_item(mgr); - - expr_ref prefixAst(mk_string(prefix), mgr); - expr_ref x_eq_prefix(ctx.mk_eq_atom(m, prefixAst), mgr); - and_item.push_back(x_eq_prefix); - pos += 1; - - and_item.push_back( - ctx.mk_eq_atom(mk_strlen(m), mk_strlen(prefixAst))); - pos += 1; - - // adding length constraint for _ = constStr seems slowing things down. - - expr_ref suffixAst(mk_string(suffix), mgr); - expr_ref y_eq_suffix(ctx.mk_eq_atom(y, suffixAst), mgr); - and_item.push_back(y_eq_suffix); - pos += 1; - - and_item.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_strlen(suffixAst))); - pos += 1; - - expr_ref option2(mk_and(and_item), mgr); - arrangement_disjunction.push_back(option2); - double priority; - // prefer the option "str1" = x - if (prefix == str1Value) { - priority = 0.5; - } else { - priority = 0.1; + int splitType = -1; + if (x_len_exists && m_len_exists) { + if (x_len < m_len) + splitType = 0; + else if (x_len == m_len) + splitType = 1; + else + splitType = 2; } - add_theory_aware_branching_info(option2, priority, l_true); - } - - // case 6: concat("str1", y) = concat(m, "str2") - - expr_ref implyR(mk_or(arrangement_disjunction), mgr); - - if (m_params.m_StrongArrangements) { - expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr); - assert_axiom(ax_strong); - } else { - assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); - } - generate_mutual_exclusion(arrangement_disjunction); -} - -void theory_str::process_unroll_eq_const_str(expr * unrollFunc, expr * constStr) { - ast_manager & m = get_manager(); - - if (!u.re.is_unroll(to_app(unrollFunc))) { - return; - } - if (!u.str.is_string(constStr)) { - return; - } - - expr * funcInUnroll = to_app(unrollFunc)->get_arg(0); - zstring strValue; - u.str.is_string(constStr, strValue); - - TRACE("str", tout << "unrollFunc: " << mk_pp(unrollFunc, m) << std::endl - << "constStr: " << mk_pp(constStr, m) << std::endl;); - - if (strValue == "") { - return; - } - - if (u.re.is_to_re(to_app(funcInUnroll))) { - unroll_str2reg_constStr(unrollFunc, constStr); - return; - } -} - -void theory_str::process_concat_eq_unroll(expr * concat, expr * unroll) { - context & ctx = get_context(); - ast_manager & mgr = get_manager(); - - TRACE("str", tout << "concat = " << mk_pp(concat, mgr) << ", unroll = " << mk_pp(unroll, mgr) << std::endl;); - - std::pair key = std::make_pair(concat, unroll); - expr_ref toAssert(mgr); - - if (concat_eq_unroll_ast_map.find(key) == concat_eq_unroll_ast_map.end()) { - expr_ref arg1(to_app(concat)->get_arg(0), mgr); - expr_ref arg2(to_app(concat)->get_arg(1), mgr); - expr_ref r1(to_app(unroll)->get_arg(0), mgr); - expr_ref t1(to_app(unroll)->get_arg(1), mgr); - - expr_ref v1(mk_regex_rep_var(), mgr); - expr_ref v2(mk_regex_rep_var(), mgr); - expr_ref v3(mk_regex_rep_var(), mgr); - expr_ref v4(mk_regex_rep_var(), mgr); - expr_ref v5(mk_regex_rep_var(), mgr); - - expr_ref t2(mk_unroll_bound_var(), mgr); - expr_ref t3(mk_unroll_bound_var(), mgr); - expr_ref emptyStr(mk_string(""), mgr); - - expr_ref unroll1(mk_unroll(r1, t2), mgr); - expr_ref unroll2(mk_unroll(r1, t3), mgr); - - expr_ref op0(ctx.mk_eq_atom(t1, mk_int(0)), mgr); - expr_ref op1(m_autil.mk_ge(t1, mk_int(1)), mgr); - - expr_ref_vector op1Items(mgr); - expr_ref_vector op2Items(mgr); - - op1Items.push_back(ctx.mk_eq_atom(arg1, emptyStr)); - op1Items.push_back(ctx.mk_eq_atom(arg2, emptyStr)); - op1Items.push_back(ctx.mk_eq_atom(mk_strlen(arg1), mk_int(0))); - op1Items.push_back(ctx.mk_eq_atom(mk_strlen(arg2), mk_int(0))); - expr_ref opAnd1(ctx.mk_eq_atom(op0, mk_and(op1Items)), mgr); - - expr_ref v1v2(mk_concat(v1, v2), mgr); - op2Items.push_back(ctx.mk_eq_atom(arg1, v1v2)); - op2Items.push_back(ctx.mk_eq_atom(mk_strlen(arg1), m_autil.mk_add(mk_strlen(v1), mk_strlen(v2)))); - expr_ref v3v4(mk_concat(v3, v4), mgr); - op2Items.push_back(ctx.mk_eq_atom(arg2, v3v4)); - op2Items.push_back(ctx.mk_eq_atom(mk_strlen(arg2), m_autil.mk_add(mk_strlen(v3), mk_strlen(v4)))); - - op2Items.push_back(ctx.mk_eq_atom(v1, unroll1)); - op2Items.push_back(ctx.mk_eq_atom(mk_strlen(v1), mk_strlen(unroll1))); - op2Items.push_back(ctx.mk_eq_atom(v4, unroll2)); - op2Items.push_back(ctx.mk_eq_atom(mk_strlen(v4), mk_strlen(unroll2))); - expr_ref v2v3(mk_concat(v2, v3), mgr); - op2Items.push_back(ctx.mk_eq_atom(v5, v2v3)); - reduce_virtual_regex_in(v5, r1, op2Items); - op2Items.push_back(ctx.mk_eq_atom(mk_strlen(v5), m_autil.mk_add(mk_strlen(v2), mk_strlen(v3)))); - op2Items.push_back(ctx.mk_eq_atom(m_autil.mk_add(t2, t3), m_autil.mk_add(t1, mk_int(-1)))); - expr_ref opAnd2(ctx.mk_eq_atom(op1, mk_and(op2Items)), mgr); - - toAssert = mgr.mk_and(opAnd1, opAnd2); - m_trail.push_back(toAssert); - concat_eq_unroll_ast_map[key] = toAssert; - } else { - toAssert = concat_eq_unroll_ast_map[key]; - } - - assert_axiom(toAssert); -} - -void theory_str::unroll_str2reg_constStr(expr * unrollFunc, expr * eqConstStr) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - - expr * str2RegFunc = to_app(unrollFunc)->get_arg(0); - expr * strInStr2RegFunc = to_app(str2RegFunc)->get_arg(0); - expr * oriCnt = to_app(unrollFunc)->get_arg(1); - - zstring strValue; - u.str.is_string(eqConstStr, strValue); - zstring regStrValue; - u.str.is_string(strInStr2RegFunc, regStrValue); - unsigned int strLen = strValue.length(); - unsigned int regStrLen = regStrValue.length(); - SASSERT(regStrLen != 0); // this should never occur -- the case for empty string is handled elsewhere - unsigned int cnt = strLen / regStrLen; - - expr_ref implyL(ctx.mk_eq_atom(unrollFunc, eqConstStr), m); - expr_ref implyR1(ctx.mk_eq_atom(oriCnt, mk_int(cnt)), m); - expr_ref implyR2(ctx.mk_eq_atom(mk_strlen(unrollFunc), mk_int(strLen)), m); - expr_ref axiomRHS(m.mk_and(implyR1, implyR2), m); - SASSERT(implyL); - SASSERT(axiomRHS); - assert_implication(implyL, axiomRHS); -} - -/* - * Look through the equivalence class of n to find a string constant. - * Return that constant if it is found, and set hasEqcValue to true. - * Otherwise, return n, and set hasEqcValue to false. - */ - -expr * theory_str::get_eqc_value(expr * n, bool & hasEqcValue) { - return z3str2_get_eqc_value(n, hasEqcValue); -} - - -// Simulate the behaviour of get_eqc_value() from Z3str2. -// We only check m_find for a string constant. - -expr * theory_str::z3str2_get_eqc_value(expr * n , bool & hasEqcValue) { - expr * curr = n; - do { - if (u.str.is_string(curr)) { - hasEqcValue = true; - return curr; + if (splitType == -1 && y_len_exists && str_len_exists) { + if (y_len > str_len) + splitType = 0; + else if (y_len == str_len) + splitType = 1; + else + splitType = 2; } - curr = get_eqc_next(curr); - } while (curr != n); - hasEqcValue = false; - return n; -} -// from Z3: theory_seq.cpp + TRACE("str", tout << "Split type " << splitType << std::endl;); -static theory_mi_arith* get_th_arith(context& ctx, theory_id afid, expr* e) { - theory* th = ctx.get_theory(afid); - if (th && ctx.e_internalized(e)) { - return dynamic_cast(th); - } - else { - return 0; - } -} + // Provide fewer split options when length information is available. -bool theory_str::get_value(expr* e, rational& val) const { - if (opt_DisableIntegerTheoryIntegration) { - TRACE("str", tout << "WARNING: integer theory integration disabled" << std::endl;); - return false; - } + if (splitType == 0) { + // M cuts Y + // | x | y | + // | m | str | + expr_ref temp1_strAst(mk_concat(temp1, strAst), mgr); + if (can_two_nodes_eq(y, temp1_strAst)) { + expr_ref_vector l_items(mgr); + l_items.push_back(ctx.mk_eq_atom(concatAst1, concatAst2)); - context& ctx = get_context(); - ast_manager & m = get_manager(); - theory_mi_arith* tha = get_th_arith(ctx, m_autil.get_family_id(), e); - if (!tha) { - return false; - } - TRACE("str", tout << "checking eqc of " << mk_pp(e, m) << " for arithmetic value" << std::endl;); - expr_ref _val(m); - enode * en_e = ctx.get_enode(e); - enode * it = en_e; - do { - if (m_autil.is_numeral(it->get_owner(), val) && val.is_int()) { - // found an arithmetic term - TRACE("str", tout << mk_pp(it->get_owner(), m) << " is an integer ( ~= " << val << " )" - << std::endl;); - return true; - } else { - TRACE("str", tout << mk_pp(it->get_owner(), m) << " not a numeral" << std::endl;); - } - it = it->get_next(); - } while (it != en_e); - TRACE("str", tout << "no arithmetic values found in eqc" << std::endl;); - return false; -} + expr_ref_vector r_items(mgr); + expr_ref x_temp1(mk_concat(x, temp1), mgr); + r_items.push_back(ctx.mk_eq_atom(m, x_temp1)); + r_items.push_back(ctx.mk_eq_atom(y, temp1_strAst)); -bool theory_str::lower_bound(expr* _e, rational& lo) { - if (opt_DisableIntegerTheoryIntegration) { - TRACE("str", tout << "WARNING: integer theory integration disabled" << std::endl;); - return false; - } - - context& ctx = get_context(); - ast_manager & m = get_manager(); - theory_mi_arith* tha = get_th_arith(ctx, m_autil.get_family_id(), _e); - expr_ref _lo(m); - if (!tha || !tha->get_lower(ctx.get_enode(_e), _lo)) return false; - return m_autil.is_numeral(_lo, lo) && lo.is_int(); -} - -bool theory_str::upper_bound(expr* _e, rational& hi) { - if (opt_DisableIntegerTheoryIntegration) { - TRACE("str", tout << "WARNING: integer theory integration disabled" << std::endl;); - return false; - } - - context& ctx = get_context(); - ast_manager & m = get_manager(); - theory_mi_arith* tha = get_th_arith(ctx, m_autil.get_family_id(), _e); - expr_ref _hi(m); - if (!tha || !tha->get_upper(ctx.get_enode(_e), _hi)) return false; - return m_autil.is_numeral(_hi, hi) && hi.is_int(); -} - -bool theory_str::get_len_value(expr* e, rational& val) { - if (opt_DisableIntegerTheoryIntegration) { - TRACE("str", tout << "WARNING: integer theory integration disabled" << std::endl;); - return false; - } - - context& ctx = get_context(); - ast_manager & m = get_manager(); - - theory* th = ctx.get_theory(m_autil.get_family_id()); - if (!th) { - TRACE("str", tout << "oops, can't get m_autil's theory" << std::endl;); - return false; - } - theory_mi_arith* tha = dynamic_cast(th); - if (!tha) { - TRACE("str", tout << "oops, can't cast to theory_mi_arith" << std::endl;); - return false; - } - - TRACE("str", tout << "checking len value of " << mk_ismt2_pp(e, m) << std::endl;); - - rational val1; - expr_ref len(m), len_val(m); - expr* e1, *e2; - ptr_vector todo; - todo.push_back(e); - val.reset(); - while (!todo.empty()) { - expr* c = todo.back(); - todo.pop_back(); - if (u.str.is_concat(to_app(c))) { - e1 = to_app(c)->get_arg(0); - e2 = to_app(c)->get_arg(1); - todo.push_back(e1); - todo.push_back(e2); - } - else if (u.str.is_string(to_app(c))) { - zstring tmp; - u.str.is_string(to_app(c), tmp); - unsigned int sl = tmp.length(); - val += rational(sl); - } - else { - len = mk_strlen(c); - - // debugging - TRACE("str", { - tout << mk_pp(len, m) << ":" << std::endl - << (ctx.is_relevant(len.get()) ? "relevant" : "not relevant") << std::endl - << (ctx.e_internalized(len) ? "internalized" : "not internalized") << std::endl - ; - if (ctx.e_internalized(len)) { - enode * e_len = ctx.get_enode(len); - tout << "has " << e_len->get_num_th_vars() << " theory vars" << std::endl; - - // eqc debugging - { - tout << "dump equivalence class of " << mk_pp(len, get_manager()) << std::endl; - enode * nNode = ctx.get_enode(len); - enode * eqcNode = nNode; - do { - app * ast = eqcNode->get_owner(); - tout << mk_pp(ast, get_manager()) << std::endl; - eqcNode = eqcNode->get_next(); - } while (eqcNode != nNode); - } - } - }); - - if (ctx.e_internalized(len) && get_value(len, val1)) { - val += val1; - TRACE("str", tout << "integer theory: subexpression " << mk_ismt2_pp(len, m) << " has length " << val1 << std::endl;); - } - else { - TRACE("str", tout << "integer theory: subexpression " << mk_ismt2_pp(len, m) << " has no length assignment; bailing out" << std::endl;); - return false; - } - } - } - - TRACE("str", tout << "length of " << mk_ismt2_pp(e, m) << " is " << val << std::endl;); - return val.is_int(); -} - -/* - * Decide whether n1 and n2 are already in the same equivalence class. - * This only checks whether the core considers them to be equal; - * they may not actually be equal. - */ -bool theory_str::in_same_eqc(expr * n1, expr * n2) { - if (n1 == n2) return true; - context & ctx = get_context(); - ast_manager & m = get_manager(); - - // similar to get_eqc_value(), make absolutely sure - // that we've set this up properly for the context - - if (!ctx.e_internalized(n1)) { - TRACE("str", tout << "WARNING: expression " << mk_ismt2_pp(n1, m) << " was not internalized" << std::endl;); - ctx.internalize(n1, false); - } - if (!ctx.e_internalized(n2)) { - TRACE("str", tout << "WARNING: expression " << mk_ismt2_pp(n2, m) << " was not internalized" << std::endl;); - ctx.internalize(n2, false); - } - - expr * curr = get_eqc_next(n1); - while (curr != n1) { - if (curr == n2) - return true; - curr = get_eqc_next(curr); - } - return false; -} - -expr * theory_str::collect_eq_nodes(expr * n, expr_ref_vector & eqcSet) { - context & ctx = get_context(); - expr * constStrNode = NULL; - - expr * ex = n; - do { - if (u.str.is_string(to_app(ex))) { - constStrNode = ex; - } - eqcSet.push_back(ex); - - ex = get_eqc_next(ex); - } while (ex != n); - return constStrNode; -} - -/* - * Collect constant strings (from left to right) in an AST node. - */ -void theory_str::get_const_str_asts_in_node(expr * node, expr_ref_vector & astList) { - ast_manager & m = get_manager(); - if (u.str.is_string(node)) { - astList.push_back(node); - //} else if (getNodeType(t, node) == my_Z3_Func) { - } else if (is_app(node)) { - app * func_app = to_app(node); - unsigned int argCount = func_app->get_num_args(); - for (unsigned int i = 0; i < argCount; i++) { - expr * argAst = func_app->get_arg(i); - get_const_str_asts_in_node(argAst, astList); - } - } -} - -void theory_str::check_contain_by_eqc_val(expr * varNode, expr * constNode) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - - TRACE("str", tout << "varNode = " << mk_pp(varNode, m) << ", constNode = " << mk_pp(constNode, m) << std::endl;); - - expr_ref_vector litems(m); - - if (contain_pair_idx_map.find(varNode) != contain_pair_idx_map.end()) { - std::set >::iterator itor1 = contain_pair_idx_map[varNode].begin(); - for (; itor1 != contain_pair_idx_map[varNode].end(); ++itor1) { - expr * strAst = itor1->first; - expr * substrAst = itor1->second; - - expr * boolVar; - if (!contain_pair_bool_map.find(strAst, substrAst, boolVar)) { - TRACE("str", tout << "warning: no entry for boolVar in contain_pair_bool_map" << std::endl;); - } - // boolVar is actually a Contains term - app * containsApp = to_app(boolVar); - - // we only want to inspect the Contains terms where either of strAst or substrAst - // are equal to varNode. - - TRACE("t_str_detail", tout << "considering Contains with strAst = " << mk_pp(strAst, m) << ", substrAst = " << mk_pp(substrAst, m) << "..." << std::endl;); - - if (varNode != strAst && varNode != substrAst) { - TRACE("str", tout << "varNode not equal to strAst or substrAst, skip" << std::endl;); - continue; - } - TRACE("str", tout << "varNode matched one of strAst or substrAst. Continuing" << std::endl;); - - // varEqcNode is str - if (strAst == varNode) { - expr_ref implyR(m); - litems.reset(); - - if (strAst != constNode) { - litems.push_back(ctx.mk_eq_atom(strAst, constNode)); - } - zstring strConst; - u.str.is_string(constNode, strConst); - bool subStrHasEqcValue = false; - expr * substrValue = get_eqc_value(substrAst, subStrHasEqcValue); - if (substrValue != substrAst) { - litems.push_back(ctx.mk_eq_atom(substrAst, substrValue)); + if (x_len_exists && m_len_exists) { + l_items.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_int(x_len))); + l_items.push_back(ctx.mk_eq_atom(mk_strlen(m), mk_int(m_len))); + rational m_sub_x = (m_len - x_len); + r_items.push_back(ctx.mk_eq_atom(mk_strlen(temp1), mk_int(m_sub_x))); + } else { + l_items.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_int(y_len))); + l_items.push_back(ctx.mk_eq_atom(mk_strlen(strAst), mk_int(str_len))); + rational y_sub_str = (y_len - str_len); + r_items.push_back(ctx.mk_eq_atom(mk_strlen(temp1), mk_int(y_sub_str))); } - if (subStrHasEqcValue) { - // subStr has an eqc constant value - zstring subStrConst; - u.str.is_string(substrValue, subStrConst); + expr_ref ax_l(mk_and(l_items), mgr); + expr_ref ax_r(mk_and(r_items), mgr); - TRACE("t_str_detail", tout << "strConst = " << strConst << ", subStrConst = " << subStrConst << "\n";); + if (!avoidLoopCut || !(has_self_cut(m, y))) { + // break down option 2-1 + add_cut_info_merge(temp1, sLevel, y); + add_cut_info_merge(temp1, sLevel, m); - if (strConst.contains(subStrConst)) { - //implyR = ctx.mk_eq(ctx, boolVar, Z3_mk_true(ctx)); - implyR = boolVar; + if (m_params.m_StrongArrangements) { + expr_ref ax_strong(ctx.mk_eq_atom(ax_l, ax_r), mgr); + assert_axiom(ax_strong); } else { - //implyR = Z3_mk_eq(ctx, boolVar, Z3_mk_false(ctx)); - implyR = m.mk_not(boolVar); + assert_implication(ax_l, ax_r); } } else { - // ------------------------------------------------------------------------------------------------ - // subStr doesn't have an eqc contant value - // however, subStr equals to some concat(arg_1, arg_2, ..., arg_n) - // if arg_j is a constant and is not a part of the strConst, it's sure that the contains is false - // ** This check is needed here because the "strConst" and "strAst" may not be in a same eqc yet - // ------------------------------------------------------------------------------------------------ - // collect eqc concat - std::set eqcConcats; - get_concats_in_eqc(substrAst, eqcConcats); - for (std::set::iterator concatItor = eqcConcats.begin(); - concatItor != eqcConcats.end(); concatItor++) { - expr_ref_vector constList(m); - bool counterEgFound = false; - // get constant strings in concat - expr * aConcat = *concatItor; - get_const_str_asts_in_node(aConcat, constList); - for (expr_ref_vector::iterator cstItor = constList.begin(); - cstItor != constList.end(); cstItor++) { - zstring pieceStr; - u.str.is_string(*cstItor, pieceStr); - if (!strConst.contains(pieceStr)) { - counterEgFound = true; - if (aConcat != substrAst) { - litems.push_back(ctx.mk_eq_atom(substrAst, aConcat)); - } - //implyR = Z3_mk_eq(ctx, boolVar, Z3_mk_false(ctx)); - implyR = m.mk_not(boolVar); - break; + loopDetected = true; + + if (m_params.m_FiniteOverlapModels) { + expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2); + assert_implication(ax_l, tester); + add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true); + } else { + TRACE("str", tout << "AVOID LOOP: SKIP" << std::endl;); + TRACE("str", {print_cut_var(m, tout); print_cut_var(y, tout);}); + + if (!overlapAssumptionUsed) { + overlapAssumptionUsed = true; + assert_implication(ax_l, m_theoryStrOverlapAssumption_term); + } + } + } + } + } else if (splitType == 1) { + // | x | y | + // | m | str | + expr_ref ax_l1(ctx.mk_eq_atom(concatAst1, concatAst2), mgr); + expr_ref ax_l2(mgr.mk_or( + ctx.mk_eq_atom(mk_strlen(x), mk_strlen(m)), + ctx.mk_eq_atom(mk_strlen(y), mk_strlen(strAst))), mgr); + expr_ref ax_l(mgr.mk_and(ax_l1, ax_l2), mgr); + expr_ref ax_r(mgr.mk_and(ctx.mk_eq_atom(x, m), ctx.mk_eq_atom(y, strAst)), mgr); + assert_implication(ax_l, ax_r); + } else if (splitType == 2) { + // m cut y, + // | x | y | + // | m | str | + rational lenDelta; + expr_ref_vector l_items(mgr); + l_items.push_back(ctx.mk_eq_atom(concatAst1, concatAst2)); + if (x_len_exists && m_len_exists) { + l_items.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_int(x_len))); + l_items.push_back(ctx.mk_eq_atom(mk_strlen(m), mk_int(m_len))); + lenDelta = x_len - m_len; + } else { + l_items.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_int(y_len))); + lenDelta = str_len - y_len; + } + TRACE("str", + tout + << "xLen? " << (x_len_exists ? "yes" : "no") << std::endl + << "mLen? " << (m_len_exists ? "yes" : "no") << std::endl + << "yLen? " << (y_len_exists ? "yes" : "no") << std::endl + << "xLen = " << x_len.to_string() << std::endl + << "yLen = " << y_len.to_string() << std::endl + << "mLen = " << m_len.to_string() << std::endl + << "strLen = " << str_len.to_string() << std::endl + << "lenDelta = " << lenDelta.to_string() << std::endl + << "strValue = \"" << strValue << "\" (len=" << strValue.length() << ")" << "\n" + ; + ); + + zstring part1Str = strValue.extract(0, lenDelta.get_unsigned()); + zstring part2Str = strValue.extract(lenDelta.get_unsigned(), strValue.length() - lenDelta.get_unsigned()); + + expr_ref prefixStr(mk_string(part1Str), mgr); + expr_ref x_concat(mk_concat(m, prefixStr), mgr); + expr_ref cropStr(mk_string(part2Str), mgr); + + if (can_two_nodes_eq(x, x_concat) && can_two_nodes_eq(y, cropStr)) { + expr_ref_vector r_items(mgr); + r_items.push_back(ctx.mk_eq_atom(x, x_concat)); + r_items.push_back(ctx.mk_eq_atom(y, cropStr)); + expr_ref ax_l(mk_and(l_items), mgr); + expr_ref ax_r(mk_and(r_items), mgr); + + if (m_params.m_StrongArrangements) { + expr_ref ax_strong(ctx.mk_eq_atom(ax_l, ax_r), mgr); + assert_axiom(ax_strong); + } else { + assert_implication(ax_l, ax_r); + } + } else { + // negate! It's impossible to split str with these lengths + TRACE("str", tout << "CONFLICT: Impossible to split str with these lengths." << std::endl;); + expr_ref ax_l(mk_and(l_items), mgr); + assert_axiom(mgr.mk_not(ax_l)); + } + } else { + // Split type -1: no idea about the length... + expr_ref_vector arrangement_disjunction(mgr); + + expr_ref temp1_strAst(mk_concat(temp1, strAst), mgr); + + // m cuts y + if (can_two_nodes_eq(y, temp1_strAst)) { + if (!avoidLoopCut || !has_self_cut(m, y)) { + // break down option 2-1 + expr_ref_vector and_item(mgr); + + expr_ref x_temp1(mk_concat(x, temp1), mgr); + and_item.push_back(ctx.mk_eq_atom(m, x_temp1)); + and_item.push_back(ctx.mk_eq_atom(y, temp1_strAst)); + + and_item.push_back(ctx.mk_eq_atom(mk_strlen(m), + m_autil.mk_add(mk_strlen(x), mk_strlen(temp1)))); + + expr_ref option1(mk_and(and_item), mgr); + arrangement_disjunction.push_back(option1); + add_theory_aware_branching_info(option1, 0.1, l_true); + add_cut_info_merge(temp1, ctx.get_scope_level(), y); + add_cut_info_merge(temp1, ctx.get_scope_level(), m); + } else { + loopDetected = true; + if (m_params.m_FiniteOverlapModels) { + expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2); + arrangement_disjunction.push_back(tester); + add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true); + } else { + TRACE("str", tout << "AVOID LOOP: SKIPPED" << std::endl;); + TRACE("str", {print_cut_var(m, tout); print_cut_var(y, tout);}); + + if (!overlapAssumptionUsed) { + overlapAssumptionUsed = true; + arrangement_disjunction.push_back(m_theoryStrOverlapAssumption_term); + } + } + } + } + + for (unsigned int i = 0; i <= strValue.length(); ++i) { + zstring part1Str = strValue.extract(0, i); + zstring part2Str = strValue.extract(i, strValue.length() - i); + expr_ref prefixStr(mk_string(part1Str), mgr); + expr_ref x_concat(mk_concat(m, prefixStr), mgr); + expr_ref cropStr(mk_string(part2Str), mgr); + if (can_two_nodes_eq(x, x_concat) && can_two_nodes_eq(y, cropStr)) { + // break down option 2-2 + expr_ref_vector and_item(mgr); + and_item.push_back(ctx.mk_eq_atom(x, x_concat)); + and_item.push_back(ctx.mk_eq_atom(y, cropStr)); + and_item.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_int(part2Str.length()))); + expr_ref option2(mk_and(and_item), mgr); + arrangement_disjunction.push_back(option2); + double priority; + // prioritize the option where y is equal to the original string + if (i == 0) { + priority = 0.5; + } else { + priority = 0.1; + } + add_theory_aware_branching_info(option2, priority, l_true); + } + } + + if (!arrangement_disjunction.empty()) { + expr_ref implyR(mk_or(arrangement_disjunction), mgr); + + if (m_params.m_StrongArrangements) { + expr_ref implyLHS(ctx.mk_eq_atom(concatAst1, concatAst2), mgr); + expr_ref ax_strong(ctx.mk_eq_atom(implyLHS, implyR), mgr); + assert_axiom(ax_strong); + } else { + assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); + } + generate_mutual_exclusion(arrangement_disjunction); + } else { + TRACE("str", tout << "STOP: Should not split two EQ concats." << std::endl;); + } + } // (splitType == -1) + } + + /************************************************************* + * Type 3: concat(x, y) = concat("str", n) + *************************************************************/ + bool theory_str::is_concat_eq_type3(expr * concatAst1, expr * concatAst2) { + expr * v1_arg0 = to_app(concatAst1)->get_arg(0); + expr * v1_arg1 = to_app(concatAst1)->get_arg(1); + expr * v2_arg0 = to_app(concatAst2)->get_arg(0); + expr * v2_arg1 = to_app(concatAst2)->get_arg(1); + + if (u.str.is_string(v1_arg0) && (!u.str.is_string(v1_arg1)) + && (!u.str.is_string(v2_arg0)) && (!u.str.is_string(v2_arg1))) { + return true; + } else if (u.str.is_string(v2_arg0) && (!u.str.is_string(v2_arg1)) + && (!u.str.is_string(v1_arg0)) && (!u.str.is_string(v1_arg1))) { + return true; + } else { + return false; + } + } + + void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { + ast_manager & mgr = get_manager(); + context & ctx = get_context(); + + bool overlapAssumptionUsed = false; + + TRACE("str", tout << "process_concat_eq TYPE 3" << std::endl + << "concatAst1 = " << mk_ismt2_pp(concatAst1, mgr) << std::endl + << "concatAst2 = " << mk_ismt2_pp(concatAst2, mgr) << std::endl; + ); + + if (!u.str.is_concat(to_app(concatAst1))) { + TRACE("str", tout << "concatAst1 is not a concat function" << std::endl;); + return; + } + if (!u.str.is_concat(to_app(concatAst2))) { + TRACE("str", tout << "concatAst2 is not a concat function" << std::endl;); + return; + } + + expr * v1_arg0 = to_app(concatAst1)->get_arg(0); + expr * v1_arg1 = to_app(concatAst1)->get_arg(1); + expr * v2_arg0 = to_app(concatAst2)->get_arg(0); + expr * v2_arg1 = to_app(concatAst2)->get_arg(1); + + expr * x = NULL; + expr * y = NULL; + expr * strAst = NULL; + expr * n = NULL; + + if (u.str.is_string(v1_arg0) && !u.str.is_string(v2_arg0)) { + strAst = v1_arg0; + n = v1_arg1; + x = v2_arg0; + y = v2_arg1; + } else { + strAst = v2_arg0; + n = v2_arg1; + x = v1_arg0; + y = v1_arg1; + } + + zstring strValue; + u.str.is_string(strAst, strValue); + + rational x_len, y_len, str_len, n_len; + bool x_len_exists = get_len_value(x, x_len); + bool y_len_exists = get_len_value(y, y_len); + str_len = rational((unsigned)(strValue.length())); + bool n_len_exists = get_len_value(n, n_len); + + expr_ref xorFlag(mgr); + expr_ref temp1(mgr); + std::pair key1(concatAst1, concatAst2); + std::pair key2(concatAst2, concatAst1); + + // check the entries in this map to make sure they're still in scope + // before we use them. + + std::map, std::map >::iterator entry1 = varForBreakConcat.find(key1); + std::map, std::map >::iterator entry2 = varForBreakConcat.find(key2); + + bool entry1InScope; + if (entry1 == varForBreakConcat.end()) { + entry1InScope = false; + } else { + if (internal_variable_set.find((entry1->second)[0]) == internal_variable_set.end() + /* || internal_variable_set.find((entry1->second)[1]) == internal_variable_set.end() */) { + entry1InScope = false; + } else { + entry1InScope = true; + } + } + + bool entry2InScope; + if (entry2 == varForBreakConcat.end()) { + entry2InScope = false; + } else { + if (internal_variable_set.find((entry2->second)[0]) == internal_variable_set.end() + /* || internal_variable_set.find((entry2->second)[1]) == internal_variable_set.end() */) { + entry2InScope = false; + } else { + entry2InScope = true; + } + } + + TRACE("str", tout << "entry 1 " << (entry1InScope ? "in scope" : "not in scope") << std::endl + << "entry 2 " << (entry2InScope ? "in scope" : "not in scope") << std::endl;); + + + if (!entry1InScope && !entry2InScope) { + temp1 = mk_nonempty_str_var(); + xorFlag = mk_internal_xor_var(); + + varForBreakConcat[key1][0] = temp1; + varForBreakConcat[key1][1] = xorFlag; + } else { + if (entry1InScope) { + temp1 = varForBreakConcat[key1][0]; + xorFlag = varForBreakConcat[key1][1]; + } else if (varForBreakConcat.find(key2) != varForBreakConcat.end()) { + temp1 = varForBreakConcat[key2][0]; + xorFlag = varForBreakConcat[key2][1]; + } + refresh_theory_var(temp1); + add_nonempty_constraint(temp1); + } + + + + int splitType = -1; + if (x_len_exists) { + if (x_len < str_len) + splitType = 0; + else if (x_len == str_len) + splitType = 1; + else + splitType = 2; + } + if (splitType == -1 && y_len_exists && n_len_exists) { + if (y_len > n_len) + splitType = 0; + else if (y_len == n_len) + splitType = 1; + else + splitType = 2; + } + + TRACE("str", tout << "Split type " << splitType << std::endl;); + + // Provide fewer split options when length information is available. + if (splitType == 0) { + // | x | y | + // | str | n | + expr_ref_vector litems(mgr); + litems.push_back(ctx.mk_eq_atom(concatAst1, concatAst2)); + rational prefixLen; + if (!x_len_exists) { + prefixLen = str_len - (y_len - n_len); + litems.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_int(y_len))); + litems.push_back(ctx.mk_eq_atom(mk_strlen(n), mk_int(n_len))); + } else { + prefixLen = x_len; + litems.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_int(x_len))); + } + zstring prefixStr = strValue.extract(0, prefixLen.get_unsigned()); + rational str_sub_prefix = str_len - prefixLen; + zstring suffixStr = strValue.extract(prefixLen.get_unsigned(), str_sub_prefix.get_unsigned()); + expr_ref prefixAst(mk_string(prefixStr), mgr); + expr_ref suffixAst(mk_string(suffixStr), mgr); + expr_ref ax_l(mgr.mk_and(litems.size(), litems.c_ptr()), mgr); + + expr_ref suf_n_concat(mk_concat(suffixAst, n), mgr); + if (can_two_nodes_eq(x, prefixAst) && can_two_nodes_eq(y, suf_n_concat)) { + expr_ref_vector r_items(mgr); + r_items.push_back(ctx.mk_eq_atom(x, prefixAst)); + r_items.push_back(ctx.mk_eq_atom(y, suf_n_concat)); + + if (m_params.m_StrongArrangements) { + expr_ref ax_strong(ctx.mk_eq_atom(ax_l, mk_and(r_items)), mgr); + assert_axiom(ax_strong); + } else { + assert_implication(ax_l, mk_and(r_items)); + } + } else { + // negate! It's impossible to split str with these lengths + TRACE("str", tout << "CONFLICT: Impossible to split str with these lengths." << std::endl;); + assert_axiom(mgr.mk_not(ax_l)); + } + } + else if (splitType == 1) { + expr_ref ax_l1(ctx.mk_eq_atom(concatAst1, concatAst2), mgr); + expr_ref ax_l2(mgr.mk_or( + ctx.mk_eq_atom(mk_strlen(x), mk_strlen(strAst)), + ctx.mk_eq_atom(mk_strlen(y), mk_strlen(n))), mgr); + expr_ref ax_l(mgr.mk_and(ax_l1, ax_l2), mgr); + expr_ref ax_r(mgr.mk_and(ctx.mk_eq_atom(x, strAst), ctx.mk_eq_atom(y, n)), mgr); + + if (m_params.m_StrongArrangements) { + expr_ref ax_strong(ctx.mk_eq_atom(ax_l, ax_r), mgr); + assert_axiom(ax_strong); + } else { + assert_implication(ax_l, ax_r); + } + } + else if (splitType == 2) { + // | x | y | + // | str | n | + expr_ref_vector litems(mgr); + litems.push_back(ctx.mk_eq_atom(concatAst1, concatAst2)); + rational tmpLen; + if (!x_len_exists) { + tmpLen = n_len - y_len; + litems.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_int(y_len))); + litems.push_back(ctx.mk_eq_atom(mk_strlen(n), mk_int(n_len))); + } else { + tmpLen = x_len - str_len; + litems.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_int(x_len))); + } + expr_ref ax_l(mgr.mk_and(litems.size(), litems.c_ptr()), mgr); + + expr_ref str_temp1(mk_concat(strAst, temp1), mgr); + expr_ref temp1_y(mk_concat(temp1, y), mgr); + + if (can_two_nodes_eq(x, str_temp1)) { + if (!avoidLoopCut || !(has_self_cut(x, n))) { + expr_ref_vector r_items(mgr); + r_items.push_back(ctx.mk_eq_atom(x, str_temp1)); + r_items.push_back(ctx.mk_eq_atom(n, temp1_y)); + r_items.push_back(ctx.mk_eq_atom(mk_strlen(temp1), mk_int(tmpLen))); + expr_ref ax_r(mk_and(r_items), mgr); + + //Cut Info + add_cut_info_merge(temp1, sLevel, x); + add_cut_info_merge(temp1, sLevel, n); + + if (m_params.m_StrongArrangements) { + expr_ref ax_strong(ctx.mk_eq_atom(ax_l, ax_r), mgr); + assert_axiom(ax_strong); + } else { + assert_implication(ax_l, ax_r); + } + } else { + loopDetected = true; + if (m_params.m_FiniteOverlapModels) { + expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2); + assert_implication(ax_l, tester); + add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true); + } else { + TRACE("str", tout << "AVOID LOOP: SKIPPED" << std::endl;); + TRACE("str", {print_cut_var(x, tout); print_cut_var(n, tout);}); + + if (!overlapAssumptionUsed) { + overlapAssumptionUsed = true; + assert_implication(ax_l, m_theoryStrOverlapAssumption_term); + } + } + } + } + // else { + // // negate! It's impossible to split str with these lengths + // __debugPrint(logFile, "[Conflict] Negate! It's impossible to split str with these lengths @ %d.\n", __LINE__); + // addAxiom(t, Z3_mk_not(ctx, ax_l), __LINE__); + // } + } + else { + // Split type -1. We know nothing about the length... + + expr_ref_vector arrangement_disjunction(mgr); + + int pos = 1; + for (unsigned int i = 0; i <= strValue.length(); i++) { + zstring part1Str = strValue.extract(0, i); + zstring part2Str = strValue.extract(i, strValue.length() - i); + expr_ref cropStr(mk_string(part1Str), mgr); + expr_ref suffixStr(mk_string(part2Str), mgr); + expr_ref y_concat(mk_concat(suffixStr, n), mgr); + + if (can_two_nodes_eq(x, cropStr) && can_two_nodes_eq(y, y_concat)) { + expr_ref_vector and_item(mgr); + // break down option 3-1 + expr_ref x_eq_str(ctx.mk_eq_atom(x, cropStr), mgr); + + and_item.push_back(x_eq_str); ++pos; + and_item.push_back(ctx.mk_eq_atom(y, y_concat)); + and_item.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_strlen(cropStr))); ++pos; + + // and_item[pos++] = Z3_mk_eq(ctx, or_item[option], Z3_mk_eq(ctx, mk_length(t, y), mk_length(t, y_concat))); + // adding length constraint for _ = constStr seems slowing things down. + + expr_ref option1(mk_and(and_item), mgr); + arrangement_disjunction.push_back(option1); + double priority; + if (i == strValue.length()) { + priority = 0.5; + } else { + priority = 0.1; + } + add_theory_aware_branching_info(option1, priority, l_true); + } + } + + expr_ref strAst_temp1(mk_concat(strAst, temp1), mgr); + + + //-------------------------------------------------------- + // x cut n + //-------------------------------------------------------- + if (can_two_nodes_eq(x, strAst_temp1)) { + if (!avoidLoopCut || !(has_self_cut(x, n))) { + // break down option 3-2 + expr_ref_vector and_item(mgr); + + expr_ref temp1_y(mk_concat(temp1, y), mgr); + and_item.push_back(ctx.mk_eq_atom(x, strAst_temp1)); ++pos; + and_item.push_back(ctx.mk_eq_atom(n, temp1_y)); ++pos; + + and_item.push_back(ctx.mk_eq_atom(mk_strlen(x), + m_autil.mk_add(mk_strlen(strAst), mk_strlen(temp1)) ) ); ++pos; + + expr_ref option2(mk_and(and_item), mgr); + arrangement_disjunction.push_back(option2); + add_theory_aware_branching_info(option2, 0.1, l_true); + + add_cut_info_merge(temp1, sLevel, x); + add_cut_info_merge(temp1, sLevel, n); + } else { + loopDetected = true; + if (m_params.m_FiniteOverlapModels) { + expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2); + arrangement_disjunction.push_back(tester); + add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true); + } else { + TRACE("str", tout << "AVOID LOOP: SKIPPED." << std::endl;); + TRACE("str", {print_cut_var(x, tout); print_cut_var(n, tout);}); + + if (!overlapAssumptionUsed) { + overlapAssumptionUsed = true; + arrangement_disjunction.push_back(m_theoryStrOverlapAssumption_term); + } + } + } + } + + + if (!arrangement_disjunction.empty()) { + expr_ref implyR(mk_or(arrangement_disjunction), mgr); + + if (m_params.m_StrongArrangements) { + expr_ref ax_lhs(ctx.mk_eq_atom(concatAst1, concatAst2), mgr); + expr_ref ax_strong(ctx.mk_eq_atom(ax_lhs, implyR), mgr); + assert_axiom(ax_strong); + } else { + assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); + } + generate_mutual_exclusion(arrangement_disjunction); + } else { + TRACE("str", tout << "STOP: should not split two eq. concats" << std::endl;); + } + } + + } + + /************************************************************* + * Type 4: concat("str1", y) = concat("str2", n) + *************************************************************/ + bool theory_str::is_concat_eq_type4(expr * concatAst1, expr * concatAst2) { + expr * v1_arg0 = to_app(concatAst1)->get_arg(0); + expr * v1_arg1 = to_app(concatAst1)->get_arg(1); + expr * v2_arg0 = to_app(concatAst2)->get_arg(0); + expr * v2_arg1 = to_app(concatAst2)->get_arg(1); + + if (u.str.is_string(v1_arg0) && (!u.str.is_string(v1_arg1)) + && u.str.is_string(v2_arg0) && (!u.str.is_string(v2_arg1))) { + return true; + } else { + return false; + } + } + + void theory_str::process_concat_eq_type4(expr * concatAst1, expr * concatAst2) { + ast_manager & mgr = get_manager(); + context & ctx = get_context(); + TRACE("str", tout << "process_concat_eq TYPE 4" << std::endl + << "concatAst1 = " << mk_ismt2_pp(concatAst1, mgr) << std::endl + << "concatAst2 = " << mk_ismt2_pp(concatAst2, mgr) << std::endl; + ); + + if (!u.str.is_concat(to_app(concatAst1))) { + TRACE("str", tout << "concatAst1 is not a concat function" << std::endl;); + return; + } + if (!u.str.is_concat(to_app(concatAst2))) { + TRACE("str", tout << "concatAst2 is not a concat function" << std::endl;); + return; + } + + expr * v1_arg0 = to_app(concatAst1)->get_arg(0); + expr * v1_arg1 = to_app(concatAst1)->get_arg(1); + expr * v2_arg0 = to_app(concatAst2)->get_arg(0); + expr * v2_arg1 = to_app(concatAst2)->get_arg(1); + + expr * str1Ast = v1_arg0; + expr * y = v1_arg1; + expr * str2Ast = v2_arg0; + expr * n = v2_arg1; + + zstring str1Value, str2Value; + u.str.is_string(str1Ast, str1Value); + u.str.is_string(str2Ast, str2Value); + + unsigned int str1Len = str1Value.length(); + unsigned int str2Len = str2Value.length(); + + int commonLen = (str1Len > str2Len) ? str2Len : str1Len; + if (str1Value.extract(0, commonLen) != str2Value.extract(0, commonLen)) { + TRACE("str", tout << "Conflict: " << mk_ismt2_pp(concatAst1, mgr) + << " has no common prefix with " << mk_ismt2_pp(concatAst2, mgr) << std::endl;); + expr_ref toNegate(mgr.mk_not(ctx.mk_eq_atom(concatAst1, concatAst2)), mgr); + assert_axiom(toNegate); + return; + } else { + if (str1Len > str2Len) { + zstring deltaStr = str1Value.extract(str2Len, str1Len - str2Len); + expr_ref tmpAst(mk_concat(mk_string(deltaStr), y), mgr); + if (!in_same_eqc(tmpAst, n)) { + // break down option 4-1 + expr_ref implyR(ctx.mk_eq_atom(n, tmpAst), mgr); + if (m_params.m_StrongArrangements) { + expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr); + assert_axiom(ax_strong); + } else { + assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); + } + } + } else if (str1Len == str2Len) { + if (!in_same_eqc(n, y)) { + //break down option 4-2 + expr_ref implyR(ctx.mk_eq_atom(n, y), mgr); + + if (m_params.m_StrongArrangements) { + expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr); + assert_axiom(ax_strong); + } else { + assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); + } + } + } else { + zstring deltaStr = str2Value.extract(str1Len, str2Len - str1Len); + expr_ref tmpAst(mk_concat(mk_string(deltaStr), n), mgr); + if (!in_same_eqc(y, tmpAst)) { + //break down option 4-3 + expr_ref implyR(ctx.mk_eq_atom(y, tmpAst), mgr); + if (m_params.m_StrongArrangements) { + expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr); + assert_axiom(ax_strong); + } else { + assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); + } + } + } + } + } + + /************************************************************* + * case 5: concat(x, "str1") = concat(m, "str2") + *************************************************************/ + bool theory_str::is_concat_eq_type5(expr * concatAst1, expr * concatAst2) { + expr * v1_arg0 = to_app(concatAst1)->get_arg(0); + expr * v1_arg1 = to_app(concatAst1)->get_arg(1); + expr * v2_arg0 = to_app(concatAst2)->get_arg(0); + expr * v2_arg1 = to_app(concatAst2)->get_arg(1); + + if ((!u.str.is_string(v1_arg0)) && u.str.is_string(v1_arg1) + && (!u.str.is_string(v2_arg0)) && u.str.is_string(v2_arg1)) { + return true; + } else { + return false; + } + } + + void theory_str::process_concat_eq_type5(expr * concatAst1, expr * concatAst2) { + ast_manager & mgr = get_manager(); + context & ctx = get_context(); + TRACE("str", tout << "process_concat_eq TYPE 5" << std::endl + << "concatAst1 = " << mk_ismt2_pp(concatAst1, mgr) << std::endl + << "concatAst2 = " << mk_ismt2_pp(concatAst2, mgr) << std::endl; + ); + + if (!u.str.is_concat(to_app(concatAst1))) { + TRACE("str", tout << "concatAst1 is not a concat function" << std::endl;); + return; + } + if (!u.str.is_concat(to_app(concatAst2))) { + TRACE("str", tout << "concatAst2 is not a concat function" << std::endl;); + return; + } + + expr * v1_arg0 = to_app(concatAst1)->get_arg(0); + expr * v1_arg1 = to_app(concatAst1)->get_arg(1); + expr * v2_arg0 = to_app(concatAst2)->get_arg(0); + expr * v2_arg1 = to_app(concatAst2)->get_arg(1); + + expr * x = v1_arg0; + expr * str1Ast = v1_arg1; + expr * m = v2_arg0; + expr * str2Ast = v2_arg1; + + zstring str1Value, str2Value; + u.str.is_string(str1Ast, str1Value); + u.str.is_string(str2Ast, str2Value); + + unsigned int str1Len = str1Value.length(); + unsigned int str2Len = str2Value.length(); + + int cLen = (str1Len > str2Len) ? str2Len : str1Len; + if (str1Value.extract(str1Len - cLen, cLen) != str2Value.extract(str2Len - cLen, cLen)) { + TRACE("str", tout << "Conflict: " << mk_ismt2_pp(concatAst1, mgr) + << " has no common suffix with " << mk_ismt2_pp(concatAst2, mgr) << std::endl;); + expr_ref toNegate(mgr.mk_not(ctx.mk_eq_atom(concatAst1, concatAst2)), mgr); + assert_axiom(toNegate); + return; + } else { + if (str1Len > str2Len) { + zstring deltaStr = str1Value.extract(0, str1Len - str2Len); + expr_ref x_deltaStr(mk_concat(x, mk_string(deltaStr)), mgr); + if (!in_same_eqc(m, x_deltaStr)) { + expr_ref implyR(ctx.mk_eq_atom(m, x_deltaStr), mgr); + if (m_params.m_StrongArrangements) { + expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr); + assert_axiom(ax_strong); + } else { + assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); + } + } + } else if (str1Len == str2Len) { + // test + if (!in_same_eqc(x, m)) { + expr_ref implyR(ctx.mk_eq_atom(x, m), mgr); + if (m_params.m_StrongArrangements) { + expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr); + assert_axiom(ax_strong); + } else { + assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); + } + } + } else { + zstring deltaStr = str2Value.extract(0, str2Len - str1Len); + expr_ref m_deltaStr(mk_concat(m, mk_string(deltaStr)), mgr); + if (!in_same_eqc(x, m_deltaStr)) { + expr_ref implyR(ctx.mk_eq_atom(x, m_deltaStr), mgr); + if (m_params.m_StrongArrangements) { + expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr); + assert_axiom(ax_strong); + } else { + assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); + } + } + } + } + } + + /************************************************************* + * case 6: concat("str1", y) = concat(m, "str2") + *************************************************************/ + bool theory_str::is_concat_eq_type6(expr * concatAst1, expr * concatAst2) { + expr * v1_arg0 = to_app(concatAst1)->get_arg(0); + expr * v1_arg1 = to_app(concatAst1)->get_arg(1); + expr * v2_arg0 = to_app(concatAst2)->get_arg(0); + expr * v2_arg1 = to_app(concatAst2)->get_arg(1); + + if (u.str.is_string(v1_arg0) && (!u.str.is_string(v1_arg1)) + && (!u.str.is_string(v2_arg0)) && u.str.is_string(v2_arg1)) { + return true; + } else if (u.str.is_string(v2_arg0) && (!u.str.is_string(v2_arg1)) + && (!u.str.is_string(v1_arg0)) && u.str.is_string(v1_arg1)) { + return true; + } else { + return false; + } + } + + void theory_str::process_concat_eq_type6(expr * concatAst1, expr * concatAst2) { + ast_manager & mgr = get_manager(); + context & ctx = get_context(); + TRACE("str", tout << "process_concat_eq TYPE 6" << std::endl + << "concatAst1 = " << mk_ismt2_pp(concatAst1, mgr) << std::endl + << "concatAst2 = " << mk_ismt2_pp(concatAst2, mgr) << std::endl; + ); + + if (!u.str.is_concat(to_app(concatAst1))) { + TRACE("str", tout << "concatAst1 is not a concat function" << std::endl;); + return; + } + if (!u.str.is_concat(to_app(concatAst2))) { + TRACE("str", tout << "concatAst2 is not a concat function" << std::endl;); + return; + } + + expr * v1_arg0 = to_app(concatAst1)->get_arg(0); + expr * v1_arg1 = to_app(concatAst1)->get_arg(1); + expr * v2_arg0 = to_app(concatAst2)->get_arg(0); + expr * v2_arg1 = to_app(concatAst2)->get_arg(1); + + + expr * str1Ast = NULL; + expr * y = NULL; + expr * m = NULL; + expr * str2Ast = NULL; + + if (u.str.is_string(v1_arg0)) { + str1Ast = v1_arg0; + y = v1_arg1; + m = v2_arg0; + str2Ast = v2_arg1; + } else { + str1Ast = v2_arg0; + y = v2_arg1; + m = v1_arg0; + str2Ast = v1_arg1; + } + + zstring str1Value, str2Value; + u.str.is_string(str1Ast, str1Value); + u.str.is_string(str2Ast, str2Value); + + unsigned int str1Len = str1Value.length(); + unsigned int str2Len = str2Value.length(); + + //---------------------------------------- + //(a) |---str1---|----y----| + // |--m--|-----str2-----| + // + //(b) |---str1---|----y----| + // |-----m----|--str2---| + // + //(c) |---str1---|----y----| + // |------m------|-str2-| + //---------------------------------------- + + std::list overlapLen; + overlapLen.push_back(0); + + for (unsigned int i = 1; i <= str1Len && i <= str2Len; i++) { + if (str1Value.extract(str1Len - i, i) == str2Value.extract(0, i)) + overlapLen.push_back(i); + } + + //---------------------------------------------------------------- + expr * commonVar = NULL; + expr * xorFlag = NULL; + std::pair key1(concatAst1, concatAst2); + std::pair key2(concatAst2, concatAst1); + + // check the entries in this map to make sure they're still in scope + // before we use them. + + std::map, std::map >::iterator entry1 = varForBreakConcat.find(key1); + std::map, std::map >::iterator entry2 = varForBreakConcat.find(key2); + + bool entry1InScope; + if (entry1 == varForBreakConcat.end()) { + entry1InScope = false; + } else { + if (internal_variable_set.find((entry1->second)[0]) == internal_variable_set.end() + /* || internal_variable_set.find((entry1->second)[1]) == internal_variable_set.end() */) { + entry1InScope = false; + } else { + entry1InScope = true; + } + } + + bool entry2InScope; + if (entry2 == varForBreakConcat.end()) { + entry2InScope = false; + } else { + if (internal_variable_set.find((entry2->second)[0]) == internal_variable_set.end() + /* || internal_variable_set.find((entry2->second)[1]) == internal_variable_set.end() */) { + entry2InScope = false; + } else { + entry2InScope = true; + } + } + + TRACE("str", tout << "entry 1 " << (entry1InScope ? "in scope" : "not in scope") << std::endl + << "entry 2 " << (entry2InScope ? "in scope" : "not in scope") << std::endl;); + + if (!entry1InScope && !entry2InScope) { + commonVar = mk_nonempty_str_var(); + xorFlag = mk_internal_xor_var(); + varForBreakConcat[key1][0] = commonVar; + varForBreakConcat[key1][1] = xorFlag; + } else { + if (entry1InScope) { + commonVar = (entry1->second)[0]; + xorFlag = (entry1->second)[1]; + } else { + commonVar = (entry2->second)[0]; + xorFlag = (entry2->second)[1]; + } + refresh_theory_var(commonVar); + add_nonempty_constraint(commonVar); + } + + bool overlapAssumptionUsed = false; + + expr_ref_vector arrangement_disjunction(mgr); + int pos = 1; + + if (!avoidLoopCut || !has_self_cut(m, y)) { + expr_ref_vector and_item(mgr); + + expr_ref str1_commonVar(mk_concat(str1Ast, commonVar), mgr); + and_item.push_back(ctx.mk_eq_atom(m, str1_commonVar)); + pos += 1; + + expr_ref commonVar_str2(mk_concat(commonVar, str2Ast), mgr); + and_item.push_back(ctx.mk_eq_atom(y, commonVar_str2)); + pos += 1; + + and_item.push_back(ctx.mk_eq_atom(mk_strlen(m), + m_autil.mk_add(mk_strlen(str1Ast), mk_strlen(commonVar)) )); + pos += 1; + + // addItems[0] = mk_length(t, commonVar); + // addItems[1] = mk_length(t, str2Ast); + // and_item[pos++] = Z3_mk_eq(ctx, or_item[option], Z3_mk_eq(ctx, mk_length(t, y), Z3_mk_add(ctx, 2, addItems))); + + expr_ref option1(mk_and(and_item), mgr); + arrangement_disjunction.push_back(option1); + add_theory_aware_branching_info(option1, 0.1, l_true); + } else { + loopDetected = true; + + if (m_params.m_FiniteOverlapModels) { + expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2); + arrangement_disjunction.push_back(tester); + add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true); + } else { + TRACE("str", tout << "AVOID LOOP: SKIPPED." << std::endl;); + TRACE("str", print_cut_var(m, tout); print_cut_var(y, tout);); + + // only add the overlap assumption one time + if (!overlapAssumptionUsed) { + arrangement_disjunction.push_back(m_theoryStrOverlapAssumption_term); + overlapAssumptionUsed = true; + } + } + } + + for (std::list::iterator itor = overlapLen.begin(); itor != overlapLen.end(); itor++) { + unsigned int overLen = *itor; + zstring prefix = str1Value.extract(0, str1Len - overLen); + zstring suffix = str2Value.extract(overLen, str2Len - overLen); + + expr_ref_vector and_item(mgr); + + expr_ref prefixAst(mk_string(prefix), mgr); + expr_ref x_eq_prefix(ctx.mk_eq_atom(m, prefixAst), mgr); + and_item.push_back(x_eq_prefix); + pos += 1; + + and_item.push_back( + ctx.mk_eq_atom(mk_strlen(m), mk_strlen(prefixAst))); + pos += 1; + + // adding length constraint for _ = constStr seems slowing things down. + + expr_ref suffixAst(mk_string(suffix), mgr); + expr_ref y_eq_suffix(ctx.mk_eq_atom(y, suffixAst), mgr); + and_item.push_back(y_eq_suffix); + pos += 1; + + and_item.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_strlen(suffixAst))); + pos += 1; + + expr_ref option2(mk_and(and_item), mgr); + arrangement_disjunction.push_back(option2); + double priority; + // prefer the option "str1" = x + if (prefix == str1Value) { + priority = 0.5; + } else { + priority = 0.1; + } + add_theory_aware_branching_info(option2, priority, l_true); + } + + // case 6: concat("str1", y) = concat(m, "str2") + + expr_ref implyR(mk_or(arrangement_disjunction), mgr); + + if (m_params.m_StrongArrangements) { + expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr); + assert_axiom(ax_strong); + } else { + assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); + } + generate_mutual_exclusion(arrangement_disjunction); + } + + void theory_str::process_unroll_eq_const_str(expr * unrollFunc, expr * constStr) { + ast_manager & m = get_manager(); + + if (!u.re.is_unroll(to_app(unrollFunc))) { + return; + } + if (!u.str.is_string(constStr)) { + return; + } + + expr * funcInUnroll = to_app(unrollFunc)->get_arg(0); + zstring strValue; + u.str.is_string(constStr, strValue); + + TRACE("str", tout << "unrollFunc: " << mk_pp(unrollFunc, m) << std::endl + << "constStr: " << mk_pp(constStr, m) << std::endl;); + + if (strValue == "") { + return; + } + + if (u.re.is_to_re(to_app(funcInUnroll))) { + unroll_str2reg_constStr(unrollFunc, constStr); + return; + } + } + + void theory_str::process_concat_eq_unroll(expr * concat, expr * unroll) { + context & ctx = get_context(); + ast_manager & mgr = get_manager(); + + TRACE("str", tout << "concat = " << mk_pp(concat, mgr) << ", unroll = " << mk_pp(unroll, mgr) << std::endl;); + + std::pair key = std::make_pair(concat, unroll); + expr_ref toAssert(mgr); + + if (concat_eq_unroll_ast_map.find(key) == concat_eq_unroll_ast_map.end()) { + expr_ref arg1(to_app(concat)->get_arg(0), mgr); + expr_ref arg2(to_app(concat)->get_arg(1), mgr); + expr_ref r1(to_app(unroll)->get_arg(0), mgr); + expr_ref t1(to_app(unroll)->get_arg(1), mgr); + + expr_ref v1(mk_regex_rep_var(), mgr); + expr_ref v2(mk_regex_rep_var(), mgr); + expr_ref v3(mk_regex_rep_var(), mgr); + expr_ref v4(mk_regex_rep_var(), mgr); + expr_ref v5(mk_regex_rep_var(), mgr); + + expr_ref t2(mk_unroll_bound_var(), mgr); + expr_ref t3(mk_unroll_bound_var(), mgr); + expr_ref emptyStr(mk_string(""), mgr); + + expr_ref unroll1(mk_unroll(r1, t2), mgr); + expr_ref unroll2(mk_unroll(r1, t3), mgr); + + expr_ref op0(ctx.mk_eq_atom(t1, mk_int(0)), mgr); + expr_ref op1(m_autil.mk_ge(t1, mk_int(1)), mgr); + + expr_ref_vector op1Items(mgr); + expr_ref_vector op2Items(mgr); + + op1Items.push_back(ctx.mk_eq_atom(arg1, emptyStr)); + op1Items.push_back(ctx.mk_eq_atom(arg2, emptyStr)); + op1Items.push_back(ctx.mk_eq_atom(mk_strlen(arg1), mk_int(0))); + op1Items.push_back(ctx.mk_eq_atom(mk_strlen(arg2), mk_int(0))); + expr_ref opAnd1(ctx.mk_eq_atom(op0, mk_and(op1Items)), mgr); + + expr_ref v1v2(mk_concat(v1, v2), mgr); + op2Items.push_back(ctx.mk_eq_atom(arg1, v1v2)); + op2Items.push_back(ctx.mk_eq_atom(mk_strlen(arg1), m_autil.mk_add(mk_strlen(v1), mk_strlen(v2)))); + expr_ref v3v4(mk_concat(v3, v4), mgr); + op2Items.push_back(ctx.mk_eq_atom(arg2, v3v4)); + op2Items.push_back(ctx.mk_eq_atom(mk_strlen(arg2), m_autil.mk_add(mk_strlen(v3), mk_strlen(v4)))); + + op2Items.push_back(ctx.mk_eq_atom(v1, unroll1)); + op2Items.push_back(ctx.mk_eq_atom(mk_strlen(v1), mk_strlen(unroll1))); + op2Items.push_back(ctx.mk_eq_atom(v4, unroll2)); + op2Items.push_back(ctx.mk_eq_atom(mk_strlen(v4), mk_strlen(unroll2))); + expr_ref v2v3(mk_concat(v2, v3), mgr); + op2Items.push_back(ctx.mk_eq_atom(v5, v2v3)); + reduce_virtual_regex_in(v5, r1, op2Items); + op2Items.push_back(ctx.mk_eq_atom(mk_strlen(v5), m_autil.mk_add(mk_strlen(v2), mk_strlen(v3)))); + op2Items.push_back(ctx.mk_eq_atom(m_autil.mk_add(t2, t3), m_autil.mk_add(t1, mk_int(-1)))); + expr_ref opAnd2(ctx.mk_eq_atom(op1, mk_and(op2Items)), mgr); + + toAssert = mgr.mk_and(opAnd1, opAnd2); + m_trail.push_back(toAssert); + concat_eq_unroll_ast_map[key] = toAssert; + } else { + toAssert = concat_eq_unroll_ast_map[key]; + } + + assert_axiom(toAssert); + } + + void theory_str::unroll_str2reg_constStr(expr * unrollFunc, expr * eqConstStr) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + expr * str2RegFunc = to_app(unrollFunc)->get_arg(0); + expr * strInStr2RegFunc = to_app(str2RegFunc)->get_arg(0); + expr * oriCnt = to_app(unrollFunc)->get_arg(1); + + zstring strValue; + u.str.is_string(eqConstStr, strValue); + zstring regStrValue; + u.str.is_string(strInStr2RegFunc, regStrValue); + unsigned int strLen = strValue.length(); + unsigned int regStrLen = regStrValue.length(); + SASSERT(regStrLen != 0); // this should never occur -- the case for empty string is handled elsewhere + unsigned int cnt = strLen / regStrLen; + + expr_ref implyL(ctx.mk_eq_atom(unrollFunc, eqConstStr), m); + expr_ref implyR1(ctx.mk_eq_atom(oriCnt, mk_int(cnt)), m); + expr_ref implyR2(ctx.mk_eq_atom(mk_strlen(unrollFunc), mk_int(strLen)), m); + expr_ref axiomRHS(m.mk_and(implyR1, implyR2), m); + SASSERT(implyL); + SASSERT(axiomRHS); + assert_implication(implyL, axiomRHS); + } + + /* + * Look through the equivalence class of n to find a string constant. + * Return that constant if it is found, and set hasEqcValue to true. + * Otherwise, return n, and set hasEqcValue to false. + */ + + expr * theory_str::get_eqc_value(expr * n, bool & hasEqcValue) { + return z3str2_get_eqc_value(n, hasEqcValue); + } + + + // Simulate the behaviour of get_eqc_value() from Z3str2. + // We only check m_find for a string constant. + + expr * theory_str::z3str2_get_eqc_value(expr * n , bool & hasEqcValue) { + expr * curr = n; + do { + if (u.str.is_string(curr)) { + hasEqcValue = true; + return curr; + } + curr = get_eqc_next(curr); + } while (curr != n); + hasEqcValue = false; + return n; + } + + // from Z3: theory_seq.cpp + + static theory_mi_arith* get_th_arith(context& ctx, theory_id afid, expr* e) { + theory* th = ctx.get_theory(afid); + if (th && ctx.e_internalized(e)) { + return dynamic_cast(th); + } + else { + return 0; + } + } + + bool theory_str::get_value(expr* e, rational& val) const { + if (opt_DisableIntegerTheoryIntegration) { + TRACE("str", tout << "WARNING: integer theory integration disabled" << std::endl;); + return false; + } + + context& ctx = get_context(); + ast_manager & m = get_manager(); + theory_mi_arith* tha = get_th_arith(ctx, m_autil.get_family_id(), e); + if (!tha) { + return false; + } + TRACE("str", tout << "checking eqc of " << mk_pp(e, m) << " for arithmetic value" << std::endl;); + expr_ref _val(m); + enode * en_e = ctx.get_enode(e); + enode * it = en_e; + do { + if (m_autil.is_numeral(it->get_owner(), val) && val.is_int()) { + // found an arithmetic term + TRACE("str", tout << mk_pp(it->get_owner(), m) << " is an integer ( ~= " << val << " )" + << std::endl;); + return true; + } else { + TRACE("str", tout << mk_pp(it->get_owner(), m) << " not a numeral" << std::endl;); + } + it = it->get_next(); + } while (it != en_e); + TRACE("str", tout << "no arithmetic values found in eqc" << std::endl;); + return false; + } + + bool theory_str::lower_bound(expr* _e, rational& lo) { + if (opt_DisableIntegerTheoryIntegration) { + TRACE("str", tout << "WARNING: integer theory integration disabled" << std::endl;); + return false; + } + + context& ctx = get_context(); + ast_manager & m = get_manager(); + theory_mi_arith* tha = get_th_arith(ctx, m_autil.get_family_id(), _e); + expr_ref _lo(m); + if (!tha || !tha->get_lower(ctx.get_enode(_e), _lo)) return false; + return m_autil.is_numeral(_lo, lo) && lo.is_int(); + } + + bool theory_str::upper_bound(expr* _e, rational& hi) { + if (opt_DisableIntegerTheoryIntegration) { + TRACE("str", tout << "WARNING: integer theory integration disabled" << std::endl;); + return false; + } + + context& ctx = get_context(); + ast_manager & m = get_manager(); + theory_mi_arith* tha = get_th_arith(ctx, m_autil.get_family_id(), _e); + expr_ref _hi(m); + if (!tha || !tha->get_upper(ctx.get_enode(_e), _hi)) return false; + return m_autil.is_numeral(_hi, hi) && hi.is_int(); + } + + bool theory_str::get_len_value(expr* e, rational& val) { + if (opt_DisableIntegerTheoryIntegration) { + TRACE("str", tout << "WARNING: integer theory integration disabled" << std::endl;); + return false; + } + + context& ctx = get_context(); + ast_manager & m = get_manager(); + + theory* th = ctx.get_theory(m_autil.get_family_id()); + if (!th) { + TRACE("str", tout << "oops, can't get m_autil's theory" << std::endl;); + return false; + } + theory_mi_arith* tha = dynamic_cast(th); + if (!tha) { + TRACE("str", tout << "oops, can't cast to theory_mi_arith" << std::endl;); + return false; + } + + TRACE("str", tout << "checking len value of " << mk_ismt2_pp(e, m) << std::endl;); + + rational val1; + expr_ref len(m), len_val(m); + expr* e1, *e2; + ptr_vector todo; + todo.push_back(e); + val.reset(); + while (!todo.empty()) { + expr* c = todo.back(); + todo.pop_back(); + if (u.str.is_concat(to_app(c))) { + e1 = to_app(c)->get_arg(0); + e2 = to_app(c)->get_arg(1); + todo.push_back(e1); + todo.push_back(e2); + } + else if (u.str.is_string(to_app(c))) { + zstring tmp; + u.str.is_string(to_app(c), tmp); + unsigned int sl = tmp.length(); + val += rational(sl); + } + else { + len = mk_strlen(c); + + // debugging + TRACE("str", { + tout << mk_pp(len, m) << ":" << std::endl + << (ctx.is_relevant(len.get()) ? "relevant" : "not relevant") << std::endl + << (ctx.e_internalized(len) ? "internalized" : "not internalized") << std::endl + ; + if (ctx.e_internalized(len)) { + enode * e_len = ctx.get_enode(len); + tout << "has " << e_len->get_num_th_vars() << " theory vars" << std::endl; + + // eqc debugging + { + tout << "dump equivalence class of " << mk_pp(len, get_manager()) << std::endl; + enode * nNode = ctx.get_enode(len); + enode * eqcNode = nNode; + do { + app * ast = eqcNode->get_owner(); + tout << mk_pp(ast, get_manager()) << std::endl; + eqcNode = eqcNode->get_next(); + } while (eqcNode != nNode); } } - if (counterEgFound) { - TRACE("str", tout << "Inconsistency found!" << std::endl;); - break; - } - } + }); + + if (ctx.e_internalized(len) && get_value(len, val1)) { + val += val1; + TRACE("str", tout << "integer theory: subexpression " << mk_ismt2_pp(len, m) << " has length " << val1 << std::endl;); } - // add assertion - if (implyR) { - expr_ref implyLHS(mk_and(litems), m); - assert_implication(implyLHS, implyR); + else { + TRACE("str", tout << "integer theory: subexpression " << mk_ismt2_pp(len, m) << " has no length assignment; bailing out" << std::endl;); + return false; } } - // varEqcNode is subStr - else if (substrAst == varNode) { - expr_ref implyR(m); - litems.reset(); + } - if (substrAst != constNode) { - litems.push_back(ctx.mk_eq_atom(substrAst, constNode)); - } - bool strHasEqcValue = false; - expr * strValue = get_eqc_value(strAst, strHasEqcValue); - if (strValue != strAst) { - litems.push_back(ctx.mk_eq_atom(strAst, strValue)); - } + TRACE("str", tout << "length of " << mk_ismt2_pp(e, m) << " is " << val << std::endl;); + return val.is_int(); + } - if (strHasEqcValue) { - zstring strConst, subStrConst; - u.str.is_string(strValue, strConst); - u.str.is_string(constNode, subStrConst); - if (strConst.contains(subStrConst)) { - //implyR = Z3_mk_eq(ctx, boolVar, Z3_mk_true(ctx)); - implyR = boolVar; - } else { - // implyR = Z3_mk_eq(ctx, boolVar, Z3_mk_false(ctx)); - implyR = m.mk_not(boolVar); - } - } + /* + * Decide whether n1 and n2 are already in the same equivalence class. + * This only checks whether the core considers them to be equal; + * they may not actually be equal. + */ + bool theory_str::in_same_eqc(expr * n1, expr * n2) { + if (n1 == n2) return true; + context & ctx = get_context(); + ast_manager & m = get_manager(); - // add assertion - if (implyR) { - expr_ref implyLHS(mk_and(litems), m); - assert_implication(implyLHS, implyR); - } + // similar to get_eqc_value(), make absolutely sure + // that we've set this up properly for the context + + if (!ctx.e_internalized(n1)) { + TRACE("str", tout << "WARNING: expression " << mk_ismt2_pp(n1, m) << " was not internalized" << std::endl;); + ctx.internalize(n1, false); + } + if (!ctx.e_internalized(n2)) { + TRACE("str", tout << "WARNING: expression " << mk_ismt2_pp(n2, m) << " was not internalized" << std::endl;); + ctx.internalize(n2, false); + } + + expr * curr = get_eqc_next(n1); + while (curr != n1) { + if (curr == n2) + return true; + curr = get_eqc_next(curr); + } + return false; + } + + expr * theory_str::collect_eq_nodes(expr * n, expr_ref_vector & eqcSet) { + context & ctx = get_context(); + expr * constStrNode = NULL; + + expr * ex = n; + do { + if (u.str.is_string(to_app(ex))) { + constStrNode = ex; } - } // for (itor1 : contains_map) - } // if varNode in contain_pair_idx_map -} + eqcSet.push_back(ex); -void theory_str::check_contain_by_substr(expr * varNode, expr_ref_vector & willEqClass) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - expr_ref_vector litems(m); + ex = get_eqc_next(ex); + } while (ex != n); + return constStrNode; + } - if (contain_pair_idx_map.find(varNode) != contain_pair_idx_map.end()) { - std::set >::iterator itor1 = contain_pair_idx_map[varNode].begin(); - for (; itor1 != contain_pair_idx_map[varNode].end(); ++itor1) { - expr * strAst = itor1->first; - expr * substrAst = itor1->second; - - expr * boolVar; - if (!contain_pair_bool_map.find(strAst, substrAst, boolVar)) { - TRACE("str", tout << "warning: no entry for boolVar in contain_pair_bool_map" << std::endl;); + /* + * Collect constant strings (from left to right) in an AST node. + */ + void theory_str::get_const_str_asts_in_node(expr * node, expr_ref_vector & astList) { + ast_manager & m = get_manager(); + if (u.str.is_string(node)) { + astList.push_back(node); + //} else if (getNodeType(t, node) == my_Z3_Func) { + } else if (is_app(node)) { + app * func_app = to_app(node); + unsigned int argCount = func_app->get_num_args(); + for (unsigned int i = 0; i < argCount; i++) { + expr * argAst = func_app->get_arg(i); + get_const_str_asts_in_node(argAst, astList); } - // boolVar is actually a Contains term - app * containsApp = to_app(boolVar); + } + } - // we only want to inspect the Contains terms where either of strAst or substrAst - // are equal to varNode. + void theory_str::check_contain_by_eqc_val(expr * varNode, expr * constNode) { + context & ctx = get_context(); + ast_manager & m = get_manager(); - TRACE("t_str_detail", tout << "considering Contains with strAst = " << mk_pp(strAst, m) << ", substrAst = " << mk_pp(substrAst, m) << "..." << std::endl;); + TRACE("str", tout << "varNode = " << mk_pp(varNode, m) << ", constNode = " << mk_pp(constNode, m) << std::endl;); - if (varNode != strAst && varNode != substrAst) { - TRACE("str", tout << "varNode not equal to strAst or substrAst, skip" << std::endl;); - continue; - } - TRACE("str", tout << "varNode matched one of strAst or substrAst. Continuing" << std::endl;); + expr_ref_vector litems(m); - if (substrAst == varNode) { - bool strAstHasVal = false; - expr * strValue = get_eqc_value(strAst, strAstHasVal); - if (strAstHasVal) { - TRACE("str", tout << mk_pp(strAst, m) << " has constant eqc value " << mk_pp(strValue, m) << std::endl;); - if (strValue != strAst) { - litems.push_back(ctx.mk_eq_atom(strAst, strValue)); + if (contain_pair_idx_map.find(varNode) != contain_pair_idx_map.end()) { + std::set >::iterator itor1 = contain_pair_idx_map[varNode].begin(); + for (; itor1 != contain_pair_idx_map[varNode].end(); ++itor1) { + expr * strAst = itor1->first; + expr * substrAst = itor1->second; + + expr * boolVar; + if (!contain_pair_bool_map.find(strAst, substrAst, boolVar)) { + TRACE("str", tout << "warning: no entry for boolVar in contain_pair_bool_map" << std::endl;); + } + // boolVar is actually a Contains term + app * containsApp = to_app(boolVar); + + // we only want to inspect the Contains terms where either of strAst or substrAst + // are equal to varNode. + + TRACE("t_str_detail", tout << "considering Contains with strAst = " << mk_pp(strAst, m) << ", substrAst = " << mk_pp(substrAst, m) << "..." << std::endl;); + + if (varNode != strAst && varNode != substrAst) { + TRACE("str", tout << "varNode not equal to strAst or substrAst, skip" << std::endl;); + continue; + } + TRACE("str", tout << "varNode matched one of strAst or substrAst. Continuing" << std::endl;); + + // varEqcNode is str + if (strAst == varNode) { + expr_ref implyR(m); + litems.reset(); + + if (strAst != constNode) { + litems.push_back(ctx.mk_eq_atom(strAst, constNode)); } zstring strConst; - u.str.is_string(strValue, strConst); - // iterate eqc (also eqc-to-be) of substr - for (expr_ref_vector::iterator itAst = willEqClass.begin(); itAst != willEqClass.end(); itAst++) { - bool counterEgFound = false; - if (u.str.is_concat(to_app(*itAst))) { + u.str.is_string(constNode, strConst); + bool subStrHasEqcValue = false; + expr * substrValue = get_eqc_value(substrAst, subStrHasEqcValue); + if (substrValue != substrAst) { + litems.push_back(ctx.mk_eq_atom(substrAst, substrValue)); + } + + if (subStrHasEqcValue) { + // subStr has an eqc constant value + zstring subStrConst; + u.str.is_string(substrValue, subStrConst); + + TRACE("t_str_detail", tout << "strConst = " << strConst << ", subStrConst = " << subStrConst << "\n";); + + if (strConst.contains(subStrConst)) { + //implyR = ctx.mk_eq(ctx, boolVar, Z3_mk_true(ctx)); + implyR = boolVar; + } else { + //implyR = Z3_mk_eq(ctx, boolVar, Z3_mk_false(ctx)); + implyR = m.mk_not(boolVar); + } + } else { + // ------------------------------------------------------------------------------------------------ + // subStr doesn't have an eqc contant value + // however, subStr equals to some concat(arg_1, arg_2, ..., arg_n) + // if arg_j is a constant and is not a part of the strConst, it's sure that the contains is false + // ** This check is needed here because the "strConst" and "strAst" may not be in a same eqc yet + // ------------------------------------------------------------------------------------------------ + // collect eqc concat + std::set eqcConcats; + get_concats_in_eqc(substrAst, eqcConcats); + for (std::set::iterator concatItor = eqcConcats.begin(); + concatItor != eqcConcats.end(); concatItor++) { expr_ref_vector constList(m); + bool counterEgFound = false; // get constant strings in concat - app * aConcat = to_app(*itAst); + expr * aConcat = *concatItor; get_const_str_asts_in_node(aConcat, constList); for (expr_ref_vector::iterator cstItor = constList.begin(); - cstItor != constList.end(); cstItor++) { + cstItor != constList.end(); cstItor++) { zstring pieceStr; u.str.is_string(*cstItor, pieceStr); if (!strConst.contains(pieceStr)) { - TRACE("str", tout << "Inconsistency found!" << std::endl;); counterEgFound = true; if (aConcat != substrAst) { litems.push_back(ctx.mk_eq_atom(substrAst, aConcat)); } - expr_ref implyLHS(mk_and(litems), m); - expr_ref implyR(m.mk_not(boolVar), m); - assert_implication(implyLHS, implyR); + //implyR = Z3_mk_eq(ctx, boolVar, Z3_mk_false(ctx)); + implyR = m.mk_not(boolVar); break; } } - } - if (counterEgFound) { - break; + if (counterEgFound) { + TRACE("str", tout << "Inconsistency found!" << std::endl;); + break; + } } } + // add assertion + if (implyR) { + expr_ref implyLHS(mk_and(litems), m); + assert_implication(implyLHS, implyR); + } } - } - } - } // varNode in contain_pair_idx_map -} + // varEqcNode is subStr + else if (substrAst == varNode) { + expr_ref implyR(m); + litems.reset(); -bool theory_str::in_contain_idx_map(expr * n) { - return contain_pair_idx_map.find(n) != contain_pair_idx_map.end(); -} + if (substrAst != constNode) { + litems.push_back(ctx.mk_eq_atom(substrAst, constNode)); + } + bool strHasEqcValue = false; + expr * strValue = get_eqc_value(strAst, strHasEqcValue); + if (strValue != strAst) { + litems.push_back(ctx.mk_eq_atom(strAst, strValue)); + } -void theory_str::check_contain_by_eq_nodes(expr * n1, expr * n2) { - context & ctx = get_context(); - ast_manager & m = get_manager(); + if (strHasEqcValue) { + zstring strConst, subStrConst; + u.str.is_string(strValue, strConst); + u.str.is_string(constNode, subStrConst); + if (strConst.contains(subStrConst)) { + //implyR = Z3_mk_eq(ctx, boolVar, Z3_mk_true(ctx)); + implyR = boolVar; + } else { + // implyR = Z3_mk_eq(ctx, boolVar, Z3_mk_false(ctx)); + implyR = m.mk_not(boolVar); + } + } - if (in_contain_idx_map(n1) && in_contain_idx_map(n2)) { - std::set >::iterator keysItor1 = contain_pair_idx_map[n1].begin(); - std::set >::iterator keysItor2; - - for (; keysItor1 != contain_pair_idx_map[n1].end(); keysItor1++) { - // keysItor1 is on set {<.., n1>, ..., , ...} - std::pair key1 = *keysItor1; - if (key1.first == n1 && key1.second == n2) { - expr_ref implyL(m); - expr_ref implyR(contain_pair_bool_map[key1], m); - if (n1 != n2) { - implyL = ctx.mk_eq_atom(n1, n2); - assert_implication(implyL, implyR); - } else { - assert_axiom(implyR); + // add assertion + if (implyR) { + expr_ref implyLHS(mk_and(litems), m); + assert_implication(implyLHS, implyR); + } } - } + } // for (itor1 : contains_map) + } // if varNode in contain_pair_idx_map + } - for (keysItor2 = contain_pair_idx_map[n2].begin(); - keysItor2 != contain_pair_idx_map[n2].end(); keysItor2++) { - // keysItor2 is on set {<.., n2>, ..., , ...} - std::pair key2 = *keysItor2; - // skip if the pair is eq - if (key1 == key2) { + void theory_str::check_contain_by_substr(expr * varNode, expr_ref_vector & willEqClass) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + expr_ref_vector litems(m); + + if (contain_pair_idx_map.find(varNode) != contain_pair_idx_map.end()) { + std::set >::iterator itor1 = contain_pair_idx_map[varNode].begin(); + for (; itor1 != contain_pair_idx_map[varNode].end(); ++itor1) { + expr * strAst = itor1->first; + expr * substrAst = itor1->second; + + expr * boolVar; + if (!contain_pair_bool_map.find(strAst, substrAst, boolVar)) { + TRACE("str", tout << "warning: no entry for boolVar in contain_pair_bool_map" << std::endl;); + } + // boolVar is actually a Contains term + app * containsApp = to_app(boolVar); + + // we only want to inspect the Contains terms where either of strAst or substrAst + // are equal to varNode. + + TRACE("t_str_detail", tout << "considering Contains with strAst = " << mk_pp(strAst, m) << ", substrAst = " << mk_pp(substrAst, m) << "..." << std::endl;); + + if (varNode != strAst && varNode != substrAst) { + TRACE("str", tout << "varNode not equal to strAst or substrAst, skip" << std::endl;); continue; } + TRACE("str", tout << "varNode matched one of strAst or substrAst. Continuing" << std::endl;); - // *************************** - // Case 1: Contains(m, ...) /\ Contains(n, ) /\ m = n - // *************************** - if (key1.first == n1 && key2.first == n2) { - expr * subAst1 = key1.second; - expr * subAst2 = key2.second; - bool subAst1HasValue = false; - bool subAst2HasValue = false; - expr * subValue1 = get_eqc_value(subAst1, subAst1HasValue); - expr * subValue2 = get_eqc_value(subAst2, subAst2HasValue); - - TRACE("str", - tout << "(Contains " << mk_pp(n1, m) << " " << mk_pp(subAst1, m) << ")" << std::endl; - tout << "(Contains " << mk_pp(n2, m) << " " << mk_pp(subAst2, m) << ")" << std::endl; - if (subAst1 != subValue1) { - tout << mk_pp(subAst1, m) << " = " << mk_pp(subValue1, m) << std::endl; + if (substrAst == varNode) { + bool strAstHasVal = false; + expr * strValue = get_eqc_value(strAst, strAstHasVal); + if (strAstHasVal) { + TRACE("str", tout << mk_pp(strAst, m) << " has constant eqc value " << mk_pp(strValue, m) << std::endl;); + if (strValue != strAst) { + litems.push_back(ctx.mk_eq_atom(strAst, strValue)); + } + zstring strConst; + u.str.is_string(strValue, strConst); + // iterate eqc (also eqc-to-be) of substr + for (expr_ref_vector::iterator itAst = willEqClass.begin(); itAst != willEqClass.end(); itAst++) { + bool counterEgFound = false; + if (u.str.is_concat(to_app(*itAst))) { + expr_ref_vector constList(m); + // get constant strings in concat + app * aConcat = to_app(*itAst); + get_const_str_asts_in_node(aConcat, constList); + for (expr_ref_vector::iterator cstItor = constList.begin(); + cstItor != constList.end(); cstItor++) { + zstring pieceStr; + u.str.is_string(*cstItor, pieceStr); + if (!strConst.contains(pieceStr)) { + TRACE("str", tout << "Inconsistency found!" << std::endl;); + counterEgFound = true; + if (aConcat != substrAst) { + litems.push_back(ctx.mk_eq_atom(substrAst, aConcat)); + } + expr_ref implyLHS(mk_and(litems), m); + expr_ref implyR(m.mk_not(boolVar), m); + assert_implication(implyLHS, implyR); + break; + } + } } - if (subAst2 != subValue2) { - tout << mk_pp(subAst2, m) << " = " << mk_pp(subValue2, m) << std::endl; - } - ); - - if (subAst1HasValue && subAst2HasValue) { - expr_ref_vector litems1(m); - if (n1 != n2) { - litems1.push_back(ctx.mk_eq_atom(n1, n2)); - } - if (subValue1 != subAst1) { - litems1.push_back(ctx.mk_eq_atom(subAst1, subValue1)); - } - if (subValue2 != subAst2) { - litems1.push_back(ctx.mk_eq_atom(subAst2, subValue2)); - } - - zstring subConst1, subConst2; - u.str.is_string(subValue1, subConst1); - u.str.is_string(subValue2, subConst2); - expr_ref implyR(m); - if (subConst1 == subConst2) { - // key1.first = key2.first /\ key1.second = key2.second - // ==> (containPairBoolMap[key1] = containPairBoolMap[key2]) - implyR = ctx.mk_eq_atom(contain_pair_bool_map[key1], contain_pair_bool_map[key2]); - } else if (subConst1.contains(subConst2)) { - // key1.first = key2.first /\ Contains(key1.second, key2.second) - // ==> (containPairBoolMap[key1] --> containPairBoolMap[key2]) - implyR = rewrite_implication(contain_pair_bool_map[key1], contain_pair_bool_map[key2]); - } else if (subConst2.contains(subConst1)) { - // key1.first = key2.first /\ Contains(key2.second, key1.second) - // ==> (containPairBoolMap[key2] --> containPairBoolMap[key1]) - implyR = rewrite_implication(contain_pair_bool_map[key2], contain_pair_bool_map[key1]); - } - - if (implyR) { - if (litems1.empty()) { - assert_axiom(implyR); - } else { - assert_implication(mk_and(litems1), implyR); + if (counterEgFound) { + break; } } + } + } + } + } // varNode in contain_pair_idx_map + } + + bool theory_str::in_contain_idx_map(expr * n) { + return contain_pair_idx_map.find(n) != contain_pair_idx_map.end(); + } + + void theory_str::check_contain_by_eq_nodes(expr * n1, expr * n2) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + if (in_contain_idx_map(n1) && in_contain_idx_map(n2)) { + std::set >::iterator keysItor1 = contain_pair_idx_map[n1].begin(); + std::set >::iterator keysItor2; + + for (; keysItor1 != contain_pair_idx_map[n1].end(); keysItor1++) { + // keysItor1 is on set {<.., n1>, ..., , ...} + std::pair key1 = *keysItor1; + if (key1.first == n1 && key1.second == n2) { + expr_ref implyL(m); + expr_ref implyR(contain_pair_bool_map[key1], m); + if (n1 != n2) { + implyL = ctx.mk_eq_atom(n1, n2); + assert_implication(implyL, implyR); } else { - expr_ref_vector subAst1Eqc(m); - expr_ref_vector subAst2Eqc(m); - collect_eq_nodes(subAst1, subAst1Eqc); - collect_eq_nodes(subAst2, subAst2Eqc); + assert_axiom(implyR); + } + } - if (subAst1Eqc.contains(subAst2)) { - // ----------------------------------------------------------- - // * key1.first = key2.first /\ key1.second = key2.second - // --> containPairBoolMap[key1] = containPairBoolMap[key2] - // ----------------------------------------------------------- - expr_ref_vector litems2(m); + for (keysItor2 = contain_pair_idx_map[n2].begin(); + keysItor2 != contain_pair_idx_map[n2].end(); keysItor2++) { + // keysItor2 is on set {<.., n2>, ..., , ...} + std::pair key2 = *keysItor2; + // skip if the pair is eq + if (key1 == key2) { + continue; + } + + // *************************** + // Case 1: Contains(m, ...) /\ Contains(n, ) /\ m = n + // *************************** + if (key1.first == n1 && key2.first == n2) { + expr * subAst1 = key1.second; + expr * subAst2 = key2.second; + bool subAst1HasValue = false; + bool subAst2HasValue = false; + expr * subValue1 = get_eqc_value(subAst1, subAst1HasValue); + expr * subValue2 = get_eqc_value(subAst2, subAst2HasValue); + + TRACE("str", + tout << "(Contains " << mk_pp(n1, m) << " " << mk_pp(subAst1, m) << ")" << std::endl; + tout << "(Contains " << mk_pp(n2, m) << " " << mk_pp(subAst2, m) << ")" << std::endl; + if (subAst1 != subValue1) { + tout << mk_pp(subAst1, m) << " = " << mk_pp(subValue1, m) << std::endl; + } + if (subAst2 != subValue2) { + tout << mk_pp(subAst2, m) << " = " << mk_pp(subValue2, m) << std::endl; + } + ); + + if (subAst1HasValue && subAst2HasValue) { + expr_ref_vector litems1(m); if (n1 != n2) { - litems2.push_back(ctx.mk_eq_atom(n1, n2)); + litems1.push_back(ctx.mk_eq_atom(n1, n2)); } - if (subAst1 != subAst2) { - litems2.push_back(ctx.mk_eq_atom(subAst1, subAst2)); + if (subValue1 != subAst1) { + litems1.push_back(ctx.mk_eq_atom(subAst1, subValue1)); } - expr_ref implyR(ctx.mk_eq_atom(contain_pair_bool_map[key1], contain_pair_bool_map[key2]), m); - if (litems2.empty()) { - assert_axiom(implyR); - } else { - assert_implication(mk_and(litems2), implyR); + if (subValue2 != subAst2) { + litems1.push_back(ctx.mk_eq_atom(subAst2, subValue2)); + } + + zstring subConst1, subConst2; + u.str.is_string(subValue1, subConst1); + u.str.is_string(subValue2, subConst2); + expr_ref implyR(m); + if (subConst1 == subConst2) { + // key1.first = key2.first /\ key1.second = key2.second + // ==> (containPairBoolMap[key1] = containPairBoolMap[key2]) + implyR = ctx.mk_eq_atom(contain_pair_bool_map[key1], contain_pair_bool_map[key2]); + } else if (subConst1.contains(subConst2)) { + // key1.first = key2.first /\ Contains(key1.second, key2.second) + // ==> (containPairBoolMap[key1] --> containPairBoolMap[key2]) + implyR = rewrite_implication(contain_pair_bool_map[key1], contain_pair_bool_map[key2]); + } else if (subConst2.contains(subConst1)) { + // key1.first = key2.first /\ Contains(key2.second, key1.second) + // ==> (containPairBoolMap[key2] --> containPairBoolMap[key1]) + implyR = rewrite_implication(contain_pair_bool_map[key2], contain_pair_bool_map[key1]); + } + + if (implyR) { + if (litems1.empty()) { + assert_axiom(implyR); + } else { + assert_implication(mk_and(litems1), implyR); + } } } else { - // ----------------------------------------------------------- - // * key1.first = key2.first - // check eqc(key1.second) and eqc(key2.second) - // ----------------------------------------------------------- - expr_ref_vector::iterator eqItorSub1 = subAst1Eqc.begin(); - for (; eqItorSub1 != subAst1Eqc.end(); eqItorSub1++) { - expr_ref_vector::iterator eqItorSub2 = subAst2Eqc.begin(); - for (; eqItorSub2 != subAst2Eqc.end(); eqItorSub2++) { - // ------------ - // key1.first = key2.first /\ containPairBoolMap[] - // ==> (containPairBoolMap[key1] --> containPairBoolMap[key2]) - // ------------ - { - expr_ref_vector litems3(m); - if (n1 != n2) { - litems3.push_back(ctx.mk_eq_atom(n1, n2)); + expr_ref_vector subAst1Eqc(m); + expr_ref_vector subAst2Eqc(m); + collect_eq_nodes(subAst1, subAst1Eqc); + collect_eq_nodes(subAst2, subAst2Eqc); + + if (subAst1Eqc.contains(subAst2)) { + // ----------------------------------------------------------- + // * key1.first = key2.first /\ key1.second = key2.second + // --> containPairBoolMap[key1] = containPairBoolMap[key2] + // ----------------------------------------------------------- + expr_ref_vector litems2(m); + if (n1 != n2) { + litems2.push_back(ctx.mk_eq_atom(n1, n2)); + } + if (subAst1 != subAst2) { + litems2.push_back(ctx.mk_eq_atom(subAst1, subAst2)); + } + expr_ref implyR(ctx.mk_eq_atom(contain_pair_bool_map[key1], contain_pair_bool_map[key2]), m); + if (litems2.empty()) { + assert_axiom(implyR); + } else { + assert_implication(mk_and(litems2), implyR); + } + } else { + // ----------------------------------------------------------- + // * key1.first = key2.first + // check eqc(key1.second) and eqc(key2.second) + // ----------------------------------------------------------- + expr_ref_vector::iterator eqItorSub1 = subAst1Eqc.begin(); + for (; eqItorSub1 != subAst1Eqc.end(); eqItorSub1++) { + expr_ref_vector::iterator eqItorSub2 = subAst2Eqc.begin(); + for (; eqItorSub2 != subAst2Eqc.end(); eqItorSub2++) { + // ------------ + // key1.first = key2.first /\ containPairBoolMap[] + // ==> (containPairBoolMap[key1] --> containPairBoolMap[key2]) + // ------------ + { + expr_ref_vector litems3(m); + if (n1 != n2) { + litems3.push_back(ctx.mk_eq_atom(n1, n2)); + } + expr * eqSubVar1 = *eqItorSub1; + if (eqSubVar1 != subAst1) { + litems3.push_back(ctx.mk_eq_atom(subAst1, eqSubVar1)); + } + expr * eqSubVar2 = *eqItorSub2; + if (eqSubVar2 != subAst2) { + litems3.push_back(ctx.mk_eq_atom(subAst2, eqSubVar2)); + } + std::pair tryKey1 = std::make_pair(eqSubVar1, eqSubVar2); + if (contain_pair_bool_map.contains(tryKey1)) { + TRACE("str", tout << "(Contains " << mk_pp(eqSubVar1, m) << " " << mk_pp(eqSubVar2, m) << ")" << std::endl;); + litems3.push_back(contain_pair_bool_map[tryKey1]); + expr_ref implR(rewrite_implication(contain_pair_bool_map[key1], contain_pair_bool_map[key2]), m); + assert_implication(mk_and(litems3), implR); + } } - expr * eqSubVar1 = *eqItorSub1; - if (eqSubVar1 != subAst1) { - litems3.push_back(ctx.mk_eq_atom(subAst1, eqSubVar1)); - } - expr * eqSubVar2 = *eqItorSub2; - if (eqSubVar2 != subAst2) { - litems3.push_back(ctx.mk_eq_atom(subAst2, eqSubVar2)); - } - std::pair tryKey1 = std::make_pair(eqSubVar1, eqSubVar2); - if (contain_pair_bool_map.contains(tryKey1)) { - TRACE("str", tout << "(Contains " << mk_pp(eqSubVar1, m) << " " << mk_pp(eqSubVar2, m) << ")" << std::endl;); - litems3.push_back(contain_pair_bool_map[tryKey1]); - expr_ref implR(rewrite_implication(contain_pair_bool_map[key1], contain_pair_bool_map[key2]), m); - assert_implication(mk_and(litems3), implR); - } - } - // ------------ - // key1.first = key2.first /\ containPairBoolMap[] - // ==> (containPairBoolMap[key2] --> containPairBoolMap[key1]) - // ------------ - { - expr_ref_vector litems4(m); - if (n1 != n2) { - litems4.push_back(ctx.mk_eq_atom(n1, n2)); - } - expr * eqSubVar1 = *eqItorSub1; - if (eqSubVar1 != subAst1) { - litems4.push_back(ctx.mk_eq_atom(subAst1, eqSubVar1)); - } - expr * eqSubVar2 = *eqItorSub2; - if (eqSubVar2 != subAst2) { - litems4.push_back(ctx.mk_eq_atom(subAst2, eqSubVar2)); - } - std::pair tryKey2 = std::make_pair(eqSubVar2, eqSubVar1); - if (contain_pair_bool_map.contains(tryKey2)) { - TRACE("str", tout << "(Contains " << mk_pp(eqSubVar2, m) << " " << mk_pp(eqSubVar1, m) << ")" << std::endl;); - litems4.push_back(contain_pair_bool_map[tryKey2]); - expr_ref implR(rewrite_implication(contain_pair_bool_map[key2], contain_pair_bool_map[key1]), m); - assert_implication(mk_and(litems4), implR); + // ------------ + // key1.first = key2.first /\ containPairBoolMap[] + // ==> (containPairBoolMap[key2] --> containPairBoolMap[key1]) + // ------------ + { + expr_ref_vector litems4(m); + if (n1 != n2) { + litems4.push_back(ctx.mk_eq_atom(n1, n2)); + } + expr * eqSubVar1 = *eqItorSub1; + if (eqSubVar1 != subAst1) { + litems4.push_back(ctx.mk_eq_atom(subAst1, eqSubVar1)); + } + expr * eqSubVar2 = *eqItorSub2; + if (eqSubVar2 != subAst2) { + litems4.push_back(ctx.mk_eq_atom(subAst2, eqSubVar2)); + } + std::pair tryKey2 = std::make_pair(eqSubVar2, eqSubVar1); + if (contain_pair_bool_map.contains(tryKey2)) { + TRACE("str", tout << "(Contains " << mk_pp(eqSubVar2, m) << " " << mk_pp(eqSubVar1, m) << ")" << std::endl;); + litems4.push_back(contain_pair_bool_map[tryKey2]); + expr_ref implR(rewrite_implication(contain_pair_bool_map[key2], contain_pair_bool_map[key1]), m); + assert_implication(mk_and(litems4), implR); + } } } } } } } - } - // *************************** - // Case 2: Contains(..., m) /\ Contains(... , n) /\ m = n - // *************************** - else if (key1.second == n1 && key2.second == n2) { - expr * str1 = key1.first; - expr * str2 = key2.first; - bool str1HasValue = false; - bool str2HasValue = false; - expr * strVal1 = get_eqc_value(str1, str1HasValue); - expr * strVal2 = get_eqc_value(str2, str2HasValue); + // *************************** + // Case 2: Contains(..., m) /\ Contains(... , n) /\ m = n + // *************************** + else if (key1.second == n1 && key2.second == n2) { + expr * str1 = key1.first; + expr * str2 = key2.first; + bool str1HasValue = false; + bool str2HasValue = false; + expr * strVal1 = get_eqc_value(str1, str1HasValue); + expr * strVal2 = get_eqc_value(str2, str2HasValue); - TRACE("str", - tout << "(Contains " << mk_pp(str1, m) << " " << mk_pp(n1, m) << ")" << std::endl; - tout << "(Contains " << mk_pp(str2, m) << " " << mk_pp(n2, m) << ")" << std::endl; - if (str1 != strVal1) { - tout << mk_pp(str1, m) << " = " << mk_pp(strVal1, m) << std::endl; - } - if (str2 != strVal2) { - tout << mk_pp(str2, m) << " = " << mk_pp(strVal2, m) << std::endl; - } - ); + TRACE("str", + tout << "(Contains " << mk_pp(str1, m) << " " << mk_pp(n1, m) << ")" << std::endl; + tout << "(Contains " << mk_pp(str2, m) << " " << mk_pp(n2, m) << ")" << std::endl; + if (str1 != strVal1) { + tout << mk_pp(str1, m) << " = " << mk_pp(strVal1, m) << std::endl; + } + if (str2 != strVal2) { + tout << mk_pp(str2, m) << " = " << mk_pp(strVal2, m) << std::endl; + } + ); - if (str1HasValue && str2HasValue) { - expr_ref_vector litems1(m); - if (n1 != n2) { - litems1.push_back(ctx.mk_eq_atom(n1, n2)); - } - if (strVal1 != str1) { - litems1.push_back(ctx.mk_eq_atom(str1, strVal1)); - } - if (strVal2 != str2) { - litems1.push_back(ctx.mk_eq_atom(str2, strVal2)); - } - - zstring const1, const2; - u.str.is_string(strVal1, const1); - u.str.is_string(strVal2, const2); - expr_ref implyR(m); - - if (const1 == const2) { - // key1.second = key2.second /\ key1.first = key2.first - // ==> (containPairBoolMap[key1] = containPairBoolMap[key2]) - implyR = ctx.mk_eq_atom(contain_pair_bool_map[key1], contain_pair_bool_map[key2]); - } else if (const1.contains(const2)) { - // key1.second = key2.second /\ Contains(key1.first, key2.first) - // ==> (containPairBoolMap[key2] --> containPairBoolMap[key1]) - implyR = rewrite_implication(contain_pair_bool_map[key2], contain_pair_bool_map[key1]); - } else if (const2.contains(const1)) { - // key1.first = key2.first /\ Contains(key2.first, key1.first) - // ==> (containPairBoolMap[key1] --> containPairBoolMap[key2]) - implyR = rewrite_implication(contain_pair_bool_map[key1], contain_pair_bool_map[key2]); - } - - if (implyR) { - if (litems1.size() == 0) { - assert_axiom(implyR); - } else { - assert_implication(mk_and(litems1), implyR); - } - } - } - - else { - expr_ref_vector str1Eqc(m); - expr_ref_vector str2Eqc(m); - collect_eq_nodes(str1, str1Eqc); - collect_eq_nodes(str2, str2Eqc); - - if (str1Eqc.contains(str2)) { - // ----------------------------------------------------------- - // * key1.first = key2.first /\ key1.second = key2.second - // --> containPairBoolMap[key1] = containPairBoolMap[key2] - // ----------------------------------------------------------- - expr_ref_vector litems2(m); + if (str1HasValue && str2HasValue) { + expr_ref_vector litems1(m); if (n1 != n2) { - litems2.push_back(ctx.mk_eq_atom(n1, n2)); + litems1.push_back(ctx.mk_eq_atom(n1, n2)); } - if (str1 != str2) { - litems2.push_back(ctx.mk_eq_atom(str1, str2)); + if (strVal1 != str1) { + litems1.push_back(ctx.mk_eq_atom(str1, strVal1)); } - expr_ref implyR(ctx.mk_eq_atom(contain_pair_bool_map[key1], contain_pair_bool_map[key2]), m); - if (litems2.empty()) { - assert_axiom(implyR); + if (strVal2 != str2) { + litems1.push_back(ctx.mk_eq_atom(str2, strVal2)); + } + + zstring const1, const2; + u.str.is_string(strVal1, const1); + u.str.is_string(strVal2, const2); + expr_ref implyR(m); + + if (const1 == const2) { + // key1.second = key2.second /\ key1.first = key2.first + // ==> (containPairBoolMap[key1] = containPairBoolMap[key2]) + implyR = ctx.mk_eq_atom(contain_pair_bool_map[key1], contain_pair_bool_map[key2]); + } else if (const1.contains(const2)) { + // key1.second = key2.second /\ Contains(key1.first, key2.first) + // ==> (containPairBoolMap[key2] --> containPairBoolMap[key1]) + implyR = rewrite_implication(contain_pair_bool_map[key2], contain_pair_bool_map[key1]); + } else if (const2.contains(const1)) { + // key1.first = key2.first /\ Contains(key2.first, key1.first) + // ==> (containPairBoolMap[key1] --> containPairBoolMap[key2]) + implyR = rewrite_implication(contain_pair_bool_map[key1], contain_pair_bool_map[key2]); + } + + if (implyR) { + if (litems1.size() == 0) { + assert_axiom(implyR); + } else { + assert_implication(mk_and(litems1), implyR); + } + } + } + + else { + expr_ref_vector str1Eqc(m); + expr_ref_vector str2Eqc(m); + collect_eq_nodes(str1, str1Eqc); + collect_eq_nodes(str2, str2Eqc); + + if (str1Eqc.contains(str2)) { + // ----------------------------------------------------------- + // * key1.first = key2.first /\ key1.second = key2.second + // --> containPairBoolMap[key1] = containPairBoolMap[key2] + // ----------------------------------------------------------- + expr_ref_vector litems2(m); + if (n1 != n2) { + litems2.push_back(ctx.mk_eq_atom(n1, n2)); + } + if (str1 != str2) { + litems2.push_back(ctx.mk_eq_atom(str1, str2)); + } + expr_ref implyR(ctx.mk_eq_atom(contain_pair_bool_map[key1], contain_pair_bool_map[key2]), m); + if (litems2.empty()) { + assert_axiom(implyR); + } else { + assert_implication(mk_and(litems2), implyR); + } } else { - assert_implication(mk_and(litems2), implyR); - } - } else { - // ----------------------------------------------------------- - // * key1.second = key2.second - // check eqc(key1.first) and eqc(key2.first) - // ----------------------------------------------------------- - expr_ref_vector::iterator eqItorStr1 = str1Eqc.begin(); - for (; eqItorStr1 != str1Eqc.end(); eqItorStr1++) { - expr_ref_vector::iterator eqItorStr2 = str2Eqc.begin(); - for (; eqItorStr2 != str2Eqc.end(); eqItorStr2++) { - { - expr_ref_vector litems3(m); - if (n1 != n2) { - litems3.push_back(ctx.mk_eq_atom(n1, n2)); - } - expr * eqStrVar1 = *eqItorStr1; - if (eqStrVar1 != str1) { - litems3.push_back(ctx.mk_eq_atom(str1, eqStrVar1)); - } - expr * eqStrVar2 = *eqItorStr2; - if (eqStrVar2 != str2) { - litems3.push_back(ctx.mk_eq_atom(str2, eqStrVar2)); - } - std::pair tryKey1 = std::make_pair(eqStrVar1, eqStrVar2); - if (contain_pair_bool_map.contains(tryKey1)) { - TRACE("str", tout << "(Contains " << mk_pp(eqStrVar1, m) << " " << mk_pp(eqStrVar2, m) << ")" << std::endl;); - litems3.push_back(contain_pair_bool_map[tryKey1]); + // ----------------------------------------------------------- + // * key1.second = key2.second + // check eqc(key1.first) and eqc(key2.first) + // ----------------------------------------------------------- + expr_ref_vector::iterator eqItorStr1 = str1Eqc.begin(); + for (; eqItorStr1 != str1Eqc.end(); eqItorStr1++) { + expr_ref_vector::iterator eqItorStr2 = str2Eqc.begin(); + for (; eqItorStr2 != str2Eqc.end(); eqItorStr2++) { + { + expr_ref_vector litems3(m); + if (n1 != n2) { + litems3.push_back(ctx.mk_eq_atom(n1, n2)); + } + expr * eqStrVar1 = *eqItorStr1; + if (eqStrVar1 != str1) { + litems3.push_back(ctx.mk_eq_atom(str1, eqStrVar1)); + } + expr * eqStrVar2 = *eqItorStr2; + if (eqStrVar2 != str2) { + litems3.push_back(ctx.mk_eq_atom(str2, eqStrVar2)); + } + std::pair tryKey1 = std::make_pair(eqStrVar1, eqStrVar2); + if (contain_pair_bool_map.contains(tryKey1)) { + TRACE("str", tout << "(Contains " << mk_pp(eqStrVar1, m) << " " << mk_pp(eqStrVar2, m) << ")" << std::endl;); + litems3.push_back(contain_pair_bool_map[tryKey1]); - // ------------ - // key1.second = key2.second /\ containPairBoolMap[] - // ==> (containPairBoolMap[key2] --> containPairBoolMap[key1]) - // ------------ - expr_ref implR(rewrite_implication(contain_pair_bool_map[key2], contain_pair_bool_map[key1]), m); - assert_implication(mk_and(litems3), implR); + // ------------ + // key1.second = key2.second /\ containPairBoolMap[] + // ==> (containPairBoolMap[key2] --> containPairBoolMap[key1]) + // ------------ + expr_ref implR(rewrite_implication(contain_pair_bool_map[key2], contain_pair_bool_map[key1]), m); + assert_implication(mk_and(litems3), implR); + } } - } - { - expr_ref_vector litems4(m); - if (n1 != n2) { - litems4.push_back(ctx.mk_eq_atom(n1, n2)); - } - expr * eqStrVar1 = *eqItorStr1; - if (eqStrVar1 != str1) { - litems4.push_back(ctx.mk_eq_atom(str1, eqStrVar1)); - } - expr *eqStrVar2 = *eqItorStr2; - if (eqStrVar2 != str2) { - litems4.push_back(ctx.mk_eq_atom(str2, eqStrVar2)); - } - std::pair tryKey2 = std::make_pair(eqStrVar2, eqStrVar1); + { + expr_ref_vector litems4(m); + if (n1 != n2) { + litems4.push_back(ctx.mk_eq_atom(n1, n2)); + } + expr * eqStrVar1 = *eqItorStr1; + if (eqStrVar1 != str1) { + litems4.push_back(ctx.mk_eq_atom(str1, eqStrVar1)); + } + expr *eqStrVar2 = *eqItorStr2; + if (eqStrVar2 != str2) { + litems4.push_back(ctx.mk_eq_atom(str2, eqStrVar2)); + } + std::pair tryKey2 = std::make_pair(eqStrVar2, eqStrVar1); - if (contain_pair_bool_map.contains(tryKey2)) { - TRACE("str", tout << "(Contains " << mk_pp(eqStrVar2, m) << " " << mk_pp(eqStrVar1, m) << ")" << std::endl;); - litems4.push_back(contain_pair_bool_map[tryKey2]); - // ------------ - // key1.first = key2.first /\ containPairBoolMap[] - // ==> (containPairBoolMap[key1] --> containPairBoolMap[key2]) - // ------------ - expr_ref implR(rewrite_implication(contain_pair_bool_map[key1], contain_pair_bool_map[key2]), m); - assert_implication(mk_and(litems4), implR); + if (contain_pair_bool_map.contains(tryKey2)) { + TRACE("str", tout << "(Contains " << mk_pp(eqStrVar2, m) << " " << mk_pp(eqStrVar1, m) << ")" << std::endl;); + litems4.push_back(contain_pair_bool_map[tryKey2]); + // ------------ + // key1.first = key2.first /\ containPairBoolMap[] + // ==> (containPairBoolMap[key1] --> containPairBoolMap[key2]) + // ------------ + expr_ref implR(rewrite_implication(contain_pair_bool_map[key1], contain_pair_bool_map[key2]), m); + assert_implication(mk_and(litems4), implR); + } } } } } } - } - } - } - - if (n1 == n2) { - break; - } - } - } // (in_contain_idx_map(n1) && in_contain_idx_map(n2)) -} - -void theory_str::check_contain_in_new_eq(expr * n1, expr * n2) { - if (contains_map.empty()) { - return; - } - - context & ctx = get_context(); - ast_manager & m = get_manager(); - TRACE("str", tout << "consistency check for contains wrt. " << mk_pp(n1, m) << " and " << mk_pp(n2, m) << std::endl;); - - expr_ref_vector willEqClass(m); - expr * constStrAst_1 = collect_eq_nodes(n1, willEqClass); - expr * constStrAst_2 = collect_eq_nodes(n2, willEqClass); - expr * constStrAst = (constStrAst_1 != NULL) ? constStrAst_1 : constStrAst_2; - - TRACE("str", tout << "eqc of n1 is {"; - for (expr_ref_vector::iterator it = willEqClass.begin(); it != willEqClass.end(); ++it) { - expr * el = *it; - tout << " " << mk_pp(el, m); - } - tout << std::endl; - if (constStrAst == NULL) { - tout << "constStrAst = NULL" << std::endl; - } else { - tout << "constStrAst = " << mk_pp(constStrAst, m) << std::endl; - } - ); - - // step 1: we may have constant values for Contains checks now - if (constStrAst != NULL) { - expr_ref_vector::iterator itAst = willEqClass.begin(); - for (; itAst != willEqClass.end(); itAst++) { - if (*itAst == constStrAst) { - continue; - } - check_contain_by_eqc_val(*itAst, constStrAst); - } - } else { - // no concrete value to be put in eqc, solely based on context - // Check here is used to detected the facts as follows: - // * known: contains(Z, Y) /\ Z = "abcdefg" /\ Y = M - // * new fact: M = concat(..., "jio", ...) - // Note that in this branch, either M or concat(..., "jio", ...) has a constant value - // So, only need to check - // * "EQC(M) U EQC(concat(..., "jio", ...))" as substr and - // * If strAst registered has an eqc constant in the context - // ------------------------------------------------------------- - expr_ref_vector::iterator itAst = willEqClass.begin(); - for (; itAst != willEqClass.end(); ++itAst) { - check_contain_by_substr(*itAst, willEqClass); - } - } - - // ------------------------------------------ - // step 2: check for b1 = contains(x, m), b2 = contains(y, n) - // (1) x = y /\ m = n ==> b1 = b2 - // (2) x = y /\ Contains(const(m), const(n)) ==> (b1 -> b2) - // (3) x = y /\ Contains(const(n), const(m)) ==> (b2 -> b1) - // (4) x = y /\ containPairBoolMap[] ==> (b1 -> b2) - // (5) x = y /\ containPairBoolMap[] ==> (b2 -> b1) - // (6) Contains(const(x), const(y)) /\ m = n ==> (b2 -> b1) - // (7) Contains(const(y), const(x)) /\ m = n ==> (b1 -> b2) - // (8) containPairBoolMap[] /\ m = n ==> (b2 -> b1) - // (9) containPairBoolMap[] /\ m = n ==> (b1 -> b2) - // ------------------------------------------ - - expr_ref_vector::iterator varItor1 = willEqClass.begin(); - for (; varItor1 != willEqClass.end(); ++varItor1) { - expr * varAst1 = *varItor1; - expr_ref_vector::iterator varItor2 = varItor1; - for (; varItor2 != willEqClass.end(); ++varItor2) { - expr * varAst2 = *varItor2; - check_contain_by_eq_nodes(varAst1, varAst2); - } - } -} - -expr * theory_str::dealias_node(expr * node, std::map & varAliasMap, std::map & concatAliasMap) { - if (variable_set.find(node) != variable_set.end()) { - return get_alias_index_ast(varAliasMap, node); - } else if (u.str.is_concat(to_app(node))) { - return get_alias_index_ast(concatAliasMap, node); - } - return node; -} - -void theory_str::get_grounded_concats(expr* node, std::map & varAliasMap, - std::map & concatAliasMap, std::map & varConstMap, - std::map & concatConstMap, std::map > & varEqConcatMap, - std::map, std::set > > & groundedMap) { - if (u.re.is_unroll(to_app(node))) { - return; - } - // ************************************************** - // first deAlias the node if it is a var or concat - // ************************************************** - node = dealias_node(node, varAliasMap, concatAliasMap); - - if (groundedMap.find(node) != groundedMap.end()) { - return; - } - - // haven't computed grounded concats for "node" (de-aliased) - // --------------------------------------------------------- - - context & ctx = get_context(); - ast_manager & m = get_manager(); - - // const strings: node is de-aliased - if (u.str.is_string(node)) { - std::vector concatNodes; - concatNodes.push_back(node); - groundedMap[node][concatNodes].clear(); // no condition - } - // Concat functions - else if (u.str.is_concat(to_app(node))) { - // if "node" equals to a constant string, thenjust push the constant into the concat vector - // Again "node" has been de-aliased at the very beginning - if (concatConstMap.find(node) != concatConstMap.end()) { - std::vector concatNodes; - concatNodes.push_back(concatConstMap[node]); - groundedMap[node][concatNodes].clear(); - groundedMap[node][concatNodes].insert(ctx.mk_eq_atom(node, concatConstMap[node])); - } - // node doesn't have eq constant value. Process its children. - else { - // merge arg0 and arg1 - expr * arg0 = to_app(node)->get_arg(0); - expr * arg1 = to_app(node)->get_arg(1); - expr * arg0DeAlias = dealias_node(arg0, varAliasMap, concatAliasMap); - expr * arg1DeAlias = dealias_node(arg1, varAliasMap, concatAliasMap); - get_grounded_concats(arg0DeAlias, varAliasMap, concatAliasMap, varConstMap, concatConstMap, varEqConcatMap, groundedMap); - get_grounded_concats(arg1DeAlias, varAliasMap, concatAliasMap, varConstMap, concatConstMap, varEqConcatMap, groundedMap); - - std::map, std::set >::iterator arg0_grdItor = groundedMap[arg0DeAlias].begin(); - std::map, std::set >::iterator arg1_grdItor; - for (; arg0_grdItor != groundedMap[arg0DeAlias].end(); arg0_grdItor++) { - arg1_grdItor = groundedMap[arg1DeAlias].begin(); - for (; arg1_grdItor != groundedMap[arg1DeAlias].end(); arg1_grdItor++) { - std::vector ndVec; - ndVec.insert(ndVec.end(), arg0_grdItor->first.begin(), arg0_grdItor->first.end()); - int arg0VecSize = arg0_grdItor->first.size(); - int arg1VecSize = arg1_grdItor->first.size(); - if (arg0VecSize > 0 && arg1VecSize > 0 && u.str.is_string(arg0_grdItor->first[arg0VecSize - 1]) && u.str.is_string(arg1_grdItor->first[0])) { - ndVec.pop_back(); - ndVec.push_back(mk_concat(arg0_grdItor->first[arg0VecSize - 1], arg1_grdItor->first[0])); - for (int i = 1; i < arg1VecSize; i++) { - ndVec.push_back(arg1_grdItor->first[i]); - } - } else { - ndVec.insert(ndVec.end(), arg1_grdItor->first.begin(), arg1_grdItor->first.end()); - } - // only insert if we don't know "node = concat(ndVec)" since one set of condition leads to this is enough - if (groundedMap[node].find(ndVec) == groundedMap[node].end()) { - groundedMap[node][ndVec]; - if (arg0 != arg0DeAlias) { - groundedMap[node][ndVec].insert(ctx.mk_eq_atom(arg0, arg0DeAlias)); - } - groundedMap[node][ndVec].insert(arg0_grdItor->second.begin(), arg0_grdItor->second.end()); - - if (arg1 != arg1DeAlias) { - groundedMap[node][ndVec].insert(ctx.mk_eq_atom(arg1, arg1DeAlias)); - } - groundedMap[node][ndVec].insert(arg1_grdItor->second.begin(), arg1_grdItor->second.end()); } } + + if (n1 == n2) { + break; + } + } + } // (in_contain_idx_map(n1) && in_contain_idx_map(n2)) + } + + void theory_str::check_contain_in_new_eq(expr * n1, expr * n2) { + if (contains_map.empty()) { + return; + } + + context & ctx = get_context(); + ast_manager & m = get_manager(); + TRACE("str", tout << "consistency check for contains wrt. " << mk_pp(n1, m) << " and " << mk_pp(n2, m) << std::endl;); + + expr_ref_vector willEqClass(m); + expr * constStrAst_1 = collect_eq_nodes(n1, willEqClass); + expr * constStrAst_2 = collect_eq_nodes(n2, willEqClass); + expr * constStrAst = (constStrAst_1 != NULL) ? constStrAst_1 : constStrAst_2; + + TRACE("str", tout << "eqc of n1 is {"; + for (expr_ref_vector::iterator it = willEqClass.begin(); it != willEqClass.end(); ++it) { + expr * el = *it; + tout << " " << mk_pp(el, m); + } + tout << std::endl; + if (constStrAst == NULL) { + tout << "constStrAst = NULL" << std::endl; + } else { + tout << "constStrAst = " << mk_pp(constStrAst, m) << std::endl; + } + ); + + // step 1: we may have constant values for Contains checks now + if (constStrAst != NULL) { + expr_ref_vector::iterator itAst = willEqClass.begin(); + for (; itAst != willEqClass.end(); itAst++) { + if (*itAst == constStrAst) { + continue; + } + check_contain_by_eqc_val(*itAst, constStrAst); + } + } else { + // no concrete value to be put in eqc, solely based on context + // Check here is used to detected the facts as follows: + // * known: contains(Z, Y) /\ Z = "abcdefg" /\ Y = M + // * new fact: M = concat(..., "jio", ...) + // Note that in this branch, either M or concat(..., "jio", ...) has a constant value + // So, only need to check + // * "EQC(M) U EQC(concat(..., "jio", ...))" as substr and + // * If strAst registered has an eqc constant in the context + // ------------------------------------------------------------- + expr_ref_vector::iterator itAst = willEqClass.begin(); + for (; itAst != willEqClass.end(); ++itAst) { + check_contain_by_substr(*itAst, willEqClass); + } + } + + // ------------------------------------------ + // step 2: check for b1 = contains(x, m), b2 = contains(y, n) + // (1) x = y /\ m = n ==> b1 = b2 + // (2) x = y /\ Contains(const(m), const(n)) ==> (b1 -> b2) + // (3) x = y /\ Contains(const(n), const(m)) ==> (b2 -> b1) + // (4) x = y /\ containPairBoolMap[] ==> (b1 -> b2) + // (5) x = y /\ containPairBoolMap[] ==> (b2 -> b1) + // (6) Contains(const(x), const(y)) /\ m = n ==> (b2 -> b1) + // (7) Contains(const(y), const(x)) /\ m = n ==> (b1 -> b2) + // (8) containPairBoolMap[] /\ m = n ==> (b2 -> b1) + // (9) containPairBoolMap[] /\ m = n ==> (b1 -> b2) + // ------------------------------------------ + + expr_ref_vector::iterator varItor1 = willEqClass.begin(); + for (; varItor1 != willEqClass.end(); ++varItor1) { + expr * varAst1 = *varItor1; + expr_ref_vector::iterator varItor2 = varItor1; + for (; varItor2 != willEqClass.end(); ++varItor2) { + expr * varAst2 = *varItor2; + check_contain_by_eq_nodes(varAst1, varAst2); } } } - // string variables - else if (variable_set.find(node) != variable_set.end()) { - // deAliasedVar = Constant - if (varConstMap.find(node) != varConstMap.end()) { - std::vector concatNodes; - concatNodes.push_back(varConstMap[node]); - groundedMap[node][concatNodes].clear(); - groundedMap[node][concatNodes].insert(ctx.mk_eq_atom(node, varConstMap[node])); - } - // deAliasedVar = someConcat - else if (varEqConcatMap.find(node) != varEqConcatMap.end()) { - expr * eqConcat = varEqConcatMap[node].begin()->first; - expr * deAliasedEqConcat = dealias_node(eqConcat, varAliasMap, concatAliasMap); - get_grounded_concats(deAliasedEqConcat, varAliasMap, concatAliasMap, varConstMap, concatConstMap, varEqConcatMap, groundedMap); - std::map, std::set >::iterator grdItor = groundedMap[deAliasedEqConcat].begin(); - for (; grdItor != groundedMap[deAliasedEqConcat].end(); grdItor++) { - std::vector ndVec; - ndVec.insert(ndVec.end(), grdItor->first.begin(), grdItor->first.end()); - // only insert if we don't know "node = concat(ndVec)" since one set of condition leads to this is enough - if (groundedMap[node].find(ndVec) == groundedMap[node].end()) { - // condition: node = deAliasedEqConcat - groundedMap[node][ndVec].insert(ctx.mk_eq_atom(node, deAliasedEqConcat)); - // appending conditions for "deAliasedEqConcat = CONCAT(ndVec)" - groundedMap[node][ndVec].insert(grdItor->second.begin(), grdItor->second.end()); - } - } + expr * theory_str::dealias_node(expr * node, std::map & varAliasMap, std::map & concatAliasMap) { + if (variable_set.find(node) != variable_set.end()) { + return get_alias_index_ast(varAliasMap, node); + } else if (u.str.is_concat(to_app(node))) { + return get_alias_index_ast(concatAliasMap, node); } - // node (has been de-aliased) != constant && node (has been de-aliased) != any concat - // just push in the deAliasedVar - else { + return node; + } + + void theory_str::get_grounded_concats(expr* node, std::map & varAliasMap, + std::map & concatAliasMap, std::map & varConstMap, + std::map & concatConstMap, std::map > & varEqConcatMap, + std::map, std::set > > & groundedMap) { + if (u.re.is_unroll(to_app(node))) { + return; + } + // ************************************************** + // first deAlias the node if it is a var or concat + // ************************************************** + node = dealias_node(node, varAliasMap, concatAliasMap); + + if (groundedMap.find(node) != groundedMap.end()) { + return; + } + + // haven't computed grounded concats for "node" (de-aliased) + // --------------------------------------------------------- + + context & ctx = get_context(); + ast_manager & m = get_manager(); + + // const strings: node is de-aliased + if (u.str.is_string(node)) { std::vector concatNodes; concatNodes.push_back(node); - groundedMap[node][concatNodes]; + groundedMap[node][concatNodes].clear(); // no condition + } + // Concat functions + else if (u.str.is_concat(to_app(node))) { + // if "node" equals to a constant string, thenjust push the constant into the concat vector + // Again "node" has been de-aliased at the very beginning + if (concatConstMap.find(node) != concatConstMap.end()) { + std::vector concatNodes; + concatNodes.push_back(concatConstMap[node]); + groundedMap[node][concatNodes].clear(); + groundedMap[node][concatNodes].insert(ctx.mk_eq_atom(node, concatConstMap[node])); + } + // node doesn't have eq constant value. Process its children. + else { + // merge arg0 and arg1 + expr * arg0 = to_app(node)->get_arg(0); + expr * arg1 = to_app(node)->get_arg(1); + expr * arg0DeAlias = dealias_node(arg0, varAliasMap, concatAliasMap); + expr * arg1DeAlias = dealias_node(arg1, varAliasMap, concatAliasMap); + get_grounded_concats(arg0DeAlias, varAliasMap, concatAliasMap, varConstMap, concatConstMap, varEqConcatMap, groundedMap); + get_grounded_concats(arg1DeAlias, varAliasMap, concatAliasMap, varConstMap, concatConstMap, varEqConcatMap, groundedMap); + + std::map, std::set >::iterator arg0_grdItor = groundedMap[arg0DeAlias].begin(); + std::map, std::set >::iterator arg1_grdItor; + for (; arg0_grdItor != groundedMap[arg0DeAlias].end(); arg0_grdItor++) { + arg1_grdItor = groundedMap[arg1DeAlias].begin(); + for (; arg1_grdItor != groundedMap[arg1DeAlias].end(); arg1_grdItor++) { + std::vector ndVec; + ndVec.insert(ndVec.end(), arg0_grdItor->first.begin(), arg0_grdItor->first.end()); + int arg0VecSize = arg0_grdItor->first.size(); + int arg1VecSize = arg1_grdItor->first.size(); + if (arg0VecSize > 0 && arg1VecSize > 0 && u.str.is_string(arg0_grdItor->first[arg0VecSize - 1]) && u.str.is_string(arg1_grdItor->first[0])) { + ndVec.pop_back(); + ndVec.push_back(mk_concat(arg0_grdItor->first[arg0VecSize - 1], arg1_grdItor->first[0])); + for (int i = 1; i < arg1VecSize; i++) { + ndVec.push_back(arg1_grdItor->first[i]); + } + } else { + ndVec.insert(ndVec.end(), arg1_grdItor->first.begin(), arg1_grdItor->first.end()); + } + // only insert if we don't know "node = concat(ndVec)" since one set of condition leads to this is enough + if (groundedMap[node].find(ndVec) == groundedMap[node].end()) { + groundedMap[node][ndVec]; + if (arg0 != arg0DeAlias) { + groundedMap[node][ndVec].insert(ctx.mk_eq_atom(arg0, arg0DeAlias)); + } + groundedMap[node][ndVec].insert(arg0_grdItor->second.begin(), arg0_grdItor->second.end()); + + if (arg1 != arg1DeAlias) { + groundedMap[node][ndVec].insert(ctx.mk_eq_atom(arg1, arg1DeAlias)); + } + groundedMap[node][ndVec].insert(arg1_grdItor->second.begin(), arg1_grdItor->second.end()); + } + } + } + } + } + // string variables + else if (variable_set.find(node) != variable_set.end()) { + // deAliasedVar = Constant + if (varConstMap.find(node) != varConstMap.end()) { + std::vector concatNodes; + concatNodes.push_back(varConstMap[node]); + groundedMap[node][concatNodes].clear(); + groundedMap[node][concatNodes].insert(ctx.mk_eq_atom(node, varConstMap[node])); + } + // deAliasedVar = someConcat + else if (varEqConcatMap.find(node) != varEqConcatMap.end()) { + expr * eqConcat = varEqConcatMap[node].begin()->first; + expr * deAliasedEqConcat = dealias_node(eqConcat, varAliasMap, concatAliasMap); + get_grounded_concats(deAliasedEqConcat, varAliasMap, concatAliasMap, varConstMap, concatConstMap, varEqConcatMap, groundedMap); + + std::map, std::set >::iterator grdItor = groundedMap[deAliasedEqConcat].begin(); + for (; grdItor != groundedMap[deAliasedEqConcat].end(); grdItor++) { + std::vector ndVec; + ndVec.insert(ndVec.end(), grdItor->first.begin(), grdItor->first.end()); + // only insert if we don't know "node = concat(ndVec)" since one set of condition leads to this is enough + if (groundedMap[node].find(ndVec) == groundedMap[node].end()) { + // condition: node = deAliasedEqConcat + groundedMap[node][ndVec].insert(ctx.mk_eq_atom(node, deAliasedEqConcat)); + // appending conditions for "deAliasedEqConcat = CONCAT(ndVec)" + groundedMap[node][ndVec].insert(grdItor->second.begin(), grdItor->second.end()); + } + } + } + // node (has been de-aliased) != constant && node (has been de-aliased) != any concat + // just push in the deAliasedVar + else { + std::vector concatNodes; + concatNodes.push_back(node); + groundedMap[node][concatNodes]; + } } } -} -void theory_str::print_grounded_concat(expr * node, std::map, std::set > > & groundedMap) { - ast_manager & m = get_manager(); - TRACE("str", tout << mk_pp(node, m) << std::endl;); - if (groundedMap.find(node) != groundedMap.end()) { - std::map, std::set >::iterator itor = groundedMap[node].begin(); - for (; itor != groundedMap[node].end(); ++itor) { - TRACE("str", - tout << "\t[grounded] "; - std::vector::const_iterator vIt = itor->first.begin(); - for (; vIt != itor->first.end(); ++vIt) { - tout << mk_pp(*vIt, m) << ", "; - } - tout << std::endl; - tout << "\t[condition] "; - std::set::iterator sIt = itor->second.begin(); - for (; sIt != itor->second.end(); sIt++) { - tout << mk_pp(*sIt, m) << ", "; - } - tout << std::endl; - ); + void theory_str::print_grounded_concat(expr * node, std::map, std::set > > & groundedMap) { + ast_manager & m = get_manager(); + TRACE("str", tout << mk_pp(node, m) << std::endl;); + if (groundedMap.find(node) != groundedMap.end()) { + std::map, std::set >::iterator itor = groundedMap[node].begin(); + for (; itor != groundedMap[node].end(); ++itor) { + TRACE("str", + tout << "\t[grounded] "; + std::vector::const_iterator vIt = itor->first.begin(); + for (; vIt != itor->first.end(); ++vIt) { + tout << mk_pp(*vIt, m) << ", "; + } + tout << std::endl; + tout << "\t[condition] "; + std::set::iterator sIt = itor->second.begin(); + for (; sIt != itor->second.end(); sIt++) { + tout << mk_pp(*sIt, m) << ", "; + } + tout << std::endl; + ); + } + } else { + TRACE("str", tout << "not found" << std::endl;); } - } else { - TRACE("str", tout << "not found" << std::endl;); - } -} - -bool theory_str::is_partial_in_grounded_concat(const std::vector & strVec, const std::vector & subStrVec) { - int strCnt = strVec.size(); - int subStrCnt = subStrVec.size(); - - if (strCnt == 0 || subStrCnt == 0) { - return false; } - // The assumption is that all consecutive constant strings are merged into one node - if (strCnt < subStrCnt) { - return false; - } + bool theory_str::is_partial_in_grounded_concat(const std::vector & strVec, const std::vector & subStrVec) { + int strCnt = strVec.size(); + int subStrCnt = subStrVec.size(); - if (subStrCnt == 1) { - zstring subStrVal; - if (u.str.is_string(subStrVec[0], subStrVal)) { - for (int i = 0; i < strCnt; i++) { - zstring strVal; - if (u.str.is_string(strVec[i], strVal)) { - if (strVal.contains(subStrVal)) { + if (strCnt == 0 || subStrCnt == 0) { + return false; + } + + // The assumption is that all consecutive constant strings are merged into one node + if (strCnt < subStrCnt) { + return false; + } + + if (subStrCnt == 1) { + zstring subStrVal; + if (u.str.is_string(subStrVec[0], subStrVal)) { + for (int i = 0; i < strCnt; i++) { + zstring strVal; + if (u.str.is_string(strVec[i], strVal)) { + if (strVal.contains(subStrVal)) { + return true; + } + } + } + } else { + for (int i = 0; i < strCnt; i++) { + if (strVec[i] == subStrVec[0]) { return true; } } } + return false; } else { - for (int i = 0; i < strCnt; i++) { - if (strVec[i] == subStrVec[0]) { - return true; - } - } - } - return false; - } else { - for (int i = 0; i <= (strCnt - subStrCnt); i++) { - // The first node in subStrVect should be - // * constant: a suffix of a note in strVec[i] - // * variable: - bool firstNodesOK = true; - zstring subStrHeadVal; - if (u.str.is_string(subStrVec[0], subStrHeadVal)) { - zstring strHeadVal; - if (u.str.is_string(strVec[i], strHeadVal)) { - if (strHeadVal.length() >= subStrHeadVal.length()) { - zstring suffix = strHeadVal.extract(strHeadVal.length() - subStrHeadVal.length(), subStrHeadVal.length()); - if (suffix != subStrHeadVal) { + for (int i = 0; i <= (strCnt - subStrCnt); i++) { + // The first node in subStrVect should be + // * constant: a suffix of a note in strVec[i] + // * variable: + bool firstNodesOK = true; + zstring subStrHeadVal; + if (u.str.is_string(subStrVec[0], subStrHeadVal)) { + zstring strHeadVal; + if (u.str.is_string(strVec[i], strHeadVal)) { + if (strHeadVal.length() >= subStrHeadVal.length()) { + zstring suffix = strHeadVal.extract(strHeadVal.length() - subStrHeadVal.length(), subStrHeadVal.length()); + if (suffix != subStrHeadVal) { + firstNodesOK = false; + } + } else { firstNodesOK = false; } } else { - firstNodesOK = false; - } - } else { - if (subStrVec[0] != strVec[i]) { - firstNodesOK = false; + if (subStrVec[0] != strVec[i]) { + firstNodesOK = false; + } } } - } - if (!firstNodesOK) { - continue; - } - - // middle nodes - bool midNodesOK = true; - for (int j = 1; j < subStrCnt - 1; j++) { - if (subStrVec[j] != strVec[i + j]) { - midNodesOK = false; - break; + if (!firstNodesOK) { + continue; } - } - if (!midNodesOK) { - continue; - } - // tail nodes - int tailIdx = i + subStrCnt - 1; - zstring subStrTailVal; - if (u.str.is_string(subStrVec[subStrCnt - 1], subStrTailVal)) { - zstring strTailVal; - if (u.str.is_string(strVec[tailIdx], strTailVal)) { - if (strTailVal.length() >= subStrTailVal.length()) { - zstring prefix = strTailVal.extract(0, subStrTailVal.length()); - if (prefix == subStrTailVal) { - return true; + // middle nodes + bool midNodesOK = true; + for (int j = 1; j < subStrCnt - 1; j++) { + if (subStrVec[j] != strVec[i + j]) { + midNodesOK = false; + break; + } + } + if (!midNodesOK) { + continue; + } + + // tail nodes + int tailIdx = i + subStrCnt - 1; + zstring subStrTailVal; + if (u.str.is_string(subStrVec[subStrCnt - 1], subStrTailVal)) { + zstring strTailVal; + if (u.str.is_string(strVec[tailIdx], strTailVal)) { + if (strTailVal.length() >= subStrTailVal.length()) { + zstring prefix = strTailVal.extract(0, subStrTailVal.length()); + if (prefix == subStrTailVal) { + return true; + } else { + continue; + } } else { continue; } + } + } else { + if (subStrVec[subStrCnt - 1] == strVec[tailIdx]) { + return true; } else { continue; } } - } else { - if (subStrVec[subStrCnt - 1] == strVec[tailIdx]) { - return true; - } else { - continue; - } } - } - return false; - } -} - -void theory_str::check_subsequence(expr* str, expr* strDeAlias, expr* subStr, expr* subStrDeAlias, expr* boolVar, - std::map, std::set > > & groundedMap) { - - context & ctx = get_context(); - ast_manager & m = get_manager(); - std::map, std::set >::iterator itorStr = groundedMap[strDeAlias].begin(); - std::map, std::set >::iterator itorSubStr; - for (; itorStr != groundedMap[strDeAlias].end(); itorStr++) { - itorSubStr = groundedMap[subStrDeAlias].begin(); - for (; itorSubStr != groundedMap[subStrDeAlias].end(); itorSubStr++) { - bool contain = is_partial_in_grounded_concat(itorStr->first, itorSubStr->first); - if (contain) { - expr_ref_vector litems(m); - if (str != strDeAlias) { - litems.push_back(ctx.mk_eq_atom(str, strDeAlias)); - } - if (subStr != subStrDeAlias) { - litems.push_back(ctx.mk_eq_atom(subStr, subStrDeAlias)); - } - - //litems.insert(itorStr->second.begin(), itorStr->second.end()); - //litems.insert(itorSubStr->second.begin(), itorSubStr->second.end()); - for (std::set::const_iterator i1 = itorStr->second.begin(); - i1 != itorStr->second.end(); ++i1) { - litems.push_back(*i1); - } - for (std::set::const_iterator i1 = itorSubStr->second.begin(); - i1 != itorSubStr->second.end(); ++i1) { - litems.push_back(*i1); - } - - expr_ref implyR(boolVar, m); - - if (litems.empty()) { - assert_axiom(implyR); - } else { - expr_ref implyL(mk_and(litems), m); - assert_implication(implyL, implyR); - } - - } - } - } -} - -void theory_str::compute_contains(std::map & varAliasMap, - std::map & concatAliasMap, std::map & varConstMap, - std::map & concatConstMap, std::map > & varEqConcatMap) { - std::map, std::set > > groundedMap; - theory_str_contain_pair_bool_map_t::iterator containItor = contain_pair_bool_map.begin(); - for (; containItor != contain_pair_bool_map.end(); containItor++) { - expr* containBoolVar = containItor->get_value(); - expr* str = containItor->get_key1(); - expr* subStr = containItor->get_key2(); - - expr* strDeAlias = dealias_node(str, varAliasMap, concatAliasMap); - expr* subStrDeAlias = dealias_node(subStr, varAliasMap, concatAliasMap); - - get_grounded_concats(strDeAlias, varAliasMap, concatAliasMap, varConstMap, concatConstMap, varEqConcatMap, groundedMap); - get_grounded_concats(subStrDeAlias, varAliasMap, concatAliasMap, varConstMap, concatConstMap, varEqConcatMap, groundedMap); - - // debugging - print_grounded_concat(strDeAlias, groundedMap); - print_grounded_concat(subStrDeAlias, groundedMap); - - check_subsequence(str, strDeAlias, subStr, subStrDeAlias, containBoolVar, groundedMap); - } -} - -bool theory_str::can_concat_eq_str(expr * concat, zstring& str) { - unsigned int strLen = str.length(); - if (u.str.is_concat(to_app(concat))) { - ptr_vector args; - get_nodes_in_concat(concat, args); - expr * ml_node = args[0]; - expr * mr_node = args[args.size() - 1]; - - zstring ml_str; - if (u.str.is_string(ml_node, ml_str)) { - unsigned int ml_len = ml_str.length(); - if (ml_len > strLen) { - return false; - } - unsigned int cLen = ml_len; - if (ml_str != str.extract(0, cLen)) { - return false; - } - } - - zstring mr_str; - if (u.str.is_string(mr_node, mr_str)) { - unsigned int mr_len = mr_str.length(); - if (mr_len > strLen) { - return false; - } - unsigned int cLen = mr_len; - if (mr_str != str.extract(strLen - cLen, cLen)) { - return false; - } - } - - unsigned int sumLen = 0; - for (unsigned int i = 0 ; i < args.size() ; i++) { - expr * oneArg = args[i]; - zstring arg_str; - if (u.str.is_string(oneArg, arg_str)) { - if (!str.contains(arg_str)) { - return false; - } - sumLen += arg_str.length(); - } - } - - if (sumLen > strLen) { - return false; - } - } - return true; -} - -bool theory_str::can_concat_eq_concat(expr * concat1, expr * concat2) { - if (u.str.is_concat(to_app(concat1)) && u.str.is_concat(to_app(concat2))) { - { - // Suppose concat1 = (Concat X Y) and concat2 = (Concat M N). - expr * concat1_mostL = getMostLeftNodeInConcat(concat1); - expr * concat2_mostL = getMostLeftNodeInConcat(concat2); - // if both X and M are constant strings, check whether they have the same prefix - zstring concat1_mostL_str, concat2_mostL_str; - if (u.str.is_string(concat1_mostL, concat1_mostL_str) && u.str.is_string(concat2_mostL, concat2_mostL_str)) { - unsigned int cLen = std::min(concat1_mostL_str.length(), concat2_mostL_str.length()); - if (concat1_mostL_str.extract(0, cLen) != concat2_mostL_str.extract(0, cLen)) { - return false; - } - } - } - - { - // Similarly, if both Y and N are constant strings, check whether they have the same suffix - expr * concat1_mostR = getMostRightNodeInConcat(concat1); - expr * concat2_mostR = getMostRightNodeInConcat(concat2); - zstring concat1_mostR_str, concat2_mostR_str; - if (u.str.is_string(concat1_mostR, concat1_mostR_str) && u.str.is_string(concat2_mostR, concat2_mostR_str)) { - unsigned int cLen = std::min(concat1_mostR_str.length(), concat2_mostR_str.length()); - if (concat1_mostR_str.extract(concat1_mostR_str.length() - cLen, cLen) != - concat2_mostR_str.extract(concat2_mostR_str.length() - cLen, cLen)) { - return false; - } - } - } - } - return true; -} - -/* - * Check whether n1 and n2 could be equal. - * Returns true if n1 could equal n2 (maybe), - * and false if n1 is definitely not equal to n2 (no). - */ -bool theory_str::can_two_nodes_eq(expr * n1, expr * n2) { - app * n1_curr = to_app(n1); - app * n2_curr = to_app(n2); - - // case 0: n1_curr is const string, n2_curr is const string - if (u.str.is_string(n1_curr) && u.str.is_string(n2_curr)) { - if (n1_curr != n2_curr) { - return false; - } - } - // case 1: n1_curr is concat, n2_curr is const string - else if (u.str.is_concat(n1_curr) && u.str.is_string(n2_curr)) { - zstring n2_curr_str; - u.str.is_string(n2_curr, n2_curr_str); - if (!can_concat_eq_str(n1_curr, n2_curr_str)) { return false; } } - // case 2: n2_curr is concat, n1_curr is const string - else if (u.str.is_concat(n2_curr) && u.str.is_string(n1_curr)) { - zstring n1_curr_str; - u.str.is_string(n1_curr, n1_curr_str); - if (!can_concat_eq_str(n2_curr, n1_curr_str)) { - return false; + + void theory_str::check_subsequence(expr* str, expr* strDeAlias, expr* subStr, expr* subStrDeAlias, expr* boolVar, + std::map, std::set > > & groundedMap) { + + context & ctx = get_context(); + ast_manager & m = get_manager(); + std::map, std::set >::iterator itorStr = groundedMap[strDeAlias].begin(); + std::map, std::set >::iterator itorSubStr; + for (; itorStr != groundedMap[strDeAlias].end(); itorStr++) { + itorSubStr = groundedMap[subStrDeAlias].begin(); + for (; itorSubStr != groundedMap[subStrDeAlias].end(); itorSubStr++) { + bool contain = is_partial_in_grounded_concat(itorStr->first, itorSubStr->first); + if (contain) { + expr_ref_vector litems(m); + if (str != strDeAlias) { + litems.push_back(ctx.mk_eq_atom(str, strDeAlias)); + } + if (subStr != subStrDeAlias) { + litems.push_back(ctx.mk_eq_atom(subStr, subStrDeAlias)); + } + + //litems.insert(itorStr->second.begin(), itorStr->second.end()); + //litems.insert(itorSubStr->second.begin(), itorSubStr->second.end()); + for (std::set::const_iterator i1 = itorStr->second.begin(); + i1 != itorStr->second.end(); ++i1) { + litems.push_back(*i1); + } + for (std::set::const_iterator i1 = itorSubStr->second.begin(); + i1 != itorSubStr->second.end(); ++i1) { + litems.push_back(*i1); + } + + expr_ref implyR(boolVar, m); + + if (litems.empty()) { + assert_axiom(implyR); + } else { + expr_ref implyL(mk_and(litems), m); + assert_implication(implyL, implyR); + } + + } + } } } - // case 3: both are concats - else if (u.str.is_concat(n1_curr) && u.str.is_concat(n2_curr)) { - if (!can_concat_eq_concat(n1_curr, n2_curr)) { - return false; - } + + void theory_str::compute_contains(std::map & varAliasMap, + std::map & concatAliasMap, std::map & varConstMap, + std::map & concatConstMap, std::map > & varEqConcatMap) { + std::map, std::set > > groundedMap; + theory_str_contain_pair_bool_map_t::iterator containItor = contain_pair_bool_map.begin(); + for (; containItor != contain_pair_bool_map.end(); containItor++) { + expr* containBoolVar = containItor->get_value(); + expr* str = containItor->get_key1(); + expr* subStr = containItor->get_key2(); + + expr* strDeAlias = dealias_node(str, varAliasMap, concatAliasMap); + expr* subStrDeAlias = dealias_node(subStr, varAliasMap, concatAliasMap); + + get_grounded_concats(strDeAlias, varAliasMap, concatAliasMap, varConstMap, concatConstMap, varEqConcatMap, groundedMap); + get_grounded_concats(subStrDeAlias, varAliasMap, concatAliasMap, varConstMap, concatConstMap, varEqConcatMap, groundedMap); + + // debugging + print_grounded_concat(strDeAlias, groundedMap); + print_grounded_concat(subStrDeAlias, groundedMap); + + check_subsequence(str, strDeAlias, subStr, subStrDeAlias, containBoolVar, groundedMap); + } } - return true; -} + bool theory_str::can_concat_eq_str(expr * concat, zstring& str) { + unsigned int strLen = str.length(); + if (u.str.is_concat(to_app(concat))) { + ptr_vector args; + get_nodes_in_concat(concat, args); + expr * ml_node = args[0]; + expr * mr_node = args[args.size() - 1]; -// was checkLength2ConstStr() in Z3str2 -// returns true if everything is OK, or false if inconsistency detected -// - note that these are different from the semantics in Z3str2 -bool theory_str::check_length_const_string(expr * n1, expr * constStr) { - ast_manager & mgr = get_manager(); - context & ctx = get_context(); - - zstring tmp; - u.str.is_string(constStr, tmp); - rational strLen(tmp.length()); - - if (u.str.is_concat(to_app(n1))) { - ptr_vector args; - expr_ref_vector items(mgr); - - get_nodes_in_concat(n1, args); - - rational sumLen(0); - for (unsigned int i = 0; i < args.size(); ++i) { - rational argLen; - bool argLen_exists = get_len_value(args[i], argLen); - if (argLen_exists) { - if (!u.str.is_string(args[i])) { - items.push_back(ctx.mk_eq_atom(mk_strlen(args[i]), mk_int(argLen))); + zstring ml_str; + if (u.str.is_string(ml_node, ml_str)) { + unsigned int ml_len = ml_str.length(); + if (ml_len > strLen) { + return false; } - TRACE("str", tout << "concat arg: " << mk_pp(args[i], mgr) << " has len = " << argLen.to_string() << std::endl;); - sumLen += argLen; - if (sumLen > strLen) { - items.push_back(ctx.mk_eq_atom(n1, constStr)); - expr_ref toAssert(mgr.mk_not(mk_and(items)), mgr); - TRACE("str", tout << "inconsistent length: concat (len = " << sumLen << ") <==> string constant (len = " << strLen << ")" << std::endl;); - assert_axiom(toAssert); + unsigned int cLen = ml_len; + if (ml_str != str.extract(0, cLen)) { return false; } } + + zstring mr_str; + if (u.str.is_string(mr_node, mr_str)) { + unsigned int mr_len = mr_str.length(); + if (mr_len > strLen) { + return false; + } + unsigned int cLen = mr_len; + if (mr_str != str.extract(strLen - cLen, cLen)) { + return false; + } + } + + unsigned int sumLen = 0; + for (unsigned int i = 0 ; i < args.size() ; i++) { + expr * oneArg = args[i]; + zstring arg_str; + if (u.str.is_string(oneArg, arg_str)) { + if (!str.contains(arg_str)) { + return false; + } + sumLen += arg_str.length(); + } + } + + if (sumLen > strLen) { + return false; + } } - } else { // !is_concat(n1) - rational oLen; - bool oLen_exists = get_len_value(n1, oLen); - if (oLen_exists && oLen != strLen) { - TRACE("str", tout << "inconsistent length: var (len = " << oLen << ") <==> string constant (len = " << strLen << ")" << std::endl;); + return true; + } + + bool theory_str::can_concat_eq_concat(expr * concat1, expr * concat2) { + if (u.str.is_concat(to_app(concat1)) && u.str.is_concat(to_app(concat2))) { + { + // Suppose concat1 = (Concat X Y) and concat2 = (Concat M N). + expr * concat1_mostL = getMostLeftNodeInConcat(concat1); + expr * concat2_mostL = getMostLeftNodeInConcat(concat2); + // if both X and M are constant strings, check whether they have the same prefix + zstring concat1_mostL_str, concat2_mostL_str; + if (u.str.is_string(concat1_mostL, concat1_mostL_str) && u.str.is_string(concat2_mostL, concat2_mostL_str)) { + unsigned int cLen = std::min(concat1_mostL_str.length(), concat2_mostL_str.length()); + if (concat1_mostL_str.extract(0, cLen) != concat2_mostL_str.extract(0, cLen)) { + return false; + } + } + } + + { + // Similarly, if both Y and N are constant strings, check whether they have the same suffix + expr * concat1_mostR = getMostRightNodeInConcat(concat1); + expr * concat2_mostR = getMostRightNodeInConcat(concat2); + zstring concat1_mostR_str, concat2_mostR_str; + if (u.str.is_string(concat1_mostR, concat1_mostR_str) && u.str.is_string(concat2_mostR, concat2_mostR_str)) { + unsigned int cLen = std::min(concat1_mostR_str.length(), concat2_mostR_str.length()); + if (concat1_mostR_str.extract(concat1_mostR_str.length() - cLen, cLen) != + concat2_mostR_str.extract(concat2_mostR_str.length() - cLen, cLen)) { + return false; + } + } + } + } + return true; + } + + /* + * Check whether n1 and n2 could be equal. + * Returns true if n1 could equal n2 (maybe), + * and false if n1 is definitely not equal to n2 (no). + */ + bool theory_str::can_two_nodes_eq(expr * n1, expr * n2) { + app * n1_curr = to_app(n1); + app * n2_curr = to_app(n2); + + // case 0: n1_curr is const string, n2_curr is const string + if (u.str.is_string(n1_curr) && u.str.is_string(n2_curr)) { + if (n1_curr != n2_curr) { + return false; + } + } + // case 1: n1_curr is concat, n2_curr is const string + else if (u.str.is_concat(n1_curr) && u.str.is_string(n2_curr)) { + zstring n2_curr_str; + u.str.is_string(n2_curr, n2_curr_str); + if (!can_concat_eq_str(n1_curr, n2_curr_str)) { + return false; + } + } + // case 2: n2_curr is concat, n1_curr is const string + else if (u.str.is_concat(n2_curr) && u.str.is_string(n1_curr)) { + zstring n1_curr_str; + u.str.is_string(n1_curr, n1_curr_str); + if (!can_concat_eq_str(n2_curr, n1_curr_str)) { + return false; + } + } + // case 3: both are concats + else if (u.str.is_concat(n1_curr) && u.str.is_concat(n2_curr)) { + if (!can_concat_eq_concat(n1_curr, n2_curr)) { + return false; + } + } + + return true; + } + + // was checkLength2ConstStr() in Z3str2 + // returns true if everything is OK, or false if inconsistency detected + // - note that these are different from the semantics in Z3str2 + bool theory_str::check_length_const_string(expr * n1, expr * constStr) { + ast_manager & mgr = get_manager(); + context & ctx = get_context(); + + zstring tmp; + u.str.is_string(constStr, tmp); + rational strLen(tmp.length()); + + if (u.str.is_concat(to_app(n1))) { + ptr_vector args; + expr_ref_vector items(mgr); + + get_nodes_in_concat(n1, args); + + rational sumLen(0); + for (unsigned int i = 0; i < args.size(); ++i) { + rational argLen; + bool argLen_exists = get_len_value(args[i], argLen); + if (argLen_exists) { + if (!u.str.is_string(args[i])) { + items.push_back(ctx.mk_eq_atom(mk_strlen(args[i]), mk_int(argLen))); + } + TRACE("str", tout << "concat arg: " << mk_pp(args[i], mgr) << " has len = " << argLen.to_string() << std::endl;); + sumLen += argLen; + if (sumLen > strLen) { + items.push_back(ctx.mk_eq_atom(n1, constStr)); + expr_ref toAssert(mgr.mk_not(mk_and(items)), mgr); + TRACE("str", tout << "inconsistent length: concat (len = " << sumLen << ") <==> string constant (len = " << strLen << ")" << std::endl;); + assert_axiom(toAssert); + return false; + } + } + } + } else { // !is_concat(n1) + rational oLen; + bool oLen_exists = get_len_value(n1, oLen); + if (oLen_exists && oLen != strLen) { + TRACE("str", tout << "inconsistent length: var (len = " << oLen << ") <==> string constant (len = " << strLen << ")" << std::endl;); + expr_ref l(ctx.mk_eq_atom(n1, constStr), mgr); + expr_ref r(ctx.mk_eq_atom(mk_strlen(n1), mk_strlen(constStr)), mgr); + assert_implication(l, r); + return false; + } + } + rational unused; + if (get_len_value(n1, unused) == false) { expr_ref l(ctx.mk_eq_atom(n1, constStr), mgr); expr_ref r(ctx.mk_eq_atom(mk_strlen(n1), mk_strlen(constStr)), mgr); assert_implication(l, r); - return false; } - } - rational unused; - if (get_len_value(n1, unused) == false) { - expr_ref l(ctx.mk_eq_atom(n1, constStr), mgr); - expr_ref r(ctx.mk_eq_atom(mk_strlen(n1), mk_strlen(constStr)), mgr); - assert_implication(l, r); - } - return true; -} - -bool theory_str::check_length_concat_concat(expr * n1, expr * n2) { - context & ctx = get_context(); - ast_manager & mgr = get_manager(); - - ptr_vector concat1Args; - ptr_vector concat2Args; - get_nodes_in_concat(n1, concat1Args); - get_nodes_in_concat(n2, concat2Args); - - bool concat1LenFixed = true; - bool concat2LenFixed = true; - - expr_ref_vector items(mgr); - - rational sum1(0), sum2(0); - - for (unsigned int i = 0; i < concat1Args.size(); ++i) { - expr * oneArg = concat1Args[i]; - rational argLen; - bool argLen_exists = get_len_value(oneArg, argLen); - if (argLen_exists) { - sum1 += argLen; - if (!u.str.is_string(oneArg)) { - items.push_back(ctx.mk_eq_atom(mk_strlen(oneArg), mk_int(argLen))); - } - } else { - concat1LenFixed = false; - } - } - - for (unsigned int i = 0; i < concat2Args.size(); ++i) { - expr * oneArg = concat2Args[i]; - rational argLen; - bool argLen_exists = get_len_value(oneArg, argLen); - if (argLen_exists) { - sum2 += argLen; - if (!u.str.is_string(oneArg)) { - items.push_back(ctx.mk_eq_atom(mk_strlen(oneArg), mk_int(argLen))); - } - } else { - concat2LenFixed = false; - } - } - - items.push_back(ctx.mk_eq_atom(n1, n2)); - - bool conflict = false; - - if (concat1LenFixed && concat2LenFixed) { - if (sum1 != sum2) { - conflict = true; - } - } else if (!concat1LenFixed && concat2LenFixed) { - if (sum1 > sum2) { - conflict = true; - } - } else if (concat1LenFixed && !concat2LenFixed) { - if (sum1 < sum2) { - conflict = true; - } - } - - if (conflict) { - TRACE("str", tout << "inconsistent length detected in concat <==> concat" << std::endl;); - expr_ref toAssert(mgr.mk_not(mk_and(items)), mgr); - assert_axiom(toAssert); - return false; - } - return true; -} - -bool theory_str::check_length_concat_var(expr * concat, expr * var) { - context & ctx = get_context(); - ast_manager & mgr = get_manager(); - - rational varLen; - bool varLen_exists = get_len_value(var, varLen); - if (!varLen_exists) { return true; - } else { - rational sumLen(0); - ptr_vector args; + } + + bool theory_str::check_length_concat_concat(expr * n1, expr * n2) { + context & ctx = get_context(); + ast_manager & mgr = get_manager(); + + ptr_vector concat1Args; + ptr_vector concat2Args; + get_nodes_in_concat(n1, concat1Args); + get_nodes_in_concat(n2, concat2Args); + + bool concat1LenFixed = true; + bool concat2LenFixed = true; + expr_ref_vector items(mgr); - get_nodes_in_concat(concat, args); - for (unsigned int i = 0; i < args.size(); ++i) { - expr * oneArg = args[i]; + + rational sum1(0), sum2(0); + + for (unsigned int i = 0; i < concat1Args.size(); ++i) { + expr * oneArg = concat1Args[i]; rational argLen; bool argLen_exists = get_len_value(oneArg, argLen); if (argLen_exists) { - if (!u.str.is_string(oneArg) && !argLen.is_zero()) { + sum1 += argLen; + if (!u.str.is_string(oneArg)) { items.push_back(ctx.mk_eq_atom(mk_strlen(oneArg), mk_int(argLen))); } - sumLen += argLen; - if (sumLen > varLen) { - TRACE("str", tout << "inconsistent length detected in concat <==> var" << std::endl;); - items.push_back(ctx.mk_eq_atom(mk_strlen(var), mk_int(varLen))); - items.push_back(ctx.mk_eq_atom(concat, var)); - expr_ref toAssert(mgr.mk_not(mk_and(items)), mgr); - assert_axiom(toAssert); - return false; - } + } else { + concat1LenFixed = false; } } + + for (unsigned int i = 0; i < concat2Args.size(); ++i) { + expr * oneArg = concat2Args[i]; + rational argLen; + bool argLen_exists = get_len_value(oneArg, argLen); + if (argLen_exists) { + sum2 += argLen; + if (!u.str.is_string(oneArg)) { + items.push_back(ctx.mk_eq_atom(mk_strlen(oneArg), mk_int(argLen))); + } + } else { + concat2LenFixed = false; + } + } + + items.push_back(ctx.mk_eq_atom(n1, n2)); + + bool conflict = false; + + if (concat1LenFixed && concat2LenFixed) { + if (sum1 != sum2) { + conflict = true; + } + } else if (!concat1LenFixed && concat2LenFixed) { + if (sum1 > sum2) { + conflict = true; + } + } else if (concat1LenFixed && !concat2LenFixed) { + if (sum1 < sum2) { + conflict = true; + } + } + + if (conflict) { + TRACE("str", tout << "inconsistent length detected in concat <==> concat" << std::endl;); + expr_ref toAssert(mgr.mk_not(mk_and(items)), mgr); + assert_axiom(toAssert); + return false; + } return true; } -} -bool theory_str::check_length_var_var(expr * var1, expr * var2) { - context & ctx = get_context(); - ast_manager & mgr = get_manager(); + bool theory_str::check_length_concat_var(expr * concat, expr * var) { + context & ctx = get_context(); + ast_manager & mgr = get_manager(); - rational var1Len, var2Len; - bool var1Len_exists = get_len_value(var1, var1Len); - bool var2Len_exists = get_len_value(var2, var2Len); - - if (var1Len_exists && var2Len_exists && var1Len != var2Len) { - TRACE("str", tout << "inconsistent length detected in var <==> var" << std::endl;); - expr_ref_vector items(mgr); - items.push_back(ctx.mk_eq_atom(mk_strlen(var1), mk_int(var1Len))); - items.push_back(ctx.mk_eq_atom(mk_strlen(var2), mk_int(var2Len))); - items.push_back(ctx.mk_eq_atom(var1, var2)); - expr_ref toAssert(mgr.mk_not(mk_and(items)), mgr); - assert_axiom(toAssert); - return false; - } - return true; -} - -// returns true if everything is OK, or false if inconsistency detected -// - note that these are different from the semantics in Z3str2 -bool theory_str::check_length_eq_var_concat(expr * n1, expr * n2) { - // n1 and n2 are not const string: either variable or concat - bool n1Concat = u.str.is_concat(to_app(n1)); - bool n2Concat = u.str.is_concat(to_app(n2)); - if (n1Concat && n2Concat) { - return check_length_concat_concat(n1, n2); - } - // n1 is concat, n2 is variable - else if (n1Concat && (!n2Concat)) { - return check_length_concat_var(n1, n2); - } - // n1 is variable, n2 is concat - else if ((!n1Concat) && n2Concat) { - return check_length_concat_var(n2, n1); - } - // n1 and n2 are both variables - else { - return check_length_var_var(n1, n2); - } - return true; -} - -// returns false if an inconsistency is detected, or true if no inconsistencies were found -// - note that these are different from the semantics of checkLengConsistency() in Z3str2 -bool theory_str::check_length_consistency(expr * n1, expr * n2) { - if (u.str.is_string(n1) && u.str.is_string(n2)) { - // consistency has already been checked in can_two_nodes_eq(). - return true; - } else if (u.str.is_string(n1) && (!u.str.is_string(n2))) { - return check_length_const_string(n2, n1); - } else if (u.str.is_string(n2) && (!u.str.is_string(n1))) { - return check_length_const_string(n1, n2); - } else { - // n1 and n2 are vars or concats - return check_length_eq_var_concat(n1, n2); - } - return true; -} - -// Modified signature: returns true if nothing was learned, or false if at least one axiom was asserted. -// (This is used for deferred consistency checking) -bool theory_str::check_concat_len_in_eqc(expr * concat) { - context & ctx = get_context(); - - bool no_assertions = true; - - expr * eqc_n = concat; - do { - if (u.str.is_concat(to_app(eqc_n))) { - rational unused; - bool status = infer_len_concat(eqc_n, unused); - if (status) { - no_assertions = false; - } - } - eqc_n = get_eqc_next(eqc_n); - } while (eqc_n != concat); - - return no_assertions; -} - -// Convert a regular expression to an e-NFA using Thompson's construction -void nfa::convert_re(expr * e, unsigned & start, unsigned & end, seq_util & u) { - start = next_id(); - end = next_id(); - if (u.re.is_to_re(e)) { - app * a = to_app(e); - expr * arg_str = a->get_arg(0); - zstring str; - if (u.str.is_string(arg_str, str)) { - TRACE("str", tout << "build NFA for '" << str << "'" << "\n";); - /* - * For an n-character string, we make (n-1) intermediate states, - * labelled i_(0) through i_(n-2). - * Then we construct the following transitions: - * start --str[0]--> i_(0) --str[1]--> i_(1) --...--> i_(n-2) --str[n-1]--> final - */ - unsigned last = start; - for (int i = 0; i <= ((int)str.length()) - 2; ++i) { - unsigned i_state = next_id(); - make_transition(last, str[i], i_state); - TRACE("str", tout << "string transition " << last << "--" << str[i] << "--> " << i_state << "\n";); - last = i_state; - } - make_transition(last, str[(str.length() - 1)], end); - TRACE("str", tout << "string transition " << last << "--" << str[(str.length() - 1)] << "--> " << end << "\n";); + rational varLen; + bool varLen_exists = get_len_value(var, varLen); + if (!varLen_exists) { + return true; } else { - TRACE("str", tout << "invalid string constant in Str2Reg" << std::endl;); + rational sumLen(0); + ptr_vector args; + expr_ref_vector items(mgr); + get_nodes_in_concat(concat, args); + for (unsigned int i = 0; i < args.size(); ++i) { + expr * oneArg = args[i]; + rational argLen; + bool argLen_exists = get_len_value(oneArg, argLen); + if (argLen_exists) { + if (!u.str.is_string(oneArg) && !argLen.is_zero()) { + items.push_back(ctx.mk_eq_atom(mk_strlen(oneArg), mk_int(argLen))); + } + sumLen += argLen; + if (sumLen > varLen) { + TRACE("str", tout << "inconsistent length detected in concat <==> var" << std::endl;); + items.push_back(ctx.mk_eq_atom(mk_strlen(var), mk_int(varLen))); + items.push_back(ctx.mk_eq_atom(concat, var)); + expr_ref toAssert(mgr.mk_not(mk_and(items)), mgr); + assert_axiom(toAssert); + return false; + } + } + } + return true; + } + } + + bool theory_str::check_length_var_var(expr * var1, expr * var2) { + context & ctx = get_context(); + ast_manager & mgr = get_manager(); + + rational var1Len, var2Len; + bool var1Len_exists = get_len_value(var1, var1Len); + bool var2Len_exists = get_len_value(var2, var2Len); + + if (var1Len_exists && var2Len_exists && var1Len != var2Len) { + TRACE("str", tout << "inconsistent length detected in var <==> var" << std::endl;); + expr_ref_vector items(mgr); + items.push_back(ctx.mk_eq_atom(mk_strlen(var1), mk_int(var1Len))); + items.push_back(ctx.mk_eq_atom(mk_strlen(var2), mk_int(var2Len))); + items.push_back(ctx.mk_eq_atom(var1, var2)); + expr_ref toAssert(mgr.mk_not(mk_and(items)), mgr); + assert_axiom(toAssert); + return false; + } + return true; + } + + // returns true if everything is OK, or false if inconsistency detected + // - note that these are different from the semantics in Z3str2 + bool theory_str::check_length_eq_var_concat(expr * n1, expr * n2) { + // n1 and n2 are not const string: either variable or concat + bool n1Concat = u.str.is_concat(to_app(n1)); + bool n2Concat = u.str.is_concat(to_app(n2)); + if (n1Concat && n2Concat) { + return check_length_concat_concat(n1, n2); + } + // n1 is concat, n2 is variable + else if (n1Concat && (!n2Concat)) { + return check_length_concat_var(n1, n2); + } + // n1 is variable, n2 is concat + else if ((!n1Concat) && n2Concat) { + return check_length_concat_var(n2, n1); + } + // n1 and n2 are both variables + else { + return check_length_var_var(n1, n2); + } + return true; + } + + // returns false if an inconsistency is detected, or true if no inconsistencies were found + // - note that these are different from the semantics of checkLengConsistency() in Z3str2 + bool theory_str::check_length_consistency(expr * n1, expr * n2) { + if (u.str.is_string(n1) && u.str.is_string(n2)) { + // consistency has already been checked in can_two_nodes_eq(). + return true; + } else if (u.str.is_string(n1) && (!u.str.is_string(n2))) { + return check_length_const_string(n2, n1); + } else if (u.str.is_string(n2) && (!u.str.is_string(n1))) { + return check_length_const_string(n1, n2); + } else { + // n1 and n2 are vars or concats + return check_length_eq_var_concat(n1, n2); + } + return true; + } + + // Modified signature: returns true if nothing was learned, or false if at least one axiom was asserted. + // (This is used for deferred consistency checking) + bool theory_str::check_concat_len_in_eqc(expr * concat) { + context & ctx = get_context(); + + bool no_assertions = true; + + expr * eqc_n = concat; + do { + if (u.str.is_concat(to_app(eqc_n))) { + rational unused; + bool status = infer_len_concat(eqc_n, unused); + if (status) { + no_assertions = false; + } + } + eqc_n = get_eqc_next(eqc_n); + } while (eqc_n != concat); + + return no_assertions; + } + + // Convert a regular expression to an e-NFA using Thompson's construction + void nfa::convert_re(expr * e, unsigned & start, unsigned & end, seq_util & u) { + start = next_id(); + end = next_id(); + if (u.re.is_to_re(e)) { + app * a = to_app(e); + expr * arg_str = a->get_arg(0); + zstring str; + if (u.str.is_string(arg_str, str)) { + TRACE("str", tout << "build NFA for '" << str << "'" << "\n";); + /* + * For an n-character string, we make (n-1) intermediate states, + * labelled i_(0) through i_(n-2). + * Then we construct the following transitions: + * start --str[0]--> i_(0) --str[1]--> i_(1) --...--> i_(n-2) --str[n-1]--> final + */ + unsigned last = start; + for (int i = 0; i <= ((int)str.length()) - 2; ++i) { + unsigned i_state = next_id(); + make_transition(last, str[i], i_state); + TRACE("str", tout << "string transition " << last << "--" << str[i] << "--> " << i_state << "\n";); + last = i_state; + } + make_transition(last, str[(str.length() - 1)], end); + TRACE("str", tout << "string transition " << last << "--" << str[(str.length() - 1)] << "--> " << end << "\n";); + } else { + TRACE("str", tout << "invalid string constant in Str2Reg" << std::endl;); + m_valid = false; + return; + } + } else if (u.re.is_concat(e)){ + app * a = to_app(e); + expr * re1 = a->get_arg(0); + expr * re2 = a->get_arg(1); + unsigned start1, end1; + convert_re(re1, start1, end1, u); + unsigned start2, end2; + convert_re(re2, start2, end2, u); + // start --e--> start1 --...--> end1 --e--> start2 --...--> end2 --e--> end + make_epsilon_move(start, start1); + make_epsilon_move(end1, start2); + make_epsilon_move(end2, end); + TRACE("str", tout << "concat NFA: start = " << start << ", end = " << end << std::endl;); + } else if (u.re.is_union(e)) { + app * a = to_app(e); + expr * re1 = a->get_arg(0); + expr * re2 = a->get_arg(1); + unsigned start1, end1; + convert_re(re1, start1, end1, u); + unsigned start2, end2; + convert_re(re2, start2, end2, u); + + // start --e--> start1 ; start --e--> start2 + // end1 --e--> end ; end2 --e--> end + make_epsilon_move(start, start1); + make_epsilon_move(start, start2); + make_epsilon_move(end1, end); + make_epsilon_move(end2, end); + TRACE("str", tout << "union NFA: start = " << start << ", end = " << end << std::endl;); + } else if (u.re.is_star(e)) { + app * a = to_app(e); + expr * subex = a->get_arg(0); + unsigned start_subex, end_subex; + convert_re(subex, start_subex, end_subex, u); + // start --e--> start_subex, start --e--> end + // end_subex --e--> start_subex, end_subex --e--> end + make_epsilon_move(start, start_subex); + make_epsilon_move(start, end); + make_epsilon_move(end_subex, start_subex); + make_epsilon_move(end_subex, end); + TRACE("str", tout << "star NFA: start = " << start << ", end = " << end << std::endl;); + } else if (u.re.is_range(e)) { + // range('a', 'z') + // start --'a'--> end + // start --'b'--> end + // ... + // start --'z'--> end + app * a = to_app(e); + expr * c1 = a->get_arg(0); + expr * c2 = a->get_arg(1); + zstring s_c1, s_c2; + u.str.is_string(c1, s_c1); + u.str.is_string(c2, s_c2); + + unsigned int id1 = s_c1[0]; + unsigned int id2 = s_c2[0]; + if (id1 > id2) { + unsigned int tmp = id1; + id1 = id2; + id2 = tmp; + } + + for (unsigned int i = id1; i <= id2; ++i) { + char ch = (char)i; + make_transition(start, ch, end); + } + + TRACE("str", tout << "range NFA: start = " << start << ", end = " << end << std::endl;); + } else { + TRACE("str", tout << "invalid regular expression" << std::endl;); m_valid = false; return; } - } else if (u.re.is_concat(e)){ - app * a = to_app(e); - expr * re1 = a->get_arg(0); - expr * re2 = a->get_arg(1); - unsigned start1, end1; - convert_re(re1, start1, end1, u); - unsigned start2, end2; - convert_re(re2, start2, end2, u); - // start --e--> start1 --...--> end1 --e--> start2 --...--> end2 --e--> end - make_epsilon_move(start, start1); - make_epsilon_move(end1, start2); - make_epsilon_move(end2, end); - TRACE("str", tout << "concat NFA: start = " << start << ", end = " << end << std::endl;); - } else if (u.re.is_union(e)) { - app * a = to_app(e); - expr * re1 = a->get_arg(0); - expr * re2 = a->get_arg(1); - unsigned start1, end1; - convert_re(re1, start1, end1, u); - unsigned start2, end2; - convert_re(re2, start2, end2, u); - - // start --e--> start1 ; start --e--> start2 - // end1 --e--> end ; end2 --e--> end - make_epsilon_move(start, start1); - make_epsilon_move(start, start2); - make_epsilon_move(end1, end); - make_epsilon_move(end2, end); - TRACE("str", tout << "union NFA: start = " << start << ", end = " << end << std::endl;); - } else if (u.re.is_star(e)) { - app * a = to_app(e); - expr * subex = a->get_arg(0); - unsigned start_subex, end_subex; - convert_re(subex, start_subex, end_subex, u); - // start --e--> start_subex, start --e--> end - // end_subex --e--> start_subex, end_subex --e--> end - make_epsilon_move(start, start_subex); - make_epsilon_move(start, end); - make_epsilon_move(end_subex, start_subex); - make_epsilon_move(end_subex, end); - TRACE("str", tout << "star NFA: start = " << start << ", end = " << end << std::endl;); - } else if (u.re.is_range(e)) { - // range('a', 'z') - // start --'a'--> end - // start --'b'--> end - // ... - // start --'z'--> end - app * a = to_app(e); - expr * c1 = a->get_arg(0); - expr * c2 = a->get_arg(1); - zstring s_c1, s_c2; - u.str.is_string(c1, s_c1); - u.str.is_string(c2, s_c2); - - unsigned int id1 = s_c1[0]; - unsigned int id2 = s_c2[0]; - if (id1 > id2) { - unsigned int tmp = id1; - id1 = id2; - id2 = tmp; - } - - for (unsigned int i = id1; i <= id2; ++i) { - char ch = (char)i; - make_transition(start, ch, end); - } - - TRACE("str", tout << "range NFA: start = " << start << ", end = " << end << std::endl;); - } else { - TRACE("str", tout << "invalid regular expression" << std::endl;); - m_valid = false; - return; } -} -void nfa::epsilon_closure(unsigned start, std::set & closure) { - std::deque worklist; - closure.insert(start); - worklist.push_back(start); + void nfa::epsilon_closure(unsigned start, std::set & closure) { + std::deque worklist; + closure.insert(start); + worklist.push_back(start); - while(!worklist.empty()) { - unsigned state = worklist.front(); - worklist.pop_front(); - if (epsilon_map.find(state) != epsilon_map.end()) { - for (std::set::iterator it = epsilon_map[state].begin(); - it != epsilon_map[state].end(); ++it) { - unsigned new_state = *it; - if (closure.find(new_state) == closure.end()) { - closure.insert(new_state); - worklist.push_back(new_state); + while(!worklist.empty()) { + unsigned state = worklist.front(); + worklist.pop_front(); + if (epsilon_map.find(state) != epsilon_map.end()) { + for (std::set::iterator it = epsilon_map[state].begin(); + it != epsilon_map[state].end(); ++it) { + unsigned new_state = *it; + if (closure.find(new_state) == closure.end()) { + closure.insert(new_state); + worklist.push_back(new_state); + } + } + } + } + } + + bool nfa::matches(zstring input) { + /* + * Keep a set of all states the NFA can currently be in. + * Initially this is the e-closure of m_start_state + * For each character A in the input string, + * the set of next states contains + * all states in transition_map[S][A] for each S in current_states, + * and all states in epsilon_map[S] for each S in current_states. + * After consuming the entire input string, + * the match is successful iff current_states contains m_end_state. + */ + std::set current_states; + epsilon_closure(m_start_state, current_states); + for (unsigned i = 0; i < input.length(); ++i) { + char A = (char)input[i]; + std::set next_states; + for (std::set::iterator it = current_states.begin(); + it != current_states.end(); ++it) { + unsigned S = *it; + // check transition_map + if (transition_map[S].find(A) != transition_map[S].end()) { + next_states.insert(transition_map[S][A]); + } + } + + // take e-closure over next_states to compute the actual next_states + std::set epsilon_next_states; + for (std::set::iterator it = next_states.begin(); it != next_states.end(); ++it) { + unsigned S = *it; + std::set closure; + epsilon_closure(S, closure); + epsilon_next_states.insert(closure.begin(), closure.end()); + } + current_states = epsilon_next_states; + } + if (current_states.find(m_end_state) != current_states.end()) { + return true; + } else { + return false; + } + } + + void theory_str::check_regex_in(expr * nn1, expr * nn2) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + expr_ref_vector eqNodeSet(m); + + expr * constStr_1 = collect_eq_nodes(nn1, eqNodeSet); + expr * constStr_2 = collect_eq_nodes(nn2, eqNodeSet); + expr * constStr = (constStr_1 != NULL) ? constStr_1 : constStr_2; + + if (constStr == NULL) { + return; + } else { + expr_ref_vector::iterator itor = eqNodeSet.begin(); + for (; itor != eqNodeSet.end(); itor++) { + if (regex_in_var_reg_str_map.find(*itor) != regex_in_var_reg_str_map.end()) { + std::set::iterator strItor = regex_in_var_reg_str_map[*itor].begin(); + for (; strItor != regex_in_var_reg_str_map[*itor].end(); strItor++) { + zstring regStr = *strItor; + zstring constStrValue; + u.str.is_string(constStr, constStrValue); + std::pair key1 = std::make_pair(*itor, regStr); + if (regex_in_bool_map.find(key1) != regex_in_bool_map.end()) { + expr * boolVar = regex_in_bool_map[key1]; // actually the RegexIn term + app * a_regexIn = to_app(boolVar); + expr * regexTerm = a_regexIn->get_arg(1); + + // TODO figure out regex NFA stuff + if (regex_nfa_cache.find(regexTerm) == regex_nfa_cache.end()) { + TRACE("str", tout << "regex_nfa_cache: cache miss" << std::endl;); + regex_nfa_cache[regexTerm] = nfa(u, regexTerm); + } else { + TRACE("str", tout << "regex_nfa_cache: cache hit" << std::endl;); + } + + nfa regexNFA = regex_nfa_cache[regexTerm]; + ENSURE(regexNFA.is_valid()); + bool matchRes = regexNFA.matches(constStrValue); + + TRACE("str", tout << mk_pp(*itor, m) << " in " << regStr << " : " << (matchRes ? "yes" : "no") << std::endl;); + + expr_ref implyL(ctx.mk_eq_atom(*itor, constStr), m); + if (matchRes) { + assert_implication(implyL, boolVar); + } else { + assert_implication(implyL, m.mk_not(boolVar)); + } + } + } } } } } -} -bool nfa::matches(zstring input) { /* - * Keep a set of all states the NFA can currently be in. - * Initially this is the e-closure of m_start_state - * For each character A in the input string, - * the set of next states contains - * all states in transition_map[S][A] for each S in current_states, - * and all states in epsilon_map[S] for each S in current_states. - * After consuming the entire input string, - * the match is successful iff current_states contains m_end_state. + * strArgmt::solve_concat_eq_str() + * Solve concatenations of the form: + * const == Concat(const, X) + * const == Concat(X, const) */ - std::set current_states; - epsilon_closure(m_start_state, current_states); - for (unsigned i = 0; i < input.length(); ++i) { - char A = (char)input[i]; - std::set next_states; - for (std::set::iterator it = current_states.begin(); - it != current_states.end(); ++it) { - unsigned S = *it; - // check transition_map - if (transition_map[S].find(A) != transition_map[S].end()) { - next_states.insert(transition_map[S][A]); - } - } - - // take e-closure over next_states to compute the actual next_states - std::set epsilon_next_states; - for (std::set::iterator it = next_states.begin(); it != next_states.end(); ++it) { - unsigned S = *it; - std::set closure; - epsilon_closure(S, closure); - epsilon_next_states.insert(closure.begin(), closure.end()); - } - current_states = epsilon_next_states; - } - if (current_states.find(m_end_state) != current_states.end()) { - return true; - } else { - return false; - } -} - -void theory_str::check_regex_in(expr * nn1, expr * nn2) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - - expr_ref_vector eqNodeSet(m); - - expr * constStr_1 = collect_eq_nodes(nn1, eqNodeSet); - expr * constStr_2 = collect_eq_nodes(nn2, eqNodeSet); - expr * constStr = (constStr_1 != NULL) ? constStr_1 : constStr_2; - - if (constStr == NULL) { - return; - } else { - expr_ref_vector::iterator itor = eqNodeSet.begin(); - for (; itor != eqNodeSet.end(); itor++) { - if (regex_in_var_reg_str_map.find(*itor) != regex_in_var_reg_str_map.end()) { - std::set::iterator strItor = regex_in_var_reg_str_map[*itor].begin(); - for (; strItor != regex_in_var_reg_str_map[*itor].end(); strItor++) { - zstring regStr = *strItor; - zstring constStrValue; - u.str.is_string(constStr, constStrValue); - std::pair key1 = std::make_pair(*itor, regStr); - if (regex_in_bool_map.find(key1) != regex_in_bool_map.end()) { - expr * boolVar = regex_in_bool_map[key1]; // actually the RegexIn term - app * a_regexIn = to_app(boolVar); - expr * regexTerm = a_regexIn->get_arg(1); - - // TODO figure out regex NFA stuff - if (regex_nfa_cache.find(regexTerm) == regex_nfa_cache.end()) { - TRACE("str", tout << "regex_nfa_cache: cache miss" << std::endl;); - regex_nfa_cache[regexTerm] = nfa(u, regexTerm); - } else { - TRACE("str", tout << "regex_nfa_cache: cache hit" << std::endl;); - } - - nfa regexNFA = regex_nfa_cache[regexTerm]; - ENSURE(regexNFA.is_valid()); - bool matchRes = regexNFA.matches(constStrValue); - - TRACE("str", tout << mk_pp(*itor, m) << " in " << regStr << " : " << (matchRes ? "yes" : "no") << std::endl;); - - expr_ref implyL(ctx.mk_eq_atom(*itor, constStr), m); - if (matchRes) { - assert_implication(implyL, boolVar); - } else { - assert_implication(implyL, m.mk_not(boolVar)); - } - } - } - } - } - } -} - -/* - * strArgmt::solve_concat_eq_str() - * Solve concatenations of the form: - * const == Concat(const, X) - * const == Concat(X, const) - */ -void theory_str::solve_concat_eq_str(expr * concat, expr * str) { - ast_manager & m = get_manager(); - context & ctx = get_context(); - - TRACE("str", tout << mk_ismt2_pp(concat, m) << " == " << mk_ismt2_pp(str, m) << std::endl;); - - zstring const_str; - if (u.str.is_concat(to_app(concat)) && u.str.is_string(to_app(str), const_str)) { - app * a_concat = to_app(concat); - SASSERT(a_concat->get_num_args() == 2); - expr * a1 = a_concat->get_arg(0); - expr * a2 = a_concat->get_arg(1); - - if (const_str.empty()) { - TRACE("str", tout << "quick path: concat == \"\"" << std::endl;); - // assert the following axiom: - // ( (Concat a1 a2) == "" ) -> ( (a1 == "") AND (a2 == "") ) - - - expr_ref premise(ctx.mk_eq_atom(concat, str), m); - expr_ref c1(ctx.mk_eq_atom(a1, str), m); - expr_ref c2(ctx.mk_eq_atom(a2, str), m); - expr_ref conclusion(m.mk_and(c1, c2), m); - assert_implication(premise, conclusion); - - return; - } - bool arg1_has_eqc_value = false; - bool arg2_has_eqc_value = false; - expr * arg1 = get_eqc_value(a1, arg1_has_eqc_value); - expr * arg2 = get_eqc_value(a2, arg2_has_eqc_value); - expr_ref newConcat(m); - if (arg1 != a1 || arg2 != a2) { - TRACE("str", tout << "resolved concat argument(s) to eqc string constants" << std::endl;); - int iPos = 0; - expr_ref_vector item1(m); - if (a1 != arg1) { - item1.push_back(ctx.mk_eq_atom(a1, arg1)); - iPos += 1; - } - if (a2 != arg2) { - item1.push_back(ctx.mk_eq_atom(a2, arg2)); - iPos += 1; - } - expr_ref implyL1(mk_and(item1), m); - newConcat = mk_concat(arg1, arg2); - if (newConcat != str) { - expr_ref implyR1(ctx.mk_eq_atom(concat, newConcat), m); - assert_implication(implyL1, implyR1); - } - } else { - newConcat = concat; - } - if (newConcat == str) { - return; - } - if (!u.str.is_concat(to_app(newConcat))) { - return; - } - if (arg1_has_eqc_value && arg2_has_eqc_value) { - // Case 1: Concat(const, const) == const - TRACE("str", tout << "Case 1: Concat(const, const) == const" << std::endl;); - zstring arg1_str, arg2_str; - u.str.is_string(arg1, arg1_str); - u.str.is_string(arg2, arg2_str); - - zstring result_str = arg1_str + arg2_str; - if (result_str != const_str) { - // Inconsistency - TRACE("str", tout << "inconsistency detected: \"" - << arg1_str << "\" + \"" << arg2_str << - "\" != \"" << const_str << "\"" << "\n";); - expr_ref equality(ctx.mk_eq_atom(concat, str), m); - expr_ref diseq(m.mk_not(equality), m); - assert_axiom(diseq); - return; - } - } else if (!arg1_has_eqc_value && arg2_has_eqc_value) { - // Case 2: Concat(var, const) == const - TRACE("str", tout << "Case 2: Concat(var, const) == const" << std::endl;); - zstring arg2_str; - u.str.is_string(arg2, arg2_str); - unsigned int resultStrLen = const_str.length(); - unsigned int arg2StrLen = arg2_str.length(); - if (resultStrLen < arg2StrLen) { - // Inconsistency - TRACE("str", tout << "inconsistency detected: \"" - << arg2_str << - "\" is longer than \"" << const_str << "\"," - << " so cannot be concatenated with anything to form it" << "\n";); - expr_ref equality(ctx.mk_eq_atom(newConcat, str), m); - expr_ref diseq(m.mk_not(equality), m); - assert_axiom(diseq); - return; - } else { - int varStrLen = resultStrLen - arg2StrLen; - zstring firstPart = const_str.extract(0, varStrLen); - zstring secondPart = const_str.extract(varStrLen, arg2StrLen); - if (arg2_str != secondPart) { - // Inconsistency - TRACE("str", tout << "inconsistency detected: " - << "suffix of concatenation result expected \"" << secondPart << "\", " - << "actually \"" << arg2_str << "\"" - << "\n";); - expr_ref equality(ctx.mk_eq_atom(newConcat, str), m); - expr_ref diseq(m.mk_not(equality), m); - assert_axiom(diseq); - return; - } else { - expr_ref tmpStrConst(mk_string(firstPart), m); - expr_ref premise(ctx.mk_eq_atom(newConcat, str), m); - expr_ref conclusion(ctx.mk_eq_atom(arg1, tmpStrConst), m); - assert_implication(premise, conclusion); - return; - } - } - } else if (arg1_has_eqc_value && !arg2_has_eqc_value) { - // Case 3: Concat(const, var) == const - TRACE("str", tout << "Case 3: Concat(const, var) == const" << std::endl;); - zstring arg1_str; - u.str.is_string(arg1, arg1_str); - unsigned int resultStrLen = const_str.length(); - unsigned int arg1StrLen = arg1_str.length(); - if (resultStrLen < arg1StrLen) { - // Inconsistency - TRACE("str", tout << "inconsistency detected: \"" - << arg1_str << - "\" is longer than \"" << const_str << "\"," - << " so cannot be concatenated with anything to form it" << "\n";); - expr_ref equality(ctx.mk_eq_atom(newConcat, str), m); - expr_ref diseq(m.mk_not(equality), m); - assert_axiom(diseq); - return; - } else { - int varStrLen = resultStrLen - arg1StrLen; - zstring firstPart = const_str.extract(0, arg1StrLen); - zstring secondPart = const_str.extract(arg1StrLen, varStrLen); - if (arg1_str != firstPart) { - // Inconsistency - TRACE("str", tout << "inconsistency detected: " - << "prefix of concatenation result expected \"" << secondPart << "\", " - << "actually \"" << arg1_str << "\"" - << "\n";); - expr_ref equality(ctx.mk_eq_atom(newConcat, str), m); - expr_ref diseq(m.mk_not(equality), m); - assert_axiom(diseq); - return; - } else { - expr_ref tmpStrConst(mk_string(secondPart), m); - expr_ref premise(ctx.mk_eq_atom(newConcat, str), m); - expr_ref conclusion(ctx.mk_eq_atom(arg2, tmpStrConst), m); - assert_implication(premise, conclusion); - return; - } - } - } else { - // Case 4: Concat(var, var) == const - TRACE("str", tout << "Case 4: Concat(var, var) == const" << std::endl;); - if (eval_concat(arg1, arg2) == NULL) { - rational arg1Len, arg2Len; - bool arg1Len_exists = get_len_value(arg1, arg1Len); - bool arg2Len_exists = get_len_value(arg2, arg2Len); - rational concatStrLen((unsigned)const_str.length()); - if (arg1Len_exists || arg2Len_exists) { - expr_ref ax_l1(ctx.mk_eq_atom(concat, str), m); - expr_ref ax_l2(m); - zstring prefixStr, suffixStr; - if (arg1Len_exists) { - if (arg1Len.is_neg()) { - TRACE("str", tout << "length conflict: arg1Len = " << arg1Len << ", concatStrLen = " << concatStrLen << std::endl;); - expr_ref toAssert(m_autil.mk_ge(mk_strlen(arg1), mk_int(0)), m); - assert_axiom(toAssert); - return; - } else if (arg1Len > concatStrLen) { - TRACE("str", tout << "length conflict: arg1Len = " << arg1Len << ", concatStrLen = " << concatStrLen << std::endl;); - expr_ref ax_r1(m_autil.mk_le(mk_strlen(arg1), mk_int(concatStrLen)), m); - assert_implication(ax_l1, ax_r1); - return; - } - - prefixStr = const_str.extract(0, arg1Len.get_unsigned()); - rational concat_minus_arg1 = concatStrLen - arg1Len; - suffixStr = const_str.extract(arg1Len.get_unsigned(), concat_minus_arg1.get_unsigned()); - ax_l2 = ctx.mk_eq_atom(mk_strlen(arg1), mk_int(arg1Len)); - } else { - // arg2's length is available - if (arg2Len.is_neg()) { - TRACE("str", tout << "length conflict: arg2Len = " << arg2Len << ", concatStrLen = " << concatStrLen << std::endl;); - expr_ref toAssert(m_autil.mk_ge(mk_strlen(arg2), mk_int(0)), m); - assert_axiom(toAssert); - return; - } else if (arg2Len > concatStrLen) { - TRACE("str", tout << "length conflict: arg2Len = " << arg2Len << ", concatStrLen = " << concatStrLen << std::endl;); - expr_ref ax_r1(m_autil.mk_le(mk_strlen(arg2), mk_int(concatStrLen)), m); - assert_implication(ax_l1, ax_r1); - return; - } - - rational concat_minus_arg2 = concatStrLen - arg2Len; - prefixStr = const_str.extract(0, concat_minus_arg2.get_unsigned()); - suffixStr = const_str.extract(concat_minus_arg2.get_unsigned(), arg2Len.get_unsigned()); - ax_l2 = ctx.mk_eq_atom(mk_strlen(arg2), mk_int(arg2Len)); - } - // consistency check - if (u.str.is_concat(to_app(arg1)) && !can_concat_eq_str(arg1, prefixStr)) { - expr_ref ax_r(m.mk_not(ax_l2), m); - assert_implication(ax_l1, ax_r); - return; - } - if (u.str.is_concat(to_app(arg2)) && !can_concat_eq_str(arg2, suffixStr)) { - expr_ref ax_r(m.mk_not(ax_l2), m); - assert_implication(ax_l1, ax_r); - return; - } - expr_ref_vector r_items(m); - r_items.push_back(ctx.mk_eq_atom(arg1, mk_string(prefixStr))); - r_items.push_back(ctx.mk_eq_atom(arg2, mk_string(suffixStr))); - if (!arg1Len_exists) { - r_items.push_back(ctx.mk_eq_atom(mk_strlen(arg1), mk_int(prefixStr.length()))); - } - if (!arg2Len_exists) { - r_items.push_back(ctx.mk_eq_atom(mk_strlen(arg2), mk_int(suffixStr.length()))); - } - expr_ref lhs(m.mk_and(ax_l1, ax_l2), m); - expr_ref rhs(mk_and(r_items), m); - assert_implication(lhs, rhs); - } else { /* ! (arg1Len != 1 || arg2Len != 1) */ - expr_ref xorFlag(m); - std::pair key1(arg1, arg2); - std::pair key2(arg2, arg1); - - // check the entries in this map to make sure they're still in scope - // before we use them. - - std::map, std::map >::iterator entry1 = varForBreakConcat.find(key1); - std::map, std::map >::iterator entry2 = varForBreakConcat.find(key2); - - bool entry1InScope; - if (entry1 == varForBreakConcat.end()) { - TRACE("str", tout << "key1 no entry" << std::endl;); - entry1InScope = false; - } else { - // OVERRIDE. - entry1InScope = true; - TRACE("str", tout << "key1 entry" << std::endl;); - /* - if (internal_variable_set.find((entry1->second)[0]) == internal_variable_set.end()) { - TRACE("str", tout << "key1 entry not in scope" << std::endl;); - entry1InScope = false; - } else { - TRACE("str", tout << "key1 entry in scope" << std::endl;); - entry1InScope = true; - } - */ - } - - bool entry2InScope; - if (entry2 == varForBreakConcat.end()) { - TRACE("str", tout << "key2 no entry" << std::endl;); - entry2InScope = false; - } else { - // OVERRIDE. - entry2InScope = true; - TRACE("str", tout << "key2 entry" << std::endl;); - /* - if (internal_variable_set.find((entry2->second)[0]) == internal_variable_set.end()) { - TRACE("str", tout << "key2 entry not in scope" << std::endl;); - entry2InScope = false; - } else { - TRACE("str", tout << "key2 entry in scope" << std::endl;); - entry2InScope = true; - } - */ - } - - TRACE("str", tout << "entry 1 " << (entry1InScope ? "in scope" : "not in scope") << std::endl - << "entry 2 " << (entry2InScope ? "in scope" : "not in scope") << std::endl;); - - if (!entry1InScope && !entry2InScope) { - xorFlag = mk_internal_xor_var(); - varForBreakConcat[key1][0] = xorFlag; - } else if (entry1InScope) { - xorFlag = varForBreakConcat[key1][0]; - } else { // entry2InScope - xorFlag = varForBreakConcat[key2][0]; - } - - int concatStrLen = const_str.length(); - int and_count = 1; - - expr_ref_vector arrangement_disjunction(m); - - for (int i = 0; i < concatStrLen + 1; ++i) { - expr_ref_vector and_items(m); - zstring prefixStr = const_str.extract(0, i); - zstring suffixStr = const_str.extract(i, concatStrLen - i); - // skip invalid options - if (u.str.is_concat(to_app(arg1)) && !can_concat_eq_str(arg1, prefixStr)) { - continue; - } - if (u.str.is_concat(to_app(arg2)) && !can_concat_eq_str(arg2, suffixStr)) { - continue; - } - - expr_ref prefixAst(mk_string(prefixStr), m); - expr_ref arg1_eq (ctx.mk_eq_atom(arg1, prefixAst), m); - and_items.push_back(arg1_eq); - and_count += 1; - - expr_ref suffixAst(mk_string(suffixStr), m); - expr_ref arg2_eq (ctx.mk_eq_atom(arg2, suffixAst), m); - and_items.push_back(arg2_eq); - and_count += 1; - - arrangement_disjunction.push_back(mk_and(and_items)); - } - - expr_ref implyL(ctx.mk_eq_atom(concat, str), m); - expr_ref implyR1(m); - if (arrangement_disjunction.empty()) { - // negate - expr_ref concat_eq_str(ctx.mk_eq_atom(concat, str), m); - expr_ref negate_ast(m.mk_not(concat_eq_str), m); - assert_axiom(negate_ast); - } else { - implyR1 = mk_or(arrangement_disjunction); - if (m_params.m_StrongArrangements) { - expr_ref ax_strong(ctx.mk_eq_atom(implyL, implyR1), m); - assert_axiom(ax_strong); - } else { - assert_implication(implyL, implyR1); - } - generate_mutual_exclusion(arrangement_disjunction); - } - } /* (arg1Len != 1 || arg2Len != 1) */ - } /* if (Concat(arg1, arg2) == NULL) */ - } - } -} - -expr_ref theory_str::set_up_finite_model_test(expr * lhs, expr * rhs) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - - TRACE("str", tout << "activating finite model testing for overlapping concats " - << mk_pp(lhs, m) << " and " << mk_pp(rhs, m) << std::endl;); - std::map concatMap; - std::map unrollMap; - std::map varMap; - classify_ast_by_type(lhs, varMap, concatMap, unrollMap); - classify_ast_by_type(rhs, varMap, concatMap, unrollMap); - TRACE("str", tout << "found vars:"; - for (std::map::iterator it = varMap.begin(); it != varMap.end(); ++it) { - tout << " " << mk_pp(it->first, m); - } - tout << std::endl; - ); - - expr_ref testvar(mk_str_var("finiteModelTest"), m); - m_trail.push_back(testvar); - ptr_vector varlist; - - for (std::map::iterator it = varMap.begin(); it != varMap.end(); ++it) { - expr * v = it->first; - varlist.push_back(v); - } - - // make things easy for the core wrt. testvar - expr_ref t1(ctx.mk_eq_atom(testvar, mk_string("")), m); - expr_ref t_yes(ctx.mk_eq_atom(testvar, mk_string("yes")), m); - expr_ref testvaraxiom(m.mk_or(t1, t_yes), m); - assert_axiom(testvaraxiom); - - finite_model_test_varlists.insert(testvar, varlist); - m_trail_stack.push(insert_obj_map >(finite_model_test_varlists, testvar) ); - return t_yes; -} - -void theory_str::finite_model_test(expr * testvar, expr * str) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - - zstring s; - if (!u.str.is_string(str, s)) return; - if (s == "yes") { - TRACE("str", tout << "start finite model test for " << mk_pp(testvar, m) << std::endl;); - ptr_vector & vars = finite_model_test_varlists[testvar]; - for (ptr_vector::iterator it = vars.begin(); it != vars.end(); ++it) { - expr * v = *it; - bool v_has_eqc = false; - get_eqc_value(v, v_has_eqc); - if (v_has_eqc) { - TRACE("str", tout << "variable " << mk_pp(v,m) << " already equivalent to a string constant" << std::endl;); - continue; - } - // check for any sort of existing length tester we might interfere with - if (m_params.m_UseBinarySearch) { - if (binary_search_len_tester_stack.contains(v) && !binary_search_len_tester_stack[v].empty()) { - TRACE("str", tout << "already found existing length testers for " << mk_pp(v, m) << std::endl;); - continue; - } else { - // start binary search as normal - expr_ref implLhs(ctx.mk_eq_atom(testvar, str), m); - expr_ref implRhs(binary_search_length_test(v, NULL, ""), m); - assert_implication(implLhs, implRhs); - } - } else { - bool map_effectively_empty = false; - if (!fvar_len_count_map.contains(v)) { - map_effectively_empty = true; - } - - if (!map_effectively_empty) { - map_effectively_empty = true; - ptr_vector indicator_set = fvar_lenTester_map[v]; - for (ptr_vector::iterator it = indicator_set.begin(); it != indicator_set.end(); ++it) { - expr * indicator = *it; - if (internal_variable_set.find(indicator) != internal_variable_set.end()) { - map_effectively_empty = false; - break; - } - } - } - - if (map_effectively_empty) { - TRACE("str", tout << "no existing length testers for " << mk_pp(v, m) << std::endl;); - rational v_len; - rational v_lower_bound; - rational v_upper_bound; - expr_ref vLengthExpr(mk_strlen(v), m); - if (get_len_value(v, v_len)) { - TRACE("str", tout << "length = " << v_len.to_string() << std::endl;); - v_lower_bound = v_len; - v_upper_bound = v_len; - } else { - bool lower_bound_exists = lower_bound(vLengthExpr, v_lower_bound); - bool upper_bound_exists = upper_bound(vLengthExpr, v_upper_bound); - TRACE("str", tout << "bounds = [" << (lower_bound_exists?v_lower_bound.to_string():"?") - << ".." << (upper_bound_exists?v_upper_bound.to_string():"?") << "]" << std::endl;); - - // make sure the bounds are non-negative - if (lower_bound_exists && v_lower_bound.is_neg()) { - v_lower_bound = rational::zero(); - } - if (upper_bound_exists && v_upper_bound.is_neg()) { - v_upper_bound = rational::zero(); - } - - if (lower_bound_exists && upper_bound_exists) { - // easiest case. we will search within these bounds - } else if (upper_bound_exists && !lower_bound_exists) { - // search between 0 and the upper bound - v_lower_bound == rational::zero(); - } else if (lower_bound_exists && !upper_bound_exists) { - // check some finite portion of the search space - v_upper_bound = v_lower_bound + rational(10); - } else { - // no bounds information - v_lower_bound = rational::zero(); - v_upper_bound = v_lower_bound + rational(10); - } - } - // now create a fake length tester over this finite disjunction of lengths - - fvar_len_count_map[v] = 1; - unsigned int testNum = fvar_len_count_map[v]; - - expr_ref indicator(mk_internal_lenTest_var(v, testNum), m); - SASSERT(indicator); - m_trail.push_back(indicator); - - fvar_lenTester_map[v].shrink(0); - fvar_lenTester_map[v].push_back(indicator); - lenTester_fvar_map[indicator] = v; - - expr_ref_vector orList(m); - expr_ref_vector andList(m); - - for (rational l = v_lower_bound; l <= v_upper_bound; l += rational::one()) { - zstring lStr = zstring(l.to_string().c_str()); - expr_ref str_indicator(mk_string(lStr), m); - expr_ref or_expr(ctx.mk_eq_atom(indicator, str_indicator), m); - orList.push_back(or_expr); - expr_ref and_expr(ctx.mk_eq_atom(or_expr, ctx.mk_eq_atom(vLengthExpr, m_autil.mk_numeral(l, true))), m); - andList.push_back(and_expr); - } - andList.push_back(mk_or(orList)); - expr_ref implLhs(ctx.mk_eq_atom(testvar, str), m); - expr_ref implRhs(mk_and(andList), m); - assert_implication(implLhs, implRhs); - } else { - TRACE("str", tout << "already found existing length testers for " << mk_pp(v, m) << std::endl;); - continue; - } - } - } // foreach (v in vars) - } // (s == "yes") -} - -void theory_str::more_len_tests(expr * lenTester, zstring lenTesterValue) { - ast_manager & m = get_manager(); - if (lenTester_fvar_map.contains(lenTester)) { - expr * fVar = lenTester_fvar_map[lenTester]; - expr * toAssert = gen_len_val_options_for_free_var(fVar, lenTester, lenTesterValue); - TRACE("str", tout << "asserting more length tests for free variable " << mk_ismt2_pp(fVar, m) << std::endl;); - if (toAssert != NULL) { - assert_axiom(toAssert); - } - } -} - -void theory_str::more_value_tests(expr * valTester, zstring valTesterValue) { - ast_manager & m = get_manager(); - - expr * fVar = valueTester_fvar_map[valTester]; - if (m_params.m_UseBinarySearch) { - if (!binary_search_len_tester_stack.contains(fVar) || binary_search_len_tester_stack[fVar].empty()) { - TRACE("str", tout << "WARNING: no active length testers for " << mk_pp(fVar, m) << std::endl;); - NOT_IMPLEMENTED_YET(); - } - expr * effectiveLenInd = binary_search_len_tester_stack[fVar].back(); - bool hasEqcValue; - expr * len_indicator_value = get_eqc_value(effectiveLenInd, hasEqcValue); - if (!hasEqcValue) { - TRACE("str", tout << "WARNING: length tester " << mk_pp(effectiveLenInd, m) << " at top of stack for " << mk_pp(fVar, m) << " has no EQC value" << std::endl;); - } else { - // safety check - zstring effectiveLenIndiStr; - u.str.is_string(len_indicator_value, effectiveLenIndiStr); - if (effectiveLenIndiStr == "more" || effectiveLenIndiStr == "less") { - TRACE("str", tout << "ERROR: illegal state -- requesting 'more value tests' but a length tester is not yet concrete!" << std::endl;); - UNREACHABLE(); - } - expr * valueAssert = gen_free_var_options(fVar, effectiveLenInd, effectiveLenIndiStr, valTester, valTesterValue); - TRACE("str", tout << "asserting more value tests for free variable " << mk_ismt2_pp(fVar, m) << std::endl;); - if (valueAssert != NULL) { - assert_axiom(valueAssert); - } - } - } else { - int lenTesterCount = fvar_lenTester_map[fVar].size(); - - expr * effectiveLenInd = NULL; - zstring effectiveLenIndiStr = ""; - for (int i = 0; i < lenTesterCount; ++i) { - expr * len_indicator_pre = fvar_lenTester_map[fVar][i]; - bool indicatorHasEqcValue = false; - expr * len_indicator_value = get_eqc_value(len_indicator_pre, indicatorHasEqcValue); - if (indicatorHasEqcValue) { - zstring len_pIndiStr; - u.str.is_string(len_indicator_value, len_pIndiStr); - if (len_pIndiStr != "more") { - effectiveLenInd = len_indicator_pre; - effectiveLenIndiStr = len_pIndiStr; - break; - } - } - } - expr * valueAssert = gen_free_var_options(fVar, effectiveLenInd, effectiveLenIndiStr, valTester, valTesterValue); - TRACE("str", tout << "asserting more value tests for free variable " << mk_ismt2_pp(fVar, m) << std::endl;); - if (valueAssert != NULL) { - assert_axiom(valueAssert); - } - } -} - -bool theory_str::free_var_attempt(expr * nn1, expr * nn2) { - ast_manager & m = get_manager(); - zstring nn2_str; - if (internal_lenTest_vars.contains(nn1) && u.str.is_string(nn2, nn2_str)) { - TRACE("str", tout << "acting on equivalence between length tester var " << mk_ismt2_pp(nn1, m) - << " and constant " << mk_ismt2_pp(nn2, m) << std::endl;); - more_len_tests(nn1, nn2_str); - return true; - } else if (internal_valTest_vars.contains(nn1) && u.str.is_string(nn2, nn2_str)) { - if (nn2_str == "more") { - TRACE("str", tout << "acting on equivalence between value var " << mk_ismt2_pp(nn1, m) - << " and constant " << mk_ismt2_pp(nn2, m) << std::endl;); - more_value_tests(nn1, nn2_str); - } - return true; - } else if (internal_unrollTest_vars.contains(nn1)) { - return true; - } else { - return false; - } -} - -void theory_str::handle_equality(expr * lhs, expr * rhs) { - ast_manager & m = get_manager(); - context & ctx = get_context(); - // both terms must be of sort String - sort * lhs_sort = m.get_sort(lhs); - sort * rhs_sort = m.get_sort(rhs); - sort * str_sort = u.str.mk_string_sort(); - - if (lhs_sort != str_sort || rhs_sort != str_sort) { - TRACE("str", tout << "skip equality: not String sort" << std::endl;); - return; - } - - /* // temporarily disabled, we are borrowing these testers for something else - if (m_params.m_FiniteOverlapModels && !finite_model_test_varlists.empty()) { - if (finite_model_test_varlists.contains(lhs)) { - finite_model_test(lhs, rhs); return; - } else if (finite_model_test_varlists.contains(rhs)) { - finite_model_test(rhs, lhs); return; - } - } - */ - - if (free_var_attempt(lhs, rhs) || free_var_attempt(rhs, lhs)) { - return; - } - - if (u.str.is_concat(to_app(lhs)) && u.str.is_concat(to_app(rhs))) { - bool nn1HasEqcValue = false; - bool nn2HasEqcValue = false; - expr * nn1_value = get_eqc_value(lhs, nn1HasEqcValue); - expr * nn2_value = get_eqc_value(rhs, nn2HasEqcValue); - if (nn1HasEqcValue && !nn2HasEqcValue) { - simplify_parent(rhs, nn1_value); - } - if (!nn1HasEqcValue && nn2HasEqcValue) { - simplify_parent(lhs, nn2_value); - } - - expr * nn1_arg0 = to_app(lhs)->get_arg(0); - expr * nn1_arg1 = to_app(lhs)->get_arg(1); - expr * nn2_arg0 = to_app(rhs)->get_arg(0); - expr * nn2_arg1 = to_app(rhs)->get_arg(1); - if (nn1_arg0 == nn2_arg0 && in_same_eqc(nn1_arg1, nn2_arg1)) { - TRACE("str", tout << "skip: lhs arg0 == rhs arg0" << std::endl;); - return; - } - - if (nn1_arg1 == nn2_arg1 && in_same_eqc(nn1_arg0, nn2_arg0)) { - TRACE("str", tout << "skip: lhs arg1 == rhs arg1" << std::endl;); - return; - } - } - - if (opt_DeferEQCConsistencyCheck) { - TRACE("str", tout << "opt_DeferEQCConsistencyCheck is set; deferring new_eq_check call" << std::endl;); - } else { - // newEqCheck() -- check consistency wrt. existing equivalence classes - if (!new_eq_check(lhs, rhs)) { - return; - } - } - - // BEGIN new_eq_handler() in strTheory - - { - rational nn1Len, nn2Len; - bool nn1Len_exists = get_len_value(lhs, nn1Len); - bool nn2Len_exists = get_len_value(rhs, nn2Len); - expr * emptyStr = mk_string(""); - - if (nn1Len_exists && nn1Len.is_zero()) { - if (!in_same_eqc(lhs, emptyStr) && rhs != emptyStr) { - expr_ref eql(ctx.mk_eq_atom(mk_strlen(lhs), mk_int(0)), m); - expr_ref eqr(ctx.mk_eq_atom(lhs, emptyStr), m); - expr_ref toAssert(ctx.mk_eq_atom(eql, eqr), m); - assert_axiom(toAssert); - } - } - - if (nn2Len_exists && nn2Len.is_zero()) { - if (!in_same_eqc(rhs, emptyStr) && lhs != emptyStr) { - expr_ref eql(ctx.mk_eq_atom(mk_strlen(rhs), mk_int(0)), m); - expr_ref eqr(ctx.mk_eq_atom(rhs, emptyStr), m); - expr_ref toAssert(ctx.mk_eq_atom(eql, eqr), m); - assert_axiom(toAssert); - } - } - } - - instantiate_str_eq_length_axiom(ctx.get_enode(lhs), ctx.get_enode(rhs)); - - // group terms by equivalence class (groupNodeInEqc()) - - std::set eqc_concat_lhs; - std::set eqc_var_lhs; - std::set eqc_const_lhs; - group_terms_by_eqc(lhs, eqc_concat_lhs, eqc_var_lhs, eqc_const_lhs); - - std::set eqc_concat_rhs; - std::set eqc_var_rhs; - std::set eqc_const_rhs; - group_terms_by_eqc(rhs, eqc_concat_rhs, eqc_var_rhs, eqc_const_rhs); - - TRACE("str", - tout << "lhs eqc:" << std::endl; - tout << "Concats:" << std::endl; - for (std::set::iterator it = eqc_concat_lhs.begin(); it != eqc_concat_lhs.end(); ++it) { - expr * ex = *it; - tout << mk_ismt2_pp(ex, get_manager()) << std::endl; - } - tout << "Variables:" << std::endl; - for (std::set::iterator it = eqc_var_lhs.begin(); it != eqc_var_lhs.end(); ++it) { - expr * ex = *it; - tout << mk_ismt2_pp(ex, get_manager()) << std::endl; - } - tout << "Constants:" << std::endl; - for (std::set::iterator it = eqc_const_lhs.begin(); it != eqc_const_lhs.end(); ++it) { - expr * ex = *it; - tout << mk_ismt2_pp(ex, get_manager()) << std::endl; - } - - tout << "rhs eqc:" << std::endl; - tout << "Concats:" << std::endl; - for (std::set::iterator it = eqc_concat_rhs.begin(); it != eqc_concat_rhs.end(); ++it) { - expr * ex = *it; - tout << mk_ismt2_pp(ex, get_manager()) << std::endl; - } - tout << "Variables:" << std::endl; - for (std::set::iterator it = eqc_var_rhs.begin(); it != eqc_var_rhs.end(); ++it) { - expr * ex = *it; - tout << mk_ismt2_pp(ex, get_manager()) << std::endl; - } - tout << "Constants:" << std::endl; - for (std::set::iterator it = eqc_const_rhs.begin(); it != eqc_const_rhs.end(); ++it) { - expr * ex = *it; - tout << mk_ismt2_pp(ex, get_manager()) << std::endl; - } - ); - - // step 1: Concat == Concat - int hasCommon = 0; - if (eqc_concat_lhs.size() != 0 && eqc_concat_rhs.size() != 0) { - std::set::iterator itor1 = eqc_concat_lhs.begin(); - std::set::iterator itor2 = eqc_concat_rhs.begin(); - for (; itor1 != eqc_concat_lhs.end(); itor1++) { - if (eqc_concat_rhs.find(*itor1) != eqc_concat_rhs.end()) { - hasCommon = 1; - break; - } - } - for (; itor2 != eqc_concat_rhs.end(); itor2++) { - if (eqc_concat_lhs.find(*itor2) != eqc_concat_lhs.end()) { - hasCommon = 1; - break; - } - } - if (hasCommon == 0) { - if (opt_ConcatOverlapAvoid) { - bool found = false; - // check each pair and take the first ones that won't immediately overlap - for (itor1 = eqc_concat_lhs.begin(); itor1 != eqc_concat_lhs.end() && !found; ++itor1) { - expr * concat_lhs = *itor1; - for (itor2 = eqc_concat_rhs.begin(); itor2 != eqc_concat_rhs.end() && !found; ++itor2) { - expr * concat_rhs = *itor2; - if (will_result_in_overlap(concat_lhs, concat_rhs)) { - TRACE("str", tout << "Concats " << mk_pp(concat_lhs, m) << " and " - << mk_pp(concat_rhs, m) << " will result in overlap; skipping." << std::endl;); - } else { - TRACE("str", tout << "Concats " << mk_pp(concat_lhs, m) << " and " - << mk_pp(concat_rhs, m) << " won't overlap. Simplifying here." << std::endl;); - simplify_concat_equality(concat_lhs, concat_rhs); - found = true; - break; - } - } - } - if (!found) { - TRACE("str", tout << "All pairs of concats expected to overlap, falling back." << std::endl;); - simplify_concat_equality(*(eqc_concat_lhs.begin()), *(eqc_concat_rhs.begin())); - } - } else { - // default behaviour - simplify_concat_equality(*(eqc_concat_lhs.begin()), *(eqc_concat_rhs.begin())); - } - } - } - - // step 2: Concat == Constant - - if (eqc_const_lhs.size() != 0) { - expr * conStr = *(eqc_const_lhs.begin()); - std::set::iterator itor2 = eqc_concat_rhs.begin(); - for (; itor2 != eqc_concat_rhs.end(); itor2++) { - solve_concat_eq_str(*itor2, conStr); - } - } else if (eqc_const_rhs.size() != 0) { - expr* conStr = *(eqc_const_rhs.begin()); - std::set::iterator itor1 = eqc_concat_lhs.begin(); - for (; itor1 != eqc_concat_lhs.end(); itor1++) { - solve_concat_eq_str(*itor1, conStr); - } - } - - // simplify parents wrt. the equivalence class of both sides - bool nn1HasEqcValue = false; - bool nn2HasEqcValue = false; - // we want the Z3str2 eqc check here... - expr * nn1_value = z3str2_get_eqc_value(lhs, nn1HasEqcValue); - expr * nn2_value = z3str2_get_eqc_value(rhs, nn2HasEqcValue); - if (nn1HasEqcValue && !nn2HasEqcValue) { - simplify_parent(rhs, nn1_value); - } - - if (!nn1HasEqcValue && nn2HasEqcValue) { - simplify_parent(lhs, nn2_value); - } - - expr * nn1EqConst = NULL; - std::set nn1EqUnrollFuncs; - get_eqc_allUnroll(lhs, nn1EqConst, nn1EqUnrollFuncs); - expr * nn2EqConst = NULL; - std::set nn2EqUnrollFuncs; - get_eqc_allUnroll(rhs, nn2EqConst, nn2EqUnrollFuncs); - - if (nn2EqConst != NULL) { - for (std::set::iterator itor1 = nn1EqUnrollFuncs.begin(); itor1 != nn1EqUnrollFuncs.end(); itor1++) { - process_unroll_eq_const_str(*itor1, nn2EqConst); - } - } - - if (nn1EqConst != NULL) { - for (std::set::iterator itor2 = nn2EqUnrollFuncs.begin(); itor2 != nn2EqUnrollFuncs.end(); itor2++) { - process_unroll_eq_const_str(*itor2, nn1EqConst); - } - } - -} - -void theory_str::set_up_axioms(expr * ex) { - ast_manager & m = get_manager(); - context & ctx = get_context(); - - sort * ex_sort = m.get_sort(ex); - sort * str_sort = u.str.mk_string_sort(); - sort * bool_sort = m.mk_bool_sort(); - - family_id m_arith_fid = m.mk_family_id("arith"); - sort * int_sort = m.mk_sort(m_arith_fid, INT_SORT); - - if (ex_sort == str_sort) { - TRACE("str", tout << "setting up axioms for " << mk_ismt2_pp(ex, get_manager()) << - ": expr is of sort String" << std::endl;); - // set up basic string axioms - enode * n = ctx.get_enode(ex); - SASSERT(n); - m_basicstr_axiom_todo.push_back(n); - TRACE("str", tout << "add " << mk_pp(ex, m) << " to m_basicstr_axiom_todo" << std::endl;); - - - if (is_app(ex)) { - app * ap = to_app(ex); - if (u.str.is_concat(ap)) { - // if ex is a concat, set up concat axioms later - m_concat_axiom_todo.push_back(n); - // we also want to check whether we can eval this concat, - // in case the rewriter did not totally finish with this term - m_concat_eval_todo.push_back(n); - } else if (u.str.is_length(ap)) { - // if the argument is a variable, - // keep track of this for later, we'll need it during model gen - expr * var = ap->get_arg(0); - app * aVar = to_app(var); - if (aVar->get_num_args() == 0 && !u.str.is_string(aVar)) { - input_var_in_len.insert(var); - } - } else if (u.str.is_at(ap) || u.str.is_extract(ap) || u.str.is_replace(ap)) { - m_library_aware_axiom_todo.push_back(n); - } else if (u.str.is_itos(ap)) { - TRACE("str", tout << "found string-integer conversion term: " << mk_pp(ex, get_manager()) << std::endl;); - string_int_conversion_terms.push_back(ap); - m_library_aware_axiom_todo.push_back(n); - } else if (ap->get_num_args() == 0 && !u.str.is_string(ap)) { - // if ex is a variable, add it to our list of variables - TRACE("str", tout << "tracking variable " << mk_ismt2_pp(ap, get_manager()) << std::endl;); - variable_set.insert(ex); - ctx.mark_as_relevant(ex); - // this might help?? - theory_var v = mk_var(n); - TRACE("str", tout << "variable " << mk_ismt2_pp(ap, get_manager()) << " is #" << v << std::endl;); - } - } - } else if (ex_sort == bool_sort) { - TRACE("str", tout << "setting up axioms for " << mk_ismt2_pp(ex, get_manager()) << - ": expr is of sort Bool" << std::endl;); - // set up axioms for boolean terms - - ensure_enode(ex); - if (ctx.e_internalized(ex)) { - enode * n = ctx.get_enode(ex); - SASSERT(n); - - if (is_app(ex)) { - app * ap = to_app(ex); - if (u.str.is_prefix(ap) || u.str.is_suffix(ap) || u.str.is_contains(ap) || u.str.is_in_re(ap)) { - m_library_aware_axiom_todo.push_back(n); - } - } - } else { - TRACE("str", tout << "WARNING: Bool term " << mk_ismt2_pp(ex, get_manager()) << " not internalized. Delaying axiom setup to prevent a crash." << std::endl;); - ENSURE(!search_started); // infinite loop prevention - m_delayed_axiom_setup_terms.push_back(ex); - return; - } - } else if (ex_sort == int_sort) { - TRACE("str", tout << "setting up axioms for " << mk_ismt2_pp(ex, get_manager()) << - ": expr is of sort Int" << std::endl;); - // set up axioms for integer terms - enode * n = ensure_enode(ex); - SASSERT(n); - - if (is_app(ex)) { - app * ap = to_app(ex); - // TODO indexof2/lastindexof - if (u.str.is_index(ap) /* || is_Indexof2(ap) || is_LastIndexof(ap) */) { - m_library_aware_axiom_todo.push_back(n); - } else if (u.str.is_stoi(ap)) { - TRACE("str", tout << "found string-integer conversion term: " << mk_pp(ex, get_manager()) << std::endl;); - string_int_conversion_terms.push_back(ap); - m_library_aware_axiom_todo.push_back(n); - } - } - } else { - TRACE("str", tout << "setting up axioms for " << mk_ismt2_pp(ex, get_manager()) << - ": expr is of wrong sort, ignoring" << std::endl;); - } - - // if expr is an application, recursively inspect all arguments - if (is_app(ex)) { - app * term = (app*)ex; - unsigned num_args = term->get_num_args(); - for (unsigned i = 0; i < num_args; i++) { - set_up_axioms(term->get_arg(i)); - } - } -} - -void theory_str::add_theory_assumptions(expr_ref_vector & assumptions) { - TRACE("str", tout << "add overlap assumption for theory_str" << std::endl;); - symbol strOverlap("!!TheoryStrOverlapAssumption!!"); - seq_util m_sequtil(get_manager()); - sort * s = get_manager().mk_bool_sort(); - m_theoryStrOverlapAssumption_term = expr_ref(get_manager().mk_const(strOverlap, s), get_manager()); - assumptions.push_back(get_manager().mk_not(m_theoryStrOverlapAssumption_term)); -} - -lbool theory_str::validate_unsat_core(expr_ref_vector & unsat_core) { - bool assumptionFound = false; - - app * target_term = to_app(get_manager().mk_not(m_theoryStrOverlapAssumption_term)); - get_context().internalize(target_term, false); - for (unsigned i = 0; i < unsat_core.size(); ++i) { - app * core_term = to_app(unsat_core.get(i)); - // not sure if this is the correct way to compare terms in this context - enode * e1; - enode * e2; - e1 = get_context().get_enode(target_term); - e2 = get_context().get_enode(core_term); - if (e1 == e2) { - TRACE("str", tout << "overlap detected in unsat core, changing UNSAT to UNKNOWN" << std::endl;); - assumptionFound = true; - return l_undef; - } - } - - return l_false; -} - -void theory_str::init_search_eh() { - ast_manager & m = get_manager(); - context & ctx = get_context(); - - TRACE("str", - tout << "dumping all asserted formulas:" << std::endl; - unsigned nFormulas = ctx.get_num_asserted_formulas(); - for (unsigned i = 0; i < nFormulas; ++i) { - expr * ex = ctx.get_asserted_formula(i); - tout << mk_ismt2_pp(ex, m) << (ctx.is_relevant(ex) ? " (rel)" : " (NOT REL)") << std::endl; - } - ); - /* - * Recursive descent through all asserted formulas to set up axioms. - * Note that this is just the input structure and not necessarily things - * that we know to be true or false. We're just doing this to see - * which terms are explicitly mentioned. - */ - unsigned nFormulas = ctx.get_num_asserted_formulas(); - for (unsigned i = 0; i < nFormulas; ++i) { - expr * ex = ctx.get_asserted_formula(i); - set_up_axioms(ex); - } - - /* - * Similar recursive descent, except over all initially assigned terms. - * This is done to find equalities between terms, etc. that we otherwise - * might not get a chance to see. - */ - - /* - expr_ref_vector assignments(m); - ctx.get_assignments(assignments); - for (expr_ref_vector::iterator i = assignments.begin(); i != assignments.end(); ++i) { - expr * ex = *i; - if (m.is_eq(ex)) { - TRACE("str", tout << "processing assignment " << mk_ismt2_pp(ex, m) << - ": expr is equality" << std::endl;); - app * eq = (app*)ex; - SASSERT(eq->get_num_args() == 2); - expr * lhs = eq->get_arg(0); - expr * rhs = eq->get_arg(1); - - enode * e_lhs = ctx.get_enode(lhs); - enode * e_rhs = ctx.get_enode(rhs); - std::pair eq_pair(e_lhs, e_rhs); - m_str_eq_todo.push_back(eq_pair); - } else { - TRACE("str", tout << "processing assignment " << mk_ismt2_pp(ex, m) - << ": expr ignored" << std::endl;); - } - } - */ - - // this might be cheating but we need to make sure that certain maps are populated - // before the first call to new_eq_eh() - propagate(); - - TRACE("str", tout << "search started" << std::endl;); - search_started = true; -} - -void theory_str::new_eq_eh(theory_var x, theory_var y) { - //TRACE("str", tout << "new eq: v#" << x << " = v#" << y << std::endl;); - TRACE("str", tout << "new eq: " << mk_ismt2_pp(get_enode(x)->get_owner(), get_manager()) << " = " << - mk_ismt2_pp(get_enode(y)->get_owner(), get_manager()) << std::endl;); - - /* - if (m_find.find(x) == m_find.find(y)) { - return; - } - */ - handle_equality(get_enode(x)->get_owner(), get_enode(y)->get_owner()); - - // replicate Z3str2 behaviour: merge eqc **AFTER** handle_equality - m_find.merge(x, y); -} - -void theory_str::new_diseq_eh(theory_var x, theory_var y) { - //TRACE("str", tout << "new diseq: v#" << x << " != v#" << y << std::endl;); - TRACE("str", tout << "new diseq: " << mk_ismt2_pp(get_enode(x)->get_owner(), get_manager()) << " != " << - mk_ismt2_pp(get_enode(y)->get_owner(), get_manager()) << std::endl;); -} - -void theory_str::relevant_eh(app * n) { - TRACE("str", tout << "relevant: " << mk_ismt2_pp(n, get_manager()) << std::endl;); -} - -void theory_str::assign_eh(bool_var v, bool is_true) { - context & ctx = get_context(); - TRACE("str", tout << "assert: v" << v << " #" << ctx.bool_var2expr(v)->get_id() << " is_true: " << is_true << std::endl;); -} - -void theory_str::push_scope_eh() { - theory::push_scope_eh(); - m_trail_stack.push_scope(); - - sLevel += 1; - TRACE("str", tout << "push to " << sLevel << std::endl;); - TRACE_CODE(if (is_trace_enabled("t_str_dump_assign_on_scope_change")) { dump_assignments(); }); -} - -void theory_str::recursive_check_variable_scope(expr * ex) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - - if (is_app(ex)) { - app * a = to_app(ex); - if (a->get_num_args() == 0) { - // we only care about string variables - sort * s = m.get_sort(ex); - sort * string_sort = u.str.mk_string_sort(); - if (s != string_sort) { - return; - } - // base case: string constant / var - if (u.str.is_string(a)) { - return; - } else { - // assume var - if (variable_set.find(ex) == variable_set.end() - && internal_variable_set.find(ex) == internal_variable_set.end()) { - TRACE("str", tout << "WARNING: possible reference to out-of-scope variable " << mk_pp(ex, m) << std::endl;); - } - } - } else { - for (unsigned i = 0; i < a->get_num_args(); ++i) { - recursive_check_variable_scope(a->get_arg(i)); - } - } - } -} - -void theory_str::check_variable_scope() { - if (!opt_CheckVariableScope) { - return; - } - - if (!is_trace_enabled("t_str_detail")) { - return; - } - - TRACE("str", tout << "checking scopes of variables in the current assignment" << std::endl;); - - context & ctx = get_context(); - ast_manager & m = get_manager(); - - expr_ref_vector assignments(m); - ctx.get_assignments(assignments); - for (expr_ref_vector::iterator i = assignments.begin(); i != assignments.end(); ++i) { - expr * ex = *i; - recursive_check_variable_scope(ex); - } -} - -void theory_str::pop_scope_eh(unsigned num_scopes) { - sLevel -= num_scopes; - TRACE("str", tout << "pop " << num_scopes << " to " << sLevel << std::endl;); - context & ctx = get_context(); - ast_manager & m = get_manager(); - - TRACE_CODE(if (is_trace_enabled("t_str_dump_assign_on_scope_change")) { dump_assignments(); }); - - // list of expr* to remove from cut_var_map - ptr_vector cutvarmap_removes; - - obj_map >::iterator varItor = cut_var_map.begin(); - while (varItor != cut_var_map.end()) { - expr * e = varItor->m_key; - std::stack & val = cut_var_map[varItor->m_key]; - while ((val.size() > 0) && (val.top()->level != 0) && (val.top()->level >= sLevel)) { - TRACE("str", tout << "remove cut info for " << mk_pp(e, m) << std::endl; print_cut_var(e, tout);); - T_cut * aCut = val.top(); - val.pop(); - // dealloc(aCut); - } - if (val.size() == 0) { - cutvarmap_removes.insert(varItor->m_key); - } - varItor++; - } - - if (!cutvarmap_removes.empty()) { - ptr_vector::iterator it = cutvarmap_removes.begin(); - for (; it != cutvarmap_removes.end(); ++it) { - expr * ex = *it; - cut_var_map.remove(ex); - } - } - - ptr_vector new_m_basicstr; - for (ptr_vector::iterator it = m_basicstr_axiom_todo.begin(); it != m_basicstr_axiom_todo.end(); ++it) { - enode * e = *it; - app * a = e->get_owner(); - TRACE("str", tout << "consider deleting " << mk_pp(a, get_manager()) - << ", enode scope level is " << e->get_iscope_lvl() - << std::endl;); - if (e->get_iscope_lvl() <= (unsigned)sLevel) { - new_m_basicstr.push_back(e); - } - } - m_basicstr_axiom_todo.reset(); - m_basicstr_axiom_todo = new_m_basicstr; - - m_trail_stack.pop_scope(num_scopes); - theory::pop_scope_eh(num_scopes); - - //check_variable_scope(); -} - -void theory_str::dump_assignments() { - TRACE_CODE( + void theory_str::solve_concat_eq_str(expr * concat, expr * str) { ast_manager & m = get_manager(); context & ctx = get_context(); - tout << "dumping all assignments:" << std::endl; - expr_ref_vector assignments(m); - ctx.get_assignments(assignments); - for (expr_ref_vector::iterator i = assignments.begin(); i != assignments.end(); ++i) { - expr * ex = *i; - tout << mk_ismt2_pp(ex, m) << (ctx.is_relevant(ex) ? "" : " (NOT REL)") << std::endl; - } - ); -} -void theory_str::classify_ast_by_type(expr * node, std::map & varMap, - std::map & concatMap, std::map & unrollMap) { + TRACE("str", tout << mk_ismt2_pp(concat, m) << " == " << mk_ismt2_pp(str, m) << std::endl;); - // check whether the node is a string variable; - // testing set membership here bypasses several expensive checks. - // note that internal variables don't count if they're only length tester / value tester vars. - if (variable_set.find(node) != variable_set.end() - && internal_lenTest_vars.find(node) == internal_lenTest_vars.end() - && internal_valTest_vars.find(node) == internal_valTest_vars.end() - && internal_unrollTest_vars.find(node) == internal_unrollTest_vars.end()) { - if (varMap[node] != 1) { - TRACE("str", tout << "new variable: " << mk_pp(node, get_manager()) << std::endl;); - } - varMap[node] = 1; - } - // check whether the node is a function that we want to inspect - else if (is_app(node)) { - app * aNode = to_app(node); - if (u.str.is_length(aNode)) { - // Length - return; - } else if (u.str.is_concat(aNode)) { - expr * arg0 = aNode->get_arg(0); - expr * arg1 = aNode->get_arg(1); - bool arg0HasEq = false; - bool arg1HasEq = false; - expr * arg0Val = get_eqc_value(arg0, arg0HasEq); - expr * arg1Val = get_eqc_value(arg1, arg1HasEq); + zstring const_str; + if (u.str.is_concat(to_app(concat)) && u.str.is_string(to_app(str), const_str)) { + app * a_concat = to_app(concat); + SASSERT(a_concat->get_num_args() == 2); + expr * a1 = a_concat->get_arg(0); + expr * a2 = a_concat->get_arg(1); - int canskip = 0; - zstring tmp; - u.str.is_string(arg0Val, tmp); - if (arg0HasEq && tmp.empty()) { - canskip = 1; - } - u.str.is_string(arg1Val, tmp); - if (canskip == 0 && arg1HasEq && tmp.empty()) { - canskip = 1; - } - if (canskip == 0 && concatMap.find(node) == concatMap.end()) { - concatMap[node] = 1; - } - } else if (u.re.is_unroll(aNode)) { - // Unroll - if (unrollMap.find(node) == unrollMap.end()) { - unrollMap[node] = 1; - } - } - // recursively visit all arguments - for (unsigned i = 0; i < aNode->get_num_args(); ++i) { - expr * arg = aNode->get_arg(i); - classify_ast_by_type(arg, varMap, concatMap, unrollMap); - } - } -} + if (const_str.empty()) { + TRACE("str", tout << "quick path: concat == \"\"" << std::endl;); + // assert the following axiom: + // ( (Concat a1 a2) == "" ) -> ( (a1 == "") AND (a2 == "") ) -// NOTE: this function used to take an argument `Z3_ast node`; -// it was not used and so was removed from the signature -void theory_str::classify_ast_by_type_in_positive_context(std::map & varMap, - std::map & concatMap, std::map & unrollMap) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - expr_ref_vector assignments(m); - ctx.get_assignments(assignments); + expr_ref premise(ctx.mk_eq_atom(concat, str), m); + expr_ref c1(ctx.mk_eq_atom(a1, str), m); + expr_ref c2(ctx.mk_eq_atom(a2, str), m); + expr_ref conclusion(m.mk_and(c1, c2), m); + assert_implication(premise, conclusion); - for (expr_ref_vector::iterator it = assignments.begin(); it != assignments.end(); ++it) { - expr * argAst = *it; - // the original code jumped through some hoops to check whether the AST node - // is a function, then checked whether that function is "interesting". - // however, the only thing that's considered "interesting" is an equality predicate. - // so we bypass a huge amount of work by doing the following... - - if (m.is_eq(argAst)) { - TRACE("str", tout - << "eq ast " << mk_pp(argAst, m) << " is between args of sort " - << m.get_sort(to_app(argAst)->get_arg(0))->get_name() - << std::endl;); - classify_ast_by_type(argAst, varMap, concatMap, unrollMap); - } - } -} - -inline expr * theory_str::get_alias_index_ast(std::map & aliasIndexMap, expr * node) { - if (aliasIndexMap.find(node) != aliasIndexMap.end()) - return aliasIndexMap[node]; - else - return node; -} - -inline expr * theory_str::getMostLeftNodeInConcat(expr * node) { - app * aNode = to_app(node); - if (!u.str.is_concat(aNode)) { - return node; - } else { - expr * concatArgL = aNode->get_arg(0); - return getMostLeftNodeInConcat(concatArgL); - } -} - -inline expr * theory_str::getMostRightNodeInConcat(expr * node) { - app * aNode = to_app(node); - if (!u.str.is_concat(aNode)) { - return node; - } else { - expr * concatArgR = aNode->get_arg(1); - return getMostRightNodeInConcat(concatArgR); - } -} - -void theory_str::trace_ctx_dep(std::ofstream & tout, - std::map & aliasIndexMap, - std::map & var_eq_constStr_map, - std::map > & var_eq_concat_map, - std::map > & var_eq_unroll_map, - std::map & concat_eq_constStr_map, - std::map > & concat_eq_concat_map, - std::map > & unrollGroupMap) { -#ifdef _TRACE - context & ctx = get_context(); - ast_manager & mgr = get_manager(); - { - tout << "(0) alias: variables" << std::endl; - std::map > aliasSumMap; - std::map::iterator itor0 = aliasIndexMap.begin(); - for (; itor0 != aliasIndexMap.end(); itor0++) { - aliasSumMap[itor0->second][itor0->first] = 1; - } - std::map >::iterator keyItor = aliasSumMap.begin(); - for (; keyItor != aliasSumMap.end(); keyItor++) { - tout << " * "; - tout << mk_pp(keyItor->first, mgr); - tout << " : "; - std::map::iterator innerItor = keyItor->second.begin(); - for (; innerItor != keyItor->second.end(); innerItor++) { - tout << mk_pp(innerItor->first, mgr); - tout << ", "; + return; } - tout << std::endl; - } - tout << std::endl; - } - - { - tout << "(1) var = constStr:" << std::endl; - std::map::iterator itor1 = var_eq_constStr_map.begin(); - for (; itor1 != var_eq_constStr_map.end(); itor1++) { - tout << " * "; - tout << mk_pp(itor1->first, mgr); - tout << " = "; - tout << mk_pp(itor1->second, mgr); - if (!in_same_eqc(itor1->first, itor1->second)) { - tout << " (not true in ctx)"; - } - tout << std::endl; - } - tout << std::endl; - } - - { - tout << "(2) var = concat:" << std::endl; - std::map >::iterator itor2 = var_eq_concat_map.begin(); - for (; itor2 != var_eq_concat_map.end(); itor2++) { - tout << " * "; - tout << mk_pp(itor2->first, mgr); - tout << " = { "; - std::map::iterator i_itor = itor2->second.begin(); - for (; i_itor != itor2->second.end(); i_itor++) { - tout << mk_pp(i_itor->first, mgr); - tout << ", "; - } - tout << std::endl; - } - tout << std::endl; - } - - { - tout << "(3) var = unrollFunc:" << std::endl; - std::map >::iterator itor2 = var_eq_unroll_map.begin(); - for (; itor2 != var_eq_unroll_map.end(); itor2++) { - tout << " * " << mk_pp(itor2->first, mgr) << " = { "; - std::map::iterator i_itor = itor2->second.begin(); - for (; i_itor != itor2->second.end(); i_itor++) { - tout << mk_pp(i_itor->first, mgr) << ", "; - } - tout << " }" << std::endl; - } - tout << std::endl; - } - - { - tout << "(4) concat = constStr:" << std::endl; - std::map::iterator itor3 = concat_eq_constStr_map.begin(); - for (; itor3 != concat_eq_constStr_map.end(); itor3++) { - tout << " * "; - tout << mk_pp(itor3->first, mgr); - tout << " = "; - tout << mk_pp(itor3->second, mgr); - tout << std::endl; - - } - tout << std::endl; - } - - { - tout << "(5) eq concats:" << std::endl; - std::map >::iterator itor4 = concat_eq_concat_map.begin(); - for (; itor4 != concat_eq_concat_map.end(); itor4++) { - if (itor4->second.size() > 1) { - std::map::iterator i_itor = itor4->second.begin(); - tout << " * "; - for (; i_itor != itor4->second.end(); i_itor++) { - tout << mk_pp(i_itor->first, mgr); - tout << " , "; + bool arg1_has_eqc_value = false; + bool arg2_has_eqc_value = false; + expr * arg1 = get_eqc_value(a1, arg1_has_eqc_value); + expr * arg2 = get_eqc_value(a2, arg2_has_eqc_value); + expr_ref newConcat(m); + if (arg1 != a1 || arg2 != a2) { + TRACE("str", tout << "resolved concat argument(s) to eqc string constants" << std::endl;); + int iPos = 0; + expr_ref_vector item1(m); + if (a1 != arg1) { + item1.push_back(ctx.mk_eq_atom(a1, arg1)); + iPos += 1; } - tout << std::endl; - } - } - tout << std::endl; - } - - { - tout << "(6) eq unrolls:" << std::endl; - std::map >::iterator itor5 = unrollGroupMap.begin(); - for (; itor5 != unrollGroupMap.end(); itor5++) { - tout << " * "; - std::set::iterator i_itor = itor5->second.begin(); - for (; i_itor != itor5->second.end(); i_itor++) { - tout << mk_pp(*i_itor, mgr) << ", "; - } - tout << std::endl; - } - tout << std::endl; - } - - { - tout << "(7) unroll = concats:" << std::endl; - std::map >::iterator itor5 = unrollGroupMap.begin(); - for (; itor5 != unrollGroupMap.end(); itor5++) { - tout << " * "; - expr * unroll = itor5->first; - tout << mk_pp(unroll, mgr) << std::endl; - enode * e_curr = ctx.get_enode(unroll); - enode * e_curr_end = e_curr; - do { - app * curr = e_curr->get_owner(); - if (u.str.is_concat(curr)) { - tout << " >>> " << mk_pp(curr, mgr) << std::endl; + if (a2 != arg2) { + item1.push_back(ctx.mk_eq_atom(a2, arg2)); + iPos += 1; } - e_curr = e_curr->get_next(); - } while (e_curr != e_curr_end); - tout << std::endl; - } - tout << std::endl; - } -#else - return; -#endif // _TRACE -} - - -/* - * Dependence analysis from current context assignment - * - "freeVarMap" contains a set of variables that doesn't constrained by Concats. - * But it's possible that it's bounded by unrolls - * For the case of - * (1) var1 = unroll(r1, t1) - * var1 is in the freeVarMap - * > should unroll r1 for var1 - * (2) var1 = unroll(r1, t1) /\ var1 = Concat(var2, var3) - * var2, var3 are all in freeVar - * > should split the unroll function so that var2 and var3 are bounded by new unrolls - */ -int theory_str::ctx_dep_analysis(std::map & strVarMap, std::map & freeVarMap, - std::map > & unrollGroupMap, std::map > & var_eq_concat_map) { - std::map concatMap; - std::map unrollMap; - std::map aliasIndexMap; - std::map var_eq_constStr_map; - std::map concat_eq_constStr_map; - std::map > var_eq_unroll_map; - std::map > concat_eq_concat_map; - std::map > depMap; - - context & ctx = get_context(); - ast_manager & m = get_manager(); - - // note that the old API concatenated these assignments into - // a massive conjunction; we may have the opportunity to avoid that here - expr_ref_vector assignments(m); - ctx.get_assignments(assignments); - - // Step 1: get variables / concat AST appearing in the context - // the thing we iterate over should just be variable_set - internal_variable_set - // so we avoid computing the set difference (but this might be slower) - for(obj_hashtable::iterator it = variable_set.begin(); it != variable_set.end(); ++it) { - expr* var = *it; - if (internal_variable_set.find(var) == internal_variable_set.end()) { - TRACE("str", tout << "new variable: " << mk_pp(var, m) << std::endl;); - strVarMap[*it] = 1; - } - } - classify_ast_by_type_in_positive_context(strVarMap, concatMap, unrollMap); - - std::map aliasUnrollSet; - std::map::iterator unrollItor = unrollMap.begin(); - for (; unrollItor != unrollMap.end(); ++unrollItor) { - if (aliasUnrollSet.find(unrollItor->first) != aliasUnrollSet.end()) { - continue; - } - expr * aRoot = NULL; - enode * e_currEqc = ctx.get_enode(unrollItor->first); - enode * e_curr = e_currEqc; - do { - app * curr = e_currEqc->get_owner(); - if (u.re.is_unroll(curr)) { - if (aRoot == NULL) { - aRoot = curr; - } - aliasUnrollSet[curr] = aRoot; - } - e_currEqc = e_currEqc->get_next(); - } while (e_currEqc != e_curr); - } - - for (unrollItor = unrollMap.begin(); unrollItor != unrollMap.end(); unrollItor++) { - expr * unrFunc = unrollItor->first; - expr * urKey = aliasUnrollSet[unrFunc]; - unrollGroupMap[urKey].insert(unrFunc); - } - - // Step 2: collect alias relation - // e.g. suppose we have the equivalence class {x, y, z}; - // then we set aliasIndexMap[y] = x - // and aliasIndexMap[z] = x - - std::map::iterator varItor = strVarMap.begin(); - for (; varItor != strVarMap.end(); ++varItor) { - if (aliasIndexMap.find(varItor->first) != aliasIndexMap.end()) { - continue; - } - expr * aRoot = NULL; - expr * curr = varItor->first; - do { - if (variable_set.find(curr) != variable_set.end()) { - if (aRoot == NULL) { - aRoot = curr; - } else { - aliasIndexMap[curr] = aRoot; - } - } - curr = get_eqc_next(curr); - } while (curr != varItor->first); - } - - // Step 3: Collect interested cases - - varItor = strVarMap.begin(); - for (; varItor != strVarMap.end(); ++varItor) { - expr * deAliasNode = get_alias_index_ast(aliasIndexMap, varItor->first); - // Case 1: variable = string constant - // e.g. z = "str1" ::= var_eq_constStr_map[z] = "str1" - - if (var_eq_constStr_map.find(deAliasNode) == var_eq_constStr_map.end()) { - bool nodeHasEqcValue = false; - expr * nodeValue = get_eqc_value(deAliasNode, nodeHasEqcValue); - if (nodeHasEqcValue) { - var_eq_constStr_map[deAliasNode] = nodeValue; - } - } - - // Case 2: var_eq_concat - // e.g. z = concat("str1", b) ::= var_eq_concat[z][concat(c, "str2")] = 1 - // var_eq_unroll - // e.g. z = unroll(...) ::= var_eq_unroll[z][unroll(...)] = 1 - - if (var_eq_concat_map.find(deAliasNode) == var_eq_concat_map.end()) { - expr * curr = get_eqc_next(deAliasNode); - while (curr != deAliasNode) { - app * aCurr = to_app(curr); - // collect concat - if (u.str.is_concat(aCurr)) { - expr * arg0 = aCurr->get_arg(0); - expr * arg1 = aCurr->get_arg(1); - bool arg0HasEqcValue = false; - bool arg1HasEqcValue = false; - expr * arg0_value = get_eqc_value(arg0, arg0HasEqcValue); - expr * arg1_value = get_eqc_value(arg1, arg1HasEqcValue); - - bool is_arg0_emptyStr = false; - if (arg0HasEqcValue) { - zstring strval; - u.str.is_string(arg0_value, strval); - if (strval.empty()) { - is_arg0_emptyStr = true; - } - } - - bool is_arg1_emptyStr = false; - if (arg1HasEqcValue) { - zstring strval; - u.str.is_string(arg1_value, strval); - if (strval.empty()) { - is_arg1_emptyStr = true; - } - } - - if (!is_arg0_emptyStr && !is_arg1_emptyStr) { - var_eq_concat_map[deAliasNode][curr] = 1; - } - } else if (u.re.is_unroll(to_app(curr))) { - var_eq_unroll_map[deAliasNode][curr] = 1; - } - - curr = get_eqc_next(curr); - } - } - - } // for(varItor in strVarMap) - - // -------------------------------------------------- - // * collect aliasing relation among eq concats - // e.g EQC={concat1, concat2, concat3} - // concats_eq_Index_map[concat2] = concat1 - // concats_eq_Index_map[concat3] = concat1 - // -------------------------------------------------- - - std::map concats_eq_index_map; - std::map::iterator concatItor = concatMap.begin(); - for(; concatItor != concatMap.end(); ++concatItor) { - if (concats_eq_index_map.find(concatItor->first) != concats_eq_index_map.end()) { - continue; - } - expr * aRoot = NULL; - expr * curr = concatItor->first; - do { - if (u.str.is_concat(to_app(curr))) { - if (aRoot == NULL) { - aRoot = curr; - } else { - concats_eq_index_map[curr] = aRoot; - } - } - curr = get_eqc_next(curr); - } while (curr != concatItor->first); - } - - concatItor = concatMap.begin(); - for(; concatItor != concatMap.end(); ++concatItor) { - expr * deAliasConcat = NULL; - if (concats_eq_index_map.find(concatItor->first) != concats_eq_index_map.end()) { - deAliasConcat = concats_eq_index_map[concatItor->first]; - } else { - deAliasConcat = concatItor->first; - } - - // (3) concat_eq_conststr, e.g. concat(a,b) = "str1" - if (concat_eq_constStr_map.find(deAliasConcat) == concat_eq_constStr_map.end()) { - bool nodeHasEqcValue = false; - expr * nodeValue = get_eqc_value(deAliasConcat, nodeHasEqcValue); - if (nodeHasEqcValue) { - concat_eq_constStr_map[deAliasConcat] = nodeValue; - } - } - - // (4) concat_eq_concat, e.g. - // concat(a,b) = concat("str1", c) AND z = concat(a,b) AND z = concat(e,f) - if (concat_eq_concat_map.find(deAliasConcat) == concat_eq_concat_map.end()) { - expr * curr = deAliasConcat; - do { - if (u.str.is_concat(to_app(curr))) { - // curr cannot be reduced - if (concatMap.find(curr) != concatMap.end()) { - concat_eq_concat_map[deAliasConcat][curr] = 1; - } - } - curr = get_eqc_next(curr); - } while (curr != deAliasConcat); - } - } - - // print some debugging info - TRACE("str", trace_ctx_dep(tout, aliasIndexMap, var_eq_constStr_map, - var_eq_concat_map, var_eq_unroll_map, - concat_eq_constStr_map, concat_eq_concat_map, unrollGroupMap);); - - if (!contain_pair_bool_map.empty()) { - compute_contains(aliasIndexMap, concats_eq_index_map, var_eq_constStr_map, concat_eq_constStr_map, var_eq_concat_map); - } - - // step 4: dependence analysis - - // (1) var = string constant - for (std::map::iterator itor = var_eq_constStr_map.begin(); - itor != var_eq_constStr_map.end(); ++itor) { - expr * var = get_alias_index_ast(aliasIndexMap, itor->first); - expr * strAst = itor->second; - depMap[var][strAst] = 1; - } - - // (2) var = concat - for (std::map >::iterator itor = var_eq_concat_map.begin(); - itor != var_eq_concat_map.end(); ++itor) { - expr * var = get_alias_index_ast(aliasIndexMap, itor->first); - for (std::map::iterator itor1 = itor->second.begin(); itor1 != itor->second.end(); ++itor1) { - expr * concat = itor1->first; - std::map inVarMap; - std::map inConcatMap; - std::map inUnrollMap; - classify_ast_by_type(concat, inVarMap, inConcatMap, inUnrollMap); - for (std::map::iterator itor2 = inVarMap.begin(); itor2 != inVarMap.end(); ++itor2) { - expr * varInConcat = get_alias_index_ast(aliasIndexMap, itor2->first); - if (!(depMap[var].find(varInConcat) != depMap[var].end() && depMap[var][varInConcat] == 1)) { - depMap[var][varInConcat] = 2; - } - } - } - } - - for (std::map >::iterator itor = var_eq_unroll_map.begin(); - itor != var_eq_unroll_map.end(); itor++) { - expr * var = get_alias_index_ast(aliasIndexMap, itor->first); - for (std::map::iterator itor1 = itor->second.begin(); itor1 != itor->second.end(); itor1++) { - expr * unrollFunc = itor1->first; - std::map inVarMap; - std::map inConcatMap; - std::map inUnrollMap; - classify_ast_by_type(unrollFunc, inVarMap, inConcatMap, inUnrollMap); - for (std::map::iterator itor2 = inVarMap.begin(); itor2 != inVarMap.end(); itor2++) { - expr * varInFunc = get_alias_index_ast(aliasIndexMap, itor2->first); - - TRACE("str", tout << "var in unroll = " << - mk_ismt2_pp(itor2->first, m) << std::endl - << "dealiased var = " << mk_ismt2_pp(varInFunc, m) << std::endl;); - - // it's possible that we have both (Unroll $$_regVar_0 $$_unr_0) /\ (Unroll abcd $$_unr_0), - // while $$_regVar_0 = "abcd" - // have to exclude such cases - bool varHasValue = false; - get_eqc_value(varInFunc, varHasValue); - if (varHasValue) - continue; - - if (depMap[var].find(varInFunc) == depMap[var].end()) { - depMap[var][varInFunc] = 6; - } - } - } - } - - // (3) concat = string constant - for (std::map::iterator itor = concat_eq_constStr_map.begin(); - itor != concat_eq_constStr_map.end(); itor++) { - expr * concatAst = itor->first; - expr * constStr = itor->second; - std::map inVarMap; - std::map inConcatMap; - std::map inUnrollMap; - classify_ast_by_type(concatAst, inVarMap, inConcatMap, inUnrollMap); - for (std::map::iterator itor2 = inVarMap.begin(); itor2 != inVarMap.end(); itor2++) { - expr * varInConcat = get_alias_index_ast(aliasIndexMap, itor2->first); - if (!(depMap[varInConcat].find(constStr) != depMap[varInConcat].end() && depMap[varInConcat][constStr] == 1)) - depMap[varInConcat][constStr] = 3; - } - } - - // (4) equivalent concats - // - possibility 1 : concat("str", v1) = concat(concat(v2, v3), v4) = concat(v5, v6) - // ==> v2, v5 are constrained by "str" - // - possibility 2 : concat(v1, "str") = concat(v2, v3) = concat(v4, v5) - // ==> v2, v4 are constrained by "str" - //-------------------------------------------------------------- - - std::map mostLeftNodes; - std::map mostRightNodes; - - std::map mLIdxMap; - std::map > mLMap; - std::map mRIdxMap; - std::map > mRMap; - std::set nSet; - - for (std::map >::iterator itor = concat_eq_concat_map.begin(); - itor != concat_eq_concat_map.end(); itor++) { - mostLeftNodes.clear(); - mostRightNodes.clear(); - - expr * mLConst = NULL; - expr * mRConst = NULL; - - for (std::map::iterator itor1 = itor->second.begin(); itor1 != itor->second.end(); itor1++) { - expr * concatNode = itor1->first; - expr * mLNode = getMostLeftNodeInConcat(concatNode); - zstring strval; - if (u.str.is_string(to_app(mLNode), strval)) { - if (mLConst == NULL && strval.empty()) { - mLConst = mLNode; - } - } else { - mostLeftNodes[mLNode] = concatNode; - } - - expr * mRNode = getMostRightNodeInConcat(concatNode); - if (u.str.is_string(to_app(mRNode), strval)) { - if (mRConst == NULL && strval.empty()) { - mRConst = mRNode; - } - } else { - mostRightNodes[mRNode] = concatNode; - } - } - - if (mLConst != NULL) { - // ------------------------------------------------------------------------------------- - // The left most variable in a concat is constrained by a constant string in eqc concat - // ------------------------------------------------------------------------------------- - // e.g. Concat(x, ...) = Concat("abc", ...) - // ------------------------------------------------------------------------------------- - for (std::map::iterator itor1 = mostLeftNodes.begin(); - itor1 != mostLeftNodes.end(); itor1++) { - expr * deVar = get_alias_index_ast(aliasIndexMap, itor1->first); - if (depMap[deVar].find(mLConst) == depMap[deVar].end() || depMap[deVar][mLConst] != 1) { - depMap[deVar][mLConst] = 4; - } - } - } - - { - // ------------------------------------------------------------------------------------- - // The left most variables in eqc concats are constrained by each other - // ------------------------------------------------------------------------------------- - // e.g. concat(x, ...) = concat(u, ...) = ... - // x and u are constrained by each other - // ------------------------------------------------------------------------------------- - nSet.clear(); - std::map::iterator itl = mostLeftNodes.begin(); - for (; itl != mostLeftNodes.end(); itl++) { - bool lfHasEqcValue = false; - get_eqc_value(itl->first, lfHasEqcValue); - if (lfHasEqcValue) - continue; - expr * deVar = get_alias_index_ast(aliasIndexMap, itl->first); - nSet.insert(deVar); - } - - if (nSet.size() > 1) { - int lId = -1; - for (std::set::iterator itor2 = nSet.begin(); itor2 != nSet.end(); itor2++) { - if (mLIdxMap.find(*itor2) != mLIdxMap.end()) { - lId = mLIdxMap[*itor2]; - break; - } - } - if (lId == -1) - lId = mLMap.size(); - for (std::set::iterator itor2 = nSet.begin(); itor2 != nSet.end(); itor2++) { - bool itorHasEqcValue = false; - get_eqc_value(*itor2, itorHasEqcValue); - if (itorHasEqcValue) - continue; - mLIdxMap[*itor2] = lId; - mLMap[lId].insert(*itor2); - } - } - } - - if (mRConst != NULL) { - for (std::map::iterator itor1 = mostRightNodes.begin(); - itor1 != mostRightNodes.end(); itor1++) { - expr * deVar = get_alias_index_ast(aliasIndexMap, itor1->first); - if (depMap[deVar].find(mRConst) == depMap[deVar].end() || depMap[deVar][mRConst] != 1) { - depMap[deVar][mRConst] = 5; - } - } - } - - { - nSet.clear(); - std::map::iterator itr = mostRightNodes.begin(); - for (; itr != mostRightNodes.end(); itr++) { - expr * deVar = get_alias_index_ast(aliasIndexMap, itr->first); - nSet.insert(deVar); - } - if (nSet.size() > 1) { - int rId = -1; - std::set::iterator itor2 = nSet.begin(); - for (; itor2 != nSet.end(); itor2++) { - if (mRIdxMap.find(*itor2) != mRIdxMap.end()) { - rId = mRIdxMap[*itor2]; - break; - } - } - if (rId == -1) - rId = mRMap.size(); - for (itor2 = nSet.begin(); itor2 != nSet.end(); itor2++) { - bool rHasEqcValue = false; - get_eqc_value(*itor2, rHasEqcValue); - if (rHasEqcValue) - continue; - mRIdxMap[*itor2] = rId; - mRMap[rId].insert(*itor2); - } - } - } - } - - // print the dependence map - TRACE("str", - tout << "Dependence Map" << std::endl; - for(std::map >::iterator itor = depMap.begin(); itor != depMap.end(); itor++) { - tout << mk_pp(itor->first, m); - rational nnLen; - bool nnLen_exists = get_len_value(itor->first, nnLen); - tout << " [len = " << (nnLen_exists ? nnLen.to_string() : "?") << "] \t-->\t"; - for (std::map::iterator itor1 = itor->second.begin(); itor1 != itor->second.end(); itor1++) { - tout << mk_pp(itor1->first, m) << "(" << itor1->second << "), "; - } - tout << std::endl; - } - ); - - // step, errr, 5: compute free variables based on the dependence map - - // the case dependence map is empty, every var in VarMap is free - //--------------------------------------------------------------- - // remove L/R most var in eq concat since they are constrained with each other - std::map > lrConstrainedMap; - for (std::map >::iterator itor = mLMap.begin(); itor != mLMap.end(); itor++) { - for (std::set::iterator it1 = itor->second.begin(); it1 != itor->second.end(); it1++) { - std::set::iterator it2 = it1; - it2++; - for (; it2 != itor->second.end(); it2++) { - expr * n1 = *it1; - expr * n2 = *it2; - lrConstrainedMap[n1][n2] = 1; - lrConstrainedMap[n2][n1] = 1; - } - } - } - for (std::map >::iterator itor = mRMap.begin(); itor != mRMap.end(); itor++) { - for (std::set::iterator it1 = itor->second.begin(); it1 != itor->second.end(); it1++) { - std::set::iterator it2 = it1; - it2++; - for (; it2 != itor->second.end(); it2++) { - expr * n1 = *it1; - expr * n2 = *it2; - lrConstrainedMap[n1][n2] = 1; - lrConstrainedMap[n2][n1] = 1; - } - } - } - - if (depMap.size() == 0) { - std::map::iterator itor = strVarMap.begin(); - for (; itor != strVarMap.end(); itor++) { - expr * var = get_alias_index_ast(aliasIndexMap, itor->first); - if (lrConstrainedMap.find(var) == lrConstrainedMap.end()) { - freeVarMap[var] = 1; - } else { - int lrConstainted = 0; - std::map::iterator lrit = freeVarMap.begin(); - for (; lrit != freeVarMap.end(); lrit++) { - if (lrConstrainedMap[var].find(lrit->first) != lrConstrainedMap[var].end()) { - lrConstainted = 1; - break; - } - } - if (lrConstainted == 0) { - freeVarMap[var] = 1; - } - } - } - } else { - // if the keys in aliasIndexMap are not contained in keys in depMap, they are free - // e.g., x= y /\ x = z /\ t = "abc" - // aliasIndexMap[y]= x, aliasIndexMap[z] = x - // depMap t ~ "abc"(1) - // x should be free - std::map::iterator itor2 = strVarMap.begin(); - for (; itor2 != strVarMap.end(); itor2++) { - if (aliasIndexMap.find(itor2->first) != aliasIndexMap.end()) { - expr * var = aliasIndexMap[itor2->first]; - if (depMap.find(var) == depMap.end()) { - if (lrConstrainedMap.find(var) == lrConstrainedMap.end()) { - freeVarMap[var] = 1; - } else { - int lrConstainted = 0; - std::map::iterator lrit = freeVarMap.begin(); - for (; lrit != freeVarMap.end(); lrit++) { - if (lrConstrainedMap[var].find(lrit->first) != lrConstrainedMap[var].end()) { - lrConstainted = 1; - break; - } - } - if (lrConstainted == 0) { - freeVarMap[var] = 1; - } - } - } - } else if (aliasIndexMap.find(itor2->first) == aliasIndexMap.end()) { - // if a variable is not in aliasIndexMap and not in depMap, it's free - if (depMap.find(itor2->first) == depMap.end()) { - expr * var = itor2->first; - if (lrConstrainedMap.find(var) == lrConstrainedMap.end()) { - freeVarMap[var] = 1; - } else { - int lrConstainted = 0; - std::map::iterator lrit = freeVarMap.begin(); - for (; lrit != freeVarMap.end(); lrit++) { - if (lrConstrainedMap[var].find(lrit->first) != lrConstrainedMap[var].end()) { - lrConstainted = 1; - break; - } - } - if (lrConstainted == 0) { - freeVarMap[var] = 1; - } - } - } - } - } - - std::map >::iterator itor = depMap.begin(); - for (; itor != depMap.end(); itor++) { - for (std::map::iterator itor1 = itor->second.begin(); itor1 != itor->second.end(); itor1++) { - if (variable_set.find(itor1->first) != variable_set.end()) { // expr type = var - expr * var = get_alias_index_ast(aliasIndexMap, itor1->first); - // if a var is dep on itself and all dependence are type 2, it's a free variable - // e.g {y --> x(2), y(2), m --> m(2), n(2)} y,m are free - { - if (depMap.find(var) == depMap.end()) { - if (freeVarMap.find(var) == freeVarMap.end()) { - if (lrConstrainedMap.find(var) == lrConstrainedMap.end()) { - freeVarMap[var] = 1; - } else { - int lrConstainted = 0; - std::map::iterator lrit = freeVarMap.begin(); - for (; lrit != freeVarMap.end(); lrit++) { - if (lrConstrainedMap[var].find(lrit->first) != lrConstrainedMap[var].end()) { - lrConstainted = 1; - break; - } - } - if (lrConstainted == 0) { - freeVarMap[var] = 1; - } - } - - } else { - freeVarMap[var] = freeVarMap[var] + 1; - } - } - } - } - } - } - } - - return 0; -} - -// Check agreement between integer and string theories for the term a = (str.to-int S). -// Returns true if axioms were added, and false otherwise. -bool theory_str::finalcheck_str2int(app * a) { - bool axiomAdd = false; - context & ctx = get_context(); - ast_manager & m = get_manager(); - - expr * S = a->get_arg(0); - - // check integer theory - rational Ival; - bool Ival_exists = get_value(a, Ival); - if (Ival_exists) { - TRACE("str", tout << "integer theory assigns " << mk_pp(a, m) << " = " << Ival.to_string() << std::endl;); - // if that value is not -1, we can assert (str.to-int S) = Ival --> S = "Ival" - if (!Ival.is_minus_one()) { - zstring Ival_str(Ival.to_string().c_str()); - expr_ref premise(ctx.mk_eq_atom(a, m_autil.mk_numeral(Ival, true)), m); - expr_ref conclusion(ctx.mk_eq_atom(S, mk_string(Ival_str)), m); - expr_ref axiom(rewrite_implication(premise, conclusion), m); - if (!string_int_axioms.contains(axiom)) { - string_int_axioms.insert(axiom); - assert_axiom(axiom); - m_trail_stack.push(insert_obj_trail(string_int_axioms, axiom)); - axiomAdd = true; - } - } - } else { - TRACE("str", tout << "integer theory has no assignment for " << mk_pp(a, m) << std::endl;); - NOT_IMPLEMENTED_YET(); - } - - return axiomAdd; -} - -bool theory_str::finalcheck_int2str(app * a) { - bool axiomAdd = false; - context & ctx = get_context(); - ast_manager & m = get_manager(); - - expr * N = a->get_arg(0); - - // check string theory - bool Sval_expr_exists; - expr * Sval_expr = get_eqc_value(a, Sval_expr_exists); - if (Sval_expr_exists) { - zstring Sval; - u.str.is_string(Sval_expr, Sval); - TRACE("str", tout << "string theory assigns \"" << mk_pp(a, m) << " = " << Sval << "\n";); - // empty string --> integer value < 0 - if (Sval.empty()) { - // ignore this. we should already assert the axiom for what happens when the string is "" - } else { - // nonempty string --> convert to correct integer value, or disallow it - rational convertedRepresentation(0); - rational ten(10); - bool conversionOK = true; - for (unsigned i = 0; i < Sval.length(); ++i) { - char digit = (int)Sval[i]; - if (isdigit((int)digit)) { - std::string sDigit(1, digit); - int val = atoi(sDigit.c_str()); - convertedRepresentation = (ten * convertedRepresentation) + rational(val); - } else { - // not a digit, invalid - TRACE("str", tout << "str.to-int argument contains non-digit character '" << digit << "'" << std::endl;); - conversionOK = false; - break; - } - } - if (conversionOK) { - expr_ref premise(ctx.mk_eq_atom(a, mk_string(Sval)), m); - expr_ref conclusion(ctx.mk_eq_atom(N, m_autil.mk_numeral(convertedRepresentation, true)), m); - expr_ref axiom(rewrite_implication(premise, conclusion), m); - if (!string_int_axioms.contains(axiom)) { - string_int_axioms.insert(axiom); - assert_axiom(axiom); - m_trail_stack.push(insert_obj_trail(string_int_axioms, axiom)); - axiomAdd = true; + expr_ref implyL1(mk_and(item1), m); + newConcat = mk_concat(arg1, arg2); + if (newConcat != str) { + expr_ref implyR1(ctx.mk_eq_atom(concat, newConcat), m); + assert_implication(implyL1, implyR1); } } else { - expr_ref axiom(m.mk_not(ctx.mk_eq_atom(a, mk_string(Sval))), m); - // always assert this axiom because this is a conflict clause - assert_axiom(axiom); - axiomAdd = true; + newConcat = concat; } - } - } else { - TRACE("str", tout << "string theory has no assignment for " << mk_pp(a, m) << std::endl;); - NOT_IMPLEMENTED_YET(); - } - return axiomAdd; -} - -void theory_str::collect_var_concat(expr * node, std::set & varSet, std::set & concatSet) { - if (variable_set.find(node) != variable_set.end()) { - if (internal_lenTest_vars.find(node) == internal_lenTest_vars.end()) { - varSet.insert(node); - } - } - else if (is_app(node)) { - app * aNode = to_app(node); - if (u.str.is_length(aNode)) { - // Length - return; - } - if (u.str.is_concat(aNode)) { - expr * arg0 = aNode->get_arg(0); - expr * arg1 = aNode->get_arg(1); - if (concatSet.find(node) == concatSet.end()) { - concatSet.insert(node); + if (newConcat == str) { + return; } - } - // recursively visit all arguments - for (unsigned i = 0; i < aNode->get_num_args(); ++i) { - expr * arg = aNode->get_arg(i); - collect_var_concat(arg, varSet, concatSet); - } - } -} - -bool theory_str::propagate_length_within_eqc(expr * var) { - bool res = false; - ast_manager & m = get_manager(); - context & ctx = get_context(); - - TRACE("str", tout << "propagate_length_within_eqc: " << mk_ismt2_pp(var, m) << std::endl ;); - - enode * n_eq_enode = ctx.get_enode(var); - rational varLen; - if (! get_len_value(var, varLen)) { - bool hasLen = false; - expr * nodeWithLen= var; - do { - if (get_len_value(nodeWithLen, varLen)) { - hasLen = true; - break; + if (!u.str.is_concat(to_app(newConcat))) { + return; } - nodeWithLen = get_eqc_next(nodeWithLen); - } while (nodeWithLen != var); + if (arg1_has_eqc_value && arg2_has_eqc_value) { + // Case 1: Concat(const, const) == const + TRACE("str", tout << "Case 1: Concat(const, const) == const" << std::endl;); + zstring arg1_str, arg2_str; + u.str.is_string(arg1, arg1_str); + u.str.is_string(arg2, arg2_str); - if (hasLen) { - // var = nodeWithLen --> |var| = |nodeWithLen| - expr_ref_vector l_items(m); - expr_ref varEqNode(ctx.mk_eq_atom(var, nodeWithLen), m); - l_items.push_back(varEqNode); - - expr_ref nodeWithLenExpr (mk_strlen(nodeWithLen), m); - expr_ref varLenExpr (mk_int(varLen), m); - expr_ref lenEqNum(ctx.mk_eq_atom(nodeWithLenExpr, varLenExpr), m); - l_items.push_back(lenEqNum); - - expr_ref axl(m.mk_and(l_items.size(), l_items.c_ptr()), m); - expr_ref varLen(mk_strlen(var), m); - expr_ref axr(ctx.mk_eq_atom(varLen, mk_int(varLen)), m); - assert_implication(axl, axr); - TRACE("str", tout << mk_ismt2_pp(axl, m) << std::endl << " ---> " << std::endl << mk_ismt2_pp(axr, m);); - res = true; - } - } - return res; -} - -bool theory_str::propagate_length(std::set & varSet, std::set & concatSet, std::map & exprLenMap) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - expr_ref_vector assignments(m); - ctx.get_assignments(assignments); - bool axiomAdded = false; - // collect all concats in context - for (expr_ref_vector::iterator it = assignments.begin(); it != assignments.end(); ++it) { - if (! ctx.is_relevant(*it)) { - continue; - } - if (m.is_eq(*it)) { - collect_var_concat(*it, varSet, concatSet); - } - } - // iterate each concat - // if a concat doesn't have length info, check if the length of all leaf nodes can be resolved - for (std::set::iterator it = concatSet.begin(); it != concatSet.end(); it++) { - expr * concat = *it; - rational lenValue; - expr_ref concatlenExpr (mk_strlen(concat), m) ; - bool allLeafResolved = true; - if (! get_value(concatlenExpr, lenValue)) { - // the length fo concat is unresolved yet - if (get_len_value(concat, lenValue)) { - // but all leaf nodes have length information - TRACE("str", tout << "* length pop-up: " << mk_ismt2_pp(concat, m) << "| = " << lenValue << std::endl;); - std::set leafNodes; - get_unique_non_concat_nodes(concat, leafNodes); - expr_ref_vector l_items(m); - for (std::set::iterator leafIt = leafNodes.begin(); leafIt != leafNodes.end(); ++leafIt) { - rational leafLenValue; - if (get_len_value(*leafIt, leafLenValue)) { - expr_ref leafItLenExpr (mk_strlen(*leafIt), m); - expr_ref leafLenValueExpr (mk_int(leafLenValue), m); - expr_ref lcExpr (ctx.mk_eq_atom(leafItLenExpr, leafLenValueExpr), m); - l_items.push_back(lcExpr); + zstring result_str = arg1_str + arg2_str; + if (result_str != const_str) { + // Inconsistency + TRACE("str", tout << "inconsistency detected: \"" + << arg1_str << "\" + \"" << arg2_str << + "\" != \"" << const_str << "\"" << "\n";); + expr_ref equality(ctx.mk_eq_atom(concat, str), m); + expr_ref diseq(m.mk_not(equality), m); + assert_axiom(diseq); + return; + } + } else if (!arg1_has_eqc_value && arg2_has_eqc_value) { + // Case 2: Concat(var, const) == const + TRACE("str", tout << "Case 2: Concat(var, const) == const" << std::endl;); + zstring arg2_str; + u.str.is_string(arg2, arg2_str); + unsigned int resultStrLen = const_str.length(); + unsigned int arg2StrLen = arg2_str.length(); + if (resultStrLen < arg2StrLen) { + // Inconsistency + TRACE("str", tout << "inconsistency detected: \"" + << arg2_str << + "\" is longer than \"" << const_str << "\"," + << " so cannot be concatenated with anything to form it" << "\n";); + expr_ref equality(ctx.mk_eq_atom(newConcat, str), m); + expr_ref diseq(m.mk_not(equality), m); + assert_axiom(diseq); + return; + } else { + int varStrLen = resultStrLen - arg2StrLen; + zstring firstPart = const_str.extract(0, varStrLen); + zstring secondPart = const_str.extract(varStrLen, arg2StrLen); + if (arg2_str != secondPart) { + // Inconsistency + TRACE("str", tout << "inconsistency detected: " + << "suffix of concatenation result expected \"" << secondPart << "\", " + << "actually \"" << arg2_str << "\"" + << "\n";); + expr_ref equality(ctx.mk_eq_atom(newConcat, str), m); + expr_ref diseq(m.mk_not(equality), m); + assert_axiom(diseq); + return; } else { - allLeafResolved = false; - break; + expr_ref tmpStrConst(mk_string(firstPart), m); + expr_ref premise(ctx.mk_eq_atom(newConcat, str), m); + expr_ref conclusion(ctx.mk_eq_atom(arg1, tmpStrConst), m); + assert_implication(premise, conclusion); + return; } } - if (allLeafResolved) { - expr_ref axl(m.mk_and(l_items.size(), l_items.c_ptr()), m); - expr_ref lenValueExpr (mk_int(lenValue), m); - expr_ref axr(ctx.mk_eq_atom(concatlenExpr, lenValueExpr), m); - assert_implication(axl, axr); - TRACE("str", tout << mk_ismt2_pp(axl, m) << std::endl << " ---> " << std::endl << mk_ismt2_pp(axr, m)<< std::endl;); - axiomAdded = true; + } else if (arg1_has_eqc_value && !arg2_has_eqc_value) { + // Case 3: Concat(const, var) == const + TRACE("str", tout << "Case 3: Concat(const, var) == const" << std::endl;); + zstring arg1_str; + u.str.is_string(arg1, arg1_str); + unsigned int resultStrLen = const_str.length(); + unsigned int arg1StrLen = arg1_str.length(); + if (resultStrLen < arg1StrLen) { + // Inconsistency + TRACE("str", tout << "inconsistency detected: \"" + << arg1_str << + "\" is longer than \"" << const_str << "\"," + << " so cannot be concatenated with anything to form it" << "\n";); + expr_ref equality(ctx.mk_eq_atom(newConcat, str), m); + expr_ref diseq(m.mk_not(equality), m); + assert_axiom(diseq); + return; + } else { + int varStrLen = resultStrLen - arg1StrLen; + zstring firstPart = const_str.extract(0, arg1StrLen); + zstring secondPart = const_str.extract(arg1StrLen, varStrLen); + if (arg1_str != firstPart) { + // Inconsistency + TRACE("str", tout << "inconsistency detected: " + << "prefix of concatenation result expected \"" << secondPart << "\", " + << "actually \"" << arg1_str << "\"" + << "\n";); + expr_ref equality(ctx.mk_eq_atom(newConcat, str), m); + expr_ref diseq(m.mk_not(equality), m); + assert_axiom(diseq); + return; + } else { + expr_ref tmpStrConst(mk_string(secondPart), m); + expr_ref premise(ctx.mk_eq_atom(newConcat, str), m); + expr_ref conclusion(ctx.mk_eq_atom(arg2, tmpStrConst), m); + assert_implication(premise, conclusion); + return; + } } - } - } - } - // if no concat length is propagated, check the length of variables. - if (! axiomAdded) { - for (std::set::iterator it = varSet.begin(); it != varSet.end(); it++) { - expr * var = *it; - rational lenValue; - expr_ref varlen (mk_strlen(var), m) ; - bool allLeafResolved = true; - if (! get_value(varlen, lenValue)) { - if (propagate_length_within_eqc(var)) { - axiomAdded = true; - } - } - } - - } - return axiomAdded; -} - -void theory_str::get_unique_non_concat_nodes(expr * node, std::set & argSet) { - app * a_node = to_app(node); - if (!u.str.is_concat(a_node)) { - argSet.insert(node); - return; - } else { - SASSERT(a_node->get_num_args() == 2); - expr * leftArg = a_node->get_arg(0); - expr * rightArg = a_node->get_arg(1); - get_unique_non_concat_nodes(leftArg, argSet); - get_unique_non_concat_nodes(rightArg, argSet); - } -} - -final_check_status theory_str::final_check_eh() { - context & ctx = get_context(); - ast_manager & m = get_manager(); - - expr_ref_vector assignments(m); - ctx.get_assignments(assignments); - - if (opt_VerifyFinalCheckProgress) { - finalCheckProgressIndicator = false; - } - - TRACE("str", tout << "final check" << std::endl;); - TRACE_CODE(if (is_trace_enabled("t_str_dump_assign")) { dump_assignments(); }); - check_variable_scope(); - - if (opt_DeferEQCConsistencyCheck) { - TRACE("str", tout << "performing deferred EQC consistency check" << std::endl;); - std::set eqc_roots; - for (ptr_vector::const_iterator it = ctx.begin_enodes(); it != ctx.end_enodes(); ++it) { - enode * e = *it; - enode * root = e->get_root(); - eqc_roots.insert(root); - } - - bool found_inconsistency = false; - - for (std::set::iterator it = eqc_roots.begin(); it != eqc_roots.end(); ++it) { - enode * e = *it; - app * a = e->get_owner(); - if (!(m.get_sort(a) == u.str.mk_string_sort())) { - TRACE("str", tout << "EQC root " << mk_pp(a, m) << " not a string term; skipping" << std::endl;); } else { - TRACE("str", tout << "EQC root " << mk_pp(a, m) << " is a string term. Checking this EQC" << std::endl;); - // first call check_concat_len_in_eqc() on each member of the eqc - enode * e_it = e; - enode * e_root = e_it; - do { - bool status = check_concat_len_in_eqc(e_it->get_owner()); - if (!status) { - TRACE("str", tout << "concat-len check asserted an axiom on " << mk_pp(e_it->get_owner(), m) << std::endl;); - found_inconsistency = true; - } - e_it = e_it->get_next(); - } while (e_it != e_root); + // Case 4: Concat(var, var) == const + TRACE("str", tout << "Case 4: Concat(var, var) == const" << std::endl;); + if (eval_concat(arg1, arg2) == NULL) { + rational arg1Len, arg2Len; + bool arg1Len_exists = get_len_value(arg1, arg1Len); + bool arg2Len_exists = get_len_value(arg2, arg2Len); + rational concatStrLen((unsigned)const_str.length()); + if (arg1Len_exists || arg2Len_exists) { + expr_ref ax_l1(ctx.mk_eq_atom(concat, str), m); + expr_ref ax_l2(m); + zstring prefixStr, suffixStr; + if (arg1Len_exists) { + if (arg1Len.is_neg()) { + TRACE("str", tout << "length conflict: arg1Len = " << arg1Len << ", concatStrLen = " << concatStrLen << std::endl;); + expr_ref toAssert(m_autil.mk_ge(mk_strlen(arg1), mk_int(0)), m); + assert_axiom(toAssert); + return; + } else if (arg1Len > concatStrLen) { + TRACE("str", tout << "length conflict: arg1Len = " << arg1Len << ", concatStrLen = " << concatStrLen << std::endl;); + expr_ref ax_r1(m_autil.mk_le(mk_strlen(arg1), mk_int(concatStrLen)), m); + assert_implication(ax_l1, ax_r1); + return; + } - // now grab any two distinct elements from the EQC and call new_eq_check() on them - enode * e1 = e; - enode * e2 = e1->get_next(); - if (e1 != e2) { - TRACE("str", tout << "deferred new_eq_check() over EQC of " << mk_pp(e1->get_owner(), m) << " and " << mk_pp(e2->get_owner(), m) << std::endl;); - bool result = new_eq_check(e1->get_owner(), e2->get_owner()); - if (!result) { - TRACE("str", tout << "new_eq_check found inconsistencies" << std::endl;); - found_inconsistency = true; - } - } + prefixStr = const_str.extract(0, arg1Len.get_unsigned()); + rational concat_minus_arg1 = concatStrLen - arg1Len; + suffixStr = const_str.extract(arg1Len.get_unsigned(), concat_minus_arg1.get_unsigned()); + ax_l2 = ctx.mk_eq_atom(mk_strlen(arg1), mk_int(arg1Len)); + } else { + // arg2's length is available + if (arg2Len.is_neg()) { + TRACE("str", tout << "length conflict: arg2Len = " << arg2Len << ", concatStrLen = " << concatStrLen << std::endl;); + expr_ref toAssert(m_autil.mk_ge(mk_strlen(arg2), mk_int(0)), m); + assert_axiom(toAssert); + return; + } else if (arg2Len > concatStrLen) { + TRACE("str", tout << "length conflict: arg2Len = " << arg2Len << ", concatStrLen = " << concatStrLen << std::endl;); + expr_ref ax_r1(m_autil.mk_le(mk_strlen(arg2), mk_int(concatStrLen)), m); + assert_implication(ax_l1, ax_r1); + return; + } + + rational concat_minus_arg2 = concatStrLen - arg2Len; + prefixStr = const_str.extract(0, concat_minus_arg2.get_unsigned()); + suffixStr = const_str.extract(concat_minus_arg2.get_unsigned(), arg2Len.get_unsigned()); + ax_l2 = ctx.mk_eq_atom(mk_strlen(arg2), mk_int(arg2Len)); + } + // consistency check + if (u.str.is_concat(to_app(arg1)) && !can_concat_eq_str(arg1, prefixStr)) { + expr_ref ax_r(m.mk_not(ax_l2), m); + assert_implication(ax_l1, ax_r); + return; + } + if (u.str.is_concat(to_app(arg2)) && !can_concat_eq_str(arg2, suffixStr)) { + expr_ref ax_r(m.mk_not(ax_l2), m); + assert_implication(ax_l1, ax_r); + return; + } + expr_ref_vector r_items(m); + r_items.push_back(ctx.mk_eq_atom(arg1, mk_string(prefixStr))); + r_items.push_back(ctx.mk_eq_atom(arg2, mk_string(suffixStr))); + if (!arg1Len_exists) { + r_items.push_back(ctx.mk_eq_atom(mk_strlen(arg1), mk_int(prefixStr.length()))); + } + if (!arg2Len_exists) { + r_items.push_back(ctx.mk_eq_atom(mk_strlen(arg2), mk_int(suffixStr.length()))); + } + expr_ref lhs(m.mk_and(ax_l1, ax_l2), m); + expr_ref rhs(mk_and(r_items), m); + assert_implication(lhs, rhs); + } else { /* ! (arg1Len != 1 || arg2Len != 1) */ + expr_ref xorFlag(m); + std::pair key1(arg1, arg2); + std::pair key2(arg2, arg1); + + // check the entries in this map to make sure they're still in scope + // before we use them. + + std::map, std::map >::iterator entry1 = varForBreakConcat.find(key1); + std::map, std::map >::iterator entry2 = varForBreakConcat.find(key2); + + bool entry1InScope; + if (entry1 == varForBreakConcat.end()) { + TRACE("str", tout << "key1 no entry" << std::endl;); + entry1InScope = false; + } else { + // OVERRIDE. + entry1InScope = true; + TRACE("str", tout << "key1 entry" << std::endl;); + /* + if (internal_variable_set.find((entry1->second)[0]) == internal_variable_set.end()) { + TRACE("str", tout << "key1 entry not in scope" << std::endl;); + entry1InScope = false; + } else { + TRACE("str", tout << "key1 entry in scope" << std::endl;); + entry1InScope = true; + } + */ + } + + bool entry2InScope; + if (entry2 == varForBreakConcat.end()) { + TRACE("str", tout << "key2 no entry" << std::endl;); + entry2InScope = false; + } else { + // OVERRIDE. + entry2InScope = true; + TRACE("str", tout << "key2 entry" << std::endl;); + /* + if (internal_variable_set.find((entry2->second)[0]) == internal_variable_set.end()) { + TRACE("str", tout << "key2 entry not in scope" << std::endl;); + entry2InScope = false; + } else { + TRACE("str", tout << "key2 entry in scope" << std::endl;); + entry2InScope = true; + } + */ + } + + TRACE("str", tout << "entry 1 " << (entry1InScope ? "in scope" : "not in scope") << std::endl + << "entry 2 " << (entry2InScope ? "in scope" : "not in scope") << std::endl;); + + if (!entry1InScope && !entry2InScope) { + xorFlag = mk_internal_xor_var(); + varForBreakConcat[key1][0] = xorFlag; + } else if (entry1InScope) { + xorFlag = varForBreakConcat[key1][0]; + } else { // entry2InScope + xorFlag = varForBreakConcat[key2][0]; + } + + int concatStrLen = const_str.length(); + int and_count = 1; + + expr_ref_vector arrangement_disjunction(m); + + for (int i = 0; i < concatStrLen + 1; ++i) { + expr_ref_vector and_items(m); + zstring prefixStr = const_str.extract(0, i); + zstring suffixStr = const_str.extract(i, concatStrLen - i); + // skip invalid options + if (u.str.is_concat(to_app(arg1)) && !can_concat_eq_str(arg1, prefixStr)) { + continue; + } + if (u.str.is_concat(to_app(arg2)) && !can_concat_eq_str(arg2, suffixStr)) { + continue; + } + + expr_ref prefixAst(mk_string(prefixStr), m); + expr_ref arg1_eq (ctx.mk_eq_atom(arg1, prefixAst), m); + and_items.push_back(arg1_eq); + and_count += 1; + + expr_ref suffixAst(mk_string(suffixStr), m); + expr_ref arg2_eq (ctx.mk_eq_atom(arg2, suffixAst), m); + and_items.push_back(arg2_eq); + and_count += 1; + + arrangement_disjunction.push_back(mk_and(and_items)); + } + + expr_ref implyL(ctx.mk_eq_atom(concat, str), m); + expr_ref implyR1(m); + if (arrangement_disjunction.empty()) { + // negate + expr_ref concat_eq_str(ctx.mk_eq_atom(concat, str), m); + expr_ref negate_ast(m.mk_not(concat_eq_str), m); + assert_axiom(negate_ast); + } else { + implyR1 = mk_or(arrangement_disjunction); + if (m_params.m_StrongArrangements) { + expr_ref ax_strong(ctx.mk_eq_atom(implyL, implyR1), m); + assert_axiom(ax_strong); + } else { + assert_implication(implyL, implyR1); + } + generate_mutual_exclusion(arrangement_disjunction); + } + } /* (arg1Len != 1 || arg2Len != 1) */ + } /* if (Concat(arg1, arg2) == NULL) */ } } - - if (found_inconsistency) { - TRACE("str", tout << "Found inconsistency in final check! Returning to search." << std::endl;); - return FC_CONTINUE; - } else { - TRACE("str", tout << "Deferred consistency check passed. Continuing in final check." << std::endl;); - } } - // run dependence analysis to find free string variables - std::map varAppearInAssign; - std::map freeVar_map; - std::map > unrollGroup_map; - std::map > var_eq_concat_map; - int conflictInDep = ctx_dep_analysis(varAppearInAssign, freeVar_map, unrollGroup_map, var_eq_concat_map); - if (conflictInDep == -1) { - // return Z3_TRUE; - return FC_DONE; - } - - // enhancement: improved backpropagation of string constants into var=concat terms - bool backpropagation_occurred = false; - for (std::map >::iterator veqc_map_it = var_eq_concat_map.begin(); - veqc_map_it != var_eq_concat_map.end(); ++veqc_map_it) { - expr * var = veqc_map_it->first; - for (std::map::iterator concat_map_it = veqc_map_it->second.begin(); - concat_map_it != veqc_map_it->second.end(); ++concat_map_it) { - app * concat = to_app(concat_map_it->first); - expr * concat_lhs = concat->get_arg(0); - expr * concat_rhs = concat->get_arg(1); - // If the concat LHS and RHS both have a string constant in their EQC, - // but the var does not, then we assert an axiom of the form - // (lhs = "lhs" AND rhs = "rhs") --> (Concat lhs rhs) = "lhsrhs" - bool concat_lhs_haseqc, concat_rhs_haseqc, var_haseqc; - expr * concat_lhs_str = get_eqc_value(concat_lhs, concat_lhs_haseqc); - expr * concat_rhs_str = get_eqc_value(concat_rhs, concat_rhs_haseqc); - expr * var_str = get_eqc_value(var, var_haseqc); - if (concat_lhs_haseqc && concat_rhs_haseqc && !var_haseqc) { - TRACE("str", tout << "backpropagate into " << mk_pp(var, m) << " = " << mk_pp(concat, m) << std::endl - << "LHS ~= " << mk_pp(concat_lhs_str, m) << " RHS ~= " << mk_pp(concat_rhs_str, m) << std::endl;); - zstring lhsString, rhsString; - u.str.is_string(concat_lhs_str, lhsString); - u.str.is_string(concat_rhs_str, rhsString); - zstring concatString = lhsString + rhsString; - expr_ref lhs1(ctx.mk_eq_atom(concat_lhs, concat_lhs_str), m); - expr_ref lhs2(ctx.mk_eq_atom(concat_rhs, concat_rhs_str), m); - expr_ref lhs(m.mk_and(lhs1, lhs2), m); - expr_ref rhs(ctx.mk_eq_atom(concat, mk_string(concatString)), m); - assert_implication(lhs, rhs); - backpropagation_occurred = true; - } - } - } - - if (backpropagation_occurred) { - TRACE("str", tout << "Resuming search due to axioms added by backpropagation." << std::endl;); - return FC_CONTINUE; - } - - // enhancement: improved backpropagation of length information - { - std::set varSet; - std::set concatSet; - std::map exprLenMap; - - bool length_propagation_occurred = propagate_length(varSet, concatSet, exprLenMap); - if (length_propagation_occurred) { - TRACE("str", tout << "Resuming search due to axioms added by length propagation." << std::endl;); - return FC_CONTINUE; - } - } - - bool needToAssignFreeVars = false; - std::set free_variables; - std::set unused_internal_variables; - { // Z3str2 free variables check - std::map::iterator itor = varAppearInAssign.begin(); - for (; itor != varAppearInAssign.end(); ++itor) { - /* - std::string vName = std::string(Z3_ast_to_string(ctx, itor->first)); - if (vName.length() >= 3 && vName.substr(0, 3) == "$$_") - continue; - */ - if (internal_variable_set.find(itor->first) != internal_variable_set.end() - || regex_variable_set.find(itor->first) != regex_variable_set.end()) { - // this can be ignored, I think - TRACE("str", tout << "free internal variable " << mk_pp(itor->first, m) << " ignored" << std::endl;); - continue; - } - bool hasEqcValue = false; - expr * eqcString = get_eqc_value(itor->first, hasEqcValue); - if (!hasEqcValue) { - TRACE("str", tout << "found free variable " << mk_pp(itor->first, m) << std::endl;); - needToAssignFreeVars = true; - free_variables.insert(itor->first); - // break; - } else { - // debug - TRACE("str", tout << "variable " << mk_pp(itor->first, m) << " = " << mk_pp(eqcString, m) << std::endl;); - } - } - } - - if (!needToAssignFreeVars) { - - // check string-int terms - bool addedStrIntAxioms = false; - for (unsigned i = 0; i < string_int_conversion_terms.size(); ++i) { - app * ex = to_app(string_int_conversion_terms[i].get()); - if (u.str.is_stoi(ex)) { - bool axiomAdd = finalcheck_str2int(ex); - if (axiomAdd) { - addedStrIntAxioms = true; - } - } else if (u.str.is_itos(ex)) { - bool axiomAdd = finalcheck_int2str(ex); - if (axiomAdd) { - addedStrIntAxioms = true; - } - } else { - UNREACHABLE(); - } - } - if (addedStrIntAxioms) { - TRACE("str", tout << "Resuming search due to addition of string-integer conversion axioms." << std::endl;); - return FC_CONTINUE; - } - - if (unused_internal_variables.empty()) { - TRACE("str", tout << "All variables are assigned. Done!" << std::endl;); - return FC_DONE; - } else { - TRACE("str", tout << "Assigning decoy values to free internal variables." << std::endl;); - for (std::set::iterator it = unused_internal_variables.begin(); it != unused_internal_variables.end(); ++it) { - expr * var = *it; - expr_ref assignment(m.mk_eq(var, mk_string("**unused**")), m); - assert_axiom(assignment); - } - return FC_CONTINUE; - } - } - - CTRACE("str", needToAssignFreeVars, - tout << "Need to assign values to the following free variables:" << std::endl; - for (std::set::iterator itx = free_variables.begin(); itx != free_variables.end(); ++itx) { - tout << mk_ismt2_pp(*itx, m) << std::endl; - } - tout << "freeVar_map has the following entries:" << std::endl; - for (std::map::iterator fvIt = freeVar_map.begin(); fvIt != freeVar_map.end(); fvIt++) { - expr * var = fvIt->first; - tout << mk_ismt2_pp(var, m) << std::endl; - } - ); - - // ----------------------------------------------------------- - // variables in freeVar are those not bounded by Concats - // classify variables in freeVarMap: - // (1) freeVar = unroll(r1, t1) - // (2) vars are not bounded by either concat or unroll - // ----------------------------------------------------------- - std::map > fv_unrolls_map; - std::set tmpSet; - expr * constValue = NULL; - for (std::map::iterator fvIt2 = freeVar_map.begin(); fvIt2 != freeVar_map.end(); fvIt2++) { - expr * var = fvIt2->first; - tmpSet.clear(); - get_eqc_allUnroll(var, constValue, tmpSet); - if (tmpSet.size() > 0) { - fv_unrolls_map[var] = tmpSet; - } - } - // erase var bounded by an unroll function from freeVar_map - for (std::map >::iterator fvIt3 = fv_unrolls_map.begin(); - fvIt3 != fv_unrolls_map.end(); fvIt3++) { - expr * var = fvIt3->first; - TRACE("str", tout << "erase free variable " << mk_pp(var, m) << " from freeVar_map, it is bounded by an Unroll" << std::endl;); - freeVar_map.erase(var); - } - - // collect the case: - // * Concat(X, Y) = unroll(r1, t1) /\ Concat(X, Y) = unroll(r2, t2) - // concatEqUnrollsMap[Concat(X, Y)] = {unroll(r1, t1), unroll(r2, t2)} - - std::map > concatEqUnrollsMap; - for (std::map >::iterator urItor = unrollGroup_map.begin(); - urItor != unrollGroup_map.end(); urItor++) { - expr * unroll = urItor->first; - expr * curr = unroll; - do { - if (u.str.is_concat(to_app(curr))) { - concatEqUnrollsMap[curr].insert(unroll); - concatEqUnrollsMap[curr].insert(unrollGroup_map[unroll].begin(), unrollGroup_map[unroll].end()); - } - enode * e_curr = ctx.get_enode(curr); - curr = e_curr->get_next()->get_owner(); - // curr = get_eqc_next(curr); - } while (curr != unroll); - } - - std::map > concatFreeArgsEqUnrollsMap; - std::set fvUnrollSet; - for (std::map >::iterator concatItor = concatEqUnrollsMap.begin(); - concatItor != concatEqUnrollsMap.end(); concatItor++) { - expr * concat = concatItor->first; - expr * concatArg1 = to_app(concat)->get_arg(0); - expr * concatArg2 = to_app(concat)->get_arg(1); - bool arg1Bounded = false; - bool arg2Bounded = false; - // arg1 - if (variable_set.find(concatArg1) != variable_set.end()) { - if (freeVar_map.find(concatArg1) == freeVar_map.end()) { - arg1Bounded = true; - } else { - fvUnrollSet.insert(concatArg1); - } - } else if (u.str.is_concat(to_app(concatArg1))) { - if (concatEqUnrollsMap.find(concatArg1) == concatEqUnrollsMap.end()) { - arg1Bounded = true; - } - } - // arg2 - if (variable_set.find(concatArg2) != variable_set.end()) { - if (freeVar_map.find(concatArg2) == freeVar_map.end()) { - arg2Bounded = true; - } else { - fvUnrollSet.insert(concatArg2); - } - } else if (u.str.is_concat(to_app(concatArg2))) { - if (concatEqUnrollsMap.find(concatArg2) == concatEqUnrollsMap.end()) { - arg2Bounded = true; - } - } - if (!arg1Bounded && !arg2Bounded) { - concatFreeArgsEqUnrollsMap[concat].insert( - concatEqUnrollsMap[concat].begin(), - concatEqUnrollsMap[concat].end()); - } - } - for (std::set::iterator vItor = fvUnrollSet.begin(); vItor != fvUnrollSet.end(); vItor++) { - TRACE("str", tout << "remove " << mk_pp(*vItor, m) << " from freeVar_map" << std::endl;); - freeVar_map.erase(*vItor); - } - - // Assign free variables - std::set fSimpUnroll; - - constValue = NULL; - - { - TRACE("str", tout << "free var map (#" << freeVar_map.size() << "):" << std::endl; - for (std::map::iterator freeVarItor1 = freeVar_map.begin(); freeVarItor1 != freeVar_map.end(); freeVarItor1++) { - expr * freeVar = freeVarItor1->first; - rational lenValue; - bool lenValue_exists = get_len_value(freeVar, lenValue); - tout << mk_pp(freeVar, m) << " [depCnt = " << freeVarItor1->second << ", length = " - << (lenValue_exists ? lenValue.to_string() : "?") - << "]" << std::endl; - } - ); - } - - for (std::map >::iterator fvIt2 = concatFreeArgsEqUnrollsMap.begin(); - fvIt2 != concatFreeArgsEqUnrollsMap.end(); fvIt2++) { - expr * concat = fvIt2->first; - for (std::set::iterator urItor = fvIt2->second.begin(); urItor != fvIt2->second.end(); urItor++) { - expr * unroll = *urItor; - process_concat_eq_unroll(concat, unroll); - } - } - - // -------- - // experimental free variable assignment - begin - // * special handling for variables that are not used in concat - // -------- - bool testAssign = true; - if (!testAssign) { - for (std::map::iterator fvIt = freeVar_map.begin(); fvIt != freeVar_map.end(); fvIt++) { - expr * freeVar = fvIt->first; - /* - std::string vName = std::string(Z3_ast_to_string(ctx, freeVar)); - if (vName.length() >= 9 && vName.substr(0, 9) == "$$_regVar") { - continue; - } - */ - expr * toAssert = gen_len_val_options_for_free_var(freeVar, NULL, ""); - if (toAssert != NULL) { - assert_axiom(toAssert); - } - } - } else { - process_free_var(freeVar_map); - } - // experimental free variable assignment - end - - // now deal with removed free variables that are bounded by an unroll - TRACE("str", tout << "fv_unrolls_map (#" << fv_unrolls_map.size() << "):" << std::endl;); - for (std::map >::iterator fvIt1 = fv_unrolls_map.begin(); - fvIt1 != fv_unrolls_map.end(); fvIt1++) { - expr * var = fvIt1->first; - fSimpUnroll.clear(); - get_eqc_simpleUnroll(var, constValue, fSimpUnroll); - if (fSimpUnroll.size() == 0) { - gen_assign_unroll_reg(fv_unrolls_map[var]); - } else { - expr * toAssert = gen_assign_unroll_Str2Reg(var, fSimpUnroll); - if (toAssert != NULL) { - assert_axiom(toAssert); - } - } - } - - if (opt_VerifyFinalCheckProgress && !finalCheckProgressIndicator) { - TRACE("str", tout << "BUG: no progress in final check, giving up!!" << std::endl;); - m.raise_exception("no progress in theory_str final check"); - } - - return FC_CONTINUE; // since by this point we've added axioms -} - -inline zstring int_to_string(int i) { - std::stringstream ss; - ss << i; - std::string str = ss.str(); - return zstring(str.c_str()); -} - -inline std::string longlong_to_string(long long i) { - std::stringstream ss; - ss << i; - return ss.str(); -} - -void theory_str::print_value_tester_list(svector > & testerList) { - ast_manager & m = get_manager(); - TRACE("str", - int ss = testerList.size(); - tout << "valueTesterList = {"; - for (int i = 0; i < ss; ++i) { - if (i % 4 == 0) { - tout << std::endl; - } - tout << "(" << testerList[i].first << ", "; - tout << mk_ismt2_pp(testerList[i].second, m); - tout << "), "; - } - tout << std::endl << "}" << std::endl; - ); -} - -zstring theory_str::gen_val_string(int len, int_vector & encoding) { - SASSERT(charSetSize > 0); - SASSERT(char_set != NULL); - - std::string re(len, char_set[0]); - for (int i = 0; i < (int) encoding.size() - 1; i++) { - int idx = encoding[i]; - re[len - 1 - i] = char_set[idx]; - } - return zstring(re.c_str()); -} - -/* - * The return value indicates whether we covered the search space. - * - If the next encoding is valid, return false - * - Otherwise, return true - */ -bool theory_str::get_next_val_encode(int_vector & base, int_vector & next) { - SASSERT(charSetSize > 0); - - TRACE("str", tout << "base vector: [ "; - for (unsigned i = 0; i < base.size(); ++i) { - tout << base[i] << " "; - } - tout << "]" << std::endl; - ); - - int s = 0; - int carry = 0; - next.reset(); - - for (int i = 0; i < (int) base.size(); i++) { - if (i == 0) { - s = base[i] + 1; - carry = s / charSetSize; - s = s % charSetSize; - next.push_back(s); - } else { - s = base[i] + carry; - carry = s / charSetSize; - s = s % charSetSize; - next.push_back(s); - } - } - if (next[next.size() - 1] > 0) { - next.reset(); - return true; - } else { - return false; - } -} - -expr * theory_str::gen_val_options(expr * freeVar, expr * len_indicator, expr * val_indicator, - zstring lenStr, int tries) { - ast_manager & m = get_manager(); - context & ctx = get_context(); - - int distance = 32; - - // ---------------------------------------------------------------------------------------- - // generate value options encoding - // encoding is a vector of size (len + 1) - // e.g, len = 2, - // encoding {1, 2, 0} means the value option is "charSet[2]"."charSet[1]" - // the last item in the encoding indicates whether the whole space is covered - // for example, if the charSet = {a, b}. All valid encodings are - // {0, 0, 0}, {1, 0, 0}, {0, 1, 0}, {1, 1, 0} - // if add 1 to the last one, we get - // {0, 0, 1} - // the last item "1" shows this is not a valid encoding, and we have covered all space - // ---------------------------------------------------------------------------------------- - int len = atoi(lenStr.encode().c_str()); - bool coverAll = false; - svector options; - int_vector base; - - TRACE("str", tout - << "freeVar = " << mk_ismt2_pp(freeVar, m) << std::endl - << "len_indicator = " << mk_ismt2_pp(len_indicator, m) << std::endl - << "val_indicator = " << mk_ismt2_pp(val_indicator, m) << std::endl - << "lenstr = " << lenStr << "\n" - << "tries = " << tries << "\n"; - if (m_params.m_AggressiveValueTesting) { - tout << "note: aggressive value testing is enabled" << std::endl; - } - ); - - if (tries == 0) { - base = int_vector(len + 1, 0); - coverAll = false; - } else { - expr * lastestValIndi = fvar_valueTester_map[freeVar][len][tries - 1].second; - TRACE("str", tout << "last value tester = " << mk_ismt2_pp(lastestValIndi, m) << std::endl;); - coverAll = get_next_val_encode(val_range_map[lastestValIndi], base); - } - - long long l = (tries) * distance; - long long h = l; - for (int i = 0; i < distance; i++) { - if (coverAll) - break; - options.push_back(base); - h++; - coverAll = get_next_val_encode(options[options.size() - 1], base); - } - val_range_map[val_indicator] = options[options.size() - 1]; - - TRACE("str", - tout << "value tester encoding " << "{" << std::endl; - int_vector vec = val_range_map[val_indicator]; - - for (int_vector::iterator it = vec.begin(); it != vec.end(); ++it) { - tout << *it << std::endl; - } - tout << "}" << std::endl; - ); - - // ---------------------------------------------------------------------------------------- - - ptr_vector orList; - ptr_vector andList; - - for (long long i = l; i < h; i++) { - orList.push_back(m.mk_eq(val_indicator, mk_string(longlong_to_string(i).c_str()) )); - if (m_params.m_AggressiveValueTesting) { - literal l = mk_eq(val_indicator, mk_string(longlong_to_string(i).c_str()), false); - ctx.mark_as_relevant(l); - ctx.force_phase(l); - } - - zstring aStr = gen_val_string(len, options[i - l]); - expr * strAst; - if (m_params.m_UseFastValueTesterCache) { - if (!valueTesterCache.find(aStr, strAst)) { - strAst = mk_string(aStr); - valueTesterCache.insert(aStr, strAst); - m_trail.push_back(strAst); - } - } else { - strAst = mk_string(aStr); - } - andList.push_back(m.mk_eq(orList[orList.size() - 1], m.mk_eq(freeVar, strAst))); - } - if (!coverAll) { - orList.push_back(m.mk_eq(val_indicator, mk_string("more"))); - if (m_params.m_AggressiveValueTesting) { - literal l = mk_eq(val_indicator, mk_string("more"), false); - ctx.mark_as_relevant(l); - ctx.force_phase(~l); - } - } - - expr ** or_items = alloc_svect(expr*, orList.size()); - expr ** and_items = alloc_svect(expr*, andList.size() + 1); - - for (int i = 0; i < (int) orList.size(); i++) { - or_items[i] = orList[i]; - } - if (orList.size() > 1) - and_items[0] = m.mk_or(orList.size(), or_items); - else - and_items[0] = or_items[0]; - - for (int i = 0; i < (int) andList.size(); i++) { - and_items[i + 1] = andList[i]; - } - expr * valTestAssert = m.mk_and(andList.size() + 1, and_items); - - // --------------------------------------- - // If the new value tester is $$_val_x_16_i - // Should add ($$_len_x_j = 16) /\ ($$_val_x_16_i = "more") - // --------------------------------------- - andList.reset(); - andList.push_back(m.mk_eq(len_indicator, mk_string(lenStr))); - for (int i = 0; i < tries; i++) { - expr * vTester = fvar_valueTester_map[freeVar][len][i].second; - if (vTester != val_indicator) - andList.push_back(m.mk_eq(vTester, mk_string("more"))); - } - expr * assertL = NULL; - if (andList.size() == 1) { - assertL = andList[0]; - } else { - expr ** and_items = alloc_svect(expr*, andList.size()); - for (int i = 0; i < (int) andList.size(); i++) { - and_items[i] = andList[i]; - } - assertL = m.mk_and(andList.size(), and_items); - } - - // (assertL => valTestAssert) <=> (!assertL OR valTestAssert) - valTestAssert = m.mk_or(m.mk_not(assertL), valTestAssert); - return valTestAssert; -} - -expr * theory_str::gen_free_var_options(expr * freeVar, expr * len_indicator, - zstring len_valueStr, expr * valTesterInCbEq, zstring valTesterValueStr) { - ast_manager & m = get_manager(); - - int len = atoi(len_valueStr.encode().c_str()); - - // check whether any value tester is actually in scope - TRACE("str", tout << "checking scope of previous value testers" << std::endl;); - bool map_effectively_empty = true; - if (fvar_valueTester_map[freeVar].find(len) != fvar_valueTester_map[freeVar].end()) { - // there's *something* in the map, but check its scope - svector > entries = fvar_valueTester_map[freeVar][len]; - for (svector >::iterator it = entries.begin(); it != entries.end(); ++it) { - std::pair entry = *it; - expr * aTester = entry.second; - if (internal_variable_set.find(aTester) == internal_variable_set.end()) { - TRACE("str", tout << mk_pp(aTester, m) << " out of scope" << std::endl;); - } else { - TRACE("str", tout << mk_pp(aTester, m) << " in scope" << std::endl;); - map_effectively_empty = false; - break; - } - } - } - - if (map_effectively_empty) { - TRACE("str", tout << "no previous value testers, or none of them were in scope" << std::endl;); - int tries = 0; - expr * val_indicator = mk_internal_valTest_var(freeVar, len, tries); - valueTester_fvar_map[val_indicator] = freeVar; - fvar_valueTester_map[freeVar][len].push_back(std::make_pair(sLevel, val_indicator)); - print_value_tester_list(fvar_valueTester_map[freeVar][len]); - return gen_val_options(freeVar, len_indicator, val_indicator, len_valueStr, tries); - } else { - TRACE("str", tout << "checking previous value testers" << std::endl;); - print_value_tester_list(fvar_valueTester_map[freeVar][len]); - - // go through all previous value testers - // If some doesn't have an eqc value, add its assertion again. - int testerTotal = fvar_valueTester_map[freeVar][len].size(); - int i = 0; - for (; i < testerTotal; i++) { - expr * aTester = fvar_valueTester_map[freeVar][len][i].second; - - // it's probably worth checking scope here, actually - if (internal_variable_set.find(aTester) == internal_variable_set.end()) { - TRACE("str", tout << "value tester " << mk_pp(aTester, m) << " out of scope, skipping" << std::endl;); - continue; - } - - if (aTester == valTesterInCbEq) { - break; - } - - bool anEqcHasValue = false; - // Z3_ast anEqc = get_eqc_value(t, aTester, anEqcHasValue); - expr * aTester_eqc_value = get_eqc_value(aTester, anEqcHasValue); - if (!anEqcHasValue) { - TRACE("str", tout << "value tester " << mk_ismt2_pp(aTester, m) - << " doesn't have an equivalence class value." << std::endl;); - refresh_theory_var(aTester); - - expr * makeupAssert = gen_val_options(freeVar, len_indicator, aTester, len_valueStr, i); - - TRACE("str", tout << "var: " << mk_ismt2_pp(freeVar, m) << std::endl - << mk_ismt2_pp(makeupAssert, m) << std::endl;); - assert_axiom(makeupAssert); - } else { - TRACE("str", tout << "value tester " << mk_ismt2_pp(aTester, m) - << " == " << mk_ismt2_pp(aTester_eqc_value, m) << std::endl;); - } - } - - if (valTesterValueStr == "more") { - expr * valTester = NULL; - if (i + 1 < testerTotal) { - valTester = fvar_valueTester_map[freeVar][len][i + 1].second; - refresh_theory_var(valTester); - } else { - valTester = mk_internal_valTest_var(freeVar, len, i + 1); - valueTester_fvar_map[valTester] = freeVar; - fvar_valueTester_map[freeVar][len].push_back(std::make_pair(sLevel, valTester)); - print_value_tester_list(fvar_valueTester_map[freeVar][len]); - } - expr * nextAssert = gen_val_options(freeVar, len_indicator, valTester, len_valueStr, i + 1); - return nextAssert; - } - - return NULL; - } -} - -void theory_str::reduce_virtual_regex_in(expr * var, expr * regex, expr_ref_vector & items) { - context & ctx = get_context(); - ast_manager & mgr = get_manager(); - - TRACE("str", tout << "reduce regex " << mk_pp(regex, mgr) << " with respect to variable " << mk_pp(var, mgr) << std::endl;); - - app * regexFuncDecl = to_app(regex); - if (u.re.is_to_re(regexFuncDecl)) { - // --------------------------------------------------------- - // var \in Str2Reg(s1) - // ==> - // var = s1 /\ length(var) = length(s1) - // --------------------------------------------------------- - expr * strInside = to_app(regex)->get_arg(0); - items.push_back(ctx.mk_eq_atom(var, strInside)); - items.push_back(ctx.mk_eq_atom(mk_strlen(var), mk_strlen(strInside))); - return; - } - // RegexUnion - else if (u.re.is_union(regexFuncDecl)) { - // --------------------------------------------------------- - // var \in RegexUnion(r1, r2) - // ==> - // (var = newVar1 \/ var = newVar2) - // (var = newVar1 --> length(var) = length(newVar1)) /\ (var = newVar2 --> length(var) = length(newVar2)) - // /\ (newVar1 \in r1) /\ (newVar2 \in r2) - // --------------------------------------------------------- - expr_ref newVar1(mk_regex_rep_var(), mgr); - expr_ref newVar2(mk_regex_rep_var(), mgr); - items.push_back(mgr.mk_or(ctx.mk_eq_atom(var, newVar1), ctx.mk_eq_atom(var, newVar2))); - items.push_back(mgr.mk_or( - mgr.mk_not(ctx.mk_eq_atom(var, newVar1)), - ctx.mk_eq_atom(mk_strlen(var), mk_strlen(newVar1)))); - items.push_back(mgr.mk_or( - mgr.mk_not(ctx.mk_eq_atom(var, newVar2)), - ctx.mk_eq_atom(mk_strlen(var), mk_strlen(newVar2)))); - - expr * regArg1 = to_app(regex)->get_arg(0); - reduce_virtual_regex_in(newVar1, regArg1, items); - - expr * regArg2 = to_app(regex)->get_arg(1); - reduce_virtual_regex_in(newVar2, regArg2, items); - - return; - } - // RegexConcat - else if (u.re.is_concat(regexFuncDecl)) { - // --------------------------------------------------------- - // var \in RegexConcat(r1, r2) - // ==> - // (var = newVar1 . newVar2) /\ (length(var) = length(vewVar1 . newVar2) ) - // /\ (newVar1 \in r1) /\ (newVar2 \in r2) - // --------------------------------------------------------- - expr_ref newVar1(mk_regex_rep_var(), mgr); - expr_ref newVar2(mk_regex_rep_var(), mgr); - expr_ref concatAst(mk_concat(newVar1, newVar2), mgr); - items.push_back(ctx.mk_eq_atom(var, concatAst)); - items.push_back(ctx.mk_eq_atom(mk_strlen(var), - m_autil.mk_add(mk_strlen(newVar1), mk_strlen(newVar2)))); - - expr * regArg1 = to_app(regex)->get_arg(0); - reduce_virtual_regex_in(newVar1, regArg1, items); - expr * regArg2 = to_app(regex)->get_arg(1); - reduce_virtual_regex_in(newVar2, regArg2, items); - return; - } - // Unroll - else if (u.re.is_star(regexFuncDecl)) { - // --------------------------------------------------------- - // var \in Star(r1) - // ==> - // var = unroll(r1, t1) /\ |var| = |unroll(r1, t1)| - // --------------------------------------------------------- - expr * regArg = to_app(regex)->get_arg(0); - expr_ref unrollCnt(mk_unroll_bound_var(), mgr); - expr_ref unrollFunc(mk_unroll(regArg, unrollCnt), mgr); - items.push_back(ctx.mk_eq_atom(var, unrollFunc)); - items.push_back(ctx.mk_eq_atom(mk_strlen(var), mk_strlen(unrollFunc))); - return; - } - // re.range - else if (u.re.is_range(regexFuncDecl)) { - // var in range("a", "z") - // ==> - // (var = "a" or var = "b" or ... or var = "z") - expr_ref lo(regexFuncDecl->get_arg(0), mgr); - expr_ref hi(regexFuncDecl->get_arg(1), mgr); - zstring str_lo, str_hi; - SASSERT(u.str.is_string(lo)); - SASSERT(u.str.is_string(hi)); - u.str.is_string(lo, str_lo); - u.str.is_string(hi, str_hi); - SASSERT(str_lo.length() == 1); - SASSERT(str_hi.length() == 1); - unsigned int c1 = str_lo[0]; - unsigned int c2 = str_hi[0]; - if (c1 > c2) { - // exchange - unsigned int tmp = c1; - c1 = c2; - c2 = tmp; - } - expr_ref_vector range_cases(mgr); - for (unsigned int ch = c1; ch <= c2; ++ch) { - zstring s_ch(ch); - expr_ref rhs(ctx.mk_eq_atom(var, u.str.mk_string(s_ch)), mgr); - range_cases.push_back(rhs); - } - expr_ref rhs(mk_or(range_cases), mgr); - SASSERT(rhs); - assert_axiom(rhs); - return; - } else { - get_manager().raise_exception("unrecognized regex operator"); - UNREACHABLE(); - } -} - -void theory_str::gen_assign_unroll_reg(std::set & unrolls) { - context & ctx = get_context(); - ast_manager & mgr = get_manager(); - - expr_ref_vector items(mgr); - for (std::set::iterator itor = unrolls.begin(); itor != unrolls.end(); itor++) { - expr * unrFunc = *itor; - TRACE("str", tout << "generating assignment for unroll " << mk_pp(unrFunc, mgr) << std::endl;); - - expr * regexInUnr = to_app(unrFunc)->get_arg(0); - expr * cntInUnr = to_app(unrFunc)->get_arg(1); - items.reset(); - - rational low, high; - bool low_exists = lower_bound(cntInUnr, low); - bool high_exists = upper_bound(cntInUnr, high); - - TRACE("str", - tout << "unroll " << mk_pp(unrFunc, mgr) << std::endl; - rational unrLenValue; - bool unrLenValue_exists = get_len_value(unrFunc, unrLenValue); - tout << "unroll length: " << (unrLenValue_exists ? unrLenValue.to_string() : "?") << std::endl; - rational cntInUnrValue; - bool cntHasValue = get_value(cntInUnr, cntInUnrValue); - tout << "unroll count: " << (cntHasValue ? cntInUnrValue.to_string() : "?") - << " low = " - << (low_exists ? low.to_string() : "?") - << " high = " - << (high_exists ? high.to_string() : "?") - << std::endl; - ); - - expr_ref toAssert(mgr); - if (low.is_neg()) { - toAssert = m_autil.mk_ge(cntInUnr, mk_int(0)); - } else { - if (unroll_var_map.find(unrFunc) == unroll_var_map.end()) { - - expr_ref newVar1(mk_regex_rep_var(), mgr); - expr_ref newVar2(mk_regex_rep_var(), mgr); - expr_ref concatAst(mk_concat(newVar1, newVar2), mgr); - expr_ref newCnt(mk_unroll_bound_var(), mgr); - expr_ref newUnrollFunc(mk_unroll(regexInUnr, newCnt), mgr); - - // unroll(r1, t1) = newVar1 . newVar2 - items.push_back(ctx.mk_eq_atom(unrFunc, concatAst)); - items.push_back(ctx.mk_eq_atom(mk_strlen(unrFunc), m_autil.mk_add(mk_strlen(newVar1), mk_strlen(newVar2)))); - // mk_strlen(unrFunc) >= mk_strlen(newVar{1,2}) - items.push_back(m_autil.mk_ge(m_autil.mk_add(mk_strlen(unrFunc), m_autil.mk_mul(mk_int(-1), mk_strlen(newVar1))), mk_int(0))); - items.push_back(m_autil.mk_ge(m_autil.mk_add(mk_strlen(unrFunc), m_autil.mk_mul(mk_int(-1), mk_strlen(newVar2))), mk_int(0))); - // newVar1 \in r1 - reduce_virtual_regex_in(newVar1, regexInUnr, items); - items.push_back(ctx.mk_eq_atom(cntInUnr, m_autil.mk_add(newCnt, mk_int(1)))); - items.push_back(ctx.mk_eq_atom(newVar2, newUnrollFunc)); - items.push_back(ctx.mk_eq_atom(mk_strlen(newVar2), mk_strlen(newUnrollFunc))); - toAssert = ctx.mk_eq_atom( - m_autil.mk_ge(cntInUnr, mk_int(1)), - mk_and(items)); - - // option 0 - expr_ref op0(ctx.mk_eq_atom(cntInUnr, mk_int(0)), mgr); - expr_ref ast1(ctx.mk_eq_atom(unrFunc, mk_string("")), mgr); - expr_ref ast2(ctx.mk_eq_atom(mk_strlen(unrFunc), mk_int(0)), mgr); - expr_ref and1(mgr.mk_and(ast1, ast2), mgr); - - // put together - toAssert = mgr.mk_and(ctx.mk_eq_atom(op0, and1), toAssert); - - unroll_var_map[unrFunc] = toAssert; - } else { - toAssert = unroll_var_map[unrFunc]; - } - } - m_trail.push_back(toAssert); - assert_axiom(toAssert); - } -} - -static int computeGCD(int x, int y) { - if (x == 0) { - return y; - } - while (y != 0) { - if (x > y) { - x = x - y; - } else { - y = y - x; - } - } - return x; -} - -static int computeLCM(int a, int b) { - int temp = computeGCD(a, b); - return temp ? (a / temp * b) : 0; -} - -static zstring get_unrolled_string(zstring core, int count) { - zstring res(""); - for (int i = 0; i < count; i++) { - res = res + core; - } - return res; -} - -expr * theory_str::gen_assign_unroll_Str2Reg(expr * n, std::set & unrolls) { - context & ctx = get_context(); - ast_manager & mgr = get_manager(); - - int lcm = 1; - int coreValueCount = 0; - expr * oneUnroll = NULL; - zstring oneCoreStr(""); - for (std::set::iterator itor = unrolls.begin(); itor != unrolls.end(); itor++) { - expr * str2RegFunc = to_app(*itor)->get_arg(0); - expr * coreVal = to_app(str2RegFunc)->get_arg(0); - zstring coreStr; - u.str.is_string(coreVal, coreStr); - if (oneUnroll == NULL) { - oneUnroll = *itor; - oneCoreStr = coreStr; - } - coreValueCount++; - int core1Len = coreStr.length(); - lcm = computeLCM(lcm, core1Len); - } - // - bool canHaveNonEmptyAssign = true; - expr_ref_vector litems(mgr); - zstring lcmStr = get_unrolled_string(oneCoreStr, (lcm / oneCoreStr.length())); - for (std::set::iterator itor = unrolls.begin(); itor != unrolls.end(); itor++) { - expr * str2RegFunc = to_app(*itor)->get_arg(0); - expr * coreVal = to_app(str2RegFunc)->get_arg(0); - zstring coreStr; - u.str.is_string(coreVal, coreStr); - unsigned int core1Len = coreStr.length(); - zstring uStr = get_unrolled_string(coreStr, (lcm / core1Len)); - if (uStr != lcmStr) { - canHaveNonEmptyAssign = false; - } - litems.push_back(ctx.mk_eq_atom(n, *itor)); - } - - if (canHaveNonEmptyAssign) { - return gen_unroll_conditional_options(n, unrolls, lcmStr); - } else { - expr_ref implyL(mk_and(litems), mgr); - expr_ref implyR(ctx.mk_eq_atom(n, mk_string("")), mgr); - // want to return (implyL -> implyR) - expr * final_axiom = rewrite_implication(implyL, implyR); - return final_axiom; - } -} - -expr * theory_str::gen_unroll_conditional_options(expr * var, std::set & unrolls, zstring lcmStr) { - context & ctx = get_context(); - ast_manager & mgr = get_manager(); - - int dist = opt_LCMUnrollStep; - expr_ref_vector litems(mgr); - expr_ref moreAst(mk_string("more"), mgr); - for (std::set::iterator itor = unrolls.begin(); itor != unrolls.end(); itor++) { - expr_ref item(ctx.mk_eq_atom(var, *itor), mgr); - TRACE("str", tout << "considering unroll " << mk_pp(item, mgr) << std::endl;); - litems.push_back(item); - } - - // handle out-of-scope entries in unroll_tries_map - - ptr_vector outOfScopeTesters; - - for (ptr_vector::iterator it = unroll_tries_map[var][unrolls].begin(); - it != unroll_tries_map[var][unrolls].end(); ++it) { - expr * tester = *it; - bool inScope = (internal_unrollTest_vars.find(tester) != internal_unrollTest_vars.end()); - TRACE("str", tout << "unroll test var " << mk_pp(tester, mgr) - << (inScope ? " in scope" : " out of scope") - << std::endl;); - if (!inScope) { - outOfScopeTesters.push_back(tester); - } - } - - for (ptr_vector::iterator it = outOfScopeTesters.begin(); - it != outOfScopeTesters.end(); ++it) { - unroll_tries_map[var][unrolls].erase(*it); - } - - - if (unroll_tries_map[var][unrolls].size() == 0) { - unroll_tries_map[var][unrolls].push_back(mk_unroll_test_var()); - } - - int tries = unroll_tries_map[var][unrolls].size(); - for (int i = 0; i < tries; i++) { - expr * tester = unroll_tries_map[var][unrolls][i]; - // TESTING - refresh_theory_var(tester); - bool testerHasValue = false; - expr * testerVal = get_eqc_value(tester, testerHasValue); - if (!testerHasValue) { - // generate make-up assertion - int l = i * dist; - int h = (i + 1) * dist; - expr_ref lImp(mk_and(litems), mgr); - expr_ref rImp(gen_unroll_assign(var, lcmStr, tester, l, h), mgr); - - SASSERT(lImp); - TRACE("str", tout << "lImp = " << mk_pp(lImp, mgr) << std::endl;); - SASSERT(rImp); - TRACE("str", tout << "rImp = " << mk_pp(rImp, mgr) << std::endl;); - - expr_ref toAssert(mgr.mk_or(mgr.mk_not(lImp), rImp), mgr); - SASSERT(toAssert); - TRACE("str", tout << "Making up assignments for variable which is equal to unbounded Unroll" << std::endl;); - m_trail.push_back(toAssert); - return toAssert; - - // note: this is how the code looks in Z3str2's strRegex.cpp:genUnrollConditionalOptions. - // the return is in the same place - - // insert [tester = "more"] to litems so that the implyL for next tester is correct - litems.push_back(ctx.mk_eq_atom(tester, moreAst)); - } else { - zstring testerStr; - u.str.is_string(testerVal, testerStr); - TRACE("str", tout << "Tester [" << mk_pp(tester, mgr) << "] = " << testerStr << "\n";); - if (testerStr == "more") { - litems.push_back(ctx.mk_eq_atom(tester, moreAst)); - } - } - } - expr * tester = mk_unroll_test_var(); - unroll_tries_map[var][unrolls].push_back(tester); - int l = tries * dist; - int h = (tries + 1) * dist; - expr_ref lImp(mk_and(litems), mgr); - expr_ref rImp(gen_unroll_assign(var, lcmStr, tester, l, h), mgr); - SASSERT(lImp); - SASSERT(rImp); - expr_ref toAssert(mgr.mk_or(mgr.mk_not(lImp), rImp), mgr); - SASSERT(toAssert); - TRACE("str", tout << "Generating assignment for variable which is equal to unbounded Unroll" << std::endl;); - m_trail.push_back(toAssert); - return toAssert; -} - -expr * theory_str::gen_unroll_assign(expr * var, zstring lcmStr, expr * testerVar, int l, int h) { - context & ctx = get_context(); - ast_manager & mgr = get_manager(); - - TRACE("str", tout << "entry: var = " << mk_pp(var, mgr) << ", lcmStr = " << lcmStr - << ", l = " << l << ", h = " << h << "\n";); - - if (m_params.m_AggressiveUnrollTesting) { - TRACE("str", tout << "note: aggressive unroll testing is active" << std::endl;); - } - - expr_ref_vector orItems(mgr); - expr_ref_vector andItems(mgr); - - for (int i = l; i < h; i++) { - zstring iStr = int_to_string(i); - expr_ref testerEqAst(ctx.mk_eq_atom(testerVar, mk_string(iStr)), mgr); - TRACE("str", tout << "testerEqAst = " << mk_pp(testerEqAst, mgr) << std::endl;); - if (m_params.m_AggressiveUnrollTesting) { - literal l = mk_eq(testerVar, mk_string(iStr), false); - ctx.mark_as_relevant(l); - ctx.force_phase(l); - } - - orItems.push_back(testerEqAst); - zstring unrollStrInstance = get_unrolled_string(lcmStr, i); - - expr_ref x1(ctx.mk_eq_atom(testerEqAst, ctx.mk_eq_atom(var, mk_string(unrollStrInstance))), mgr); - TRACE("str", tout << "x1 = " << mk_pp(x1, mgr) << std::endl;); - andItems.push_back(x1); - - expr_ref x2(ctx.mk_eq_atom(testerEqAst, ctx.mk_eq_atom(mk_strlen(var), mk_int(i * lcmStr.length()))), mgr); - TRACE("str", tout << "x2 = " << mk_pp(x2, mgr) << std::endl;); - andItems.push_back(x2); - } - expr_ref testerEqMore(ctx.mk_eq_atom(testerVar, mk_string("more")), mgr); - TRACE("str", tout << "testerEqMore = " << mk_pp(testerEqMore, mgr) << std::endl;); - if (m_params.m_AggressiveUnrollTesting) { - literal l = mk_eq(testerVar, mk_string("more"), false); - ctx.mark_as_relevant(l); - ctx.force_phase(~l); - } - - orItems.push_back(testerEqMore); - int nextLowerLenBound = h * lcmStr.length(); - expr_ref more2(ctx.mk_eq_atom(testerEqMore, - //Z3_mk_ge(mk_length(t, var), mk_int(ctx, nextLowerLenBound)) - m_autil.mk_ge(m_autil.mk_add(mk_strlen(var), mk_int(-1 * nextLowerLenBound)), mk_int(0)) - ), mgr); - TRACE("str", tout << "more2 = " << mk_pp(more2, mgr) << std::endl;); - andItems.push_back(more2); - - expr_ref finalOR(mgr.mk_or(orItems.size(), orItems.c_ptr()), mgr); - TRACE("str", tout << "finalOR = " << mk_pp(finalOR, mgr) << std::endl;); - andItems.push_back(mk_or(orItems)); - - expr_ref finalAND(mgr.mk_and(andItems.size(), andItems.c_ptr()), mgr); - TRACE("str", tout << "finalAND = " << mk_pp(finalAND, mgr) << std::endl;); - - // doing the following avoids a segmentation fault - m_trail.push_back(finalAND); - return finalAND; -} - -expr * theory_str::gen_len_test_options(expr * freeVar, expr * indicator, int tries) { - ast_manager & m = get_manager(); - context & ctx = get_context(); - - expr_ref freeVarLen(mk_strlen(freeVar), m); - SASSERT(freeVarLen); - - expr_ref_vector orList(m); - expr_ref_vector andList(m); - - int distance = 3; - int l = (tries - 1) * distance; - int h = tries * distance; - - TRACE("str", - tout << "building andList and orList" << std::endl; - if (m_params.m_AggressiveLengthTesting) { - tout << "note: aggressive length testing is active" << std::endl; - } - ); - - // experimental theory-aware case split support - literal_vector case_split_literals; - - for (int i = l; i < h; ++i) { - expr_ref str_indicator(m); - if (m_params.m_UseFastLengthTesterCache) { - rational ri(i); - expr * lookup_val; - if(lengthTesterCache.find(ri, lookup_val)) { - str_indicator = expr_ref(lookup_val, m); - } else { - // no match; create and insert - zstring i_str = int_to_string(i); - expr_ref new_val(mk_string(i_str), m); - lengthTesterCache.insert(ri, new_val); - m_trail.push_back(new_val); - str_indicator = expr_ref(new_val, m); - } - } else { - zstring i_str = int_to_string(i); - str_indicator = expr_ref(mk_string(i_str), m); - } - expr_ref or_expr(ctx.mk_eq_atom(indicator, str_indicator), m); - orList.push_back(or_expr); - - double priority; - // give high priority to small lengths if this is available - if (i <= 5) { - priority = 0.3; - } else { - // prioritize over "more" - priority = 0.2; - } - add_theory_aware_branching_info(or_expr, priority, l_true); - - if (m_params.m_AggressiveLengthTesting) { - literal l = mk_eq(indicator, str_indicator, false); - ctx.mark_as_relevant(l); - ctx.force_phase(l); - } - - case_split_literals.insert(mk_eq(freeVarLen, mk_int(i), false)); - - expr_ref and_expr(ctx.mk_eq_atom(orList.get(orList.size() - 1), m.mk_eq(freeVarLen, mk_int(i))), m); - andList.push_back(and_expr); - } - - expr_ref more_option(ctx.mk_eq_atom(indicator, mk_string("more")), m); - orList.push_back(more_option); - // decrease priority of this option - add_theory_aware_branching_info(more_option, -0.1, l_true); - if (m_params.m_AggressiveLengthTesting) { - literal l = mk_eq(indicator, mk_string("more"), false); - ctx.mark_as_relevant(l); - ctx.force_phase(~l); - } - - andList.push_back(ctx.mk_eq_atom(orList.get(orList.size() - 1), m_autil.mk_ge(freeVarLen, mk_int(h)))); - - /* - { // more experimental theory case split support - expr_ref tmp(m_autil.mk_ge(freeVarLen, mk_int(h)), m); - ctx.internalize(m_autil.mk_ge(freeVarLen, mk_int(h)), false); - case_split_literals.push_back(ctx.get_literal(tmp)); - ctx.mk_th_case_split(case_split_literals.size(), case_split_literals.c_ptr()); - } - */ - - expr_ref_vector or_items(m); - expr_ref_vector and_items(m); - - for (unsigned i = 0; i < orList.size(); ++i) { - or_items.push_back(orList.get(i)); - } - - and_items.push_back(mk_or(or_items)); - for(unsigned i = 0; i < andList.size(); ++i) { - and_items.push_back(andList.get(i)); - } - - TRACE("str", tout << "check: " << mk_pp(mk_and(and_items), m) << std::endl;); - - expr_ref lenTestAssert = mk_and(and_items); - SASSERT(lenTestAssert); - TRACE("str", tout << "crash avoidance lenTestAssert: " << mk_pp(lenTestAssert, m) << std::endl;); - - int testerCount = tries - 1; - if (testerCount > 0) { - expr_ref_vector and_items_LHS(m); - expr_ref moreAst(mk_string("more"), m); - for (int i = 0; i < testerCount; ++i) { - expr * indicator = fvar_lenTester_map[freeVar][i]; - if (internal_variable_set.find(indicator) == internal_variable_set.end()) { - TRACE("str", tout << "indicator " << mk_pp(indicator, m) << " out of scope; continuing" << std::endl;); - continue; - } else { - TRACE("str", tout << "indicator " << mk_pp(indicator, m) << " in scope" << std::endl;); - and_items_LHS.push_back(ctx.mk_eq_atom(indicator, moreAst)); - } - } - expr_ref assertL(mk_and(and_items_LHS), m); - SASSERT(assertL); - expr * finalAxiom = m.mk_or(m.mk_not(assertL), lenTestAssert.get()); - SASSERT(finalAxiom != NULL); - TRACE("str", tout << "crash avoidance finalAxiom: " << mk_pp(finalAxiom, m) << std::endl;); - return finalAxiom; - } else { - TRACE("str", tout << "crash avoidance lenTestAssert.get(): " << mk_pp(lenTestAssert.get(), m) << std::endl;); - m_trail.push_back(lenTestAssert.get()); - return lenTestAssert.get(); - } -} - -// Return an expression of the form -// (tester = "less" | tester = "N" | tester = "more") & -// (tester = "less" iff len(freeVar) < N) & (tester = "more" iff len(freeVar) > N) & (tester = "N" iff len(freeVar) = N)) -expr_ref theory_str::binary_search_case_split(expr * freeVar, expr * tester, binary_search_info & bounds, literal_vector & case_split) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - rational N = bounds.midPoint; - rational N_minus_one = N - rational::one(); - rational N_plus_one = N + rational::one(); - expr_ref lenFreeVar(mk_strlen(freeVar), m); - - TRACE("str", tout << "create case split for free var " << mk_pp(freeVar, m) - << " over " << mk_pp(tester, m) << " with midpoint " << N << std::endl;); - - expr_ref_vector combinedCaseSplit(m); - expr_ref_vector testerCases(m); - - expr_ref caseLess(ctx.mk_eq_atom(tester, mk_string("less")), m); - testerCases.push_back(caseLess); - combinedCaseSplit.push_back(ctx.mk_eq_atom(caseLess, m_autil.mk_le(lenFreeVar, m_autil.mk_numeral(N_minus_one, true) ))); - - expr_ref caseMore(ctx.mk_eq_atom(tester, mk_string("more")), m); - testerCases.push_back(caseMore); - combinedCaseSplit.push_back(ctx.mk_eq_atom(caseMore, m_autil.mk_ge(lenFreeVar, m_autil.mk_numeral(N_plus_one, true) ))); - - expr_ref caseEq(ctx.mk_eq_atom(tester, mk_string(N.to_string().c_str())), m); - testerCases.push_back(caseEq); - combinedCaseSplit.push_back(ctx.mk_eq_atom(caseEq, ctx.mk_eq_atom(lenFreeVar, m_autil.mk_numeral(N, true)))); - - combinedCaseSplit.push_back(mk_or(testerCases)); - - // force internalization on all terms in testerCases so we can extract literals - for (unsigned i = 0; i < testerCases.size(); ++i) { - expr * testerCase = testerCases.get(i); - if (!ctx.b_internalized(testerCase)) { - ctx.internalize(testerCase, false); - } - literal l = ctx.get_literal(testerCase); - case_split.push_back(l); - } - - expr_ref final_term(mk_and(combinedCaseSplit), m); - SASSERT(final_term); - TRACE("str", tout << "final term: " << mk_pp(final_term, m) << std::endl;); - return final_term; -} - -expr * theory_str::binary_search_length_test(expr * freeVar, expr * previousLenTester, zstring previousLenTesterValue) { - ast_manager & m = get_manager(); - context & ctx = get_context(); - - if (binary_search_len_tester_stack.contains(freeVar) && !binary_search_len_tester_stack[freeVar].empty()) { - TRACE("str", tout << "checking existing length testers for " << mk_pp(freeVar, m) << std::endl; - for (ptr_vector::const_iterator it = binary_search_len_tester_stack[freeVar].begin(); - it != binary_search_len_tester_stack[freeVar].end(); ++it) { - expr * tester = *it; - tout << mk_pp(tester, m) << ": "; - if (binary_search_len_tester_info.contains(tester)) { - binary_search_info & bounds = binary_search_len_tester_info[tester]; - tout << "[" << bounds.lowerBound << " | " << bounds.midPoint << " | " << bounds.upperBound << "]!" << bounds.windowSize; - } else { - tout << "[WARNING: no bounds info available]"; - } - bool hasEqcValue; - expr * testerEqcValue = get_eqc_value(tester, hasEqcValue); - if (hasEqcValue) { - tout << " = " << mk_pp(testerEqcValue, m); - } else { - tout << " [no eqc value]"; - } - tout << std::endl; - } - ); - expr * lastTester = binary_search_len_tester_stack[freeVar].back(); - bool lastTesterHasEqcValue; - expr * lastTesterValue = get_eqc_value(lastTester, lastTesterHasEqcValue); - zstring lastTesterConstant; - if (!lastTesterHasEqcValue) { - TRACE("str", tout << "length tester " << mk_pp(lastTester, m) << " at top of stack doesn't have an EQC value yet" << std::endl;); - // check previousLenTester - if (previousLenTester == lastTester) { - lastTesterConstant = previousLenTesterValue; - TRACE("str", tout << "invoked with previousLenTester info matching top of stack" << std::endl;); - } else { - TRACE("str", tout << "WARNING: unexpected reordering of length testers!" << std::endl;); - UNREACHABLE(); return NULL; - } - } else { - u.str.is_string(lastTesterValue, lastTesterConstant); - } - TRACE("str", tout << "last length tester is assigned \"" << lastTesterConstant << "\"" << "\n";); - if (lastTesterConstant == "more" || lastTesterConstant == "less") { - // use the previous bounds info to generate a new midpoint - binary_search_info lastBounds; - if (!binary_search_len_tester_info.find(lastTester, lastBounds)) { - // unexpected - TRACE("str", tout << "WARNING: no bounds information available for last tester!" << std::endl;); - UNREACHABLE(); - } - TRACE("str", tout << "last bounds are [" << lastBounds.lowerBound << " | " << lastBounds.midPoint << " | " << lastBounds.upperBound << "]!" << lastBounds.windowSize << std::endl;); - binary_search_info newBounds; - expr * newTester; - if (lastTesterConstant == "more") { - // special case: if the midpoint, upper bound, and window size are all equal, - // we double the window size and adjust the bounds - if (lastBounds.midPoint == lastBounds.upperBound && lastBounds.upperBound == lastBounds.windowSize) { - TRACE("str", tout << "search hit window size; expanding" << std::endl;); - newBounds.lowerBound = lastBounds.windowSize + rational::one(); - newBounds.windowSize = lastBounds.windowSize * rational(2); - newBounds.upperBound = newBounds.windowSize; - newBounds.calculate_midpoint(); - } else if (false) { - // handle the case where the midpoint can't be increased further - // (e.g. a window like [50 | 50 | 50]!64 and we don't answer "50") - } else { - // general case - newBounds.lowerBound = lastBounds.midPoint + rational::one(); - newBounds.windowSize = lastBounds.windowSize; - newBounds.upperBound = lastBounds.upperBound; - newBounds.calculate_midpoint(); - } - if (!binary_search_next_var_high.find(lastTester, newTester)) { - newTester = mk_internal_lenTest_var(freeVar, newBounds.midPoint.get_int32()); - binary_search_next_var_high.insert(lastTester, newTester); - } - refresh_theory_var(newTester); - } else if (lastTesterConstant == "less") { - if (false) { - // handle the case where the midpoint can't be decreased further - // (e.g. a window like [0 | 0 | 0]!64 and we don't answer "0" - } else { - // general case - newBounds.upperBound = lastBounds.midPoint - rational::one(); - newBounds.windowSize = lastBounds.windowSize; - newBounds.lowerBound = lastBounds.lowerBound; - newBounds.calculate_midpoint(); - } - if (!binary_search_next_var_low.find(lastTester, newTester)) { - newTester = mk_internal_lenTest_var(freeVar, newBounds.midPoint.get_int32()); - binary_search_next_var_low.insert(lastTester, newTester); - } - refresh_theory_var(newTester); - } - TRACE("str", tout << "new bounds are [" << newBounds.lowerBound << " | " << newBounds.midPoint << " | " << newBounds.upperBound << "]!" << newBounds.windowSize << std::endl;); - binary_search_len_tester_stack[freeVar].push_back(newTester); - m_trail_stack.push(binary_search_trail(binary_search_len_tester_stack, freeVar)); - binary_search_len_tester_info.insert(newTester, newBounds); - m_trail_stack.push(insert_obj_map(binary_search_len_tester_info, newTester)); - - literal_vector case_split_literals; - expr_ref next_case_split(binary_search_case_split(freeVar, newTester, newBounds, case_split_literals)); - m_trail.push_back(next_case_split); - // ctx.mk_th_case_split(case_split_literals.size(), case_split_literals.c_ptr()); - return next_case_split; - } else { // lastTesterConstant is a concrete value - TRACE("str", tout << "length is fixed; generating models for free var" << std::endl;); - // defensive check that this length did not converge on a negative value. - binary_search_info lastBounds; - if (!binary_search_len_tester_info.find(lastTester, lastBounds)) { - // unexpected - TRACE("str", tout << "WARNING: no bounds information available for last tester!" << std::endl;); - UNREACHABLE(); - } - if (lastBounds.midPoint.is_neg()) { - TRACE("str", tout << "WARNING: length search converged on a negative value. Negating this constraint." << std::endl;); - expr_ref axiom(m_autil.mk_ge(mk_strlen(freeVar), m_autil.mk_numeral(rational::zero(), true)), m); - return axiom; - } - // length is fixed - expr * valueAssert = gen_free_var_options(freeVar, lastTester, lastTesterConstant, NULL, zstring("")); - return valueAssert; - } - } else { - // no length testers yet - TRACE("str", tout << "no length testers for " << mk_pp(freeVar, m) << std::endl;); - binary_search_len_tester_stack.insert(freeVar, ptr_vector()); - - expr * firstTester; - rational lowerBound(0); - rational upperBound(m_params.m_BinarySearchInitialUpperBound); - rational windowSize(upperBound); - rational midPoint(floor(upperBound / rational(2))); - if (!binary_search_starting_len_tester.find(freeVar, firstTester)) { - firstTester = mk_internal_lenTest_var(freeVar, midPoint.get_int32()); - binary_search_starting_len_tester.insert(freeVar, firstTester); - } - refresh_theory_var(firstTester); - - binary_search_len_tester_stack[freeVar].push_back(firstTester); - m_trail_stack.push(binary_search_trail(binary_search_len_tester_stack, freeVar)); - binary_search_info new_info(lowerBound, midPoint, upperBound, windowSize); - binary_search_len_tester_info.insert(firstTester, new_info); - m_trail_stack.push(insert_obj_map(binary_search_len_tester_info, firstTester)); - - literal_vector case_split_literals; - expr_ref initial_case_split(binary_search_case_split(freeVar, firstTester, new_info, case_split_literals)); - m_trail.push_back(initial_case_split); - // ctx.mk_th_case_split(case_split_literals.size(), case_split_literals.c_ptr()); - return initial_case_split; - } -} - -// ----------------------------------------------------------------------------------------------------- -// True branch will be taken in final_check: -// - When we discover a variable is "free" for the first time -// lenTesterInCbEq = NULL -// lenTesterValue = "" -// False branch will be taken when invoked by new_eq_eh(). -// - After we set up length tester for a "free" var in final_check, -// when the tester is assigned to some value (e.g. "more" or "4"), -// lenTesterInCbEq != NULL, and its value will be passed by lenTesterValue -// The difference is that in new_eq_eh(), lenTesterInCbEq and its value have NOT been put into a same eqc -// ----------------------------------------------------------------------------------------------------- -expr * theory_str::gen_len_val_options_for_free_var(expr * freeVar, expr * lenTesterInCbEq, zstring lenTesterValue) { - - ast_manager & m = get_manager(); - - TRACE("str", tout << "gen for free var " << mk_ismt2_pp(freeVar, m) << std::endl;); - - if (m_params.m_UseBinarySearch) { - TRACE("str", tout << "using binary search heuristic" << std::endl;); - return binary_search_length_test(freeVar, lenTesterInCbEq, lenTesterValue); - } else { - bool map_effectively_empty = false; - if (!fvar_len_count_map.contains(freeVar)) { - TRACE("str", tout << "fvar_len_count_map is empty" << std::endl;); - map_effectively_empty = true; + expr_ref theory_str::set_up_finite_model_test(expr * lhs, expr * rhs) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + TRACE("str", tout << "activating finite model testing for overlapping concats " + << mk_pp(lhs, m) << " and " << mk_pp(rhs, m) << std::endl;); + std::map concatMap; + std::map unrollMap; + std::map varMap; + classify_ast_by_type(lhs, varMap, concatMap, unrollMap); + classify_ast_by_type(rhs, varMap, concatMap, unrollMap); + TRACE("str", tout << "found vars:"; + for (std::map::iterator it = varMap.begin(); it != varMap.end(); ++it) { + tout << " " << mk_pp(it->first, m); + } + tout << std::endl; + ); + + expr_ref testvar(mk_str_var("finiteModelTest"), m); + m_trail.push_back(testvar); + ptr_vector varlist; + + for (std::map::iterator it = varMap.begin(); it != varMap.end(); ++it) { + expr * v = it->first; + varlist.push_back(v); } - if (!map_effectively_empty) { - // check whether any entries correspond to variables that went out of scope; - // if every entry is out of scope then the map counts as being empty + // make things easy for the core wrt. testvar + expr_ref t1(ctx.mk_eq_atom(testvar, mk_string("")), m); + expr_ref t_yes(ctx.mk_eq_atom(testvar, mk_string("yes")), m); + expr_ref testvaraxiom(m.mk_or(t1, t_yes), m); + assert_axiom(testvaraxiom); - // assume empty and find a counterexample - map_effectively_empty = true; - ptr_vector indicator_set = fvar_lenTester_map[freeVar]; - for (ptr_vector::iterator it = indicator_set.begin(); it != indicator_set.end(); ++it) { - expr * indicator = *it; - if (internal_variable_set.find(indicator) != internal_variable_set.end()) { - TRACE("str", tout <<"found active internal variable " << mk_ismt2_pp(indicator, m) - << " in fvar_lenTester_map[freeVar]" << std::endl;); - map_effectively_empty = false; - break; - } - } - CTRACE("str", map_effectively_empty, tout << "all variables in fvar_lenTester_map[freeVar] out of scope" << std::endl;); - } + finite_model_test_varlists.insert(testvar, varlist); + m_trail_stack.push(insert_obj_map >(finite_model_test_varlists, testvar) ); + return t_yes; + } - if (map_effectively_empty) { - // no length assertions for this free variable have ever been added. - TRACE("str", tout << "no length assertions yet" << std::endl;); + void theory_str::finite_model_test(expr * testvar, expr * str) { + context & ctx = get_context(); + ast_manager & m = get_manager(); - fvar_len_count_map.insert(freeVar, 1); - unsigned int testNum = fvar_len_count_map[freeVar]; - - expr_ref indicator(mk_internal_lenTest_var(freeVar, testNum), m); - SASSERT(indicator); - - // since the map is "effectively empty", we can remove those variables that have left scope... - fvar_lenTester_map[freeVar].shrink(0); - fvar_lenTester_map[freeVar].push_back(indicator); - lenTester_fvar_map.insert(indicator, freeVar); - - expr * lenTestAssert = gen_len_test_options(freeVar, indicator, testNum); - SASSERT(lenTestAssert != NULL); - return lenTestAssert; - } else { - TRACE("str", tout << "found previous in-scope length assertions" << std::endl;); - - expr * effectiveLenInd = NULL; - zstring effectiveLenIndiStr(""); - int lenTesterCount = (int) fvar_lenTester_map[freeVar].size(); - - TRACE("str", - tout << lenTesterCount << " length testers in fvar_lenTester_map[" << mk_pp(freeVar, m) << "]:" << std::endl; - for (int i = 0; i < lenTesterCount; ++i) { - expr * len_indicator = fvar_lenTester_map[freeVar][i]; - tout << mk_pp(len_indicator, m) << ": "; - bool effectiveInScope = (internal_variable_set.find(len_indicator) != internal_variable_set.end()); - tout << (effectiveInScope ? "in scope" : "NOT in scope"); - tout << std::endl; - } - ); - - int i = 0; - for (; i < lenTesterCount; ++i) { - expr * len_indicator_pre = fvar_lenTester_map[freeVar][i]; - // check whether this is in scope as well - if (internal_variable_set.find(len_indicator_pre) == internal_variable_set.end()) { - TRACE("str", tout << "length indicator " << mk_pp(len_indicator_pre, m) << " not in scope" << std::endl;); + zstring s; + if (!u.str.is_string(str, s)) return; + if (s == "yes") { + TRACE("str", tout << "start finite model test for " << mk_pp(testvar, m) << std::endl;); + ptr_vector & vars = finite_model_test_varlists[testvar]; + for (ptr_vector::iterator it = vars.begin(); it != vars.end(); ++it) { + expr * v = *it; + bool v_has_eqc = false; + get_eqc_value(v, v_has_eqc); + if (v_has_eqc) { + TRACE("str", tout << "variable " << mk_pp(v,m) << " already equivalent to a string constant" << std::endl;); continue; } + // check for any sort of existing length tester we might interfere with + if (m_params.m_UseBinarySearch) { + if (binary_search_len_tester_stack.contains(v) && !binary_search_len_tester_stack[v].empty()) { + TRACE("str", tout << "already found existing length testers for " << mk_pp(v, m) << std::endl;); + continue; + } else { + // start binary search as normal + expr_ref implLhs(ctx.mk_eq_atom(testvar, str), m); + expr_ref implRhs(binary_search_length_test(v, NULL, ""), m); + assert_implication(implLhs, implRhs); + } + } else { + bool map_effectively_empty = false; + if (!fvar_len_count_map.contains(v)) { + map_effectively_empty = true; + } + if (!map_effectively_empty) { + map_effectively_empty = true; + ptr_vector indicator_set = fvar_lenTester_map[v]; + for (ptr_vector::iterator it = indicator_set.begin(); it != indicator_set.end(); ++it) { + expr * indicator = *it; + if (internal_variable_set.find(indicator) != internal_variable_set.end()) { + map_effectively_empty = false; + break; + } + } + } + + if (map_effectively_empty) { + TRACE("str", tout << "no existing length testers for " << mk_pp(v, m) << std::endl;); + rational v_len; + rational v_lower_bound; + rational v_upper_bound; + expr_ref vLengthExpr(mk_strlen(v), m); + if (get_len_value(v, v_len)) { + TRACE("str", tout << "length = " << v_len.to_string() << std::endl;); + v_lower_bound = v_len; + v_upper_bound = v_len; + } else { + bool lower_bound_exists = lower_bound(vLengthExpr, v_lower_bound); + bool upper_bound_exists = upper_bound(vLengthExpr, v_upper_bound); + TRACE("str", tout << "bounds = [" << (lower_bound_exists?v_lower_bound.to_string():"?") + << ".." << (upper_bound_exists?v_upper_bound.to_string():"?") << "]" << std::endl;); + + // make sure the bounds are non-negative + if (lower_bound_exists && v_lower_bound.is_neg()) { + v_lower_bound = rational::zero(); + } + if (upper_bound_exists && v_upper_bound.is_neg()) { + v_upper_bound = rational::zero(); + } + + if (lower_bound_exists && upper_bound_exists) { + // easiest case. we will search within these bounds + } else if (upper_bound_exists && !lower_bound_exists) { + // search between 0 and the upper bound + v_lower_bound == rational::zero(); + } else if (lower_bound_exists && !upper_bound_exists) { + // check some finite portion of the search space + v_upper_bound = v_lower_bound + rational(10); + } else { + // no bounds information + v_lower_bound = rational::zero(); + v_upper_bound = v_lower_bound + rational(10); + } + } + // now create a fake length tester over this finite disjunction of lengths + + fvar_len_count_map[v] = 1; + unsigned int testNum = fvar_len_count_map[v]; + + expr_ref indicator(mk_internal_lenTest_var(v, testNum), m); + SASSERT(indicator); + m_trail.push_back(indicator); + + fvar_lenTester_map[v].shrink(0); + fvar_lenTester_map[v].push_back(indicator); + lenTester_fvar_map[indicator] = v; + + expr_ref_vector orList(m); + expr_ref_vector andList(m); + + for (rational l = v_lower_bound; l <= v_upper_bound; l += rational::one()) { + zstring lStr = zstring(l.to_string().c_str()); + expr_ref str_indicator(mk_string(lStr), m); + expr_ref or_expr(ctx.mk_eq_atom(indicator, str_indicator), m); + orList.push_back(or_expr); + expr_ref and_expr(ctx.mk_eq_atom(or_expr, ctx.mk_eq_atom(vLengthExpr, m_autil.mk_numeral(l, true))), m); + andList.push_back(and_expr); + } + andList.push_back(mk_or(orList)); + expr_ref implLhs(ctx.mk_eq_atom(testvar, str), m); + expr_ref implRhs(mk_and(andList), m); + assert_implication(implLhs, implRhs); + } else { + TRACE("str", tout << "already found existing length testers for " << mk_pp(v, m) << std::endl;); + continue; + } + } + } // foreach (v in vars) + } // (s == "yes") + } + + void theory_str::more_len_tests(expr * lenTester, zstring lenTesterValue) { + ast_manager & m = get_manager(); + if (lenTester_fvar_map.contains(lenTester)) { + expr * fVar = lenTester_fvar_map[lenTester]; + expr * toAssert = gen_len_val_options_for_free_var(fVar, lenTester, lenTesterValue); + TRACE("str", tout << "asserting more length tests for free variable " << mk_ismt2_pp(fVar, m) << std::endl;); + if (toAssert != NULL) { + assert_axiom(toAssert); + } + } + } + + void theory_str::more_value_tests(expr * valTester, zstring valTesterValue) { + ast_manager & m = get_manager(); + + expr * fVar = valueTester_fvar_map[valTester]; + if (m_params.m_UseBinarySearch) { + if (!binary_search_len_tester_stack.contains(fVar) || binary_search_len_tester_stack[fVar].empty()) { + TRACE("str", tout << "WARNING: no active length testers for " << mk_pp(fVar, m) << std::endl;); + NOT_IMPLEMENTED_YET(); + } + expr * effectiveLenInd = binary_search_len_tester_stack[fVar].back(); + bool hasEqcValue; + expr * len_indicator_value = get_eqc_value(effectiveLenInd, hasEqcValue); + if (!hasEqcValue) { + TRACE("str", tout << "WARNING: length tester " << mk_pp(effectiveLenInd, m) << " at top of stack for " << mk_pp(fVar, m) << " has no EQC value" << std::endl;); + } else { + // safety check + zstring effectiveLenIndiStr; + u.str.is_string(len_indicator_value, effectiveLenIndiStr); + if (effectiveLenIndiStr == "more" || effectiveLenIndiStr == "less") { + TRACE("str", tout << "ERROR: illegal state -- requesting 'more value tests' but a length tester is not yet concrete!" << std::endl;); + UNREACHABLE(); + } + expr * valueAssert = gen_free_var_options(fVar, effectiveLenInd, effectiveLenIndiStr, valTester, valTesterValue); + TRACE("str", tout << "asserting more value tests for free variable " << mk_ismt2_pp(fVar, m) << std::endl;); + if (valueAssert != NULL) { + assert_axiom(valueAssert); + } + } + } else { + int lenTesterCount = fvar_lenTester_map[fVar].size(); + + expr * effectiveLenInd = NULL; + zstring effectiveLenIndiStr = ""; + for (int i = 0; i < lenTesterCount; ++i) { + expr * len_indicator_pre = fvar_lenTester_map[fVar][i]; bool indicatorHasEqcValue = false; expr * len_indicator_value = get_eqc_value(len_indicator_pre, indicatorHasEqcValue); - TRACE("str", tout << "length indicator " << mk_ismt2_pp(len_indicator_pre, m) << - " = " << mk_ismt2_pp(len_indicator_value, m) << std::endl;); if (indicatorHasEqcValue) { zstring len_pIndiStr; u.str.is_string(len_indicator_value, len_pIndiStr); @@ -10281,318 +6974,3623 @@ expr * theory_str::gen_len_val_options_for_free_var(expr * freeVar, expr * lenTe effectiveLenIndiStr = len_pIndiStr; break; } - } else { - if (lenTesterInCbEq != len_indicator_pre) { - TRACE("str", tout << "WARNING: length indicator " << mk_ismt2_pp(len_indicator_pre, m) - << " does not have an equivalence class value." - << " i = " << i << ", lenTesterCount = " << lenTesterCount << std::endl;); - if (i > 0) { - effectiveLenInd = fvar_lenTester_map[freeVar][i - 1]; - bool effectiveHasEqcValue; - expr * effective_eqc_value = get_eqc_value(effectiveLenInd, effectiveHasEqcValue); - bool effectiveInScope = (internal_variable_set.find(effectiveLenInd) != internal_variable_set.end()); - TRACE("str", tout << "checking effective length indicator " << mk_pp(effectiveLenInd, m) << ": " - << (effectiveInScope ? "in scope" : "NOT in scope") << ", "; - if (effectiveHasEqcValue) { - tout << "~= " << mk_pp(effective_eqc_value, m); - } else { - tout << "no eqc string constant"; - } - tout << std::endl;); - if (effectiveLenInd == lenTesterInCbEq) { - effectiveLenIndiStr = lenTesterValue; + } + } + expr * valueAssert = gen_free_var_options(fVar, effectiveLenInd, effectiveLenIndiStr, valTester, valTesterValue); + TRACE("str", tout << "asserting more value tests for free variable " << mk_ismt2_pp(fVar, m) << std::endl;); + if (valueAssert != NULL) { + assert_axiom(valueAssert); + } + } + } + + bool theory_str::free_var_attempt(expr * nn1, expr * nn2) { + ast_manager & m = get_manager(); + zstring nn2_str; + if (internal_lenTest_vars.contains(nn1) && u.str.is_string(nn2, nn2_str)) { + TRACE("str", tout << "acting on equivalence between length tester var " << mk_ismt2_pp(nn1, m) + << " and constant " << mk_ismt2_pp(nn2, m) << std::endl;); + more_len_tests(nn1, nn2_str); + return true; + } else if (internal_valTest_vars.contains(nn1) && u.str.is_string(nn2, nn2_str)) { + if (nn2_str == "more") { + TRACE("str", tout << "acting on equivalence between value var " << mk_ismt2_pp(nn1, m) + << " and constant " << mk_ismt2_pp(nn2, m) << std::endl;); + more_value_tests(nn1, nn2_str); + } + return true; + } else if (internal_unrollTest_vars.contains(nn1)) { + return true; + } else { + return false; + } + } + + void theory_str::handle_equality(expr * lhs, expr * rhs) { + ast_manager & m = get_manager(); + context & ctx = get_context(); + // both terms must be of sort String + sort * lhs_sort = m.get_sort(lhs); + sort * rhs_sort = m.get_sort(rhs); + sort * str_sort = u.str.mk_string_sort(); + + if (lhs_sort != str_sort || rhs_sort != str_sort) { + TRACE("str", tout << "skip equality: not String sort" << std::endl;); + return; + } + + /* // temporarily disabled, we are borrowing these testers for something else + if (m_params.m_FiniteOverlapModels && !finite_model_test_varlists.empty()) { + if (finite_model_test_varlists.contains(lhs)) { + finite_model_test(lhs, rhs); return; + } else if (finite_model_test_varlists.contains(rhs)) { + finite_model_test(rhs, lhs); return; + } + } + */ + + if (free_var_attempt(lhs, rhs) || free_var_attempt(rhs, lhs)) { + return; + } + + if (u.str.is_concat(to_app(lhs)) && u.str.is_concat(to_app(rhs))) { + bool nn1HasEqcValue = false; + bool nn2HasEqcValue = false; + expr * nn1_value = get_eqc_value(lhs, nn1HasEqcValue); + expr * nn2_value = get_eqc_value(rhs, nn2HasEqcValue); + if (nn1HasEqcValue && !nn2HasEqcValue) { + simplify_parent(rhs, nn1_value); + } + if (!nn1HasEqcValue && nn2HasEqcValue) { + simplify_parent(lhs, nn2_value); + } + + expr * nn1_arg0 = to_app(lhs)->get_arg(0); + expr * nn1_arg1 = to_app(lhs)->get_arg(1); + expr * nn2_arg0 = to_app(rhs)->get_arg(0); + expr * nn2_arg1 = to_app(rhs)->get_arg(1); + if (nn1_arg0 == nn2_arg0 && in_same_eqc(nn1_arg1, nn2_arg1)) { + TRACE("str", tout << "skip: lhs arg0 == rhs arg0" << std::endl;); + return; + } + + if (nn1_arg1 == nn2_arg1 && in_same_eqc(nn1_arg0, nn2_arg0)) { + TRACE("str", tout << "skip: lhs arg1 == rhs arg1" << std::endl;); + return; + } + } + + if (opt_DeferEQCConsistencyCheck) { + TRACE("str", tout << "opt_DeferEQCConsistencyCheck is set; deferring new_eq_check call" << std::endl;); + } else { + // newEqCheck() -- check consistency wrt. existing equivalence classes + if (!new_eq_check(lhs, rhs)) { + return; + } + } + + // BEGIN new_eq_handler() in strTheory + + { + rational nn1Len, nn2Len; + bool nn1Len_exists = get_len_value(lhs, nn1Len); + bool nn2Len_exists = get_len_value(rhs, nn2Len); + expr * emptyStr = mk_string(""); + + if (nn1Len_exists && nn1Len.is_zero()) { + if (!in_same_eqc(lhs, emptyStr) && rhs != emptyStr) { + expr_ref eql(ctx.mk_eq_atom(mk_strlen(lhs), mk_int(0)), m); + expr_ref eqr(ctx.mk_eq_atom(lhs, emptyStr), m); + expr_ref toAssert(ctx.mk_eq_atom(eql, eqr), m); + assert_axiom(toAssert); + } + } + + if (nn2Len_exists && nn2Len.is_zero()) { + if (!in_same_eqc(rhs, emptyStr) && lhs != emptyStr) { + expr_ref eql(ctx.mk_eq_atom(mk_strlen(rhs), mk_int(0)), m); + expr_ref eqr(ctx.mk_eq_atom(rhs, emptyStr), m); + expr_ref toAssert(ctx.mk_eq_atom(eql, eqr), m); + assert_axiom(toAssert); + } + } + } + + instantiate_str_eq_length_axiom(ctx.get_enode(lhs), ctx.get_enode(rhs)); + + // group terms by equivalence class (groupNodeInEqc()) + + std::set eqc_concat_lhs; + std::set eqc_var_lhs; + std::set eqc_const_lhs; + group_terms_by_eqc(lhs, eqc_concat_lhs, eqc_var_lhs, eqc_const_lhs); + + std::set eqc_concat_rhs; + std::set eqc_var_rhs; + std::set eqc_const_rhs; + group_terms_by_eqc(rhs, eqc_concat_rhs, eqc_var_rhs, eqc_const_rhs); + + TRACE("str", + tout << "lhs eqc:" << std::endl; + tout << "Concats:" << std::endl; + for (std::set::iterator it = eqc_concat_lhs.begin(); it != eqc_concat_lhs.end(); ++it) { + expr * ex = *it; + tout << mk_ismt2_pp(ex, get_manager()) << std::endl; + } + tout << "Variables:" << std::endl; + for (std::set::iterator it = eqc_var_lhs.begin(); it != eqc_var_lhs.end(); ++it) { + expr * ex = *it; + tout << mk_ismt2_pp(ex, get_manager()) << std::endl; + } + tout << "Constants:" << std::endl; + for (std::set::iterator it = eqc_const_lhs.begin(); it != eqc_const_lhs.end(); ++it) { + expr * ex = *it; + tout << mk_ismt2_pp(ex, get_manager()) << std::endl; + } + + tout << "rhs eqc:" << std::endl; + tout << "Concats:" << std::endl; + for (std::set::iterator it = eqc_concat_rhs.begin(); it != eqc_concat_rhs.end(); ++it) { + expr * ex = *it; + tout << mk_ismt2_pp(ex, get_manager()) << std::endl; + } + tout << "Variables:" << std::endl; + for (std::set::iterator it = eqc_var_rhs.begin(); it != eqc_var_rhs.end(); ++it) { + expr * ex = *it; + tout << mk_ismt2_pp(ex, get_manager()) << std::endl; + } + tout << "Constants:" << std::endl; + for (std::set::iterator it = eqc_const_rhs.begin(); it != eqc_const_rhs.end(); ++it) { + expr * ex = *it; + tout << mk_ismt2_pp(ex, get_manager()) << std::endl; + } + ); + + // step 1: Concat == Concat + int hasCommon = 0; + if (eqc_concat_lhs.size() != 0 && eqc_concat_rhs.size() != 0) { + std::set::iterator itor1 = eqc_concat_lhs.begin(); + std::set::iterator itor2 = eqc_concat_rhs.begin(); + for (; itor1 != eqc_concat_lhs.end(); itor1++) { + if (eqc_concat_rhs.find(*itor1) != eqc_concat_rhs.end()) { + hasCommon = 1; + break; + } + } + for (; itor2 != eqc_concat_rhs.end(); itor2++) { + if (eqc_concat_lhs.find(*itor2) != eqc_concat_lhs.end()) { + hasCommon = 1; + break; + } + } + if (hasCommon == 0) { + if (opt_ConcatOverlapAvoid) { + bool found = false; + // check each pair and take the first ones that won't immediately overlap + for (itor1 = eqc_concat_lhs.begin(); itor1 != eqc_concat_lhs.end() && !found; ++itor1) { + expr * concat_lhs = *itor1; + for (itor2 = eqc_concat_rhs.begin(); itor2 != eqc_concat_rhs.end() && !found; ++itor2) { + expr * concat_rhs = *itor2; + if (will_result_in_overlap(concat_lhs, concat_rhs)) { + TRACE("str", tout << "Concats " << mk_pp(concat_lhs, m) << " and " + << mk_pp(concat_rhs, m) << " will result in overlap; skipping." << std::endl;); } else { - if (effectiveHasEqcValue) { - u.str.is_string(effective_eqc_value, effectiveLenIndiStr); - } else { - NOT_IMPLEMENTED_YET(); - } + TRACE("str", tout << "Concats " << mk_pp(concat_lhs, m) << " and " + << mk_pp(concat_rhs, m) << " won't overlap. Simplifying here." << std::endl;); + simplify_concat_equality(concat_lhs, concat_rhs); + found = true; + break; } } - break; } - // lenTesterInCbEq == len_indicator_pre - else { - if (lenTesterValue != "more") { - effectiveLenInd = len_indicator_pre; - effectiveLenIndiStr = lenTesterValue; + if (!found) { + TRACE("str", tout << "All pairs of concats expected to overlap, falling back." << std::endl;); + simplify_concat_equality(*(eqc_concat_lhs.begin()), *(eqc_concat_rhs.begin())); + } + } else { + // default behaviour + simplify_concat_equality(*(eqc_concat_lhs.begin()), *(eqc_concat_rhs.begin())); + } + } + } + + // step 2: Concat == Constant + + if (eqc_const_lhs.size() != 0) { + expr * conStr = *(eqc_const_lhs.begin()); + std::set::iterator itor2 = eqc_concat_rhs.begin(); + for (; itor2 != eqc_concat_rhs.end(); itor2++) { + solve_concat_eq_str(*itor2, conStr); + } + } else if (eqc_const_rhs.size() != 0) { + expr* conStr = *(eqc_const_rhs.begin()); + std::set::iterator itor1 = eqc_concat_lhs.begin(); + for (; itor1 != eqc_concat_lhs.end(); itor1++) { + solve_concat_eq_str(*itor1, conStr); + } + } + + // simplify parents wrt. the equivalence class of both sides + bool nn1HasEqcValue = false; + bool nn2HasEqcValue = false; + // we want the Z3str2 eqc check here... + expr * nn1_value = z3str2_get_eqc_value(lhs, nn1HasEqcValue); + expr * nn2_value = z3str2_get_eqc_value(rhs, nn2HasEqcValue); + if (nn1HasEqcValue && !nn2HasEqcValue) { + simplify_parent(rhs, nn1_value); + } + + if (!nn1HasEqcValue && nn2HasEqcValue) { + simplify_parent(lhs, nn2_value); + } + + expr * nn1EqConst = NULL; + std::set nn1EqUnrollFuncs; + get_eqc_allUnroll(lhs, nn1EqConst, nn1EqUnrollFuncs); + expr * nn2EqConst = NULL; + std::set nn2EqUnrollFuncs; + get_eqc_allUnroll(rhs, nn2EqConst, nn2EqUnrollFuncs); + + if (nn2EqConst != NULL) { + for (std::set::iterator itor1 = nn1EqUnrollFuncs.begin(); itor1 != nn1EqUnrollFuncs.end(); itor1++) { + process_unroll_eq_const_str(*itor1, nn2EqConst); + } + } + + if (nn1EqConst != NULL) { + for (std::set::iterator itor2 = nn2EqUnrollFuncs.begin(); itor2 != nn2EqUnrollFuncs.end(); itor2++) { + process_unroll_eq_const_str(*itor2, nn1EqConst); + } + } + + } + + void theory_str::set_up_axioms(expr * ex) { + ast_manager & m = get_manager(); + context & ctx = get_context(); + + sort * ex_sort = m.get_sort(ex); + sort * str_sort = u.str.mk_string_sort(); + sort * bool_sort = m.mk_bool_sort(); + + family_id m_arith_fid = m.mk_family_id("arith"); + sort * int_sort = m.mk_sort(m_arith_fid, INT_SORT); + + if (ex_sort == str_sort) { + TRACE("str", tout << "setting up axioms for " << mk_ismt2_pp(ex, get_manager()) << + ": expr is of sort String" << std::endl;); + // set up basic string axioms + enode * n = ctx.get_enode(ex); + SASSERT(n); + m_basicstr_axiom_todo.push_back(n); + TRACE("str", tout << "add " << mk_pp(ex, m) << " to m_basicstr_axiom_todo" << std::endl;); + + + if (is_app(ex)) { + app * ap = to_app(ex); + if (u.str.is_concat(ap)) { + // if ex is a concat, set up concat axioms later + m_concat_axiom_todo.push_back(n); + // we also want to check whether we can eval this concat, + // in case the rewriter did not totally finish with this term + m_concat_eval_todo.push_back(n); + } else if (u.str.is_length(ap)) { + // if the argument is a variable, + // keep track of this for later, we'll need it during model gen + expr * var = ap->get_arg(0); + app * aVar = to_app(var); + if (aVar->get_num_args() == 0 && !u.str.is_string(aVar)) { + input_var_in_len.insert(var); + } + } else if (u.str.is_at(ap) || u.str.is_extract(ap) || u.str.is_replace(ap)) { + m_library_aware_axiom_todo.push_back(n); + } else if (u.str.is_itos(ap)) { + TRACE("str", tout << "found string-integer conversion term: " << mk_pp(ex, get_manager()) << std::endl;); + string_int_conversion_terms.push_back(ap); + m_library_aware_axiom_todo.push_back(n); + } else if (ap->get_num_args() == 0 && !u.str.is_string(ap)) { + // if ex is a variable, add it to our list of variables + TRACE("str", tout << "tracking variable " << mk_ismt2_pp(ap, get_manager()) << std::endl;); + variable_set.insert(ex); + ctx.mark_as_relevant(ex); + // this might help?? + theory_var v = mk_var(n); + TRACE("str", tout << "variable " << mk_ismt2_pp(ap, get_manager()) << " is #" << v << std::endl;); + } + } + } else if (ex_sort == bool_sort) { + TRACE("str", tout << "setting up axioms for " << mk_ismt2_pp(ex, get_manager()) << + ": expr is of sort Bool" << std::endl;); + // set up axioms for boolean terms + + ensure_enode(ex); + if (ctx.e_internalized(ex)) { + enode * n = ctx.get_enode(ex); + SASSERT(n); + + if (is_app(ex)) { + app * ap = to_app(ex); + if (u.str.is_prefix(ap) || u.str.is_suffix(ap) || u.str.is_contains(ap) || u.str.is_in_re(ap)) { + m_library_aware_axiom_todo.push_back(n); + } + } + } else { + TRACE("str", tout << "WARNING: Bool term " << mk_ismt2_pp(ex, get_manager()) << " not internalized. Delaying axiom setup to prevent a crash." << std::endl;); + ENSURE(!search_started); // infinite loop prevention + m_delayed_axiom_setup_terms.push_back(ex); + return; + } + } else if (ex_sort == int_sort) { + TRACE("str", tout << "setting up axioms for " << mk_ismt2_pp(ex, get_manager()) << + ": expr is of sort Int" << std::endl;); + // set up axioms for integer terms + enode * n = ensure_enode(ex); + SASSERT(n); + + if (is_app(ex)) { + app * ap = to_app(ex); + // TODO indexof2/lastindexof + if (u.str.is_index(ap) /* || is_Indexof2(ap) || is_LastIndexof(ap) */) { + m_library_aware_axiom_todo.push_back(n); + } else if (u.str.is_stoi(ap)) { + TRACE("str", tout << "found string-integer conversion term: " << mk_pp(ex, get_manager()) << std::endl;); + string_int_conversion_terms.push_back(ap); + m_library_aware_axiom_todo.push_back(n); + } + } + } else { + TRACE("str", tout << "setting up axioms for " << mk_ismt2_pp(ex, get_manager()) << + ": expr is of wrong sort, ignoring" << std::endl;); + } + + // if expr is an application, recursively inspect all arguments + if (is_app(ex)) { + app * term = (app*)ex; + unsigned num_args = term->get_num_args(); + for (unsigned i = 0; i < num_args; i++) { + set_up_axioms(term->get_arg(i)); + } + } + } + + void theory_str::add_theory_assumptions(expr_ref_vector & assumptions) { + TRACE("str", tout << "add overlap assumption for theory_str" << std::endl;); + symbol strOverlap("!!TheoryStrOverlapAssumption!!"); + seq_util m_sequtil(get_manager()); + sort * s = get_manager().mk_bool_sort(); + m_theoryStrOverlapAssumption_term = expr_ref(get_manager().mk_const(strOverlap, s), get_manager()); + assumptions.push_back(get_manager().mk_not(m_theoryStrOverlapAssumption_term)); + } + + lbool theory_str::validate_unsat_core(expr_ref_vector & unsat_core) { + bool assumptionFound = false; + + app * target_term = to_app(get_manager().mk_not(m_theoryStrOverlapAssumption_term)); + get_context().internalize(target_term, false); + for (unsigned i = 0; i < unsat_core.size(); ++i) { + app * core_term = to_app(unsat_core.get(i)); + // not sure if this is the correct way to compare terms in this context + enode * e1; + enode * e2; + e1 = get_context().get_enode(target_term); + e2 = get_context().get_enode(core_term); + if (e1 == e2) { + TRACE("str", tout << "overlap detected in unsat core, changing UNSAT to UNKNOWN" << std::endl;); + assumptionFound = true; + return l_undef; + } + } + + return l_false; + } + + void theory_str::init_search_eh() { + ast_manager & m = get_manager(); + context & ctx = get_context(); + + TRACE("str", + tout << "dumping all asserted formulas:" << std::endl; + unsigned nFormulas = ctx.get_num_asserted_formulas(); + for (unsigned i = 0; i < nFormulas; ++i) { + expr * ex = ctx.get_asserted_formula(i); + tout << mk_ismt2_pp(ex, m) << (ctx.is_relevant(ex) ? " (rel)" : " (NOT REL)") << std::endl; + } + ); + /* + * Recursive descent through all asserted formulas to set up axioms. + * Note that this is just the input structure and not necessarily things + * that we know to be true or false. We're just doing this to see + * which terms are explicitly mentioned. + */ + unsigned nFormulas = ctx.get_num_asserted_formulas(); + for (unsigned i = 0; i < nFormulas; ++i) { + expr * ex = ctx.get_asserted_formula(i); + set_up_axioms(ex); + } + + /* + * Similar recursive descent, except over all initially assigned terms. + * This is done to find equalities between terms, etc. that we otherwise + * might not get a chance to see. + */ + + /* + expr_ref_vector assignments(m); + ctx.get_assignments(assignments); + for (expr_ref_vector::iterator i = assignments.begin(); i != assignments.end(); ++i) { + expr * ex = *i; + if (m.is_eq(ex)) { + TRACE("str", tout << "processing assignment " << mk_ismt2_pp(ex, m) << + ": expr is equality" << std::endl;); + app * eq = (app*)ex; + SASSERT(eq->get_num_args() == 2); + expr * lhs = eq->get_arg(0); + expr * rhs = eq->get_arg(1); + + enode * e_lhs = ctx.get_enode(lhs); + enode * e_rhs = ctx.get_enode(rhs); + std::pair eq_pair(e_lhs, e_rhs); + m_str_eq_todo.push_back(eq_pair); + } else { + TRACE("str", tout << "processing assignment " << mk_ismt2_pp(ex, m) + << ": expr ignored" << std::endl;); + } + } + */ + + // this might be cheating but we need to make sure that certain maps are populated + // before the first call to new_eq_eh() + propagate(); + + TRACE("str", tout << "search started" << std::endl;); + search_started = true; + } + + void theory_str::new_eq_eh(theory_var x, theory_var y) { + //TRACE("str", tout << "new eq: v#" << x << " = v#" << y << std::endl;); + TRACE("str", tout << "new eq: " << mk_ismt2_pp(get_enode(x)->get_owner(), get_manager()) << " = " << + mk_ismt2_pp(get_enode(y)->get_owner(), get_manager()) << std::endl;); + + /* + if (m_find.find(x) == m_find.find(y)) { + return; + } + */ + handle_equality(get_enode(x)->get_owner(), get_enode(y)->get_owner()); + + // replicate Z3str2 behaviour: merge eqc **AFTER** handle_equality + m_find.merge(x, y); + } + + void theory_str::new_diseq_eh(theory_var x, theory_var y) { + //TRACE("str", tout << "new diseq: v#" << x << " != v#" << y << std::endl;); + TRACE("str", tout << "new diseq: " << mk_ismt2_pp(get_enode(x)->get_owner(), get_manager()) << " != " << + mk_ismt2_pp(get_enode(y)->get_owner(), get_manager()) << std::endl;); + } + + void theory_str::relevant_eh(app * n) { + TRACE("str", tout << "relevant: " << mk_ismt2_pp(n, get_manager()) << std::endl;); + } + + void theory_str::assign_eh(bool_var v, bool is_true) { + context & ctx = get_context(); + TRACE("str", tout << "assert: v" << v << " #" << ctx.bool_var2expr(v)->get_id() << " is_true: " << is_true << std::endl;); + } + + void theory_str::push_scope_eh() { + theory::push_scope_eh(); + m_trail_stack.push_scope(); + + sLevel += 1; + TRACE("str", tout << "push to " << sLevel << std::endl;); + TRACE_CODE(if (is_trace_enabled("t_str_dump_assign_on_scope_change")) { dump_assignments(); }); + } + + void theory_str::recursive_check_variable_scope(expr * ex) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + if (is_app(ex)) { + app * a = to_app(ex); + if (a->get_num_args() == 0) { + // we only care about string variables + sort * s = m.get_sort(ex); + sort * string_sort = u.str.mk_string_sort(); + if (s != string_sort) { + return; + } + // base case: string constant / var + if (u.str.is_string(a)) { + return; + } else { + // assume var + if (variable_set.find(ex) == variable_set.end() + && internal_variable_set.find(ex) == internal_variable_set.end()) { + TRACE("str", tout << "WARNING: possible reference to out-of-scope variable " << mk_pp(ex, m) << std::endl;); + } + } + } else { + for (unsigned i = 0; i < a->get_num_args(); ++i) { + recursive_check_variable_scope(a->get_arg(i)); + } + } + } + } + + void theory_str::check_variable_scope() { + if (!opt_CheckVariableScope) { + return; + } + + if (!is_trace_enabled("t_str_detail")) { + return; + } + + TRACE("str", tout << "checking scopes of variables in the current assignment" << std::endl;); + + context & ctx = get_context(); + ast_manager & m = get_manager(); + + expr_ref_vector assignments(m); + ctx.get_assignments(assignments); + for (expr_ref_vector::iterator i = assignments.begin(); i != assignments.end(); ++i) { + expr * ex = *i; + recursive_check_variable_scope(ex); + } + } + + void theory_str::pop_scope_eh(unsigned num_scopes) { + sLevel -= num_scopes; + TRACE("str", tout << "pop " << num_scopes << " to " << sLevel << std::endl;); + context & ctx = get_context(); + ast_manager & m = get_manager(); + + TRACE_CODE(if (is_trace_enabled("t_str_dump_assign_on_scope_change")) { dump_assignments(); }); + + // list of expr* to remove from cut_var_map + ptr_vector cutvarmap_removes; + + obj_map >::iterator varItor = cut_var_map.begin(); + while (varItor != cut_var_map.end()) { + expr * e = varItor->m_key; + std::stack & val = cut_var_map[varItor->m_key]; + while ((val.size() > 0) && (val.top()->level != 0) && (val.top()->level >= sLevel)) { + TRACE("str", tout << "remove cut info for " << mk_pp(e, m) << std::endl; print_cut_var(e, tout);); + T_cut * aCut = val.top(); + val.pop(); + // dealloc(aCut); + } + if (val.size() == 0) { + cutvarmap_removes.insert(varItor->m_key); + } + varItor++; + } + + if (!cutvarmap_removes.empty()) { + ptr_vector::iterator it = cutvarmap_removes.begin(); + for (; it != cutvarmap_removes.end(); ++it) { + expr * ex = *it; + cut_var_map.remove(ex); + } + } + + ptr_vector new_m_basicstr; + for (ptr_vector::iterator it = m_basicstr_axiom_todo.begin(); it != m_basicstr_axiom_todo.end(); ++it) { + enode * e = *it; + app * a = e->get_owner(); + TRACE("str", tout << "consider deleting " << mk_pp(a, get_manager()) + << ", enode scope level is " << e->get_iscope_lvl() + << std::endl;); + if (e->get_iscope_lvl() <= (unsigned)sLevel) { + new_m_basicstr.push_back(e); + } + } + m_basicstr_axiom_todo.reset(); + m_basicstr_axiom_todo = new_m_basicstr; + + m_trail_stack.pop_scope(num_scopes); + theory::pop_scope_eh(num_scopes); + + //check_variable_scope(); + } + + void theory_str::dump_assignments() { + TRACE_CODE( + ast_manager & m = get_manager(); + context & ctx = get_context(); + tout << "dumping all assignments:" << std::endl; + expr_ref_vector assignments(m); + ctx.get_assignments(assignments); + for (expr_ref_vector::iterator i = assignments.begin(); i != assignments.end(); ++i) { + expr * ex = *i; + tout << mk_ismt2_pp(ex, m) << (ctx.is_relevant(ex) ? "" : " (NOT REL)") << std::endl; + } + ); + } + + void theory_str::classify_ast_by_type(expr * node, std::map & varMap, + std::map & concatMap, std::map & unrollMap) { + + // check whether the node is a string variable; + // testing set membership here bypasses several expensive checks. + // note that internal variables don't count if they're only length tester / value tester vars. + if (variable_set.find(node) != variable_set.end() + && internal_lenTest_vars.find(node) == internal_lenTest_vars.end() + && internal_valTest_vars.find(node) == internal_valTest_vars.end() + && internal_unrollTest_vars.find(node) == internal_unrollTest_vars.end()) { + if (varMap[node] != 1) { + TRACE("str", tout << "new variable: " << mk_pp(node, get_manager()) << std::endl;); + } + varMap[node] = 1; + } + // check whether the node is a function that we want to inspect + else if (is_app(node)) { + app * aNode = to_app(node); + if (u.str.is_length(aNode)) { + // Length + return; + } else if (u.str.is_concat(aNode)) { + expr * arg0 = aNode->get_arg(0); + expr * arg1 = aNode->get_arg(1); + bool arg0HasEq = false; + bool arg1HasEq = false; + expr * arg0Val = get_eqc_value(arg0, arg0HasEq); + expr * arg1Val = get_eqc_value(arg1, arg1HasEq); + + int canskip = 0; + zstring tmp; + u.str.is_string(arg0Val, tmp); + if (arg0HasEq && tmp.empty()) { + canskip = 1; + } + u.str.is_string(arg1Val, tmp); + if (canskip == 0 && arg1HasEq && tmp.empty()) { + canskip = 1; + } + if (canskip == 0 && concatMap.find(node) == concatMap.end()) { + concatMap[node] = 1; + } + } else if (u.re.is_unroll(aNode)) { + // Unroll + if (unrollMap.find(node) == unrollMap.end()) { + unrollMap[node] = 1; + } + } + // recursively visit all arguments + for (unsigned i = 0; i < aNode->get_num_args(); ++i) { + expr * arg = aNode->get_arg(i); + classify_ast_by_type(arg, varMap, concatMap, unrollMap); + } + } + } + + // NOTE: this function used to take an argument `Z3_ast node`; + // it was not used and so was removed from the signature + void theory_str::classify_ast_by_type_in_positive_context(std::map & varMap, + std::map & concatMap, std::map & unrollMap) { + + context & ctx = get_context(); + ast_manager & m = get_manager(); + expr_ref_vector assignments(m); + ctx.get_assignments(assignments); + + for (expr_ref_vector::iterator it = assignments.begin(); it != assignments.end(); ++it) { + expr * argAst = *it; + // the original code jumped through some hoops to check whether the AST node + // is a function, then checked whether that function is "interesting". + // however, the only thing that's considered "interesting" is an equality predicate. + // so we bypass a huge amount of work by doing the following... + + if (m.is_eq(argAst)) { + TRACE("str", tout + << "eq ast " << mk_pp(argAst, m) << " is between args of sort " + << m.get_sort(to_app(argAst)->get_arg(0))->get_name() + << std::endl;); + classify_ast_by_type(argAst, varMap, concatMap, unrollMap); + } + } + } + + inline expr * theory_str::get_alias_index_ast(std::map & aliasIndexMap, expr * node) { + if (aliasIndexMap.find(node) != aliasIndexMap.end()) + return aliasIndexMap[node]; + else + return node; + } + + inline expr * theory_str::getMostLeftNodeInConcat(expr * node) { + app * aNode = to_app(node); + if (!u.str.is_concat(aNode)) { + return node; + } else { + expr * concatArgL = aNode->get_arg(0); + return getMostLeftNodeInConcat(concatArgL); + } + } + + inline expr * theory_str::getMostRightNodeInConcat(expr * node) { + app * aNode = to_app(node); + if (!u.str.is_concat(aNode)) { + return node; + } else { + expr * concatArgR = aNode->get_arg(1); + return getMostRightNodeInConcat(concatArgR); + } + } + + void theory_str::trace_ctx_dep(std::ofstream & tout, + std::map & aliasIndexMap, + std::map & var_eq_constStr_map, + std::map > & var_eq_concat_map, + std::map > & var_eq_unroll_map, + std::map & concat_eq_constStr_map, + std::map > & concat_eq_concat_map, + std::map > & unrollGroupMap) { +#ifdef _TRACE + context & ctx = get_context(); + ast_manager & mgr = get_manager(); + { + tout << "(0) alias: variables" << std::endl; + std::map > aliasSumMap; + std::map::iterator itor0 = aliasIndexMap.begin(); + for (; itor0 != aliasIndexMap.end(); itor0++) { + aliasSumMap[itor0->second][itor0->first] = 1; + } + std::map >::iterator keyItor = aliasSumMap.begin(); + for (; keyItor != aliasSumMap.end(); keyItor++) { + tout << " * "; + tout << mk_pp(keyItor->first, mgr); + tout << " : "; + std::map::iterator innerItor = keyItor->second.begin(); + for (; innerItor != keyItor->second.end(); innerItor++) { + tout << mk_pp(innerItor->first, mgr); + tout << ", "; + } + tout << std::endl; + } + tout << std::endl; + } + + { + tout << "(1) var = constStr:" << std::endl; + std::map::iterator itor1 = var_eq_constStr_map.begin(); + for (; itor1 != var_eq_constStr_map.end(); itor1++) { + tout << " * "; + tout << mk_pp(itor1->first, mgr); + tout << " = "; + tout << mk_pp(itor1->second, mgr); + if (!in_same_eqc(itor1->first, itor1->second)) { + tout << " (not true in ctx)"; + } + tout << std::endl; + } + tout << std::endl; + } + + { + tout << "(2) var = concat:" << std::endl; + std::map >::iterator itor2 = var_eq_concat_map.begin(); + for (; itor2 != var_eq_concat_map.end(); itor2++) { + tout << " * "; + tout << mk_pp(itor2->first, mgr); + tout << " = { "; + std::map::iterator i_itor = itor2->second.begin(); + for (; i_itor != itor2->second.end(); i_itor++) { + tout << mk_pp(i_itor->first, mgr); + tout << ", "; + } + tout << std::endl; + } + tout << std::endl; + } + + { + tout << "(3) var = unrollFunc:" << std::endl; + std::map >::iterator itor2 = var_eq_unroll_map.begin(); + for (; itor2 != var_eq_unroll_map.end(); itor2++) { + tout << " * " << mk_pp(itor2->first, mgr) << " = { "; + std::map::iterator i_itor = itor2->second.begin(); + for (; i_itor != itor2->second.end(); i_itor++) { + tout << mk_pp(i_itor->first, mgr) << ", "; + } + tout << " }" << std::endl; + } + tout << std::endl; + } + + { + tout << "(4) concat = constStr:" << std::endl; + std::map::iterator itor3 = concat_eq_constStr_map.begin(); + for (; itor3 != concat_eq_constStr_map.end(); itor3++) { + tout << " * "; + tout << mk_pp(itor3->first, mgr); + tout << " = "; + tout << mk_pp(itor3->second, mgr); + tout << std::endl; + + } + tout << std::endl; + } + + { + tout << "(5) eq concats:" << std::endl; + std::map >::iterator itor4 = concat_eq_concat_map.begin(); + for (; itor4 != concat_eq_concat_map.end(); itor4++) { + if (itor4->second.size() > 1) { + std::map::iterator i_itor = itor4->second.begin(); + tout << " * "; + for (; i_itor != itor4->second.end(); i_itor++) { + tout << mk_pp(i_itor->first, mgr); + tout << " , "; + } + tout << std::endl; + } + } + tout << std::endl; + } + + { + tout << "(6) eq unrolls:" << std::endl; + std::map >::iterator itor5 = unrollGroupMap.begin(); + for (; itor5 != unrollGroupMap.end(); itor5++) { + tout << " * "; + std::set::iterator i_itor = itor5->second.begin(); + for (; i_itor != itor5->second.end(); i_itor++) { + tout << mk_pp(*i_itor, mgr) << ", "; + } + tout << std::endl; + } + tout << std::endl; + } + + { + tout << "(7) unroll = concats:" << std::endl; + std::map >::iterator itor5 = unrollGroupMap.begin(); + for (; itor5 != unrollGroupMap.end(); itor5++) { + tout << " * "; + expr * unroll = itor5->first; + tout << mk_pp(unroll, mgr) << std::endl; + enode * e_curr = ctx.get_enode(unroll); + enode * e_curr_end = e_curr; + do { + app * curr = e_curr->get_owner(); + if (u.str.is_concat(curr)) { + tout << " >>> " << mk_pp(curr, mgr) << std::endl; + } + e_curr = e_curr->get_next(); + } while (e_curr != e_curr_end); + tout << std::endl; + } + tout << std::endl; + } +#else + return; +#endif // _TRACE + } + + + /* + * Dependence analysis from current context assignment + * - "freeVarMap" contains a set of variables that doesn't constrained by Concats. + * But it's possible that it's bounded by unrolls + * For the case of + * (1) var1 = unroll(r1, t1) + * var1 is in the freeVarMap + * > should unroll r1 for var1 + * (2) var1 = unroll(r1, t1) /\ var1 = Concat(var2, var3) + * var2, var3 are all in freeVar + * > should split the unroll function so that var2 and var3 are bounded by new unrolls + */ + int theory_str::ctx_dep_analysis(std::map & strVarMap, std::map & freeVarMap, + std::map > & unrollGroupMap, std::map > & var_eq_concat_map) { + std::map concatMap; + std::map unrollMap; + std::map aliasIndexMap; + std::map var_eq_constStr_map; + std::map concat_eq_constStr_map; + std::map > var_eq_unroll_map; + std::map > concat_eq_concat_map; + std::map > depMap; + + context & ctx = get_context(); + ast_manager & m = get_manager(); + + // note that the old API concatenated these assignments into + // a massive conjunction; we may have the opportunity to avoid that here + expr_ref_vector assignments(m); + ctx.get_assignments(assignments); + + // Step 1: get variables / concat AST appearing in the context + // the thing we iterate over should just be variable_set - internal_variable_set + // so we avoid computing the set difference (but this might be slower) + for(obj_hashtable::iterator it = variable_set.begin(); it != variable_set.end(); ++it) { + expr* var = *it; + if (internal_variable_set.find(var) == internal_variable_set.end()) { + TRACE("str", tout << "new variable: " << mk_pp(var, m) << std::endl;); + strVarMap[*it] = 1; + } + } + classify_ast_by_type_in_positive_context(strVarMap, concatMap, unrollMap); + + std::map aliasUnrollSet; + std::map::iterator unrollItor = unrollMap.begin(); + for (; unrollItor != unrollMap.end(); ++unrollItor) { + if (aliasUnrollSet.find(unrollItor->first) != aliasUnrollSet.end()) { + continue; + } + expr * aRoot = NULL; + enode * e_currEqc = ctx.get_enode(unrollItor->first); + enode * e_curr = e_currEqc; + do { + app * curr = e_currEqc->get_owner(); + if (u.re.is_unroll(curr)) { + if (aRoot == NULL) { + aRoot = curr; + } + aliasUnrollSet[curr] = aRoot; + } + e_currEqc = e_currEqc->get_next(); + } while (e_currEqc != e_curr); + } + + for (unrollItor = unrollMap.begin(); unrollItor != unrollMap.end(); unrollItor++) { + expr * unrFunc = unrollItor->first; + expr * urKey = aliasUnrollSet[unrFunc]; + unrollGroupMap[urKey].insert(unrFunc); + } + + // Step 2: collect alias relation + // e.g. suppose we have the equivalence class {x, y, z}; + // then we set aliasIndexMap[y] = x + // and aliasIndexMap[z] = x + + std::map::iterator varItor = strVarMap.begin(); + for (; varItor != strVarMap.end(); ++varItor) { + if (aliasIndexMap.find(varItor->first) != aliasIndexMap.end()) { + continue; + } + expr * aRoot = NULL; + expr * curr = varItor->first; + do { + if (variable_set.find(curr) != variable_set.end()) { + if (aRoot == NULL) { + aRoot = curr; + } else { + aliasIndexMap[curr] = aRoot; + } + } + curr = get_eqc_next(curr); + } while (curr != varItor->first); + } + + // Step 3: Collect interested cases + + varItor = strVarMap.begin(); + for (; varItor != strVarMap.end(); ++varItor) { + expr * deAliasNode = get_alias_index_ast(aliasIndexMap, varItor->first); + // Case 1: variable = string constant + // e.g. z = "str1" ::= var_eq_constStr_map[z] = "str1" + + if (var_eq_constStr_map.find(deAliasNode) == var_eq_constStr_map.end()) { + bool nodeHasEqcValue = false; + expr * nodeValue = get_eqc_value(deAliasNode, nodeHasEqcValue); + if (nodeHasEqcValue) { + var_eq_constStr_map[deAliasNode] = nodeValue; + } + } + + // Case 2: var_eq_concat + // e.g. z = concat("str1", b) ::= var_eq_concat[z][concat(c, "str2")] = 1 + // var_eq_unroll + // e.g. z = unroll(...) ::= var_eq_unroll[z][unroll(...)] = 1 + + if (var_eq_concat_map.find(deAliasNode) == var_eq_concat_map.end()) { + expr * curr = get_eqc_next(deAliasNode); + while (curr != deAliasNode) { + app * aCurr = to_app(curr); + // collect concat + if (u.str.is_concat(aCurr)) { + expr * arg0 = aCurr->get_arg(0); + expr * arg1 = aCurr->get_arg(1); + bool arg0HasEqcValue = false; + bool arg1HasEqcValue = false; + expr * arg0_value = get_eqc_value(arg0, arg0HasEqcValue); + expr * arg1_value = get_eqc_value(arg1, arg1HasEqcValue); + + bool is_arg0_emptyStr = false; + if (arg0HasEqcValue) { + zstring strval; + u.str.is_string(arg0_value, strval); + if (strval.empty()) { + is_arg0_emptyStr = true; + } + } + + bool is_arg1_emptyStr = false; + if (arg1HasEqcValue) { + zstring strval; + u.str.is_string(arg1_value, strval); + if (strval.empty()) { + is_arg1_emptyStr = true; + } + } + + if (!is_arg0_emptyStr && !is_arg1_emptyStr) { + var_eq_concat_map[deAliasNode][curr] = 1; + } + } else if (u.re.is_unroll(to_app(curr))) { + var_eq_unroll_map[deAliasNode][curr] = 1; + } + + curr = get_eqc_next(curr); + } + } + + } // for(varItor in strVarMap) + + // -------------------------------------------------- + // * collect aliasing relation among eq concats + // e.g EQC={concat1, concat2, concat3} + // concats_eq_Index_map[concat2] = concat1 + // concats_eq_Index_map[concat3] = concat1 + // -------------------------------------------------- + + std::map concats_eq_index_map; + std::map::iterator concatItor = concatMap.begin(); + for(; concatItor != concatMap.end(); ++concatItor) { + if (concats_eq_index_map.find(concatItor->first) != concats_eq_index_map.end()) { + continue; + } + expr * aRoot = NULL; + expr * curr = concatItor->first; + do { + if (u.str.is_concat(to_app(curr))) { + if (aRoot == NULL) { + aRoot = curr; + } else { + concats_eq_index_map[curr] = aRoot; + } + } + curr = get_eqc_next(curr); + } while (curr != concatItor->first); + } + + concatItor = concatMap.begin(); + for(; concatItor != concatMap.end(); ++concatItor) { + expr * deAliasConcat = NULL; + if (concats_eq_index_map.find(concatItor->first) != concats_eq_index_map.end()) { + deAliasConcat = concats_eq_index_map[concatItor->first]; + } else { + deAliasConcat = concatItor->first; + } + + // (3) concat_eq_conststr, e.g. concat(a,b) = "str1" + if (concat_eq_constStr_map.find(deAliasConcat) == concat_eq_constStr_map.end()) { + bool nodeHasEqcValue = false; + expr * nodeValue = get_eqc_value(deAliasConcat, nodeHasEqcValue); + if (nodeHasEqcValue) { + concat_eq_constStr_map[deAliasConcat] = nodeValue; + } + } + + // (4) concat_eq_concat, e.g. + // concat(a,b) = concat("str1", c) AND z = concat(a,b) AND z = concat(e,f) + if (concat_eq_concat_map.find(deAliasConcat) == concat_eq_concat_map.end()) { + expr * curr = deAliasConcat; + do { + if (u.str.is_concat(to_app(curr))) { + // curr cannot be reduced + if (concatMap.find(curr) != concatMap.end()) { + concat_eq_concat_map[deAliasConcat][curr] = 1; + } + } + curr = get_eqc_next(curr); + } while (curr != deAliasConcat); + } + } + + // print some debugging info + TRACE("str", trace_ctx_dep(tout, aliasIndexMap, var_eq_constStr_map, + var_eq_concat_map, var_eq_unroll_map, + concat_eq_constStr_map, concat_eq_concat_map, unrollGroupMap);); + + if (!contain_pair_bool_map.empty()) { + compute_contains(aliasIndexMap, concats_eq_index_map, var_eq_constStr_map, concat_eq_constStr_map, var_eq_concat_map); + } + + // step 4: dependence analysis + + // (1) var = string constant + for (std::map::iterator itor = var_eq_constStr_map.begin(); + itor != var_eq_constStr_map.end(); ++itor) { + expr * var = get_alias_index_ast(aliasIndexMap, itor->first); + expr * strAst = itor->second; + depMap[var][strAst] = 1; + } + + // (2) var = concat + for (std::map >::iterator itor = var_eq_concat_map.begin(); + itor != var_eq_concat_map.end(); ++itor) { + expr * var = get_alias_index_ast(aliasIndexMap, itor->first); + for (std::map::iterator itor1 = itor->second.begin(); itor1 != itor->second.end(); ++itor1) { + expr * concat = itor1->first; + std::map inVarMap; + std::map inConcatMap; + std::map inUnrollMap; + classify_ast_by_type(concat, inVarMap, inConcatMap, inUnrollMap); + for (std::map::iterator itor2 = inVarMap.begin(); itor2 != inVarMap.end(); ++itor2) { + expr * varInConcat = get_alias_index_ast(aliasIndexMap, itor2->first); + if (!(depMap[var].find(varInConcat) != depMap[var].end() && depMap[var][varInConcat] == 1)) { + depMap[var][varInConcat] = 2; + } + } + } + } + + for (std::map >::iterator itor = var_eq_unroll_map.begin(); + itor != var_eq_unroll_map.end(); itor++) { + expr * var = get_alias_index_ast(aliasIndexMap, itor->first); + for (std::map::iterator itor1 = itor->second.begin(); itor1 != itor->second.end(); itor1++) { + expr * unrollFunc = itor1->first; + std::map inVarMap; + std::map inConcatMap; + std::map inUnrollMap; + classify_ast_by_type(unrollFunc, inVarMap, inConcatMap, inUnrollMap); + for (std::map::iterator itor2 = inVarMap.begin(); itor2 != inVarMap.end(); itor2++) { + expr * varInFunc = get_alias_index_ast(aliasIndexMap, itor2->first); + + TRACE("str", tout << "var in unroll = " << + mk_ismt2_pp(itor2->first, m) << std::endl + << "dealiased var = " << mk_ismt2_pp(varInFunc, m) << std::endl;); + + // it's possible that we have both (Unroll $$_regVar_0 $$_unr_0) /\ (Unroll abcd $$_unr_0), + // while $$_regVar_0 = "abcd" + // have to exclude such cases + bool varHasValue = false; + get_eqc_value(varInFunc, varHasValue); + if (varHasValue) + continue; + + if (depMap[var].find(varInFunc) == depMap[var].end()) { + depMap[var][varInFunc] = 6; + } + } + } + } + + // (3) concat = string constant + for (std::map::iterator itor = concat_eq_constStr_map.begin(); + itor != concat_eq_constStr_map.end(); itor++) { + expr * concatAst = itor->first; + expr * constStr = itor->second; + std::map inVarMap; + std::map inConcatMap; + std::map inUnrollMap; + classify_ast_by_type(concatAst, inVarMap, inConcatMap, inUnrollMap); + for (std::map::iterator itor2 = inVarMap.begin(); itor2 != inVarMap.end(); itor2++) { + expr * varInConcat = get_alias_index_ast(aliasIndexMap, itor2->first); + if (!(depMap[varInConcat].find(constStr) != depMap[varInConcat].end() && depMap[varInConcat][constStr] == 1)) + depMap[varInConcat][constStr] = 3; + } + } + + // (4) equivalent concats + // - possibility 1 : concat("str", v1) = concat(concat(v2, v3), v4) = concat(v5, v6) + // ==> v2, v5 are constrained by "str" + // - possibility 2 : concat(v1, "str") = concat(v2, v3) = concat(v4, v5) + // ==> v2, v4 are constrained by "str" + //-------------------------------------------------------------- + + std::map mostLeftNodes; + std::map mostRightNodes; + + std::map mLIdxMap; + std::map > mLMap; + std::map mRIdxMap; + std::map > mRMap; + std::set nSet; + + for (std::map >::iterator itor = concat_eq_concat_map.begin(); + itor != concat_eq_concat_map.end(); itor++) { + mostLeftNodes.clear(); + mostRightNodes.clear(); + + expr * mLConst = NULL; + expr * mRConst = NULL; + + for (std::map::iterator itor1 = itor->second.begin(); itor1 != itor->second.end(); itor1++) { + expr * concatNode = itor1->first; + expr * mLNode = getMostLeftNodeInConcat(concatNode); + zstring strval; + if (u.str.is_string(to_app(mLNode), strval)) { + if (mLConst == NULL && strval.empty()) { + mLConst = mLNode; + } + } else { + mostLeftNodes[mLNode] = concatNode; + } + + expr * mRNode = getMostRightNodeInConcat(concatNode); + if (u.str.is_string(to_app(mRNode), strval)) { + if (mRConst == NULL && strval.empty()) { + mRConst = mRNode; + } + } else { + mostRightNodes[mRNode] = concatNode; + } + } + + if (mLConst != NULL) { + // ------------------------------------------------------------------------------------- + // The left most variable in a concat is constrained by a constant string in eqc concat + // ------------------------------------------------------------------------------------- + // e.g. Concat(x, ...) = Concat("abc", ...) + // ------------------------------------------------------------------------------------- + for (std::map::iterator itor1 = mostLeftNodes.begin(); + itor1 != mostLeftNodes.end(); itor1++) { + expr * deVar = get_alias_index_ast(aliasIndexMap, itor1->first); + if (depMap[deVar].find(mLConst) == depMap[deVar].end() || depMap[deVar][mLConst] != 1) { + depMap[deVar][mLConst] = 4; + } + } + } + + { + // ------------------------------------------------------------------------------------- + // The left most variables in eqc concats are constrained by each other + // ------------------------------------------------------------------------------------- + // e.g. concat(x, ...) = concat(u, ...) = ... + // x and u are constrained by each other + // ------------------------------------------------------------------------------------- + nSet.clear(); + std::map::iterator itl = mostLeftNodes.begin(); + for (; itl != mostLeftNodes.end(); itl++) { + bool lfHasEqcValue = false; + get_eqc_value(itl->first, lfHasEqcValue); + if (lfHasEqcValue) + continue; + expr * deVar = get_alias_index_ast(aliasIndexMap, itl->first); + nSet.insert(deVar); + } + + if (nSet.size() > 1) { + int lId = -1; + for (std::set::iterator itor2 = nSet.begin(); itor2 != nSet.end(); itor2++) { + if (mLIdxMap.find(*itor2) != mLIdxMap.end()) { + lId = mLIdxMap[*itor2]; break; } } - } // !indicatorHasEqcValue - } // for (i : [0..lenTesterCount-1]) - if (effectiveLenIndiStr == "more" || effectiveLenIndiStr == "") { - TRACE("str", tout << "length is not fixed; generating length tester options for free var" << std::endl;); - expr_ref indicator(m); - unsigned int testNum = 0; - - TRACE("str", tout << "effectiveLenIndiStr = " << effectiveLenIndiStr - << ", i = " << i << ", lenTesterCount = " << lenTesterCount << "\n";); - - if (i == lenTesterCount) { - fvar_len_count_map[freeVar] = fvar_len_count_map[freeVar] + 1; - testNum = fvar_len_count_map[freeVar]; - indicator = mk_internal_lenTest_var(freeVar, testNum); - fvar_lenTester_map[freeVar].push_back(indicator); - lenTester_fvar_map.insert(indicator, freeVar); - } else { - indicator = fvar_lenTester_map[freeVar][i]; - refresh_theory_var(indicator); - testNum = i + 1; + if (lId == -1) + lId = mLMap.size(); + for (std::set::iterator itor2 = nSet.begin(); itor2 != nSet.end(); itor2++) { + bool itorHasEqcValue = false; + get_eqc_value(*itor2, itorHasEqcValue); + if (itorHasEqcValue) + continue; + mLIdxMap[*itor2] = lId; + mLMap[lId].insert(*itor2); + } } + } + + if (mRConst != NULL) { + for (std::map::iterator itor1 = mostRightNodes.begin(); + itor1 != mostRightNodes.end(); itor1++) { + expr * deVar = get_alias_index_ast(aliasIndexMap, itor1->first); + if (depMap[deVar].find(mRConst) == depMap[deVar].end() || depMap[deVar][mRConst] != 1) { + depMap[deVar][mRConst] = 5; + } + } + } + + { + nSet.clear(); + std::map::iterator itr = mostRightNodes.begin(); + for (; itr != mostRightNodes.end(); itr++) { + expr * deVar = get_alias_index_ast(aliasIndexMap, itr->first); + nSet.insert(deVar); + } + if (nSet.size() > 1) { + int rId = -1; + std::set::iterator itor2 = nSet.begin(); + for (; itor2 != nSet.end(); itor2++) { + if (mRIdxMap.find(*itor2) != mRIdxMap.end()) { + rId = mRIdxMap[*itor2]; + break; + } + } + if (rId == -1) + rId = mRMap.size(); + for (itor2 = nSet.begin(); itor2 != nSet.end(); itor2++) { + bool rHasEqcValue = false; + get_eqc_value(*itor2, rHasEqcValue); + if (rHasEqcValue) + continue; + mRIdxMap[*itor2] = rId; + mRMap[rId].insert(*itor2); + } + } + } + } + + // print the dependence map + TRACE("str", + tout << "Dependence Map" << std::endl; + for(std::map >::iterator itor = depMap.begin(); itor != depMap.end(); itor++) { + tout << mk_pp(itor->first, m); + rational nnLen; + bool nnLen_exists = get_len_value(itor->first, nnLen); + tout << " [len = " << (nnLen_exists ? nnLen.to_string() : "?") << "] \t-->\t"; + for (std::map::iterator itor1 = itor->second.begin(); itor1 != itor->second.end(); itor1++) { + tout << mk_pp(itor1->first, m) << "(" << itor1->second << "), "; + } + tout << std::endl; + } + ); + + // step, errr, 5: compute free variables based on the dependence map + + // the case dependence map is empty, every var in VarMap is free + //--------------------------------------------------------------- + // remove L/R most var in eq concat since they are constrained with each other + std::map > lrConstrainedMap; + for (std::map >::iterator itor = mLMap.begin(); itor != mLMap.end(); itor++) { + for (std::set::iterator it1 = itor->second.begin(); it1 != itor->second.end(); it1++) { + std::set::iterator it2 = it1; + it2++; + for (; it2 != itor->second.end(); it2++) { + expr * n1 = *it1; + expr * n2 = *it2; + lrConstrainedMap[n1][n2] = 1; + lrConstrainedMap[n2][n1] = 1; + } + } + } + for (std::map >::iterator itor = mRMap.begin(); itor != mRMap.end(); itor++) { + for (std::set::iterator it1 = itor->second.begin(); it1 != itor->second.end(); it1++) { + std::set::iterator it2 = it1; + it2++; + for (; it2 != itor->second.end(); it2++) { + expr * n1 = *it1; + expr * n2 = *it2; + lrConstrainedMap[n1][n2] = 1; + lrConstrainedMap[n2][n1] = 1; + } + } + } + + if (depMap.size() == 0) { + std::map::iterator itor = strVarMap.begin(); + for (; itor != strVarMap.end(); itor++) { + expr * var = get_alias_index_ast(aliasIndexMap, itor->first); + if (lrConstrainedMap.find(var) == lrConstrainedMap.end()) { + freeVarMap[var] = 1; + } else { + int lrConstainted = 0; + std::map::iterator lrit = freeVarMap.begin(); + for (; lrit != freeVarMap.end(); lrit++) { + if (lrConstrainedMap[var].find(lrit->first) != lrConstrainedMap[var].end()) { + lrConstainted = 1; + break; + } + } + if (lrConstainted == 0) { + freeVarMap[var] = 1; + } + } + } + } else { + // if the keys in aliasIndexMap are not contained in keys in depMap, they are free + // e.g., x= y /\ x = z /\ t = "abc" + // aliasIndexMap[y]= x, aliasIndexMap[z] = x + // depMap t ~ "abc"(1) + // x should be free + std::map::iterator itor2 = strVarMap.begin(); + for (; itor2 != strVarMap.end(); itor2++) { + if (aliasIndexMap.find(itor2->first) != aliasIndexMap.end()) { + expr * var = aliasIndexMap[itor2->first]; + if (depMap.find(var) == depMap.end()) { + if (lrConstrainedMap.find(var) == lrConstrainedMap.end()) { + freeVarMap[var] = 1; + } else { + int lrConstainted = 0; + std::map::iterator lrit = freeVarMap.begin(); + for (; lrit != freeVarMap.end(); lrit++) { + if (lrConstrainedMap[var].find(lrit->first) != lrConstrainedMap[var].end()) { + lrConstainted = 1; + break; + } + } + if (lrConstainted == 0) { + freeVarMap[var] = 1; + } + } + } + } else if (aliasIndexMap.find(itor2->first) == aliasIndexMap.end()) { + // if a variable is not in aliasIndexMap and not in depMap, it's free + if (depMap.find(itor2->first) == depMap.end()) { + expr * var = itor2->first; + if (lrConstrainedMap.find(var) == lrConstrainedMap.end()) { + freeVarMap[var] = 1; + } else { + int lrConstainted = 0; + std::map::iterator lrit = freeVarMap.begin(); + for (; lrit != freeVarMap.end(); lrit++) { + if (lrConstrainedMap[var].find(lrit->first) != lrConstrainedMap[var].end()) { + lrConstainted = 1; + break; + } + } + if (lrConstainted == 0) { + freeVarMap[var] = 1; + } + } + } + } + } + + std::map >::iterator itor = depMap.begin(); + for (; itor != depMap.end(); itor++) { + for (std::map::iterator itor1 = itor->second.begin(); itor1 != itor->second.end(); itor1++) { + if (variable_set.find(itor1->first) != variable_set.end()) { // expr type = var + expr * var = get_alias_index_ast(aliasIndexMap, itor1->first); + // if a var is dep on itself and all dependence are type 2, it's a free variable + // e.g {y --> x(2), y(2), m --> m(2), n(2)} y,m are free + { + if (depMap.find(var) == depMap.end()) { + if (freeVarMap.find(var) == freeVarMap.end()) { + if (lrConstrainedMap.find(var) == lrConstrainedMap.end()) { + freeVarMap[var] = 1; + } else { + int lrConstainted = 0; + std::map::iterator lrit = freeVarMap.begin(); + for (; lrit != freeVarMap.end(); lrit++) { + if (lrConstrainedMap[var].find(lrit->first) != lrConstrainedMap[var].end()) { + lrConstainted = 1; + break; + } + } + if (lrConstainted == 0) { + freeVarMap[var] = 1; + } + } + + } else { + freeVarMap[var] = freeVarMap[var] + 1; + } + } + } + } + } + } + } + + return 0; + } + + // Check agreement between integer and string theories for the term a = (str.to-int S). + // Returns true if axioms were added, and false otherwise. + bool theory_str::finalcheck_str2int(app * a) { + bool axiomAdd = false; + context & ctx = get_context(); + ast_manager & m = get_manager(); + + expr * S = a->get_arg(0); + + // check integer theory + rational Ival; + bool Ival_exists = get_value(a, Ival); + if (Ival_exists) { + TRACE("str", tout << "integer theory assigns " << mk_pp(a, m) << " = " << Ival.to_string() << std::endl;); + // if that value is not -1, we can assert (str.to-int S) = Ival --> S = "Ival" + if (!Ival.is_minus_one()) { + zstring Ival_str(Ival.to_string().c_str()); + expr_ref premise(ctx.mk_eq_atom(a, m_autil.mk_numeral(Ival, true)), m); + expr_ref conclusion(ctx.mk_eq_atom(S, mk_string(Ival_str)), m); + expr_ref axiom(rewrite_implication(premise, conclusion), m); + if (!string_int_axioms.contains(axiom)) { + string_int_axioms.insert(axiom); + assert_axiom(axiom); + m_trail_stack.push(insert_obj_trail(string_int_axioms, axiom)); + axiomAdd = true; + } + } + } else { + TRACE("str", tout << "integer theory has no assignment for " << mk_pp(a, m) << std::endl;); + NOT_IMPLEMENTED_YET(); + } + + return axiomAdd; + } + + bool theory_str::finalcheck_int2str(app * a) { + bool axiomAdd = false; + context & ctx = get_context(); + ast_manager & m = get_manager(); + + expr * N = a->get_arg(0); + + // check string theory + bool Sval_expr_exists; + expr * Sval_expr = get_eqc_value(a, Sval_expr_exists); + if (Sval_expr_exists) { + zstring Sval; + u.str.is_string(Sval_expr, Sval); + TRACE("str", tout << "string theory assigns \"" << mk_pp(a, m) << " = " << Sval << "\n";); + // empty string --> integer value < 0 + if (Sval.empty()) { + // ignore this. we should already assert the axiom for what happens when the string is "" + } else { + // nonempty string --> convert to correct integer value, or disallow it + rational convertedRepresentation(0); + rational ten(10); + bool conversionOK = true; + for (unsigned i = 0; i < Sval.length(); ++i) { + char digit = (int)Sval[i]; + if (isdigit((int)digit)) { + std::string sDigit(1, digit); + int val = atoi(sDigit.c_str()); + convertedRepresentation = (ten * convertedRepresentation) + rational(val); + } else { + // not a digit, invalid + TRACE("str", tout << "str.to-int argument contains non-digit character '" << digit << "'" << std::endl;); + conversionOK = false; + break; + } + } + if (conversionOK) { + expr_ref premise(ctx.mk_eq_atom(a, mk_string(Sval)), m); + expr_ref conclusion(ctx.mk_eq_atom(N, m_autil.mk_numeral(convertedRepresentation, true)), m); + expr_ref axiom(rewrite_implication(premise, conclusion), m); + if (!string_int_axioms.contains(axiom)) { + string_int_axioms.insert(axiom); + assert_axiom(axiom); + m_trail_stack.push(insert_obj_trail(string_int_axioms, axiom)); + axiomAdd = true; + } + } else { + expr_ref axiom(m.mk_not(ctx.mk_eq_atom(a, mk_string(Sval))), m); + // always assert this axiom because this is a conflict clause + assert_axiom(axiom); + axiomAdd = true; + } + } + } else { + TRACE("str", tout << "string theory has no assignment for " << mk_pp(a, m) << std::endl;); + NOT_IMPLEMENTED_YET(); + } + return axiomAdd; + } + + void theory_str::collect_var_concat(expr * node, std::set & varSet, std::set & concatSet) { + if (variable_set.find(node) != variable_set.end()) { + if (internal_lenTest_vars.find(node) == internal_lenTest_vars.end()) { + varSet.insert(node); + } + } + else if (is_app(node)) { + app * aNode = to_app(node); + if (u.str.is_length(aNode)) { + // Length + return; + } + if (u.str.is_concat(aNode)) { + expr * arg0 = aNode->get_arg(0); + expr * arg1 = aNode->get_arg(1); + if (concatSet.find(node) == concatSet.end()) { + concatSet.insert(node); + } + } + // recursively visit all arguments + for (unsigned i = 0; i < aNode->get_num_args(); ++i) { + expr * arg = aNode->get_arg(i); + collect_var_concat(arg, varSet, concatSet); + } + } + } + + bool theory_str::propagate_length_within_eqc(expr * var) { + bool res = false; + ast_manager & m = get_manager(); + context & ctx = get_context(); + + TRACE("str", tout << "propagate_length_within_eqc: " << mk_ismt2_pp(var, m) << std::endl ;); + + enode * n_eq_enode = ctx.get_enode(var); + rational varLen; + if (! get_len_value(var, varLen)) { + bool hasLen = false; + expr * nodeWithLen= var; + do { + if (get_len_value(nodeWithLen, varLen)) { + hasLen = true; + break; + } + nodeWithLen = get_eqc_next(nodeWithLen); + } while (nodeWithLen != var); + + if (hasLen) { + // var = nodeWithLen --> |var| = |nodeWithLen| + expr_ref_vector l_items(m); + expr_ref varEqNode(ctx.mk_eq_atom(var, nodeWithLen), m); + l_items.push_back(varEqNode); + + expr_ref nodeWithLenExpr (mk_strlen(nodeWithLen), m); + expr_ref varLenExpr (mk_int(varLen), m); + expr_ref lenEqNum(ctx.mk_eq_atom(nodeWithLenExpr, varLenExpr), m); + l_items.push_back(lenEqNum); + + expr_ref axl(m.mk_and(l_items.size(), l_items.c_ptr()), m); + expr_ref varLen(mk_strlen(var), m); + expr_ref axr(ctx.mk_eq_atom(varLen, mk_int(varLen)), m); + assert_implication(axl, axr); + TRACE("str", tout << mk_ismt2_pp(axl, m) << std::endl << " ---> " << std::endl << mk_ismt2_pp(axr, m);); + res = true; + } + } + return res; + } + + bool theory_str::propagate_length(std::set & varSet, std::set & concatSet, std::map & exprLenMap) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + expr_ref_vector assignments(m); + ctx.get_assignments(assignments); + bool axiomAdded = false; + // collect all concats in context + for (expr_ref_vector::iterator it = assignments.begin(); it != assignments.end(); ++it) { + if (! ctx.is_relevant(*it)) { + continue; + } + if (m.is_eq(*it)) { + collect_var_concat(*it, varSet, concatSet); + } + } + // iterate each concat + // if a concat doesn't have length info, check if the length of all leaf nodes can be resolved + for (std::set::iterator it = concatSet.begin(); it != concatSet.end(); it++) { + expr * concat = *it; + rational lenValue; + expr_ref concatlenExpr (mk_strlen(concat), m) ; + bool allLeafResolved = true; + if (! get_value(concatlenExpr, lenValue)) { + // the length fo concat is unresolved yet + if (get_len_value(concat, lenValue)) { + // but all leaf nodes have length information + TRACE("str", tout << "* length pop-up: " << mk_ismt2_pp(concat, m) << "| = " << lenValue << std::endl;); + std::set leafNodes; + get_unique_non_concat_nodes(concat, leafNodes); + expr_ref_vector l_items(m); + for (std::set::iterator leafIt = leafNodes.begin(); leafIt != leafNodes.end(); ++leafIt) { + rational leafLenValue; + if (get_len_value(*leafIt, leafLenValue)) { + expr_ref leafItLenExpr (mk_strlen(*leafIt), m); + expr_ref leafLenValueExpr (mk_int(leafLenValue), m); + expr_ref lcExpr (ctx.mk_eq_atom(leafItLenExpr, leafLenValueExpr), m); + l_items.push_back(lcExpr); + } else { + allLeafResolved = false; + break; + } + } + if (allLeafResolved) { + expr_ref axl(m.mk_and(l_items.size(), l_items.c_ptr()), m); + expr_ref lenValueExpr (mk_int(lenValue), m); + expr_ref axr(ctx.mk_eq_atom(concatlenExpr, lenValueExpr), m); + assert_implication(axl, axr); + TRACE("str", tout << mk_ismt2_pp(axl, m) << std::endl << " ---> " << std::endl << mk_ismt2_pp(axr, m)<< std::endl;); + axiomAdded = true; + } + } + } + } + // if no concat length is propagated, check the length of variables. + if (! axiomAdded) { + for (std::set::iterator it = varSet.begin(); it != varSet.end(); it++) { + expr * var = *it; + rational lenValue; + expr_ref varlen (mk_strlen(var), m) ; + bool allLeafResolved = true; + if (! get_value(varlen, lenValue)) { + if (propagate_length_within_eqc(var)) { + axiomAdded = true; + } + } + } + + } + return axiomAdded; + } + + void theory_str::get_unique_non_concat_nodes(expr * node, std::set & argSet) { + app * a_node = to_app(node); + if (!u.str.is_concat(a_node)) { + argSet.insert(node); + return; + } else { + SASSERT(a_node->get_num_args() == 2); + expr * leftArg = a_node->get_arg(0); + expr * rightArg = a_node->get_arg(1); + get_unique_non_concat_nodes(leftArg, argSet); + get_unique_non_concat_nodes(rightArg, argSet); + } + } + + final_check_status theory_str::final_check_eh() { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + expr_ref_vector assignments(m); + ctx.get_assignments(assignments); + + if (opt_VerifyFinalCheckProgress) { + finalCheckProgressIndicator = false; + } + + TRACE("str", tout << "final check" << std::endl;); + TRACE_CODE(if (is_trace_enabled("t_str_dump_assign")) { dump_assignments(); }); + check_variable_scope(); + + if (opt_DeferEQCConsistencyCheck) { + TRACE("str", tout << "performing deferred EQC consistency check" << std::endl;); + std::set eqc_roots; + for (ptr_vector::const_iterator it = ctx.begin_enodes(); it != ctx.end_enodes(); ++it) { + enode * e = *it; + enode * root = e->get_root(); + eqc_roots.insert(root); + } + + bool found_inconsistency = false; + + for (std::set::iterator it = eqc_roots.begin(); it != eqc_roots.end(); ++it) { + enode * e = *it; + app * a = e->get_owner(); + if (!(m.get_sort(a) == u.str.mk_string_sort())) { + TRACE("str", tout << "EQC root " << mk_pp(a, m) << " not a string term; skipping" << std::endl;); + } else { + TRACE("str", tout << "EQC root " << mk_pp(a, m) << " is a string term. Checking this EQC" << std::endl;); + // first call check_concat_len_in_eqc() on each member of the eqc + enode * e_it = e; + enode * e_root = e_it; + do { + bool status = check_concat_len_in_eqc(e_it->get_owner()); + if (!status) { + TRACE("str", tout << "concat-len check asserted an axiom on " << mk_pp(e_it->get_owner(), m) << std::endl;); + found_inconsistency = true; + } + e_it = e_it->get_next(); + } while (e_it != e_root); + + // now grab any two distinct elements from the EQC and call new_eq_check() on them + enode * e1 = e; + enode * e2 = e1->get_next(); + if (e1 != e2) { + TRACE("str", tout << "deferred new_eq_check() over EQC of " << mk_pp(e1->get_owner(), m) << " and " << mk_pp(e2->get_owner(), m) << std::endl;); + bool result = new_eq_check(e1->get_owner(), e2->get_owner()); + if (!result) { + TRACE("str", tout << "new_eq_check found inconsistencies" << std::endl;); + found_inconsistency = true; + } + } + } + } + + if (found_inconsistency) { + TRACE("str", tout << "Found inconsistency in final check! Returning to search." << std::endl;); + return FC_CONTINUE; + } else { + TRACE("str", tout << "Deferred consistency check passed. Continuing in final check." << std::endl;); + } + } + + // run dependence analysis to find free string variables + std::map varAppearInAssign; + std::map freeVar_map; + std::map > unrollGroup_map; + std::map > var_eq_concat_map; + int conflictInDep = ctx_dep_analysis(varAppearInAssign, freeVar_map, unrollGroup_map, var_eq_concat_map); + if (conflictInDep == -1) { + // return Z3_TRUE; + return FC_DONE; + } + + // enhancement: improved backpropagation of string constants into var=concat terms + bool backpropagation_occurred = false; + for (std::map >::iterator veqc_map_it = var_eq_concat_map.begin(); + veqc_map_it != var_eq_concat_map.end(); ++veqc_map_it) { + expr * var = veqc_map_it->first; + for (std::map::iterator concat_map_it = veqc_map_it->second.begin(); + concat_map_it != veqc_map_it->second.end(); ++concat_map_it) { + app * concat = to_app(concat_map_it->first); + expr * concat_lhs = concat->get_arg(0); + expr * concat_rhs = concat->get_arg(1); + // If the concat LHS and RHS both have a string constant in their EQC, + // but the var does not, then we assert an axiom of the form + // (lhs = "lhs" AND rhs = "rhs") --> (Concat lhs rhs) = "lhsrhs" + bool concat_lhs_haseqc, concat_rhs_haseqc, var_haseqc; + expr * concat_lhs_str = get_eqc_value(concat_lhs, concat_lhs_haseqc); + expr * concat_rhs_str = get_eqc_value(concat_rhs, concat_rhs_haseqc); + expr * var_str = get_eqc_value(var, var_haseqc); + if (concat_lhs_haseqc && concat_rhs_haseqc && !var_haseqc) { + TRACE("str", tout << "backpropagate into " << mk_pp(var, m) << " = " << mk_pp(concat, m) << std::endl + << "LHS ~= " << mk_pp(concat_lhs_str, m) << " RHS ~= " << mk_pp(concat_rhs_str, m) << std::endl;); + zstring lhsString, rhsString; + u.str.is_string(concat_lhs_str, lhsString); + u.str.is_string(concat_rhs_str, rhsString); + zstring concatString = lhsString + rhsString; + expr_ref lhs1(ctx.mk_eq_atom(concat_lhs, concat_lhs_str), m); + expr_ref lhs2(ctx.mk_eq_atom(concat_rhs, concat_rhs_str), m); + expr_ref lhs(m.mk_and(lhs1, lhs2), m); + expr_ref rhs(ctx.mk_eq_atom(concat, mk_string(concatString)), m); + assert_implication(lhs, rhs); + backpropagation_occurred = true; + } + } + } + + if (backpropagation_occurred) { + TRACE("str", tout << "Resuming search due to axioms added by backpropagation." << std::endl;); + return FC_CONTINUE; + } + + // enhancement: improved backpropagation of length information + { + std::set varSet; + std::set concatSet; + std::map exprLenMap; + + bool length_propagation_occurred = propagate_length(varSet, concatSet, exprLenMap); + if (length_propagation_occurred) { + TRACE("str", tout << "Resuming search due to axioms added by length propagation." << std::endl;); + return FC_CONTINUE; + } + } + + bool needToAssignFreeVars = false; + std::set free_variables; + std::set unused_internal_variables; + { // Z3str2 free variables check + std::map::iterator itor = varAppearInAssign.begin(); + for (; itor != varAppearInAssign.end(); ++itor) { + /* + std::string vName = std::string(Z3_ast_to_string(ctx, itor->first)); + if (vName.length() >= 3 && vName.substr(0, 3) == "$$_") + continue; + */ + if (internal_variable_set.find(itor->first) != internal_variable_set.end() + || regex_variable_set.find(itor->first) != regex_variable_set.end()) { + // this can be ignored, I think + TRACE("str", tout << "free internal variable " << mk_pp(itor->first, m) << " ignored" << std::endl;); + continue; + } + bool hasEqcValue = false; + expr * eqcString = get_eqc_value(itor->first, hasEqcValue); + if (!hasEqcValue) { + TRACE("str", tout << "found free variable " << mk_pp(itor->first, m) << std::endl;); + needToAssignFreeVars = true; + free_variables.insert(itor->first); + // break; + } else { + // debug + TRACE("str", tout << "variable " << mk_pp(itor->first, m) << " = " << mk_pp(eqcString, m) << std::endl;); + } + } + } + + if (!needToAssignFreeVars) { + + // check string-int terms + bool addedStrIntAxioms = false; + for (unsigned i = 0; i < string_int_conversion_terms.size(); ++i) { + app * ex = to_app(string_int_conversion_terms[i].get()); + if (u.str.is_stoi(ex)) { + bool axiomAdd = finalcheck_str2int(ex); + if (axiomAdd) { + addedStrIntAxioms = true; + } + } else if (u.str.is_itos(ex)) { + bool axiomAdd = finalcheck_int2str(ex); + if (axiomAdd) { + addedStrIntAxioms = true; + } + } else { + UNREACHABLE(); + } + } + if (addedStrIntAxioms) { + TRACE("str", tout << "Resuming search due to addition of string-integer conversion axioms." << std::endl;); + return FC_CONTINUE; + } + + if (unused_internal_variables.empty()) { + TRACE("str", tout << "All variables are assigned. Done!" << std::endl;); + return FC_DONE; + } else { + TRACE("str", tout << "Assigning decoy values to free internal variables." << std::endl;); + for (std::set::iterator it = unused_internal_variables.begin(); it != unused_internal_variables.end(); ++it) { + expr * var = *it; + expr_ref assignment(m.mk_eq(var, mk_string("**unused**")), m); + assert_axiom(assignment); + } + return FC_CONTINUE; + } + } + + CTRACE("str", needToAssignFreeVars, + tout << "Need to assign values to the following free variables:" << std::endl; + for (std::set::iterator itx = free_variables.begin(); itx != free_variables.end(); ++itx) { + tout << mk_ismt2_pp(*itx, m) << std::endl; + } + tout << "freeVar_map has the following entries:" << std::endl; + for (std::map::iterator fvIt = freeVar_map.begin(); fvIt != freeVar_map.end(); fvIt++) { + expr * var = fvIt->first; + tout << mk_ismt2_pp(var, m) << std::endl; + } + ); + + // ----------------------------------------------------------- + // variables in freeVar are those not bounded by Concats + // classify variables in freeVarMap: + // (1) freeVar = unroll(r1, t1) + // (2) vars are not bounded by either concat or unroll + // ----------------------------------------------------------- + std::map > fv_unrolls_map; + std::set tmpSet; + expr * constValue = NULL; + for (std::map::iterator fvIt2 = freeVar_map.begin(); fvIt2 != freeVar_map.end(); fvIt2++) { + expr * var = fvIt2->first; + tmpSet.clear(); + get_eqc_allUnroll(var, constValue, tmpSet); + if (tmpSet.size() > 0) { + fv_unrolls_map[var] = tmpSet; + } + } + // erase var bounded by an unroll function from freeVar_map + for (std::map >::iterator fvIt3 = fv_unrolls_map.begin(); + fvIt3 != fv_unrolls_map.end(); fvIt3++) { + expr * var = fvIt3->first; + TRACE("str", tout << "erase free variable " << mk_pp(var, m) << " from freeVar_map, it is bounded by an Unroll" << std::endl;); + freeVar_map.erase(var); + } + + // collect the case: + // * Concat(X, Y) = unroll(r1, t1) /\ Concat(X, Y) = unroll(r2, t2) + // concatEqUnrollsMap[Concat(X, Y)] = {unroll(r1, t1), unroll(r2, t2)} + + std::map > concatEqUnrollsMap; + for (std::map >::iterator urItor = unrollGroup_map.begin(); + urItor != unrollGroup_map.end(); urItor++) { + expr * unroll = urItor->first; + expr * curr = unroll; + do { + if (u.str.is_concat(to_app(curr))) { + concatEqUnrollsMap[curr].insert(unroll); + concatEqUnrollsMap[curr].insert(unrollGroup_map[unroll].begin(), unrollGroup_map[unroll].end()); + } + enode * e_curr = ctx.get_enode(curr); + curr = e_curr->get_next()->get_owner(); + // curr = get_eqc_next(curr); + } while (curr != unroll); + } + + std::map > concatFreeArgsEqUnrollsMap; + std::set fvUnrollSet; + for (std::map >::iterator concatItor = concatEqUnrollsMap.begin(); + concatItor != concatEqUnrollsMap.end(); concatItor++) { + expr * concat = concatItor->first; + expr * concatArg1 = to_app(concat)->get_arg(0); + expr * concatArg2 = to_app(concat)->get_arg(1); + bool arg1Bounded = false; + bool arg2Bounded = false; + // arg1 + if (variable_set.find(concatArg1) != variable_set.end()) { + if (freeVar_map.find(concatArg1) == freeVar_map.end()) { + arg1Bounded = true; + } else { + fvUnrollSet.insert(concatArg1); + } + } else if (u.str.is_concat(to_app(concatArg1))) { + if (concatEqUnrollsMap.find(concatArg1) == concatEqUnrollsMap.end()) { + arg1Bounded = true; + } + } + // arg2 + if (variable_set.find(concatArg2) != variable_set.end()) { + if (freeVar_map.find(concatArg2) == freeVar_map.end()) { + arg2Bounded = true; + } else { + fvUnrollSet.insert(concatArg2); + } + } else if (u.str.is_concat(to_app(concatArg2))) { + if (concatEqUnrollsMap.find(concatArg2) == concatEqUnrollsMap.end()) { + arg2Bounded = true; + } + } + if (!arg1Bounded && !arg2Bounded) { + concatFreeArgsEqUnrollsMap[concat].insert( + concatEqUnrollsMap[concat].begin(), + concatEqUnrollsMap[concat].end()); + } + } + for (std::set::iterator vItor = fvUnrollSet.begin(); vItor != fvUnrollSet.end(); vItor++) { + TRACE("str", tout << "remove " << mk_pp(*vItor, m) << " from freeVar_map" << std::endl;); + freeVar_map.erase(*vItor); + } + + // Assign free variables + std::set fSimpUnroll; + + constValue = NULL; + + { + TRACE("str", tout << "free var map (#" << freeVar_map.size() << "):" << std::endl; + for (std::map::iterator freeVarItor1 = freeVar_map.begin(); freeVarItor1 != freeVar_map.end(); freeVarItor1++) { + expr * freeVar = freeVarItor1->first; + rational lenValue; + bool lenValue_exists = get_len_value(freeVar, lenValue); + tout << mk_pp(freeVar, m) << " [depCnt = " << freeVarItor1->second << ", length = " + << (lenValue_exists ? lenValue.to_string() : "?") + << "]" << std::endl; + } + ); + } + + for (std::map >::iterator fvIt2 = concatFreeArgsEqUnrollsMap.begin(); + fvIt2 != concatFreeArgsEqUnrollsMap.end(); fvIt2++) { + expr * concat = fvIt2->first; + for (std::set::iterator urItor = fvIt2->second.begin(); urItor != fvIt2->second.end(); urItor++) { + expr * unroll = *urItor; + process_concat_eq_unroll(concat, unroll); + } + } + + // -------- + // experimental free variable assignment - begin + // * special handling for variables that are not used in concat + // -------- + bool testAssign = true; + if (!testAssign) { + for (std::map::iterator fvIt = freeVar_map.begin(); fvIt != freeVar_map.end(); fvIt++) { + expr * freeVar = fvIt->first; + /* + std::string vName = std::string(Z3_ast_to_string(ctx, freeVar)); + if (vName.length() >= 9 && vName.substr(0, 9) == "$$_regVar") { + continue; + } + */ + expr * toAssert = gen_len_val_options_for_free_var(freeVar, NULL, ""); + if (toAssert != NULL) { + assert_axiom(toAssert); + } + } + } else { + process_free_var(freeVar_map); + } + // experimental free variable assignment - end + + // now deal with removed free variables that are bounded by an unroll + TRACE("str", tout << "fv_unrolls_map (#" << fv_unrolls_map.size() << "):" << std::endl;); + for (std::map >::iterator fvIt1 = fv_unrolls_map.begin(); + fvIt1 != fv_unrolls_map.end(); fvIt1++) { + expr * var = fvIt1->first; + fSimpUnroll.clear(); + get_eqc_simpleUnroll(var, constValue, fSimpUnroll); + if (fSimpUnroll.size() == 0) { + gen_assign_unroll_reg(fv_unrolls_map[var]); + } else { + expr * toAssert = gen_assign_unroll_Str2Reg(var, fSimpUnroll); + if (toAssert != NULL) { + assert_axiom(toAssert); + } + } + } + + if (opt_VerifyFinalCheckProgress && !finalCheckProgressIndicator) { + TRACE("str", tout << "BUG: no progress in final check, giving up!!" << std::endl;); + m.raise_exception("no progress in theory_str final check"); + } + + return FC_CONTINUE; // since by this point we've added axioms + } + + inline zstring int_to_string(int i) { + std::stringstream ss; + ss << i; + std::string str = ss.str(); + return zstring(str.c_str()); + } + + inline std::string longlong_to_string(long long i) { + std::stringstream ss; + ss << i; + return ss.str(); + } + + void theory_str::print_value_tester_list(svector > & testerList) { + ast_manager & m = get_manager(); + TRACE("str", + int ss = testerList.size(); + tout << "valueTesterList = {"; + for (int i = 0; i < ss; ++i) { + if (i % 4 == 0) { + tout << std::endl; + } + tout << "(" << testerList[i].first << ", "; + tout << mk_ismt2_pp(testerList[i].second, m); + tout << "), "; + } + tout << std::endl << "}" << std::endl; + ); + } + + zstring theory_str::gen_val_string(int len, int_vector & encoding) { + SASSERT(charSetSize > 0); + SASSERT(char_set != NULL); + + std::string re(len, char_set[0]); + for (int i = 0; i < (int) encoding.size() - 1; i++) { + int idx = encoding[i]; + re[len - 1 - i] = char_set[idx]; + } + return zstring(re.c_str()); + } + + /* + * The return value indicates whether we covered the search space. + * - If the next encoding is valid, return false + * - Otherwise, return true + */ + bool theory_str::get_next_val_encode(int_vector & base, int_vector & next) { + SASSERT(charSetSize > 0); + + TRACE("str", tout << "base vector: [ "; + for (unsigned i = 0; i < base.size(); ++i) { + tout << base[i] << " "; + } + tout << "]" << std::endl; + ); + + int s = 0; + int carry = 0; + next.reset(); + + for (int i = 0; i < (int) base.size(); i++) { + if (i == 0) { + s = base[i] + 1; + carry = s / charSetSize; + s = s % charSetSize; + next.push_back(s); + } else { + s = base[i] + carry; + carry = s / charSetSize; + s = s % charSetSize; + next.push_back(s); + } + } + if (next[next.size() - 1] > 0) { + next.reset(); + return true; + } else { + return false; + } + } + + expr * theory_str::gen_val_options(expr * freeVar, expr * len_indicator, expr * val_indicator, + zstring lenStr, int tries) { + ast_manager & m = get_manager(); + context & ctx = get_context(); + + int distance = 32; + + // ---------------------------------------------------------------------------------------- + // generate value options encoding + // encoding is a vector of size (len + 1) + // e.g, len = 2, + // encoding {1, 2, 0} means the value option is "charSet[2]"."charSet[1]" + // the last item in the encoding indicates whether the whole space is covered + // for example, if the charSet = {a, b}. All valid encodings are + // {0, 0, 0}, {1, 0, 0}, {0, 1, 0}, {1, 1, 0} + // if add 1 to the last one, we get + // {0, 0, 1} + // the last item "1" shows this is not a valid encoding, and we have covered all space + // ---------------------------------------------------------------------------------------- + int len = atoi(lenStr.encode().c_str()); + bool coverAll = false; + svector options; + int_vector base; + + TRACE("str", tout + << "freeVar = " << mk_ismt2_pp(freeVar, m) << std::endl + << "len_indicator = " << mk_ismt2_pp(len_indicator, m) << std::endl + << "val_indicator = " << mk_ismt2_pp(val_indicator, m) << std::endl + << "lenstr = " << lenStr << "\n" + << "tries = " << tries << "\n"; + if (m_params.m_AggressiveValueTesting) { + tout << "note: aggressive value testing is enabled" << std::endl; + } + ); + + if (tries == 0) { + base = int_vector(len + 1, 0); + coverAll = false; + } else { + expr * lastestValIndi = fvar_valueTester_map[freeVar][len][tries - 1].second; + TRACE("str", tout << "last value tester = " << mk_ismt2_pp(lastestValIndi, m) << std::endl;); + coverAll = get_next_val_encode(val_range_map[lastestValIndi], base); + } + + long long l = (tries) * distance; + long long h = l; + for (int i = 0; i < distance; i++) { + if (coverAll) + break; + options.push_back(base); + h++; + coverAll = get_next_val_encode(options[options.size() - 1], base); + } + val_range_map[val_indicator] = options[options.size() - 1]; + + TRACE("str", + tout << "value tester encoding " << "{" << std::endl; + int_vector vec = val_range_map[val_indicator]; + + for (int_vector::iterator it = vec.begin(); it != vec.end(); ++it) { + tout << *it << std::endl; + } + tout << "}" << std::endl; + ); + + // ---------------------------------------------------------------------------------------- + + ptr_vector orList; + ptr_vector andList; + + for (long long i = l; i < h; i++) { + orList.push_back(m.mk_eq(val_indicator, mk_string(longlong_to_string(i).c_str()) )); + if (m_params.m_AggressiveValueTesting) { + literal l = mk_eq(val_indicator, mk_string(longlong_to_string(i).c_str()), false); + ctx.mark_as_relevant(l); + ctx.force_phase(l); + } + + zstring aStr = gen_val_string(len, options[i - l]); + expr * strAst; + if (m_params.m_UseFastValueTesterCache) { + if (!valueTesterCache.find(aStr, strAst)) { + strAst = mk_string(aStr); + valueTesterCache.insert(aStr, strAst); + m_trail.push_back(strAst); + } + } else { + strAst = mk_string(aStr); + } + andList.push_back(m.mk_eq(orList[orList.size() - 1], m.mk_eq(freeVar, strAst))); + } + if (!coverAll) { + orList.push_back(m.mk_eq(val_indicator, mk_string("more"))); + if (m_params.m_AggressiveValueTesting) { + literal l = mk_eq(val_indicator, mk_string("more"), false); + ctx.mark_as_relevant(l); + ctx.force_phase(~l); + } + } + + expr ** or_items = alloc_svect(expr*, orList.size()); + expr ** and_items = alloc_svect(expr*, andList.size() + 1); + + for (int i = 0; i < (int) orList.size(); i++) { + or_items[i] = orList[i]; + } + if (orList.size() > 1) + and_items[0] = m.mk_or(orList.size(), or_items); + else + and_items[0] = or_items[0]; + + for (int i = 0; i < (int) andList.size(); i++) { + and_items[i + 1] = andList[i]; + } + expr * valTestAssert = m.mk_and(andList.size() + 1, and_items); + + // --------------------------------------- + // If the new value tester is $$_val_x_16_i + // Should add ($$_len_x_j = 16) /\ ($$_val_x_16_i = "more") + // --------------------------------------- + andList.reset(); + andList.push_back(m.mk_eq(len_indicator, mk_string(lenStr))); + for (int i = 0; i < tries; i++) { + expr * vTester = fvar_valueTester_map[freeVar][len][i].second; + if (vTester != val_indicator) + andList.push_back(m.mk_eq(vTester, mk_string("more"))); + } + expr * assertL = NULL; + if (andList.size() == 1) { + assertL = andList[0]; + } else { + expr ** and_items = alloc_svect(expr*, andList.size()); + for (int i = 0; i < (int) andList.size(); i++) { + and_items[i] = andList[i]; + } + assertL = m.mk_and(andList.size(), and_items); + } + + // (assertL => valTestAssert) <=> (!assertL OR valTestAssert) + valTestAssert = m.mk_or(m.mk_not(assertL), valTestAssert); + return valTestAssert; + } + + expr * theory_str::gen_free_var_options(expr * freeVar, expr * len_indicator, + zstring len_valueStr, expr * valTesterInCbEq, zstring valTesterValueStr) { + ast_manager & m = get_manager(); + + int len = atoi(len_valueStr.encode().c_str()); + + // check whether any value tester is actually in scope + TRACE("str", tout << "checking scope of previous value testers" << std::endl;); + bool map_effectively_empty = true; + if (fvar_valueTester_map[freeVar].find(len) != fvar_valueTester_map[freeVar].end()) { + // there's *something* in the map, but check its scope + svector > entries = fvar_valueTester_map[freeVar][len]; + for (svector >::iterator it = entries.begin(); it != entries.end(); ++it) { + std::pair entry = *it; + expr * aTester = entry.second; + if (internal_variable_set.find(aTester) == internal_variable_set.end()) { + TRACE("str", tout << mk_pp(aTester, m) << " out of scope" << std::endl;); + } else { + TRACE("str", tout << mk_pp(aTester, m) << " in scope" << std::endl;); + map_effectively_empty = false; + break; + } + } + } + + if (map_effectively_empty) { + TRACE("str", tout << "no previous value testers, or none of them were in scope" << std::endl;); + int tries = 0; + expr * val_indicator = mk_internal_valTest_var(freeVar, len, tries); + valueTester_fvar_map[val_indicator] = freeVar; + fvar_valueTester_map[freeVar][len].push_back(std::make_pair(sLevel, val_indicator)); + print_value_tester_list(fvar_valueTester_map[freeVar][len]); + return gen_val_options(freeVar, len_indicator, val_indicator, len_valueStr, tries); + } else { + TRACE("str", tout << "checking previous value testers" << std::endl;); + print_value_tester_list(fvar_valueTester_map[freeVar][len]); + + // go through all previous value testers + // If some doesn't have an eqc value, add its assertion again. + int testerTotal = fvar_valueTester_map[freeVar][len].size(); + int i = 0; + for (; i < testerTotal; i++) { + expr * aTester = fvar_valueTester_map[freeVar][len][i].second; + + // it's probably worth checking scope here, actually + if (internal_variable_set.find(aTester) == internal_variable_set.end()) { + TRACE("str", tout << "value tester " << mk_pp(aTester, m) << " out of scope, skipping" << std::endl;); + continue; + } + + if (aTester == valTesterInCbEq) { + break; + } + + bool anEqcHasValue = false; + // Z3_ast anEqc = get_eqc_value(t, aTester, anEqcHasValue); + expr * aTester_eqc_value = get_eqc_value(aTester, anEqcHasValue); + if (!anEqcHasValue) { + TRACE("str", tout << "value tester " << mk_ismt2_pp(aTester, m) + << " doesn't have an equivalence class value." << std::endl;); + refresh_theory_var(aTester); + + expr * makeupAssert = gen_val_options(freeVar, len_indicator, aTester, len_valueStr, i); + + TRACE("str", tout << "var: " << mk_ismt2_pp(freeVar, m) << std::endl + << mk_ismt2_pp(makeupAssert, m) << std::endl;); + assert_axiom(makeupAssert); + } else { + TRACE("str", tout << "value tester " << mk_ismt2_pp(aTester, m) + << " == " << mk_ismt2_pp(aTester_eqc_value, m) << std::endl;); + } + } + + if (valTesterValueStr == "more") { + expr * valTester = NULL; + if (i + 1 < testerTotal) { + valTester = fvar_valueTester_map[freeVar][len][i + 1].second; + refresh_theory_var(valTester); + } else { + valTester = mk_internal_valTest_var(freeVar, len, i + 1); + valueTester_fvar_map[valTester] = freeVar; + fvar_valueTester_map[freeVar][len].push_back(std::make_pair(sLevel, valTester)); + print_value_tester_list(fvar_valueTester_map[freeVar][len]); + } + expr * nextAssert = gen_val_options(freeVar, len_indicator, valTester, len_valueStr, i + 1); + return nextAssert; + } + + return NULL; + } + } + + void theory_str::reduce_virtual_regex_in(expr * var, expr * regex, expr_ref_vector & items) { + context & ctx = get_context(); + ast_manager & mgr = get_manager(); + + TRACE("str", tout << "reduce regex " << mk_pp(regex, mgr) << " with respect to variable " << mk_pp(var, mgr) << std::endl;); + + app * regexFuncDecl = to_app(regex); + if (u.re.is_to_re(regexFuncDecl)) { + // --------------------------------------------------------- + // var \in Str2Reg(s1) + // ==> + // var = s1 /\ length(var) = length(s1) + // --------------------------------------------------------- + expr * strInside = to_app(regex)->get_arg(0); + items.push_back(ctx.mk_eq_atom(var, strInside)); + items.push_back(ctx.mk_eq_atom(mk_strlen(var), mk_strlen(strInside))); + return; + } + // RegexUnion + else if (u.re.is_union(regexFuncDecl)) { + // --------------------------------------------------------- + // var \in RegexUnion(r1, r2) + // ==> + // (var = newVar1 \/ var = newVar2) + // (var = newVar1 --> length(var) = length(newVar1)) /\ (var = newVar2 --> length(var) = length(newVar2)) + // /\ (newVar1 \in r1) /\ (newVar2 \in r2) + // --------------------------------------------------------- + expr_ref newVar1(mk_regex_rep_var(), mgr); + expr_ref newVar2(mk_regex_rep_var(), mgr); + items.push_back(mgr.mk_or(ctx.mk_eq_atom(var, newVar1), ctx.mk_eq_atom(var, newVar2))); + items.push_back(mgr.mk_or( + mgr.mk_not(ctx.mk_eq_atom(var, newVar1)), + ctx.mk_eq_atom(mk_strlen(var), mk_strlen(newVar1)))); + items.push_back(mgr.mk_or( + mgr.mk_not(ctx.mk_eq_atom(var, newVar2)), + ctx.mk_eq_atom(mk_strlen(var), mk_strlen(newVar2)))); + + expr * regArg1 = to_app(regex)->get_arg(0); + reduce_virtual_regex_in(newVar1, regArg1, items); + + expr * regArg2 = to_app(regex)->get_arg(1); + reduce_virtual_regex_in(newVar2, regArg2, items); + + return; + } + // RegexConcat + else if (u.re.is_concat(regexFuncDecl)) { + // --------------------------------------------------------- + // var \in RegexConcat(r1, r2) + // ==> + // (var = newVar1 . newVar2) /\ (length(var) = length(vewVar1 . newVar2) ) + // /\ (newVar1 \in r1) /\ (newVar2 \in r2) + // --------------------------------------------------------- + expr_ref newVar1(mk_regex_rep_var(), mgr); + expr_ref newVar2(mk_regex_rep_var(), mgr); + expr_ref concatAst(mk_concat(newVar1, newVar2), mgr); + items.push_back(ctx.mk_eq_atom(var, concatAst)); + items.push_back(ctx.mk_eq_atom(mk_strlen(var), + m_autil.mk_add(mk_strlen(newVar1), mk_strlen(newVar2)))); + + expr * regArg1 = to_app(regex)->get_arg(0); + reduce_virtual_regex_in(newVar1, regArg1, items); + expr * regArg2 = to_app(regex)->get_arg(1); + reduce_virtual_regex_in(newVar2, regArg2, items); + return; + } + // Unroll + else if (u.re.is_star(regexFuncDecl)) { + // --------------------------------------------------------- + // var \in Star(r1) + // ==> + // var = unroll(r1, t1) /\ |var| = |unroll(r1, t1)| + // --------------------------------------------------------- + expr * regArg = to_app(regex)->get_arg(0); + expr_ref unrollCnt(mk_unroll_bound_var(), mgr); + expr_ref unrollFunc(mk_unroll(regArg, unrollCnt), mgr); + items.push_back(ctx.mk_eq_atom(var, unrollFunc)); + items.push_back(ctx.mk_eq_atom(mk_strlen(var), mk_strlen(unrollFunc))); + return; + } + // re.range + else if (u.re.is_range(regexFuncDecl)) { + // var in range("a", "z") + // ==> + // (var = "a" or var = "b" or ... or var = "z") + expr_ref lo(regexFuncDecl->get_arg(0), mgr); + expr_ref hi(regexFuncDecl->get_arg(1), mgr); + zstring str_lo, str_hi; + SASSERT(u.str.is_string(lo)); + SASSERT(u.str.is_string(hi)); + u.str.is_string(lo, str_lo); + u.str.is_string(hi, str_hi); + SASSERT(str_lo.length() == 1); + SASSERT(str_hi.length() == 1); + unsigned int c1 = str_lo[0]; + unsigned int c2 = str_hi[0]; + if (c1 > c2) { + // exchange + unsigned int tmp = c1; + c1 = c2; + c2 = tmp; + } + expr_ref_vector range_cases(mgr); + for (unsigned int ch = c1; ch <= c2; ++ch) { + zstring s_ch(ch); + expr_ref rhs(ctx.mk_eq_atom(var, u.str.mk_string(s_ch)), mgr); + range_cases.push_back(rhs); + } + expr_ref rhs(mk_or(range_cases), mgr); + SASSERT(rhs); + assert_axiom(rhs); + return; + } else { + get_manager().raise_exception("unrecognized regex operator"); + UNREACHABLE(); + } + } + + void theory_str::gen_assign_unroll_reg(std::set & unrolls) { + context & ctx = get_context(); + ast_manager & mgr = get_manager(); + + expr_ref_vector items(mgr); + for (std::set::iterator itor = unrolls.begin(); itor != unrolls.end(); itor++) { + expr * unrFunc = *itor; + TRACE("str", tout << "generating assignment for unroll " << mk_pp(unrFunc, mgr) << std::endl;); + + expr * regexInUnr = to_app(unrFunc)->get_arg(0); + expr * cntInUnr = to_app(unrFunc)->get_arg(1); + items.reset(); + + rational low, high; + bool low_exists = lower_bound(cntInUnr, low); + bool high_exists = upper_bound(cntInUnr, high); + + TRACE("str", + tout << "unroll " << mk_pp(unrFunc, mgr) << std::endl; + rational unrLenValue; + bool unrLenValue_exists = get_len_value(unrFunc, unrLenValue); + tout << "unroll length: " << (unrLenValue_exists ? unrLenValue.to_string() : "?") << std::endl; + rational cntInUnrValue; + bool cntHasValue = get_value(cntInUnr, cntInUnrValue); + tout << "unroll count: " << (cntHasValue ? cntInUnrValue.to_string() : "?") + << " low = " + << (low_exists ? low.to_string() : "?") + << " high = " + << (high_exists ? high.to_string() : "?") + << std::endl; + ); + + expr_ref toAssert(mgr); + if (low.is_neg()) { + toAssert = m_autil.mk_ge(cntInUnr, mk_int(0)); + } else { + if (unroll_var_map.find(unrFunc) == unroll_var_map.end()) { + + expr_ref newVar1(mk_regex_rep_var(), mgr); + expr_ref newVar2(mk_regex_rep_var(), mgr); + expr_ref concatAst(mk_concat(newVar1, newVar2), mgr); + expr_ref newCnt(mk_unroll_bound_var(), mgr); + expr_ref newUnrollFunc(mk_unroll(regexInUnr, newCnt), mgr); + + // unroll(r1, t1) = newVar1 . newVar2 + items.push_back(ctx.mk_eq_atom(unrFunc, concatAst)); + items.push_back(ctx.mk_eq_atom(mk_strlen(unrFunc), m_autil.mk_add(mk_strlen(newVar1), mk_strlen(newVar2)))); + // mk_strlen(unrFunc) >= mk_strlen(newVar{1,2}) + items.push_back(m_autil.mk_ge(m_autil.mk_add(mk_strlen(unrFunc), m_autil.mk_mul(mk_int(-1), mk_strlen(newVar1))), mk_int(0))); + items.push_back(m_autil.mk_ge(m_autil.mk_add(mk_strlen(unrFunc), m_autil.mk_mul(mk_int(-1), mk_strlen(newVar2))), mk_int(0))); + // newVar1 \in r1 + reduce_virtual_regex_in(newVar1, regexInUnr, items); + items.push_back(ctx.mk_eq_atom(cntInUnr, m_autil.mk_add(newCnt, mk_int(1)))); + items.push_back(ctx.mk_eq_atom(newVar2, newUnrollFunc)); + items.push_back(ctx.mk_eq_atom(mk_strlen(newVar2), mk_strlen(newUnrollFunc))); + toAssert = ctx.mk_eq_atom( + m_autil.mk_ge(cntInUnr, mk_int(1)), + mk_and(items)); + + // option 0 + expr_ref op0(ctx.mk_eq_atom(cntInUnr, mk_int(0)), mgr); + expr_ref ast1(ctx.mk_eq_atom(unrFunc, mk_string("")), mgr); + expr_ref ast2(ctx.mk_eq_atom(mk_strlen(unrFunc), mk_int(0)), mgr); + expr_ref and1(mgr.mk_and(ast1, ast2), mgr); + + // put together + toAssert = mgr.mk_and(ctx.mk_eq_atom(op0, and1), toAssert); + + unroll_var_map[unrFunc] = toAssert; + } else { + toAssert = unroll_var_map[unrFunc]; + } + } + m_trail.push_back(toAssert); + assert_axiom(toAssert); + } + } + + static int computeGCD(int x, int y) { + if (x == 0) { + return y; + } + while (y != 0) { + if (x > y) { + x = x - y; + } else { + y = y - x; + } + } + return x; + } + + static int computeLCM(int a, int b) { + int temp = computeGCD(a, b); + return temp ? (a / temp * b) : 0; + } + + static zstring get_unrolled_string(zstring core, int count) { + zstring res(""); + for (int i = 0; i < count; i++) { + res = res + core; + } + return res; + } + + expr * theory_str::gen_assign_unroll_Str2Reg(expr * n, std::set & unrolls) { + context & ctx = get_context(); + ast_manager & mgr = get_manager(); + + int lcm = 1; + int coreValueCount = 0; + expr * oneUnroll = NULL; + zstring oneCoreStr(""); + for (std::set::iterator itor = unrolls.begin(); itor != unrolls.end(); itor++) { + expr * str2RegFunc = to_app(*itor)->get_arg(0); + expr * coreVal = to_app(str2RegFunc)->get_arg(0); + zstring coreStr; + u.str.is_string(coreVal, coreStr); + if (oneUnroll == NULL) { + oneUnroll = *itor; + oneCoreStr = coreStr; + } + coreValueCount++; + int core1Len = coreStr.length(); + lcm = computeLCM(lcm, core1Len); + } + // + bool canHaveNonEmptyAssign = true; + expr_ref_vector litems(mgr); + zstring lcmStr = get_unrolled_string(oneCoreStr, (lcm / oneCoreStr.length())); + for (std::set::iterator itor = unrolls.begin(); itor != unrolls.end(); itor++) { + expr * str2RegFunc = to_app(*itor)->get_arg(0); + expr * coreVal = to_app(str2RegFunc)->get_arg(0); + zstring coreStr; + u.str.is_string(coreVal, coreStr); + unsigned int core1Len = coreStr.length(); + zstring uStr = get_unrolled_string(coreStr, (lcm / core1Len)); + if (uStr != lcmStr) { + canHaveNonEmptyAssign = false; + } + litems.push_back(ctx.mk_eq_atom(n, *itor)); + } + + if (canHaveNonEmptyAssign) { + return gen_unroll_conditional_options(n, unrolls, lcmStr); + } else { + expr_ref implyL(mk_and(litems), mgr); + expr_ref implyR(ctx.mk_eq_atom(n, mk_string("")), mgr); + // want to return (implyL -> implyR) + expr * final_axiom = rewrite_implication(implyL, implyR); + return final_axiom; + } + } + + expr * theory_str::gen_unroll_conditional_options(expr * var, std::set & unrolls, zstring lcmStr) { + context & ctx = get_context(); + ast_manager & mgr = get_manager(); + + int dist = opt_LCMUnrollStep; + expr_ref_vector litems(mgr); + expr_ref moreAst(mk_string("more"), mgr); + for (std::set::iterator itor = unrolls.begin(); itor != unrolls.end(); itor++) { + expr_ref item(ctx.mk_eq_atom(var, *itor), mgr); + TRACE("str", tout << "considering unroll " << mk_pp(item, mgr) << std::endl;); + litems.push_back(item); + } + + // handle out-of-scope entries in unroll_tries_map + + ptr_vector outOfScopeTesters; + + for (ptr_vector::iterator it = unroll_tries_map[var][unrolls].begin(); + it != unroll_tries_map[var][unrolls].end(); ++it) { + expr * tester = *it; + bool inScope = (internal_unrollTest_vars.find(tester) != internal_unrollTest_vars.end()); + TRACE("str", tout << "unroll test var " << mk_pp(tester, mgr) + << (inScope ? " in scope" : " out of scope") + << std::endl;); + if (!inScope) { + outOfScopeTesters.push_back(tester); + } + } + + for (ptr_vector::iterator it = outOfScopeTesters.begin(); + it != outOfScopeTesters.end(); ++it) { + unroll_tries_map[var][unrolls].erase(*it); + } + + + if (unroll_tries_map[var][unrolls].size() == 0) { + unroll_tries_map[var][unrolls].push_back(mk_unroll_test_var()); + } + + int tries = unroll_tries_map[var][unrolls].size(); + for (int i = 0; i < tries; i++) { + expr * tester = unroll_tries_map[var][unrolls][i]; + // TESTING + refresh_theory_var(tester); + bool testerHasValue = false; + expr * testerVal = get_eqc_value(tester, testerHasValue); + if (!testerHasValue) { + // generate make-up assertion + int l = i * dist; + int h = (i + 1) * dist; + expr_ref lImp(mk_and(litems), mgr); + expr_ref rImp(gen_unroll_assign(var, lcmStr, tester, l, h), mgr); + + SASSERT(lImp); + TRACE("str", tout << "lImp = " << mk_pp(lImp, mgr) << std::endl;); + SASSERT(rImp); + TRACE("str", tout << "rImp = " << mk_pp(rImp, mgr) << std::endl;); + + expr_ref toAssert(mgr.mk_or(mgr.mk_not(lImp), rImp), mgr); + SASSERT(toAssert); + TRACE("str", tout << "Making up assignments for variable which is equal to unbounded Unroll" << std::endl;); + m_trail.push_back(toAssert); + return toAssert; + + // note: this is how the code looks in Z3str2's strRegex.cpp:genUnrollConditionalOptions. + // the return is in the same place + + // insert [tester = "more"] to litems so that the implyL for next tester is correct + litems.push_back(ctx.mk_eq_atom(tester, moreAst)); + } else { + zstring testerStr; + u.str.is_string(testerVal, testerStr); + TRACE("str", tout << "Tester [" << mk_pp(tester, mgr) << "] = " << testerStr << "\n";); + if (testerStr == "more") { + litems.push_back(ctx.mk_eq_atom(tester, moreAst)); + } + } + } + expr * tester = mk_unroll_test_var(); + unroll_tries_map[var][unrolls].push_back(tester); + int l = tries * dist; + int h = (tries + 1) * dist; + expr_ref lImp(mk_and(litems), mgr); + expr_ref rImp(gen_unroll_assign(var, lcmStr, tester, l, h), mgr); + SASSERT(lImp); + SASSERT(rImp); + expr_ref toAssert(mgr.mk_or(mgr.mk_not(lImp), rImp), mgr); + SASSERT(toAssert); + TRACE("str", tout << "Generating assignment for variable which is equal to unbounded Unroll" << std::endl;); + m_trail.push_back(toAssert); + return toAssert; + } + + expr * theory_str::gen_unroll_assign(expr * var, zstring lcmStr, expr * testerVar, int l, int h) { + context & ctx = get_context(); + ast_manager & mgr = get_manager(); + + TRACE("str", tout << "entry: var = " << mk_pp(var, mgr) << ", lcmStr = " << lcmStr + << ", l = " << l << ", h = " << h << "\n";); + + if (m_params.m_AggressiveUnrollTesting) { + TRACE("str", tout << "note: aggressive unroll testing is active" << std::endl;); + } + + expr_ref_vector orItems(mgr); + expr_ref_vector andItems(mgr); + + for (int i = l; i < h; i++) { + zstring iStr = int_to_string(i); + expr_ref testerEqAst(ctx.mk_eq_atom(testerVar, mk_string(iStr)), mgr); + TRACE("str", tout << "testerEqAst = " << mk_pp(testerEqAst, mgr) << std::endl;); + if (m_params.m_AggressiveUnrollTesting) { + literal l = mk_eq(testerVar, mk_string(iStr), false); + ctx.mark_as_relevant(l); + ctx.force_phase(l); + } + + orItems.push_back(testerEqAst); + zstring unrollStrInstance = get_unrolled_string(lcmStr, i); + + expr_ref x1(ctx.mk_eq_atom(testerEqAst, ctx.mk_eq_atom(var, mk_string(unrollStrInstance))), mgr); + TRACE("str", tout << "x1 = " << mk_pp(x1, mgr) << std::endl;); + andItems.push_back(x1); + + expr_ref x2(ctx.mk_eq_atom(testerEqAst, ctx.mk_eq_atom(mk_strlen(var), mk_int(i * lcmStr.length()))), mgr); + TRACE("str", tout << "x2 = " << mk_pp(x2, mgr) << std::endl;); + andItems.push_back(x2); + } + expr_ref testerEqMore(ctx.mk_eq_atom(testerVar, mk_string("more")), mgr); + TRACE("str", tout << "testerEqMore = " << mk_pp(testerEqMore, mgr) << std::endl;); + if (m_params.m_AggressiveUnrollTesting) { + literal l = mk_eq(testerVar, mk_string("more"), false); + ctx.mark_as_relevant(l); + ctx.force_phase(~l); + } + + orItems.push_back(testerEqMore); + int nextLowerLenBound = h * lcmStr.length(); + expr_ref more2(ctx.mk_eq_atom(testerEqMore, + //Z3_mk_ge(mk_length(t, var), mk_int(ctx, nextLowerLenBound)) + m_autil.mk_ge(m_autil.mk_add(mk_strlen(var), mk_int(-1 * nextLowerLenBound)), mk_int(0)) + ), mgr); + TRACE("str", tout << "more2 = " << mk_pp(more2, mgr) << std::endl;); + andItems.push_back(more2); + + expr_ref finalOR(mgr.mk_or(orItems.size(), orItems.c_ptr()), mgr); + TRACE("str", tout << "finalOR = " << mk_pp(finalOR, mgr) << std::endl;); + andItems.push_back(mk_or(orItems)); + + expr_ref finalAND(mgr.mk_and(andItems.size(), andItems.c_ptr()), mgr); + TRACE("str", tout << "finalAND = " << mk_pp(finalAND, mgr) << std::endl;); + + // doing the following avoids a segmentation fault + m_trail.push_back(finalAND); + return finalAND; + } + + expr * theory_str::gen_len_test_options(expr * freeVar, expr * indicator, int tries) { + ast_manager & m = get_manager(); + context & ctx = get_context(); + + expr_ref freeVarLen(mk_strlen(freeVar), m); + SASSERT(freeVarLen); + + expr_ref_vector orList(m); + expr_ref_vector andList(m); + + int distance = 3; + int l = (tries - 1) * distance; + int h = tries * distance; + + TRACE("str", + tout << "building andList and orList" << std::endl; + if (m_params.m_AggressiveLengthTesting) { + tout << "note: aggressive length testing is active" << std::endl; + } + ); + + // experimental theory-aware case split support + literal_vector case_split_literals; + + for (int i = l; i < h; ++i) { + expr_ref str_indicator(m); + if (m_params.m_UseFastLengthTesterCache) { + rational ri(i); + expr * lookup_val; + if(lengthTesterCache.find(ri, lookup_val)) { + str_indicator = expr_ref(lookup_val, m); + } else { + // no match; create and insert + zstring i_str = int_to_string(i); + expr_ref new_val(mk_string(i_str), m); + lengthTesterCache.insert(ri, new_val); + m_trail.push_back(new_val); + str_indicator = expr_ref(new_val, m); + } + } else { + zstring i_str = int_to_string(i); + str_indicator = expr_ref(mk_string(i_str), m); + } + expr_ref or_expr(ctx.mk_eq_atom(indicator, str_indicator), m); + orList.push_back(or_expr); + + double priority; + // give high priority to small lengths if this is available + if (i <= 5) { + priority = 0.3; + } else { + // prioritize over "more" + priority = 0.2; + } + add_theory_aware_branching_info(or_expr, priority, l_true); + + if (m_params.m_AggressiveLengthTesting) { + literal l = mk_eq(indicator, str_indicator, false); + ctx.mark_as_relevant(l); + ctx.force_phase(l); + } + + case_split_literals.insert(mk_eq(freeVarLen, mk_int(i), false)); + + expr_ref and_expr(ctx.mk_eq_atom(orList.get(orList.size() - 1), m.mk_eq(freeVarLen, mk_int(i))), m); + andList.push_back(and_expr); + } + + expr_ref more_option(ctx.mk_eq_atom(indicator, mk_string("more")), m); + orList.push_back(more_option); + // decrease priority of this option + add_theory_aware_branching_info(more_option, -0.1, l_true); + if (m_params.m_AggressiveLengthTesting) { + literal l = mk_eq(indicator, mk_string("more"), false); + ctx.mark_as_relevant(l); + ctx.force_phase(~l); + } + + andList.push_back(ctx.mk_eq_atom(orList.get(orList.size() - 1), m_autil.mk_ge(freeVarLen, mk_int(h)))); + + /* + { // more experimental theory case split support + expr_ref tmp(m_autil.mk_ge(freeVarLen, mk_int(h)), m); + ctx.internalize(m_autil.mk_ge(freeVarLen, mk_int(h)), false); + case_split_literals.push_back(ctx.get_literal(tmp)); + ctx.mk_th_case_split(case_split_literals.size(), case_split_literals.c_ptr()); + } + */ + + expr_ref_vector or_items(m); + expr_ref_vector and_items(m); + + for (unsigned i = 0; i < orList.size(); ++i) { + or_items.push_back(orList.get(i)); + } + + and_items.push_back(mk_or(or_items)); + for(unsigned i = 0; i < andList.size(); ++i) { + and_items.push_back(andList.get(i)); + } + + TRACE("str", tout << "check: " << mk_pp(mk_and(and_items), m) << std::endl;); + + expr_ref lenTestAssert = mk_and(and_items); + SASSERT(lenTestAssert); + TRACE("str", tout << "crash avoidance lenTestAssert: " << mk_pp(lenTestAssert, m) << std::endl;); + + int testerCount = tries - 1; + if (testerCount > 0) { + expr_ref_vector and_items_LHS(m); + expr_ref moreAst(mk_string("more"), m); + for (int i = 0; i < testerCount; ++i) { + expr * indicator = fvar_lenTester_map[freeVar][i]; + if (internal_variable_set.find(indicator) == internal_variable_set.end()) { + TRACE("str", tout << "indicator " << mk_pp(indicator, m) << " out of scope; continuing" << std::endl;); + continue; + } else { + TRACE("str", tout << "indicator " << mk_pp(indicator, m) << " in scope" << std::endl;); + and_items_LHS.push_back(ctx.mk_eq_atom(indicator, moreAst)); + } + } + expr_ref assertL(mk_and(and_items_LHS), m); + SASSERT(assertL); + expr * finalAxiom = m.mk_or(m.mk_not(assertL), lenTestAssert.get()); + SASSERT(finalAxiom != NULL); + TRACE("str", tout << "crash avoidance finalAxiom: " << mk_pp(finalAxiom, m) << std::endl;); + return finalAxiom; + } else { + TRACE("str", tout << "crash avoidance lenTestAssert.get(): " << mk_pp(lenTestAssert.get(), m) << std::endl;); + m_trail.push_back(lenTestAssert.get()); + return lenTestAssert.get(); + } + } + + // Return an expression of the form + // (tester = "less" | tester = "N" | tester = "more") & + // (tester = "less" iff len(freeVar) < N) & (tester = "more" iff len(freeVar) > N) & (tester = "N" iff len(freeVar) = N)) + expr_ref theory_str::binary_search_case_split(expr * freeVar, expr * tester, binary_search_info & bounds, literal_vector & case_split) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + rational N = bounds.midPoint; + rational N_minus_one = N - rational::one(); + rational N_plus_one = N + rational::one(); + expr_ref lenFreeVar(mk_strlen(freeVar), m); + + TRACE("str", tout << "create case split for free var " << mk_pp(freeVar, m) + << " over " << mk_pp(tester, m) << " with midpoint " << N << std::endl;); + + expr_ref_vector combinedCaseSplit(m); + expr_ref_vector testerCases(m); + + expr_ref caseLess(ctx.mk_eq_atom(tester, mk_string("less")), m); + testerCases.push_back(caseLess); + combinedCaseSplit.push_back(ctx.mk_eq_atom(caseLess, m_autil.mk_le(lenFreeVar, m_autil.mk_numeral(N_minus_one, true) ))); + + expr_ref caseMore(ctx.mk_eq_atom(tester, mk_string("more")), m); + testerCases.push_back(caseMore); + combinedCaseSplit.push_back(ctx.mk_eq_atom(caseMore, m_autil.mk_ge(lenFreeVar, m_autil.mk_numeral(N_plus_one, true) ))); + + expr_ref caseEq(ctx.mk_eq_atom(tester, mk_string(N.to_string().c_str())), m); + testerCases.push_back(caseEq); + combinedCaseSplit.push_back(ctx.mk_eq_atom(caseEq, ctx.mk_eq_atom(lenFreeVar, m_autil.mk_numeral(N, true)))); + + combinedCaseSplit.push_back(mk_or(testerCases)); + + // force internalization on all terms in testerCases so we can extract literals + for (unsigned i = 0; i < testerCases.size(); ++i) { + expr * testerCase = testerCases.get(i); + if (!ctx.b_internalized(testerCase)) { + ctx.internalize(testerCase, false); + } + literal l = ctx.get_literal(testerCase); + case_split.push_back(l); + } + + expr_ref final_term(mk_and(combinedCaseSplit), m); + SASSERT(final_term); + TRACE("str", tout << "final term: " << mk_pp(final_term, m) << std::endl;); + return final_term; + } + + expr * theory_str::binary_search_length_test(expr * freeVar, expr * previousLenTester, zstring previousLenTesterValue) { + ast_manager & m = get_manager(); + context & ctx = get_context(); + + if (binary_search_len_tester_stack.contains(freeVar) && !binary_search_len_tester_stack[freeVar].empty()) { + TRACE("str", tout << "checking existing length testers for " << mk_pp(freeVar, m) << std::endl; + for (ptr_vector::const_iterator it = binary_search_len_tester_stack[freeVar].begin(); + it != binary_search_len_tester_stack[freeVar].end(); ++it) { + expr * tester = *it; + tout << mk_pp(tester, m) << ": "; + if (binary_search_len_tester_info.contains(tester)) { + binary_search_info & bounds = binary_search_len_tester_info[tester]; + tout << "[" << bounds.lowerBound << " | " << bounds.midPoint << " | " << bounds.upperBound << "]!" << bounds.windowSize; + } else { + tout << "[WARNING: no bounds info available]"; + } + bool hasEqcValue; + expr * testerEqcValue = get_eqc_value(tester, hasEqcValue); + if (hasEqcValue) { + tout << " = " << mk_pp(testerEqcValue, m); + } else { + tout << " [no eqc value]"; + } + tout << std::endl; + } + ); + expr * lastTester = binary_search_len_tester_stack[freeVar].back(); + bool lastTesterHasEqcValue; + expr * lastTesterValue = get_eqc_value(lastTester, lastTesterHasEqcValue); + zstring lastTesterConstant; + if (!lastTesterHasEqcValue) { + TRACE("str", tout << "length tester " << mk_pp(lastTester, m) << " at top of stack doesn't have an EQC value yet" << std::endl;); + // check previousLenTester + if (previousLenTester == lastTester) { + lastTesterConstant = previousLenTesterValue; + TRACE("str", tout << "invoked with previousLenTester info matching top of stack" << std::endl;); + } else { + TRACE("str", tout << "WARNING: unexpected reordering of length testers!" << std::endl;); + UNREACHABLE(); return NULL; + } + } else { + u.str.is_string(lastTesterValue, lastTesterConstant); + } + TRACE("str", tout << "last length tester is assigned \"" << lastTesterConstant << "\"" << "\n";); + if (lastTesterConstant == "more" || lastTesterConstant == "less") { + // use the previous bounds info to generate a new midpoint + binary_search_info lastBounds; + if (!binary_search_len_tester_info.find(lastTester, lastBounds)) { + // unexpected + TRACE("str", tout << "WARNING: no bounds information available for last tester!" << std::endl;); + UNREACHABLE(); + } + TRACE("str", tout << "last bounds are [" << lastBounds.lowerBound << " | " << lastBounds.midPoint << " | " << lastBounds.upperBound << "]!" << lastBounds.windowSize << std::endl;); + binary_search_info newBounds; + expr * newTester; + if (lastTesterConstant == "more") { + // special case: if the midpoint, upper bound, and window size are all equal, + // we double the window size and adjust the bounds + if (lastBounds.midPoint == lastBounds.upperBound && lastBounds.upperBound == lastBounds.windowSize) { + TRACE("str", tout << "search hit window size; expanding" << std::endl;); + newBounds.lowerBound = lastBounds.windowSize + rational::one(); + newBounds.windowSize = lastBounds.windowSize * rational(2); + newBounds.upperBound = newBounds.windowSize; + newBounds.calculate_midpoint(); + } else if (false) { + // handle the case where the midpoint can't be increased further + // (e.g. a window like [50 | 50 | 50]!64 and we don't answer "50") + } else { + // general case + newBounds.lowerBound = lastBounds.midPoint + rational::one(); + newBounds.windowSize = lastBounds.windowSize; + newBounds.upperBound = lastBounds.upperBound; + newBounds.calculate_midpoint(); + } + if (!binary_search_next_var_high.find(lastTester, newTester)) { + newTester = mk_internal_lenTest_var(freeVar, newBounds.midPoint.get_int32()); + binary_search_next_var_high.insert(lastTester, newTester); + } + refresh_theory_var(newTester); + } else if (lastTesterConstant == "less") { + if (false) { + // handle the case where the midpoint can't be decreased further + // (e.g. a window like [0 | 0 | 0]!64 and we don't answer "0" + } else { + // general case + newBounds.upperBound = lastBounds.midPoint - rational::one(); + newBounds.windowSize = lastBounds.windowSize; + newBounds.lowerBound = lastBounds.lowerBound; + newBounds.calculate_midpoint(); + } + if (!binary_search_next_var_low.find(lastTester, newTester)) { + newTester = mk_internal_lenTest_var(freeVar, newBounds.midPoint.get_int32()); + binary_search_next_var_low.insert(lastTester, newTester); + } + refresh_theory_var(newTester); + } + TRACE("str", tout << "new bounds are [" << newBounds.lowerBound << " | " << newBounds.midPoint << " | " << newBounds.upperBound << "]!" << newBounds.windowSize << std::endl;); + binary_search_len_tester_stack[freeVar].push_back(newTester); + m_trail_stack.push(binary_search_trail(binary_search_len_tester_stack, freeVar)); + binary_search_len_tester_info.insert(newTester, newBounds); + m_trail_stack.push(insert_obj_map(binary_search_len_tester_info, newTester)); + + literal_vector case_split_literals; + expr_ref next_case_split(binary_search_case_split(freeVar, newTester, newBounds, case_split_literals)); + m_trail.push_back(next_case_split); + // ctx.mk_th_case_split(case_split_literals.size(), case_split_literals.c_ptr()); + return next_case_split; + } else { // lastTesterConstant is a concrete value + TRACE("str", tout << "length is fixed; generating models for free var" << std::endl;); + // defensive check that this length did not converge on a negative value. + binary_search_info lastBounds; + if (!binary_search_len_tester_info.find(lastTester, lastBounds)) { + // unexpected + TRACE("str", tout << "WARNING: no bounds information available for last tester!" << std::endl;); + UNREACHABLE(); + } + if (lastBounds.midPoint.is_neg()) { + TRACE("str", tout << "WARNING: length search converged on a negative value. Negating this constraint." << std::endl;); + expr_ref axiom(m_autil.mk_ge(mk_strlen(freeVar), m_autil.mk_numeral(rational::zero(), true)), m); + return axiom; + } + // length is fixed + expr * valueAssert = gen_free_var_options(freeVar, lastTester, lastTesterConstant, NULL, zstring("")); + return valueAssert; + } + } else { + // no length testers yet + TRACE("str", tout << "no length testers for " << mk_pp(freeVar, m) << std::endl;); + binary_search_len_tester_stack.insert(freeVar, ptr_vector()); + + expr * firstTester; + rational lowerBound(0); + rational upperBound(m_params.m_BinarySearchInitialUpperBound); + rational windowSize(upperBound); + rational midPoint(floor(upperBound / rational(2))); + if (!binary_search_starting_len_tester.find(freeVar, firstTester)) { + firstTester = mk_internal_lenTest_var(freeVar, midPoint.get_int32()); + binary_search_starting_len_tester.insert(freeVar, firstTester); + } + refresh_theory_var(firstTester); + + binary_search_len_tester_stack[freeVar].push_back(firstTester); + m_trail_stack.push(binary_search_trail(binary_search_len_tester_stack, freeVar)); + binary_search_info new_info(lowerBound, midPoint, upperBound, windowSize); + binary_search_len_tester_info.insert(firstTester, new_info); + m_trail_stack.push(insert_obj_map(binary_search_len_tester_info, firstTester)); + + literal_vector case_split_literals; + expr_ref initial_case_split(binary_search_case_split(freeVar, firstTester, new_info, case_split_literals)); + m_trail.push_back(initial_case_split); + // ctx.mk_th_case_split(case_split_literals.size(), case_split_literals.c_ptr()); + return initial_case_split; + } + } + + // ----------------------------------------------------------------------------------------------------- + // True branch will be taken in final_check: + // - When we discover a variable is "free" for the first time + // lenTesterInCbEq = NULL + // lenTesterValue = "" + // False branch will be taken when invoked by new_eq_eh(). + // - After we set up length tester for a "free" var in final_check, + // when the tester is assigned to some value (e.g. "more" or "4"), + // lenTesterInCbEq != NULL, and its value will be passed by lenTesterValue + // The difference is that in new_eq_eh(), lenTesterInCbEq and its value have NOT been put into a same eqc + // ----------------------------------------------------------------------------------------------------- + expr * theory_str::gen_len_val_options_for_free_var(expr * freeVar, expr * lenTesterInCbEq, zstring lenTesterValue) { + + ast_manager & m = get_manager(); + + TRACE("str", tout << "gen for free var " << mk_ismt2_pp(freeVar, m) << std::endl;); + + if (m_params.m_UseBinarySearch) { + TRACE("str", tout << "using binary search heuristic" << std::endl;); + return binary_search_length_test(freeVar, lenTesterInCbEq, lenTesterValue); + } else { + bool map_effectively_empty = false; + if (!fvar_len_count_map.contains(freeVar)) { + TRACE("str", tout << "fvar_len_count_map is empty" << std::endl;); + map_effectively_empty = true; + } + + if (!map_effectively_empty) { + // check whether any entries correspond to variables that went out of scope; + // if every entry is out of scope then the map counts as being empty + + // assume empty and find a counterexample + map_effectively_empty = true; + ptr_vector indicator_set = fvar_lenTester_map[freeVar]; + for (ptr_vector::iterator it = indicator_set.begin(); it != indicator_set.end(); ++it) { + expr * indicator = *it; + if (internal_variable_set.find(indicator) != internal_variable_set.end()) { + TRACE("str", tout <<"found active internal variable " << mk_ismt2_pp(indicator, m) + << " in fvar_lenTester_map[freeVar]" << std::endl;); + map_effectively_empty = false; + break; + } + } + CTRACE("str", map_effectively_empty, tout << "all variables in fvar_lenTester_map[freeVar] out of scope" << std::endl;); + } + + if (map_effectively_empty) { + // no length assertions for this free variable have ever been added. + TRACE("str", tout << "no length assertions yet" << std::endl;); + + fvar_len_count_map.insert(freeVar, 1); + unsigned int testNum = fvar_len_count_map[freeVar]; + + expr_ref indicator(mk_internal_lenTest_var(freeVar, testNum), m); + SASSERT(indicator); + + // since the map is "effectively empty", we can remove those variables that have left scope... + fvar_lenTester_map[freeVar].shrink(0); + fvar_lenTester_map[freeVar].push_back(indicator); + lenTester_fvar_map.insert(indicator, freeVar); + expr * lenTestAssert = gen_len_test_options(freeVar, indicator, testNum); SASSERT(lenTestAssert != NULL); return lenTestAssert; } else { - TRACE("str", tout << "length is fixed; generating models for free var" << std::endl;); - // length is fixed - expr * valueAssert = gen_free_var_options(freeVar, effectiveLenInd, effectiveLenIndiStr, NULL, zstring("")); - return valueAssert; + TRACE("str", tout << "found previous in-scope length assertions" << std::endl;); + + expr * effectiveLenInd = NULL; + zstring effectiveLenIndiStr(""); + int lenTesterCount = (int) fvar_lenTester_map[freeVar].size(); + + TRACE("str", + tout << lenTesterCount << " length testers in fvar_lenTester_map[" << mk_pp(freeVar, m) << "]:" << std::endl; + for (int i = 0; i < lenTesterCount; ++i) { + expr * len_indicator = fvar_lenTester_map[freeVar][i]; + tout << mk_pp(len_indicator, m) << ": "; + bool effectiveInScope = (internal_variable_set.find(len_indicator) != internal_variable_set.end()); + tout << (effectiveInScope ? "in scope" : "NOT in scope"); + tout << std::endl; + } + ); + + int i = 0; + for (; i < lenTesterCount; ++i) { + expr * len_indicator_pre = fvar_lenTester_map[freeVar][i]; + // check whether this is in scope as well + if (internal_variable_set.find(len_indicator_pre) == internal_variable_set.end()) { + TRACE("str", tout << "length indicator " << mk_pp(len_indicator_pre, m) << " not in scope" << std::endl;); + continue; + } + + bool indicatorHasEqcValue = false; + expr * len_indicator_value = get_eqc_value(len_indicator_pre, indicatorHasEqcValue); + TRACE("str", tout << "length indicator " << mk_ismt2_pp(len_indicator_pre, m) << + " = " << mk_ismt2_pp(len_indicator_value, m) << std::endl;); + if (indicatorHasEqcValue) { + zstring len_pIndiStr; + u.str.is_string(len_indicator_value, len_pIndiStr); + if (len_pIndiStr != "more") { + effectiveLenInd = len_indicator_pre; + effectiveLenIndiStr = len_pIndiStr; + break; + } + } else { + if (lenTesterInCbEq != len_indicator_pre) { + TRACE("str", tout << "WARNING: length indicator " << mk_ismt2_pp(len_indicator_pre, m) + << " does not have an equivalence class value." + << " i = " << i << ", lenTesterCount = " << lenTesterCount << std::endl;); + if (i > 0) { + effectiveLenInd = fvar_lenTester_map[freeVar][i - 1]; + bool effectiveHasEqcValue; + expr * effective_eqc_value = get_eqc_value(effectiveLenInd, effectiveHasEqcValue); + bool effectiveInScope = (internal_variable_set.find(effectiveLenInd) != internal_variable_set.end()); + TRACE("str", tout << "checking effective length indicator " << mk_pp(effectiveLenInd, m) << ": " + << (effectiveInScope ? "in scope" : "NOT in scope") << ", "; + if (effectiveHasEqcValue) { + tout << "~= " << mk_pp(effective_eqc_value, m); + } else { + tout << "no eqc string constant"; + } + tout << std::endl;); + if (effectiveLenInd == lenTesterInCbEq) { + effectiveLenIndiStr = lenTesterValue; + } else { + if (effectiveHasEqcValue) { + u.str.is_string(effective_eqc_value, effectiveLenIndiStr); + } else { + NOT_IMPLEMENTED_YET(); + } + } + } + break; + } + // lenTesterInCbEq == len_indicator_pre + else { + if (lenTesterValue != "more") { + effectiveLenInd = len_indicator_pre; + effectiveLenIndiStr = lenTesterValue; + break; + } + } + } // !indicatorHasEqcValue + } // for (i : [0..lenTesterCount-1]) + if (effectiveLenIndiStr == "more" || effectiveLenIndiStr == "") { + TRACE("str", tout << "length is not fixed; generating length tester options for free var" << std::endl;); + expr_ref indicator(m); + unsigned int testNum = 0; + + TRACE("str", tout << "effectiveLenIndiStr = " << effectiveLenIndiStr + << ", i = " << i << ", lenTesterCount = " << lenTesterCount << "\n";); + + if (i == lenTesterCount) { + fvar_len_count_map[freeVar] = fvar_len_count_map[freeVar] + 1; + testNum = fvar_len_count_map[freeVar]; + indicator = mk_internal_lenTest_var(freeVar, testNum); + fvar_lenTester_map[freeVar].push_back(indicator); + lenTester_fvar_map.insert(indicator, freeVar); + } else { + indicator = fvar_lenTester_map[freeVar][i]; + refresh_theory_var(indicator); + testNum = i + 1; + } + expr * lenTestAssert = gen_len_test_options(freeVar, indicator, testNum); + SASSERT(lenTestAssert != NULL); + return lenTestAssert; + } else { + TRACE("str", tout << "length is fixed; generating models for free var" << std::endl;); + // length is fixed + expr * valueAssert = gen_free_var_options(freeVar, effectiveLenInd, effectiveLenIndiStr, NULL, zstring("")); + return valueAssert; + } + } // fVarLenCountMap.find(...) + + } // !UseBinarySearch + } + + void theory_str::get_concats_in_eqc(expr * n, std::set & concats) { + context & ctx = get_context(); + + expr * eqcNode = n; + do { + if (u.str.is_concat(to_app(eqcNode))) { + concats.insert(eqcNode); } - } // fVarLenCountMap.find(...) + eqcNode = get_eqc_next(eqcNode); + } while (eqcNode != n); + } - } // !UseBinarySearch -} + void theory_str::get_var_in_eqc(expr * n, std::set & varSet) { + context & ctx = get_context(); -void theory_str::get_concats_in_eqc(expr * n, std::set & concats) { - context & ctx = get_context(); + expr * eqcNode = n; + do { + if (variable_set.find(eqcNode) != variable_set.end()) { + varSet.insert(eqcNode); + } + eqcNode = get_eqc_next(eqcNode); + } while (eqcNode != n); + } - expr * eqcNode = n; - do { - if (u.str.is_concat(to_app(eqcNode))) { - concats.insert(eqcNode); - } - eqcNode = get_eqc_next(eqcNode); - } while (eqcNode != n); -} + bool cmpvarnames(expr * lhs, expr * rhs) { + symbol lhs_name = to_app(lhs)->get_decl()->get_name(); + symbol rhs_name = to_app(rhs)->get_decl()->get_name(); + return lhs_name.str() < rhs_name.str(); + } -void theory_str::get_var_in_eqc(expr * n, std::set & varSet) { - context & ctx = get_context(); + void theory_str::process_free_var(std::map & freeVar_map) { + context & ctx = get_context(); + ast_manager & m = get_manager(); - expr * eqcNode = n; - do { - if (variable_set.find(eqcNode) != variable_set.end()) { - varSet.insert(eqcNode); - } - eqcNode = get_eqc_next(eqcNode); - } while (eqcNode != n); -} + std::set eqcRepSet; + std::set leafVarSet; + std::map > aloneVars; -bool cmpvarnames(expr * lhs, expr * rhs) { - symbol lhs_name = to_app(lhs)->get_decl()->get_name(); - symbol rhs_name = to_app(rhs)->get_decl()->get_name(); - return lhs_name.str() < rhs_name.str(); -} + for (std::map::iterator fvIt = freeVar_map.begin(); fvIt != freeVar_map.end(); fvIt++) { + expr * freeVar = fvIt->first; + // skip all regular expression vars + if (regex_variable_set.find(freeVar) != regex_variable_set.end()) { + continue; + } -void theory_str::process_free_var(std::map & freeVar_map) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - - std::set eqcRepSet; - std::set leafVarSet; - std::map > aloneVars; - - for (std::map::iterator fvIt = freeVar_map.begin(); fvIt != freeVar_map.end(); fvIt++) { - expr * freeVar = fvIt->first; - // skip all regular expression vars - if (regex_variable_set.find(freeVar) != regex_variable_set.end()) { - continue; - } - - // Iterate the EQC of freeVar, its eqc variable should not be in the eqcRepSet. - // If found, have to filter it out - std::set eqVarSet; - get_var_in_eqc(freeVar, eqVarSet); - bool duplicated = false; - expr * dupVar = NULL; - for (std::set::iterator itorEqv = eqVarSet.begin(); itorEqv != eqVarSet.end(); itorEqv++) { - if (eqcRepSet.find(*itorEqv) != eqcRepSet.end()) { - duplicated = true; - dupVar = *itorEqv; - break; - } - } - if (duplicated && dupVar != NULL) { - TRACE("str", tout << "Duplicated free variable found:" << mk_ismt2_pp(freeVar, m) - << " = " << mk_ismt2_pp(dupVar, m) << " (SKIP)" << std::endl;); - continue; - } else { - eqcRepSet.insert(freeVar); - } - } - - for (std::set::iterator fvIt = eqcRepSet.begin(); fvIt != eqcRepSet.end(); fvIt++) { - bool standAlone = true; - expr * freeVar = *fvIt; - // has length constraint initially - if (input_var_in_len.find(freeVar) != input_var_in_len.end()) { - standAlone = false; - } - // iterate parents - if (standAlone) { - // I hope this works! - enode * e_freeVar = ctx.get_enode(freeVar); - enode_vector::iterator it = e_freeVar->begin_parents(); - for (; it != e_freeVar->end_parents(); ++it) { - expr * parentAst = (*it)->get_owner(); - if (u.str.is_concat(to_app(parentAst))) { - standAlone = false; - break; - } - } - } - - if (standAlone) { - rational len_value; - bool len_value_exists = get_len_value(freeVar, len_value); - if (len_value_exists) { - leafVarSet.insert(freeVar); - } else { - aloneVars[-1].insert(freeVar); - } - } else { - leafVarSet.insert(freeVar); - } - } - - for(std::set::iterator itor1 = leafVarSet.begin(); - itor1 != leafVarSet.end(); ++itor1) { - expr * toAssert = gen_len_val_options_for_free_var(*itor1, NULL, ""); - // gen_len_val_options_for_free_var() can legally return NULL, - // as methods that it calls may assert their own axioms instead. - if (toAssert != NULL) { - assert_axiom(toAssert); - } - } - - for (std::map >::iterator mItor = aloneVars.begin(); - mItor != aloneVars.end(); ++mItor) { - std::set::iterator itor2 = mItor->second.begin(); - for(; itor2 != mItor->second.end(); ++itor2) { - expr * toAssert = gen_len_val_options_for_free_var(*itor2, NULL, ""); - // same deal with returning a NULL axiom here - if(toAssert != NULL) { - assert_axiom(toAssert); - } - } - } -} - -/* - * Collect all unroll functions - * and constant string in eqc of node n - */ -void theory_str::get_eqc_allUnroll(expr * n, expr * &constStr, std::set & unrollFuncSet) { - constStr = NULL; - unrollFuncSet.clear(); - context & ctx = get_context(); - - expr * curr = n; - do { - if (u.str.is_string(to_app(curr))) { - constStr = curr; - } else if (u.re.is_unroll(to_app(curr))) { - if (unrollFuncSet.find(curr) == unrollFuncSet.end()) { - unrollFuncSet.insert(curr); + // Iterate the EQC of freeVar, its eqc variable should not be in the eqcRepSet. + // If found, have to filter it out + std::set eqVarSet; + get_var_in_eqc(freeVar, eqVarSet); + bool duplicated = false; + expr * dupVar = NULL; + for (std::set::iterator itorEqv = eqVarSet.begin(); itorEqv != eqVarSet.end(); itorEqv++) { + if (eqcRepSet.find(*itorEqv) != eqcRepSet.end()) { + duplicated = true; + dupVar = *itorEqv; + break; + } + } + if (duplicated && dupVar != NULL) { + TRACE("str", tout << "Duplicated free variable found:" << mk_ismt2_pp(freeVar, m) + << " = " << mk_ismt2_pp(dupVar, m) << " (SKIP)" << std::endl;); + continue; + } else { + eqcRepSet.insert(freeVar); } } - curr = get_eqc_next(curr); - } while (curr != n); -} -// Collect simple Unroll functions (whose core is Str2Reg) and constant strings in the EQC of n. -void theory_str::get_eqc_simpleUnroll(expr * n, expr * &constStr, std::set & unrollFuncSet) { - constStr = NULL; - unrollFuncSet.clear(); - context & ctx = get_context(); + for (std::set::iterator fvIt = eqcRepSet.begin(); fvIt != eqcRepSet.end(); fvIt++) { + bool standAlone = true; + expr * freeVar = *fvIt; + // has length constraint initially + if (input_var_in_len.find(freeVar) != input_var_in_len.end()) { + standAlone = false; + } + // iterate parents + if (standAlone) { + // I hope this works! + enode * e_freeVar = ctx.get_enode(freeVar); + enode_vector::iterator it = e_freeVar->begin_parents(); + for (; it != e_freeVar->end_parents(); ++it) { + expr * parentAst = (*it)->get_owner(); + if (u.str.is_concat(to_app(parentAst))) { + standAlone = false; + break; + } + } + } - expr * curr = n; - do { - if (u.str.is_string(to_app(curr))) { - constStr = curr; - } else if (u.re.is_unroll(to_app(curr))) { - expr * core = to_app(curr)->get_arg(0); - if (u.re.is_to_re(to_app(core))) { - if (unrollFuncSet.find(curr) == unrollFuncSet.end()) { - unrollFuncSet.insert(curr); - } - } - } - curr = get_eqc_next(curr); - } while (curr != n); -} + if (standAlone) { + rational len_value; + bool len_value_exists = get_len_value(freeVar, len_value); + if (len_value_exists) { + leafVarSet.insert(freeVar); + } else { + aloneVars[-1].insert(freeVar); + } + } else { + leafVarSet.insert(freeVar); + } + } -void theory_str::init_model(model_generator & mg) { - //TRACE("str", tout << "initializing model" << std::endl; display(tout);); - m_factory = alloc(str_value_factory, get_manager(), get_family_id()); - mg.register_factory(m_factory); -} + for(std::set::iterator itor1 = leafVarSet.begin(); + itor1 != leafVarSet.end(); ++itor1) { + expr * toAssert = gen_len_val_options_for_free_var(*itor1, NULL, ""); + // gen_len_val_options_for_free_var() can legally return NULL, + // as methods that it calls may assert their own axioms instead. + if (toAssert != NULL) { + assert_axiom(toAssert); + } + } -/* - * Helper function for mk_value(). - * Attempts to resolve the expression 'n' to a string constant. - * Stronger than get_eqc_value() in that it will perform recursive descent - * through every subexpression and attempt to resolve those to concrete values as well. - * Returns the concrete value obtained from this process, - * guaranteed to satisfy m_strutil.is_string(), - * if one could be obtained, - * or else returns NULL if no concrete value was derived. - */ -app * theory_str::mk_value_helper(app * n) { - if (u.str.is_string(n)) { - return n; - } else if (u.str.is_concat(n)) { - // recursively call this function on each argument - SASSERT(n->get_num_args() == 2); - expr * a0 = n->get_arg(0); - expr * a1 = n->get_arg(1); - - app * a0_conststr = mk_value_helper(to_app(a0)); - app * a1_conststr = mk_value_helper(to_app(a1)); - - if (a0_conststr != NULL && a1_conststr != NULL) { - zstring a0_s, a1_s; - u.str.is_string(a0_conststr, a0_s); - u.str.is_string(a1_conststr, a1_s); - zstring result = a0_s + a1_s; - return to_app(mk_string(result)); + for (std::map >::iterator mItor = aloneVars.begin(); + mItor != aloneVars.end(); ++mItor) { + std::set::iterator itor2 = mItor->second.begin(); + for(; itor2 != mItor->second.end(); ++itor2) { + expr * toAssert = gen_len_val_options_for_free_var(*itor2, NULL, ""); + // same deal with returning a NULL axiom here + if(toAssert != NULL) { + assert_axiom(toAssert); + } + } } } - // fallback path - // try to find some constant string, anything, in the equivalence class of n - bool hasEqc = false; - expr * n_eqc = get_eqc_value(n, hasEqc); - if (hasEqc) { - return to_app(n_eqc); - } else { - return NULL; + + /* + * Collect all unroll functions + * and constant string in eqc of node n + */ + void theory_str::get_eqc_allUnroll(expr * n, expr * &constStr, std::set & unrollFuncSet) { + constStr = NULL; + unrollFuncSet.clear(); + context & ctx = get_context(); + + expr * curr = n; + do { + if (u.str.is_string(to_app(curr))) { + constStr = curr; + } else if (u.re.is_unroll(to_app(curr))) { + if (unrollFuncSet.find(curr) == unrollFuncSet.end()) { + unrollFuncSet.insert(curr); + } + } + curr = get_eqc_next(curr); + } while (curr != n); } -} -model_value_proc * theory_str::mk_value(enode * n, model_generator & mg) { - TRACE("str", tout << "mk_value for: " << mk_ismt2_pp(n->get_owner(), get_manager()) << - " (sort " << mk_ismt2_pp(get_manager().get_sort(n->get_owner()), get_manager()) << ")" << std::endl;); - ast_manager & m = get_manager(); - context & ctx = get_context(); - app_ref owner(m); - owner = n->get_owner(); + // Collect simple Unroll functions (whose core is Str2Reg) and constant strings in the EQC of n. + void theory_str::get_eqc_simpleUnroll(expr * n, expr * &constStr, std::set & unrollFuncSet) { + constStr = NULL; + unrollFuncSet.clear(); + context & ctx = get_context(); - // If the owner is not internalized, it doesn't have an enode associated. - SASSERT(ctx.e_internalized(owner)); - - app * val = mk_value_helper(owner); - if (val != NULL) { - return alloc(expr_wrapper_proc, val); - } else { - TRACE("str", tout << "WARNING: failed to find a concrete value, falling back" << std::endl;); - return alloc(expr_wrapper_proc, to_app(mk_string("**UNUSED**"))); + expr * curr = n; + do { + if (u.str.is_string(to_app(curr))) { + constStr = curr; + } else if (u.re.is_unroll(to_app(curr))) { + expr * core = to_app(curr)->get_arg(0); + if (u.re.is_to_re(to_app(core))) { + if (unrollFuncSet.find(curr) == unrollFuncSet.end()) { + unrollFuncSet.insert(curr); + } + } + } + curr = get_eqc_next(curr); + } while (curr != n); } -} -void theory_str::finalize_model(model_generator & mg) {} + void theory_str::init_model(model_generator & mg) { + //TRACE("str", tout << "initializing model" << std::endl; display(tout);); + m_factory = alloc(str_value_factory, get_manager(), get_family_id()); + mg.register_factory(m_factory); + } -void theory_str::display(std::ostream & out) const { - out << "TODO: theory_str display" << std::endl; -} + /* + * Helper function for mk_value(). + * Attempts to resolve the expression 'n' to a string constant. + * Stronger than get_eqc_value() in that it will perform recursive descent + * through every subexpression and attempt to resolve those to concrete values as well. + * Returns the concrete value obtained from this process, + * guaranteed to satisfy m_strutil.is_string(), + * if one could be obtained, + * or else returns NULL if no concrete value was derived. + */ + app * theory_str::mk_value_helper(app * n) { + if (u.str.is_string(n)) { + return n; + } else if (u.str.is_concat(n)) { + // recursively call this function on each argument + SASSERT(n->get_num_args() == 2); + expr * a0 = n->get_arg(0); + expr * a1 = n->get_arg(1); + + app * a0_conststr = mk_value_helper(to_app(a0)); + app * a1_conststr = mk_value_helper(to_app(a1)); + + if (a0_conststr != NULL && a1_conststr != NULL) { + zstring a0_s, a1_s; + u.str.is_string(a0_conststr, a0_s); + u.str.is_string(a1_conststr, a1_s); + zstring result = a0_s + a1_s; + return to_app(mk_string(result)); + } + } + // fallback path + // try to find some constant string, anything, in the equivalence class of n + bool hasEqc = false; + expr * n_eqc = get_eqc_value(n, hasEqc); + if (hasEqc) { + return to_app(n_eqc); + } else { + return NULL; + } + } + + model_value_proc * theory_str::mk_value(enode * n, model_generator & mg) { + TRACE("str", tout << "mk_value for: " << mk_ismt2_pp(n->get_owner(), get_manager()) << + " (sort " << mk_ismt2_pp(get_manager().get_sort(n->get_owner()), get_manager()) << ")" << std::endl;); + ast_manager & m = get_manager(); + context & ctx = get_context(); + app_ref owner(m); + owner = n->get_owner(); + + // If the owner is not internalized, it doesn't have an enode associated. + SASSERT(ctx.e_internalized(owner)); + + app * val = mk_value_helper(owner); + if (val != NULL) { + return alloc(expr_wrapper_proc, val); + } else { + TRACE("str", tout << "WARNING: failed to find a concrete value, falling back" << std::endl;); + return alloc(expr_wrapper_proc, to_app(mk_string("**UNUSED**"))); + } + } + + void theory_str::finalize_model(model_generator & mg) {} + + void theory_str::display(std::ostream & out) const { + out << "TODO: theory_str display" << std::endl; + } }; /* namespace smt */ From f904b033ad8f6db0eefe37ebfe40f1bd60b310e5 Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Fri, 5 May 2017 19:29:53 -0400 Subject: [PATCH 388/410] formatting theory_str.h --- src/smt/theory_str.h | 1110 +++++++++++++++++++++--------------------- 1 file changed, 555 insertions(+), 555 deletions(-) diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 7c2df9e12..2e6d96fa7 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -1,19 +1,19 @@ /*++ -Module Name: + Module Name: - theory_str.h + theory_str.h -Abstract: + Abstract: - String Theory Plugin + String Theory Plugin -Author: + Author: - Murphy Berzish and Yunhui Zheng + Murphy Berzish and Yunhui Zheng -Revision History: + Revision History: ---*/ + --*/ #ifndef _THEORY_STR_H_ #define _THEORY_STR_H_ @@ -33,619 +33,619 @@ Revision History: namespace smt { - typedef hashtable symbol_set; +typedef hashtable symbol_set; - class str_value_factory : public value_factory { - seq_util u; - symbol_set m_strings; - std::string delim; - unsigned m_next; - public: - str_value_factory(ast_manager & m, family_id fid) : - value_factory(m, fid), - u(m), delim("!"), m_next(0) {} - virtual ~str_value_factory() {} - virtual expr * get_some_value(sort * s) { - return u.str.mk_string(symbol("some value")); - } - virtual bool get_some_values(sort * s, expr_ref & v1, expr_ref & v2) { - v1 = u.str.mk_string(symbol("value 1")); - v2 = u.str.mk_string(symbol("value 2")); - return true; - } - virtual expr * get_fresh_value(sort * s) { - if (u.is_string(s)) { - while (true) { - std::ostringstream strm; - strm << delim << std::hex << (m_next++) << std::dec << delim; - symbol sym(strm.str().c_str()); - if (m_strings.contains(sym)) continue; - m_strings.insert(sym); - return u.str.mk_string(sym); - } +class str_value_factory : public value_factory { + seq_util u; + symbol_set m_strings; + std::string delim; + unsigned m_next; +public: + str_value_factory(ast_manager & m, family_id fid) : + value_factory(m, fid), + u(m), delim("!"), m_next(0) {} + virtual ~str_value_factory() {} + virtual expr * get_some_value(sort * s) { + return u.str.mk_string(symbol("some value")); + } + virtual bool get_some_values(sort * s, expr_ref & v1, expr_ref & v2) { + v1 = u.str.mk_string(symbol("value 1")); + v2 = u.str.mk_string(symbol("value 2")); + return true; + } + virtual expr * get_fresh_value(sort * s) { + if (u.is_string(s)) { + while (true) { + std::ostringstream strm; + strm << delim << std::hex << (m_next++) << std::dec << delim; + symbol sym(strm.str().c_str()); + if (m_strings.contains(sym)) continue; + m_strings.insert(sym); + return u.str.mk_string(sym); } - sort* seq = 0; - if (u.is_re(s, seq)) { - expr* v0 = get_fresh_value(seq); - return u.re.mk_to_re(v0); - } - TRACE("t_str", tout << "unexpected sort in get_fresh_value(): " << mk_pp(s, m_manager) << std::endl;); - UNREACHABLE(); return NULL; } - virtual void register_value(expr * n) { /* Ignore */ } - }; + sort* seq = 0; + if (u.is_re(s, seq)) { + expr* v0 = get_fresh_value(seq); + return u.re.mk_to_re(v0); + } + TRACE("t_str", tout << "unexpected sort in get_fresh_value(): " << mk_pp(s, m_manager) << std::endl;); + UNREACHABLE(); return NULL; + } + virtual void register_value(expr * n) { /* Ignore */ } +}; - // rather than modify obj_pair_map I inherit from it and add my own helper methods - class theory_str_contain_pair_bool_map_t : public obj_pair_map { - public: - expr * operator[](std::pair key) const { - expr * value; - bool found = this->find(key.first, key.second, value); - if (found) { - return value; +// rather than modify obj_pair_map I inherit from it and add my own helper methods +class theory_str_contain_pair_bool_map_t : public obj_pair_map { +public: + expr * operator[](std::pair key) const { + expr * value; + bool found = this->find(key.first, key.second, value); + if (found) { + return value; + } else { + TRACE("t_str", tout << "WARNING: lookup miss in contain_pair_bool_map!" << std::endl;); + return NULL; + } + } + + bool contains(std::pair key) const { + expr * unused; + return this->find(key.first, key.second, unused); + } +}; + +template +class binary_search_trail : public trail { + obj_map > & target; + expr * entry; +public: + binary_search_trail(obj_map > & target, expr * entry) : + target(target), entry(entry) {} + virtual ~binary_search_trail() {} + virtual void undo(Ctx & ctx) { + TRACE("t_str_binary_search", tout << "in binary_search_trail::undo()" << std::endl;); + if (target.contains(entry)) { + if (!target[entry].empty()) { + target[entry].pop_back(); } else { - TRACE("t_str", tout << "WARNING: lookup miss in contain_pair_bool_map!" << std::endl;); - return NULL; + TRACE("t_str_binary_search", tout << "WARNING: attempt to remove length tester from an empty stack" << std::endl;); } + } else { + TRACE("t_str_binary_search", tout << "WARNING: attempt to access length tester map via invalid key" << std::endl;); } + } +}; - bool contains(std::pair key) const { - expr * unused; - return this->find(key.first, key.second, unused); + +class nfa { +protected: + bool m_valid; + unsigned m_next_id; + + unsigned next_id() { + unsigned retval = m_next_id; + ++m_next_id; + return retval; + } + + unsigned m_start_state; + unsigned m_end_state; + + std::map > transition_map; + std::map > epsilon_map; + + void make_transition(unsigned start, char symbol, unsigned end) { + transition_map[start][symbol] = end; + } + + void make_epsilon_move(unsigned start, unsigned end) { + epsilon_map[start].insert(end); + } + + // Convert a regular expression to an e-NFA using Thompson's construction + void convert_re(expr * e, unsigned & start, unsigned & end, seq_util & u); + +public: + nfa(seq_util & u, expr * e) +: m_valid(true), m_next_id(0), m_start_state(0), m_end_state(0) { + convert_re(e, m_start_state, m_end_state, u); + } + + nfa() : m_valid(false), m_next_id(0), m_start_state(0), m_end_state(0) {} + + bool is_valid() const { + return m_valid; + } + + void epsilon_closure(unsigned start, std::set & closure); + + bool matches(zstring input); +}; + +class theory_str : public theory { + struct T_cut + { + int level; + std::map vars; + + T_cut() { + level = -100; } }; - template - class binary_search_trail : public trail { - obj_map > & target; - expr * entry; - public: - binary_search_trail(obj_map > & target, expr * entry) : - target(target), entry(entry) {} - virtual ~binary_search_trail() {} - virtual void undo(Ctx & ctx) { - TRACE("t_str_binary_search", tout << "in binary_search_trail::undo()" << std::endl;); - if (target.contains(entry)) { - if (!target[entry].empty()) { - target[entry].pop_back(); - } else { - TRACE("t_str_binary_search", tout << "WARNING: attempt to remove length tester from an empty stack" << std::endl;); - } - } else { - TRACE("t_str_binary_search", tout << "WARNING: attempt to access length tester map via invalid key" << std::endl;); - } + typedef trail_stack th_trail_stack; + typedef union_find th_union_find; + + typedef map, default_eq > rational_map; + struct zstring_hash_proc { + unsigned operator()(zstring const & s) const { + return string_hash(s.encode().c_str(), static_cast(s.length()), 17); } }; + typedef map > string_map; +protected: + theory_str_params const & m_params; - class nfa { - protected: - bool m_valid; - unsigned m_next_id; + /* + * Setting EagerStringConstantLengthAssertions to true allows some methods, + * in particular internalize_term(), to add + * length assertions about relevant string constants. + * Note that currently this should always be set to 'true', or else *no* length assertions + * will be made about string constants. + */ + bool opt_EagerStringConstantLengthAssertions; - unsigned next_id() { - unsigned retval = m_next_id; - ++m_next_id; - return retval; + /* + * If VerifyFinalCheckProgress is set to true, continuing after final check is invoked + * without asserting any new axioms is considered a bug and will throw an exception. + */ + bool opt_VerifyFinalCheckProgress; + + /* + * This constant controls how eagerly we expand unrolls in unbounded regex membership tests. + */ + int opt_LCMUnrollStep; + + /* + * If NoQuickReturn_IntegerTheory is set to true, + * integer theory integration checks that assert axioms + * will not return from the function after asserting their axioms. + * The default behaviour of Z3str2 is to set this to 'false'. This may be incorrect. + */ + bool opt_NoQuickReturn_IntegerTheory; + + /* + * If DisableIntegerTheoryIntegration is set to true, + * ALL calls to the integer theory integration methods + * (get_value, get_len_value, lower_bound, upper_bound) + * will ignore what the arithmetic solver believes about length terms, + * and will return no information. + * + * This reduces performance significantly, but can be useful to enable + * if it is suspected that string-integer integration, or the arithmetic solver itself, + * might have a bug. + * + * The default behaviour of Z3str2 is to set this to 'false'. + */ + bool opt_DisableIntegerTheoryIntegration; + + /* + * If DeferEQCConsistencyCheck is set to true, + * expensive calls to new_eq_check() will be deferred until final check, + * at which time the consistency of *all* string equivalence classes will be validated. + */ + bool opt_DeferEQCConsistencyCheck; + + /* + * If CheckVariableScope is set to true, + * pop_scope_eh() and final_check_eh() will run extra checks + * to determine whether the current assignment + * contains references to any internal variables that are no longer in scope. + */ + bool opt_CheckVariableScope; + + /* + * If ConcatOverlapAvoid is set to true, + * the check to simplify Concat = Concat in handle_equality() will + * avoid simplifying wrt. pairs of Concat terms that will immediately + * result in an overlap. (false = Z3str2 behaviour) + */ + bool opt_ConcatOverlapAvoid; + + bool search_started; + arith_util m_autil; + seq_util u; + int sLevel; + + bool finalCheckProgressIndicator; + + expr_ref_vector m_trail; // trail for generated terms + + str_value_factory * m_factory; + + // terms we couldn't go through set_up_axioms() with because they weren't internalized + expr_ref_vector m_delayed_axiom_setup_terms; + + ptr_vector m_basicstr_axiom_todo; + svector > m_str_eq_todo; + ptr_vector m_concat_axiom_todo; + ptr_vector m_string_constant_length_todo; + ptr_vector m_concat_eval_todo; + + // enode lists for library-aware/high-level string terms (e.g. substr, contains) + ptr_vector m_library_aware_axiom_todo; + + // hashtable of all exprs for which we've already set up term-specific axioms -- + // this prevents infinite recursive descent with respect to axioms that + // include an occurrence of the term for which axioms are being generated + obj_hashtable axiomatized_terms; + + int tmpStringVarCount; + int tmpXorVarCount; + int tmpLenTestVarCount; + int tmpValTestVarCount; + std::map, std::map > varForBreakConcat; + + bool avoidLoopCut; + bool loopDetected; + obj_map > cut_var_map; + expr_ref m_theoryStrOverlapAssumption_term; + + obj_hashtable variable_set; + obj_hashtable internal_variable_set; + obj_hashtable regex_variable_set; + std::map > internal_variable_scope_levels; + + obj_hashtable internal_lenTest_vars; + obj_hashtable internal_valTest_vars; + obj_hashtable internal_unrollTest_vars; + + obj_hashtable input_var_in_len; + + obj_map fvar_len_count_map; + std::map > fvar_lenTester_map; + obj_map lenTester_fvar_map; + + std::map > > > fvar_valueTester_map; + std::map valueTester_fvar_map; + + std::map val_range_map; + + // This can't be an expr_ref_vector because the constructor is wrong, + // we would need to modify the allocator so we pass in ast_manager + std::map, ptr_vector > > unroll_tries_map; + std::map unroll_var_map; + std::map, expr*> concat_eq_unroll_ast_map; + + expr_ref_vector contains_map; + + theory_str_contain_pair_bool_map_t contain_pair_bool_map; + //obj_map > contain_pair_idx_map; + std::map > > contain_pair_idx_map; + + std::map, expr*> regex_in_bool_map; + std::map > regex_in_var_reg_str_map; + + std::map regex_nfa_cache; // Regex term --> NFA + + char * char_set; + std::map charSetLookupTable; + int charSetSize; + + obj_pair_map concat_astNode_map; + + // all (str.to-int) and (int.to-str) terms + expr_ref_vector string_int_conversion_terms; + obj_hashtable string_int_axioms; + + // used when opt_FastLengthTesterCache is true + rational_map lengthTesterCache; + // used when opt_FastValueTesterCache is true + string_map valueTesterCache; + + string_map stringConstantCache; + unsigned long totalCacheAccessCount; + unsigned long cacheHitCount; + unsigned long cacheMissCount; + + // cache mapping each string S to Length(S) + obj_map length_ast_map; + + th_union_find m_find; + th_trail_stack m_trail_stack; + theory_var get_var(expr * n) const; + expr * get_eqc_next(expr * n); + app * get_ast(theory_var i); + + // binary search heuristic data + struct binary_search_info { + rational lowerBound; + rational midPoint; + rational upperBound; + rational windowSize; + + binary_search_info() : lowerBound(rational::zero()), midPoint(rational::zero()), + upperBound(rational::zero()), windowSize(rational::zero()) {} + binary_search_info(rational lower, rational mid, rational upper, rational windowSize) : + lowerBound(lower), midPoint(mid), upperBound(upper), windowSize(windowSize) {} + + void calculate_midpoint() { + midPoint = floor(lowerBound + ((upperBound - lowerBound) / rational(2)) ); } - - unsigned m_start_state; - unsigned m_end_state; - - std::map > transition_map; - std::map > epsilon_map; - - void make_transition(unsigned start, char symbol, unsigned end) { - transition_map[start][symbol] = end; - } - - void make_epsilon_move(unsigned start, unsigned end) { - epsilon_map[start].insert(end); - } - - // Convert a regular expression to an e-NFA using Thompson's construction - void convert_re(expr * e, unsigned & start, unsigned & end, seq_util & u); - - public: - nfa(seq_util & u, expr * e) - : m_valid(true), m_next_id(0), m_start_state(0), m_end_state(0) { - convert_re(e, m_start_state, m_end_state, u); - } - - nfa() : m_valid(false), m_next_id(0), m_start_state(0), m_end_state(0) {} - - bool is_valid() const { - return m_valid; - } - - void epsilon_closure(unsigned start, std::set & closure); - - bool matches(zstring input); }; + // maps a free string var to a stack of active length testers. + // can use binary_search_trail to record changes to this object + obj_map > binary_search_len_tester_stack; + // maps a length tester var to the *active* search window + obj_map binary_search_len_tester_info; + // maps a free string var to the first length tester to be (re)used + obj_map binary_search_starting_len_tester; + // maps a length tester to the next length tester to be (re)used if the split is "low" + obj_map binary_search_next_var_low; + // maps a length tester to the next length tester to be (re)used if the split is "high" + obj_map binary_search_next_var_high; - class theory_str : public theory { - struct T_cut - { - int level; - std::map vars; + // finite model finding data + // maps a finite model tester var to a list of variables that will be tested + obj_map > finite_model_test_varlists; +protected: + void assert_axiom(expr * e); + void assert_implication(expr * premise, expr * conclusion); + expr * rewrite_implication(expr * premise, expr * conclusion); - T_cut() { - level = -100; - } - }; + expr * mk_string(zstring const& str); + expr * mk_string(const char * str); - typedef trail_stack th_trail_stack; - typedef union_find th_union_find; + app * mk_strlen(expr * e); + expr * mk_concat(expr * n1, expr * n2); + expr * mk_concat_const_str(expr * n1, expr * n2); + app * mk_contains(expr * haystack, expr * needle); + app * mk_indexof(expr * haystack, expr * needle); - typedef map, default_eq > rational_map; - struct zstring_hash_proc { - unsigned operator()(zstring const & s) const { - return string_hash(s.encode().c_str(), static_cast(s.length()), 17); - } - }; - typedef map > string_map; + literal mk_literal(expr* _e); + app * mk_int(int n); + app * mk_int(rational & q); - protected: - theory_str_params const & m_params; + void check_and_init_cut_var(expr * node); + void add_cut_info_one_node(expr * baseNode, int slevel, expr * node); + void add_cut_info_merge(expr * destNode, int slevel, expr * srcNode); + bool has_self_cut(expr * n1, expr * n2); - /* - * Setting EagerStringConstantLengthAssertions to true allows some methods, - * in particular internalize_term(), to add - * length assertions about relevant string constants. - * Note that currently this should always be set to 'true', or else *no* length assertions - * will be made about string constants. - */ - bool opt_EagerStringConstantLengthAssertions; + // for ConcatOverlapAvoid + bool will_result_in_overlap(expr * lhs, expr * rhs); - /* - * If VerifyFinalCheckProgress is set to true, continuing after final check is invoked - * without asserting any new axioms is considered a bug and will throw an exception. - */ - bool opt_VerifyFinalCheckProgress; + void track_variable_scope(expr * var); + app * mk_str_var(std::string name); + app * mk_int_var(std::string name); + app * mk_nonempty_str_var(); + app * mk_internal_xor_var(); + expr * mk_internal_valTest_var(expr * node, int len, int vTries); + app * mk_regex_rep_var(); + app * mk_unroll_bound_var(); + app * mk_unroll_test_var(); + void add_nonempty_constraint(expr * s); - /* - * This constant controls how eagerly we expand unrolls in unbounded regex membership tests. - */ - int opt_LCMUnrollStep; + void instantiate_concat_axiom(enode * cat); + void try_eval_concat(enode * cat); + void instantiate_basic_string_axioms(enode * str); + void instantiate_str_eq_length_axiom(enode * lhs, enode * rhs); - /* - * If NoQuickReturn_IntegerTheory is set to true, - * integer theory integration checks that assert axioms - * will not return from the function after asserting their axioms. - * The default behaviour of Z3str2 is to set this to 'false'. This may be incorrect. - */ - bool opt_NoQuickReturn_IntegerTheory; + void instantiate_axiom_CharAt(enode * e); + void instantiate_axiom_prefixof(enode * e); + void instantiate_axiom_suffixof(enode * e); + void instantiate_axiom_Contains(enode * e); + void instantiate_axiom_Indexof(enode * e); + void instantiate_axiom_Indexof2(enode * e); + void instantiate_axiom_LastIndexof(enode * e); + void instantiate_axiom_Substr(enode * e); + void instantiate_axiom_Replace(enode * e); + void instantiate_axiom_str_to_int(enode * e); + void instantiate_axiom_int_to_str(enode * e); - /* - * If DisableIntegerTheoryIntegration is set to true, - * ALL calls to the integer theory integration methods - * (get_value, get_len_value, lower_bound, upper_bound) - * will ignore what the arithmetic solver believes about length terms, - * and will return no information. - * - * This reduces performance significantly, but can be useful to enable - * if it is suspected that string-integer integration, or the arithmetic solver itself, - * might have a bug. - * - * The default behaviour of Z3str2 is to set this to 'false'. - */ - bool opt_DisableIntegerTheoryIntegration; + expr * mk_RegexIn(expr * str, expr * regexp); + void instantiate_axiom_RegexIn(enode * e); + app * mk_unroll(expr * n, expr * bound); - /* - * If DeferEQCConsistencyCheck is set to true, - * expensive calls to new_eq_check() will be deferred until final check, - * at which time the consistency of *all* string equivalence classes will be validated. - */ - bool opt_DeferEQCConsistencyCheck; + void process_unroll_eq_const_str(expr * unrollFunc, expr * constStr); + void unroll_str2reg_constStr(expr * unrollFunc, expr * eqConstStr); + void process_concat_eq_unroll(expr * concat, expr * unroll); - /* - * If CheckVariableScope is set to true, - * pop_scope_eh() and final_check_eh() will run extra checks - * to determine whether the current assignment - * contains references to any internal variables that are no longer in scope. - */ - bool opt_CheckVariableScope; + void set_up_axioms(expr * ex); + void handle_equality(expr * lhs, expr * rhs); - /* - * If ConcatOverlapAvoid is set to true, - * the check to simplify Concat = Concat in handle_equality() will - * avoid simplifying wrt. pairs of Concat terms that will immediately - * result in an overlap. (false = Z3str2 behaviour) - */ - bool opt_ConcatOverlapAvoid; + app * mk_value_helper(app * n); + expr * get_eqc_value(expr * n, bool & hasEqcValue); + expr * z3str2_get_eqc_value(expr * n , bool & hasEqcValue); + bool in_same_eqc(expr * n1, expr * n2); + expr * collect_eq_nodes(expr * n, expr_ref_vector & eqcSet); - bool search_started; - arith_util m_autil; - seq_util u; - int sLevel; + bool get_value(expr* e, rational& val) const; + bool get_len_value(expr* e, rational& val); + bool lower_bound(expr* _e, rational& lo); + bool upper_bound(expr* _e, rational& hi); - bool finalCheckProgressIndicator; - - expr_ref_vector m_trail; // trail for generated terms - - str_value_factory * m_factory; - - // terms we couldn't go through set_up_axioms() with because they weren't internalized - expr_ref_vector m_delayed_axiom_setup_terms; - - ptr_vector m_basicstr_axiom_todo; - svector > m_str_eq_todo; - ptr_vector m_concat_axiom_todo; - ptr_vector m_string_constant_length_todo; - ptr_vector m_concat_eval_todo; - - // enode lists for library-aware/high-level string terms (e.g. substr, contains) - ptr_vector m_library_aware_axiom_todo; - - // hashtable of all exprs for which we've already set up term-specific axioms -- - // this prevents infinite recursive descent with respect to axioms that - // include an occurrence of the term for which axioms are being generated - obj_hashtable axiomatized_terms; - - int tmpStringVarCount; - int tmpXorVarCount; - int tmpLenTestVarCount; - int tmpValTestVarCount; - std::map, std::map > varForBreakConcat; - - bool avoidLoopCut; - bool loopDetected; - obj_map > cut_var_map; - expr_ref m_theoryStrOverlapAssumption_term; - - obj_hashtable variable_set; - obj_hashtable internal_variable_set; - obj_hashtable regex_variable_set; - std::map > internal_variable_scope_levels; - - obj_hashtable internal_lenTest_vars; - obj_hashtable internal_valTest_vars; - obj_hashtable internal_unrollTest_vars; - - obj_hashtable input_var_in_len; - - obj_map fvar_len_count_map; - std::map > fvar_lenTester_map; - obj_map lenTester_fvar_map; - - std::map > > > fvar_valueTester_map; - std::map valueTester_fvar_map; - - std::map val_range_map; - - // This can't be an expr_ref_vector because the constructor is wrong, - // we would need to modify the allocator so we pass in ast_manager - std::map, ptr_vector > > unroll_tries_map; - std::map unroll_var_map; - std::map, expr*> concat_eq_unroll_ast_map; - - expr_ref_vector contains_map; - - theory_str_contain_pair_bool_map_t contain_pair_bool_map; - //obj_map > contain_pair_idx_map; - std::map > > contain_pair_idx_map; - - std::map, expr*> regex_in_bool_map; - std::map > regex_in_var_reg_str_map; - - std::map regex_nfa_cache; // Regex term --> NFA - - char * char_set; - std::map charSetLookupTable; - int charSetSize; - - obj_pair_map concat_astNode_map; - - // all (str.to-int) and (int.to-str) terms - expr_ref_vector string_int_conversion_terms; - obj_hashtable string_int_axioms; - - // used when opt_FastLengthTesterCache is true - rational_map lengthTesterCache; - // used when opt_FastValueTesterCache is true - string_map valueTesterCache; - - string_map stringConstantCache; - unsigned long totalCacheAccessCount; - unsigned long cacheHitCount; - unsigned long cacheMissCount; - - // cache mapping each string S to Length(S) - obj_map length_ast_map; - - th_union_find m_find; - th_trail_stack m_trail_stack; - theory_var get_var(expr * n) const; - expr * get_eqc_next(expr * n); - app * get_ast(theory_var i); - - // binary search heuristic data - struct binary_search_info { - rational lowerBound; - rational midPoint; - rational upperBound; - rational windowSize; - - binary_search_info() : lowerBound(rational::zero()), midPoint(rational::zero()), - upperBound(rational::zero()), windowSize(rational::zero()) {} - binary_search_info(rational lower, rational mid, rational upper, rational windowSize) : - lowerBound(lower), midPoint(mid), upperBound(upper), windowSize(windowSize) {} - - void calculate_midpoint() { - midPoint = floor(lowerBound + ((upperBound - lowerBound) / rational(2)) ); - } - }; - // maps a free string var to a stack of active length testers. - // can use binary_search_trail to record changes to this object - obj_map > binary_search_len_tester_stack; - // maps a length tester var to the *active* search window - obj_map binary_search_len_tester_info; - // maps a free string var to the first length tester to be (re)used - obj_map binary_search_starting_len_tester; - // maps a length tester to the next length tester to be (re)used if the split is "low" - obj_map binary_search_next_var_low; - // maps a length tester to the next length tester to be (re)used if the split is "high" - obj_map binary_search_next_var_high; - - // finite model finding data - // maps a finite model tester var to a list of variables that will be tested - obj_map > finite_model_test_varlists; - protected: - void assert_axiom(expr * e); - void assert_implication(expr * premise, expr * conclusion); - expr * rewrite_implication(expr * premise, expr * conclusion); - - expr * mk_string(zstring const& str); - expr * mk_string(const char * str); - - app * mk_strlen(expr * e); - expr * mk_concat(expr * n1, expr * n2); - expr * mk_concat_const_str(expr * n1, expr * n2); - app * mk_contains(expr * haystack, expr * needle); - app * mk_indexof(expr * haystack, expr * needle); - - literal mk_literal(expr* _e); - app * mk_int(int n); - app * mk_int(rational & q); - - void check_and_init_cut_var(expr * node); - void add_cut_info_one_node(expr * baseNode, int slevel, expr * node); - void add_cut_info_merge(expr * destNode, int slevel, expr * srcNode); - bool has_self_cut(expr * n1, expr * n2); - - // for ConcatOverlapAvoid - bool will_result_in_overlap(expr * lhs, expr * rhs); - - void track_variable_scope(expr * var); - app * mk_str_var(std::string name); - app * mk_int_var(std::string name); - app * mk_nonempty_str_var(); - app * mk_internal_xor_var(); - expr * mk_internal_valTest_var(expr * node, int len, int vTries); - app * mk_regex_rep_var(); - app * mk_unroll_bound_var(); - app * mk_unroll_test_var(); - void add_nonempty_constraint(expr * s); - - void instantiate_concat_axiom(enode * cat); - void try_eval_concat(enode * cat); - void instantiate_basic_string_axioms(enode * str); - void instantiate_str_eq_length_axiom(enode * lhs, enode * rhs); - - void instantiate_axiom_CharAt(enode * e); - void instantiate_axiom_prefixof(enode * e); - void instantiate_axiom_suffixof(enode * e); - void instantiate_axiom_Contains(enode * e); - void instantiate_axiom_Indexof(enode * e); - void instantiate_axiom_Indexof2(enode * e); - void instantiate_axiom_LastIndexof(enode * e); - void instantiate_axiom_Substr(enode * e); - void instantiate_axiom_Replace(enode * e); - void instantiate_axiom_str_to_int(enode * e); - void instantiate_axiom_int_to_str(enode * e); - - expr * mk_RegexIn(expr * str, expr * regexp); - void instantiate_axiom_RegexIn(enode * e); - app * mk_unroll(expr * n, expr * bound); - - void process_unroll_eq_const_str(expr * unrollFunc, expr * constStr); - void unroll_str2reg_constStr(expr * unrollFunc, expr * eqConstStr); - void process_concat_eq_unroll(expr * concat, expr * unroll); - - void set_up_axioms(expr * ex); - void handle_equality(expr * lhs, expr * rhs); - - app * mk_value_helper(app * n); - expr * get_eqc_value(expr * n, bool & hasEqcValue); - expr * z3str2_get_eqc_value(expr * n , bool & hasEqcValue); - bool in_same_eqc(expr * n1, expr * n2); - expr * collect_eq_nodes(expr * n, expr_ref_vector & eqcSet); - - bool get_value(expr* e, rational& val) const; - bool get_len_value(expr* e, rational& val); - bool lower_bound(expr* _e, rational& lo); - bool upper_bound(expr* _e, rational& hi); - - bool can_two_nodes_eq(expr * n1, expr * n2); - bool can_concat_eq_str(expr * concat, zstring& str); - bool can_concat_eq_concat(expr * concat1, expr * concat2); - bool check_concat_len_in_eqc(expr * concat); - bool check_length_consistency(expr * n1, expr * n2); - bool check_length_const_string(expr * n1, expr * constStr); - bool check_length_eq_var_concat(expr * n1, expr * n2); - bool check_length_concat_concat(expr * n1, expr * n2); - bool check_length_concat_var(expr * concat, expr * var); - bool check_length_var_var(expr * var1, expr * var2); - void check_contain_in_new_eq(expr * n1, expr * n2); - void check_contain_by_eqc_val(expr * varNode, expr * constNode); - void check_contain_by_substr(expr * varNode, expr_ref_vector & willEqClass); - void check_contain_by_eq_nodes(expr * n1, expr * n2); - bool in_contain_idx_map(expr * n); - void compute_contains(std::map & varAliasMap, - std::map & concatAliasMap, std::map & varConstMap, - std::map & concatConstMap, std::map > & varEqConcatMap); - expr * dealias_node(expr * node, std::map & varAliasMap, std::map & concatAliasMap); - void get_grounded_concats(expr* node, std::map & varAliasMap, - std::map & concatAliasMap, std::map & varConstMap, - std::map & concatConstMap, std::map > & varEqConcatMap, - std::map, std::set > > & groundedMap); - void print_grounded_concat(expr * node, std::map, std::set > > & groundedMap); - void check_subsequence(expr* str, expr* strDeAlias, expr* subStr, expr* subStrDeAlias, expr* boolVar, + bool can_two_nodes_eq(expr * n1, expr * n2); + bool can_concat_eq_str(expr * concat, zstring& str); + bool can_concat_eq_concat(expr * concat1, expr * concat2); + bool check_concat_len_in_eqc(expr * concat); + bool check_length_consistency(expr * n1, expr * n2); + bool check_length_const_string(expr * n1, expr * constStr); + bool check_length_eq_var_concat(expr * n1, expr * n2); + bool check_length_concat_concat(expr * n1, expr * n2); + bool check_length_concat_var(expr * concat, expr * var); + bool check_length_var_var(expr * var1, expr * var2); + void check_contain_in_new_eq(expr * n1, expr * n2); + void check_contain_by_eqc_val(expr * varNode, expr * constNode); + void check_contain_by_substr(expr * varNode, expr_ref_vector & willEqClass); + void check_contain_by_eq_nodes(expr * n1, expr * n2); + bool in_contain_idx_map(expr * n); + void compute_contains(std::map & varAliasMap, + std::map & concatAliasMap, std::map & varConstMap, + std::map & concatConstMap, std::map > & varEqConcatMap); + expr * dealias_node(expr * node, std::map & varAliasMap, std::map & concatAliasMap); + void get_grounded_concats(expr* node, std::map & varAliasMap, + std::map & concatAliasMap, std::map & varConstMap, + std::map & concatConstMap, std::map > & varEqConcatMap, std::map, std::set > > & groundedMap); - bool is_partial_in_grounded_concat(const std::vector & strVec, const std::vector & subStrVec); + void print_grounded_concat(expr * node, std::map, std::set > > & groundedMap); + void check_subsequence(expr* str, expr* strDeAlias, expr* subStr, expr* subStrDeAlias, expr* boolVar, + std::map, std::set > > & groundedMap); + bool is_partial_in_grounded_concat(const std::vector & strVec, const std::vector & subStrVec); - void get_nodes_in_concat(expr * node, ptr_vector & nodeList); - expr * simplify_concat(expr * node); + void get_nodes_in_concat(expr * node, ptr_vector & nodeList); + expr * simplify_concat(expr * node); - void simplify_parent(expr * nn, expr * eq_str); + void simplify_parent(expr * nn, expr * eq_str); - void simplify_concat_equality(expr * lhs, expr * rhs); - void solve_concat_eq_str(expr * concat, expr * str); + void simplify_concat_equality(expr * lhs, expr * rhs); + void solve_concat_eq_str(expr * concat, expr * str); - void infer_len_concat_equality(expr * nn1, expr * nn2); - bool infer_len_concat(expr * n, rational & nLen); - void infer_len_concat_arg(expr * n, rational len); + void infer_len_concat_equality(expr * nn1, expr * nn2); + bool infer_len_concat(expr * n, rational & nLen); + void infer_len_concat_arg(expr * n, rational len); - bool is_concat_eq_type1(expr * concatAst1, expr * concatAst2); - bool is_concat_eq_type2(expr * concatAst1, expr * concatAst2); - bool is_concat_eq_type3(expr * concatAst1, expr * concatAst2); - bool is_concat_eq_type4(expr * concatAst1, expr * concatAst2); - bool is_concat_eq_type5(expr * concatAst1, expr * concatAst2); - bool is_concat_eq_type6(expr * concatAst1, expr * concatAst2); + bool is_concat_eq_type1(expr * concatAst1, expr * concatAst2); + bool is_concat_eq_type2(expr * concatAst1, expr * concatAst2); + bool is_concat_eq_type3(expr * concatAst1, expr * concatAst2); + bool is_concat_eq_type4(expr * concatAst1, expr * concatAst2); + bool is_concat_eq_type5(expr * concatAst1, expr * concatAst2); + bool is_concat_eq_type6(expr * concatAst1, expr * concatAst2); - void process_concat_eq_type1(expr * concatAst1, expr * concatAst2); - void process_concat_eq_type2(expr * concatAst1, expr * concatAst2); - void process_concat_eq_type3(expr * concatAst1, expr * concatAst2); - void process_concat_eq_type4(expr * concatAst1, expr * concatAst2); - void process_concat_eq_type5(expr * concatAst1, expr * concatAst2); - void process_concat_eq_type6(expr * concatAst1, expr * concatAst2); + void process_concat_eq_type1(expr * concatAst1, expr * concatAst2); + void process_concat_eq_type2(expr * concatAst1, expr * concatAst2); + void process_concat_eq_type3(expr * concatAst1, expr * concatAst2); + void process_concat_eq_type4(expr * concatAst1, expr * concatAst2); + void process_concat_eq_type5(expr * concatAst1, expr * concatAst2); + void process_concat_eq_type6(expr * concatAst1, expr * concatAst2); - void print_cut_var(expr * node, std::ofstream & xout); + void print_cut_var(expr * node, std::ofstream & xout); - void generate_mutual_exclusion(expr_ref_vector & exprs); - void add_theory_aware_branching_info(expr * term, double priority, lbool phase); + void generate_mutual_exclusion(expr_ref_vector & exprs); + void add_theory_aware_branching_info(expr * term, double priority, lbool phase); - bool new_eq_check(expr * lhs, expr * rhs); - void group_terms_by_eqc(expr * n, std::set & concats, std::set & vars, std::set & consts); + bool new_eq_check(expr * lhs, expr * rhs); + void group_terms_by_eqc(expr * n, std::set & concats, std::set & vars, std::set & consts); - int ctx_dep_analysis(std::map & strVarMap, std::map & freeVarMap, - std::map > & unrollGroupMap, std::map > & var_eq_concat_map); - void trace_ctx_dep(std::ofstream & tout, - std::map & aliasIndexMap, - std::map & var_eq_constStr_map, - std::map > & var_eq_concat_map, - std::map > & var_eq_unroll_map, - std::map & concat_eq_constStr_map, - std::map > & concat_eq_concat_map, - std::map > & unrollGroupMap); + int ctx_dep_analysis(std::map & strVarMap, std::map & freeVarMap, + std::map > & unrollGroupMap, std::map > & var_eq_concat_map); + void trace_ctx_dep(std::ofstream & tout, + std::map & aliasIndexMap, + std::map & var_eq_constStr_map, + std::map > & var_eq_concat_map, + std::map > & var_eq_unroll_map, + std::map & concat_eq_constStr_map, + std::map > & concat_eq_concat_map, + std::map > & unrollGroupMap); - void classify_ast_by_type(expr * node, std::map & varMap, - std::map & concatMap, std::map & unrollMap); - void classify_ast_by_type_in_positive_context(std::map & varMap, - std::map & concatMap, std::map & unrollMap); + void classify_ast_by_type(expr * node, std::map & varMap, + std::map & concatMap, std::map & unrollMap); + void classify_ast_by_type_in_positive_context(std::map & varMap, + std::map & concatMap, std::map & unrollMap); - expr * mk_internal_lenTest_var(expr * node, int lTries); - expr * gen_len_val_options_for_free_var(expr * freeVar, expr * lenTesterInCbEq, zstring lenTesterValue); - void process_free_var(std::map & freeVar_map); - expr * gen_len_test_options(expr * freeVar, expr * indicator, int tries); - expr * gen_free_var_options(expr * freeVar, expr * len_indicator, - zstring len_valueStr, expr * valTesterInCbEq, zstring valTesterValueStr); - expr * gen_val_options(expr * freeVar, expr * len_indicator, expr * val_indicator, - zstring lenStr, int tries); - void print_value_tester_list(svector > & testerList); - bool get_next_val_encode(int_vector & base, int_vector & next); - zstring gen_val_string(int len, int_vector & encoding); + expr * mk_internal_lenTest_var(expr * node, int lTries); + expr * gen_len_val_options_for_free_var(expr * freeVar, expr * lenTesterInCbEq, zstring lenTesterValue); + void process_free_var(std::map & freeVar_map); + expr * gen_len_test_options(expr * freeVar, expr * indicator, int tries); + expr * gen_free_var_options(expr * freeVar, expr * len_indicator, + zstring len_valueStr, expr * valTesterInCbEq, zstring valTesterValueStr); + expr * gen_val_options(expr * freeVar, expr * len_indicator, expr * val_indicator, + zstring lenStr, int tries); + void print_value_tester_list(svector > & testerList); + bool get_next_val_encode(int_vector & base, int_vector & next); + zstring gen_val_string(int len, int_vector & encoding); - // binary search heuristic - expr * binary_search_length_test(expr * freeVar, expr * previousLenTester, zstring previousLenTesterValue); - expr_ref binary_search_case_split(expr * freeVar, expr * tester, binary_search_info & bounds, literal_vector & case_split_lits); + // binary search heuristic + expr * binary_search_length_test(expr * freeVar, expr * previousLenTester, zstring previousLenTesterValue); + expr_ref binary_search_case_split(expr * freeVar, expr * tester, binary_search_info & bounds, literal_vector & case_split_lits); - bool free_var_attempt(expr * nn1, expr * nn2); - void more_len_tests(expr * lenTester, zstring lenTesterValue); - void more_value_tests(expr * valTester, zstring valTesterValue); + bool free_var_attempt(expr * nn1, expr * nn2); + void more_len_tests(expr * lenTester, zstring lenTesterValue); + void more_value_tests(expr * valTester, zstring valTesterValue); - expr * get_alias_index_ast(std::map & aliasIndexMap, expr * node); - expr * getMostLeftNodeInConcat(expr * node); - expr * getMostRightNodeInConcat(expr * node); - void get_var_in_eqc(expr * n, std::set & varSet); - void get_concats_in_eqc(expr * n, std::set & concats); - void get_const_str_asts_in_node(expr * node, expr_ref_vector & constList); - expr * eval_concat(expr * n1, expr * n2); + expr * get_alias_index_ast(std::map & aliasIndexMap, expr * node); + expr * getMostLeftNodeInConcat(expr * node); + expr * getMostRightNodeInConcat(expr * node); + void get_var_in_eqc(expr * n, std::set & varSet); + void get_concats_in_eqc(expr * n, std::set & concats); + void get_const_str_asts_in_node(expr * node, expr_ref_vector & constList); + expr * eval_concat(expr * n1, expr * n2); - bool finalcheck_str2int(app * a); - bool finalcheck_int2str(app * a); + bool finalcheck_str2int(app * a); + bool finalcheck_int2str(app * a); - // strRegex + // strRegex - void get_eqc_allUnroll(expr * n, expr * &constStr, std::set & unrollFuncSet); - void get_eqc_simpleUnroll(expr * n, expr * &constStr, std::set & unrollFuncSet); - void gen_assign_unroll_reg(std::set & unrolls); - expr * gen_assign_unroll_Str2Reg(expr * n, std::set & unrolls); - expr * gen_unroll_conditional_options(expr * var, std::set & unrolls, zstring lcmStr); - expr * gen_unroll_assign(expr * var, zstring lcmStr, expr * testerVar, int l, int h); - void reduce_virtual_regex_in(expr * var, expr * regex, expr_ref_vector & items); - void check_regex_in(expr * nn1, expr * nn2); - zstring get_std_regex_str(expr * r); + void get_eqc_allUnroll(expr * n, expr * &constStr, std::set & unrollFuncSet); + void get_eqc_simpleUnroll(expr * n, expr * &constStr, std::set & unrollFuncSet); + void gen_assign_unroll_reg(std::set & unrolls); + expr * gen_assign_unroll_Str2Reg(expr * n, std::set & unrolls); + expr * gen_unroll_conditional_options(expr * var, std::set & unrolls, zstring lcmStr); + expr * gen_unroll_assign(expr * var, zstring lcmStr, expr * testerVar, int l, int h); + void reduce_virtual_regex_in(expr * var, expr * regex, expr_ref_vector & items); + void check_regex_in(expr * nn1, expr * nn2); + zstring get_std_regex_str(expr * r); - void dump_assignments(); - void initialize_charset(); + void dump_assignments(); + void initialize_charset(); - void check_variable_scope(); - void recursive_check_variable_scope(expr * ex); + void check_variable_scope(); + void recursive_check_variable_scope(expr * ex); - void collect_var_concat(expr * node, std::set & varSet, std::set & concatSet); - bool propagate_length(std::set & varSet, std::set & concatSet, std::map & exprLenMap); - void get_unique_non_concat_nodes(expr * node, std::set & argSet); - bool propagate_length_within_eqc(expr * var); + void collect_var_concat(expr * node, std::set & varSet, std::set & concatSet); + bool propagate_length(std::set & varSet, std::set & concatSet, std::map & exprLenMap); + void get_unique_non_concat_nodes(expr * node, std::set & argSet); + bool propagate_length_within_eqc(expr * var); - // TESTING - void refresh_theory_var(expr * e); + // TESTING + void refresh_theory_var(expr * e); - expr_ref set_up_finite_model_test(expr * lhs, expr * rhs); - void finite_model_test(expr * v, expr * c); + expr_ref set_up_finite_model_test(expr * lhs, expr * rhs); + void finite_model_test(expr * v, expr * c); - public: - theory_str(ast_manager & m, theory_str_params const & params); - virtual ~theory_str(); +public: + theory_str(ast_manager & m, theory_str_params const & params); + virtual ~theory_str(); - virtual char const * get_name() const { return "seq"; } - virtual void display(std::ostream & out) const; + virtual char const * get_name() const { return "seq"; } + virtual void display(std::ostream & out) const; - bool overlapping_variables_detected() const { return loopDetected; } + bool overlapping_variables_detected() const { return loopDetected; } - th_trail_stack& get_trail_stack() { return m_trail_stack; } - void merge_eh(theory_var, theory_var, theory_var v1, theory_var v2) {} - void after_merge_eh(theory_var r1, theory_var r2, theory_var v1, theory_var v2) { } - void unmerge_eh(theory_var v1, theory_var v2) {} - protected: - virtual bool internalize_atom(app * atom, bool gate_ctx); - virtual bool internalize_term(app * term); - virtual enode* ensure_enode(expr* e); - virtual theory_var mk_var(enode * n); + th_trail_stack& get_trail_stack() { return m_trail_stack; } + void merge_eh(theory_var, theory_var, theory_var v1, theory_var v2) {} + void after_merge_eh(theory_var r1, theory_var r2, theory_var v1, theory_var v2) { } + void unmerge_eh(theory_var v1, theory_var v2) {} +protected: + virtual bool internalize_atom(app * atom, bool gate_ctx); + virtual bool internalize_term(app * term); + virtual enode* ensure_enode(expr* e); + virtual theory_var mk_var(enode * n); - virtual void new_eq_eh(theory_var, theory_var); - virtual void new_diseq_eh(theory_var, theory_var); + virtual void new_eq_eh(theory_var, theory_var); + virtual void new_diseq_eh(theory_var, theory_var); - virtual theory* mk_fresh(context*) { return alloc(theory_str, get_manager(), m_params); } - virtual void init_search_eh(); - virtual void add_theory_assumptions(expr_ref_vector & assumptions); - virtual lbool validate_unsat_core(expr_ref_vector & unsat_core); - virtual void relevant_eh(app * n); - virtual void assign_eh(bool_var v, bool is_true); - virtual void push_scope_eh(); - virtual void pop_scope_eh(unsigned num_scopes); - virtual void reset_eh(); + virtual theory* mk_fresh(context*) { return alloc(theory_str, get_manager(), m_params); } + virtual void init_search_eh(); + virtual void add_theory_assumptions(expr_ref_vector & assumptions); + virtual lbool validate_unsat_core(expr_ref_vector & unsat_core); + virtual void relevant_eh(app * n); + virtual void assign_eh(bool_var v, bool is_true); + virtual void push_scope_eh(); + virtual void pop_scope_eh(unsigned num_scopes); + virtual void reset_eh(); - virtual bool can_propagate(); - virtual void propagate(); + virtual bool can_propagate(); + virtual void propagate(); - virtual final_check_status final_check_eh(); - virtual void attach_new_th_var(enode * n); + virtual final_check_status final_check_eh(); + virtual void attach_new_th_var(enode * n); - virtual void init_model(model_generator & m); - virtual model_value_proc * mk_value(enode * n, model_generator & mg); - virtual void finalize_model(model_generator & mg); - }; + virtual void init_model(model_generator & m); + virtual model_value_proc * mk_value(enode * n, model_generator & mg); + virtual void finalize_model(model_generator & mg); +}; }; From 82bdd26817b611829d6417094f86cf3b964c15b0 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Sat, 6 May 2017 13:40:53 -0400 Subject: [PATCH 389/410] clean up some warnings Signed-off-by: Nikolaj Bjorner --- src/smt/theory_str.cpp | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 4a6a6da5b..d156005fd 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -1903,7 +1903,6 @@ namespace smt { } void theory_str::group_terms_by_eqc(expr * n, std::set & concats, std::set & vars, std::set & consts) { - context & ctx = get_context(); expr * eqcNode = n; do { app * ast = to_app(eqcNode); @@ -4822,7 +4821,6 @@ namespace smt { } expr * theory_str::collect_eq_nodes(expr * n, expr_ref_vector & eqcSet) { - context & ctx = get_context(); expr * constStrNode = NULL; expr * ex = n; @@ -4873,8 +4871,6 @@ namespace smt { if (!contain_pair_bool_map.find(strAst, substrAst, boolVar)) { TRACE("str", tout << "warning: no entry for boolVar in contain_pair_bool_map" << std::endl;); } - // boolVar is actually a Contains term - app * containsApp = to_app(boolVar); // we only want to inspect the Contains terms where either of strAst or substrAst // are equal to varNode. @@ -5012,8 +5008,6 @@ namespace smt { if (!contain_pair_bool_map.find(strAst, substrAst, boolVar)) { TRACE("str", tout << "warning: no entry for boolVar in contain_pair_bool_map" << std::endl;); } - // boolVar is actually a Contains term - app * containsApp = to_app(boolVar); // we only want to inspect the Contains terms where either of strAst or substrAst // are equal to varNode. @@ -5421,7 +5415,6 @@ namespace smt { return; } - context & ctx = get_context(); ast_manager & m = get_manager(); TRACE("str", tout << "consistency check for contains wrt. " << mk_pp(n1, m) << " and " << mk_pp(n2, m) << std::endl;); @@ -6171,8 +6164,6 @@ namespace smt { // Modified signature: returns true if nothing was learned, or false if at least one axiom was asserted. // (This is used for deferred consistency checking) bool theory_str::check_concat_len_in_eqc(expr * concat) { - context & ctx = get_context(); - bool no_assertions = true; expr * eqc_n = concat; @@ -7542,7 +7533,6 @@ namespace smt { void theory_str::pop_scope_eh(unsigned num_scopes) { sLevel -= num_scopes; TRACE("str", tout << "pop " << num_scopes << " to " << sLevel << std::endl;); - context & ctx = get_context(); ast_manager & m = get_manager(); TRACE_CODE(if (is_trace_enabled("t_str_dump_assign_on_scope_change")) { dump_assignments(); }); @@ -10023,7 +10013,6 @@ namespace smt { expr * theory_str::binary_search_length_test(expr * freeVar, expr * previousLenTester, zstring previousLenTesterValue) { ast_manager & m = get_manager(); - context & ctx = get_context(); if (binary_search_len_tester_stack.contains(freeVar) && !binary_search_len_tester_stack[freeVar].empty()) { TRACE("str", tout << "checking existing length testers for " << mk_pp(freeVar, m) << std::endl; @@ -10353,7 +10342,6 @@ namespace smt { } void theory_str::get_concats_in_eqc(expr * n, std::set & concats) { - context & ctx = get_context(); expr * eqcNode = n; do { @@ -10502,7 +10490,6 @@ namespace smt { void theory_str::get_eqc_simpleUnroll(expr * n, expr * &constStr, std::set & unrollFuncSet) { constStr = NULL; unrollFuncSet.clear(); - context & ctx = get_context(); expr * curr = n; do { @@ -10571,12 +10558,11 @@ namespace smt { TRACE("str", tout << "mk_value for: " << mk_ismt2_pp(n->get_owner(), get_manager()) << " (sort " << mk_ismt2_pp(get_manager().get_sort(n->get_owner()), get_manager()) << ")" << std::endl;); ast_manager & m = get_manager(); - context & ctx = get_context(); app_ref owner(m); owner = n->get_owner(); // If the owner is not internalized, it doesn't have an enode associated. - SASSERT(ctx.e_internalized(owner)); + SASSERT(get_context().e_internalized(owner)); app * val = mk_value_helper(owner); if (val != NULL) { From e02392c0e3e8a52b2999990ec70466ce20a23384 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Sun, 7 May 2017 14:03:24 -0700 Subject: [PATCH 390/410] use skolem function to avoid exposing temporary variables in models Signed-off-by: Nikolaj Bjorner --- src/smt/theory_str.cpp | 13 +++++++++---- src/smt/theory_str.h | 1 + 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index d156005fd..835f3b553 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -440,6 +440,11 @@ namespace smt { return mk_int_var("$$_xor"); } + app * theory_str::mk_fresh_const(char const* name, sort* s) { + return u.mk_skolem(symbol(name), 0, 0, s); + } + + app * theory_str::mk_int_var(std::string name) { context & ctx = get_context(); ast_manager & m = get_manager(); @@ -447,7 +452,7 @@ namespace smt { TRACE("str", tout << "creating integer variable " << name << " at scope level " << sLevel << std::endl;); sort * int_sort = m.mk_sort(m_autil.get_family_id(), INT_SORT); - app * a = m.mk_fresh_const(name.c_str(), int_sort); + app * a = mk_fresh_const(name.c_str(), int_sort); ctx.internalize(a, false); SASSERT(ctx.get_enode(a) != NULL); @@ -482,7 +487,7 @@ namespace smt { TRACE("str", tout << "creating string variable " << name << " at scope level " << sLevel << std::endl;); sort * string_sort = u.str.mk_string_sort(); - app * a = m.mk_fresh_const(name.c_str(), string_sort); + app * a = mk_fresh_const(name.c_str(), string_sort); TRACE("str", tout << "a->get_family_id() = " << a->get_family_id() << std::endl << "this->get_family_id() = " << this->get_family_id() << std::endl;); @@ -509,7 +514,7 @@ namespace smt { ast_manager & m = get_manager(); sort * string_sort = u.str.mk_string_sort(); - app * a = m.mk_fresh_const("regex", string_sort); + app * a = mk_fresh_const("regex", string_sort); ctx.internalize(a, false); SASSERT(ctx.get_enode(a) != NULL); @@ -561,7 +566,7 @@ namespace smt { TRACE("str", tout << "creating nonempty string variable " << name << " at scope level " << sLevel << std::endl;); sort * string_sort = u.str.mk_string_sort(); - app * a = m.mk_fresh_const(name.c_str(), string_sort); + app * a = mk_fresh_const(name.c_str(), string_sort); ctx.internalize(a, false); SASSERT(ctx.get_enode(a) != NULL); diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 2e6d96fa7..0403b0623 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -403,6 +403,7 @@ protected: expr * mk_concat_const_str(expr * n1, expr * n2); app * mk_contains(expr * haystack, expr * needle); app * mk_indexof(expr * haystack, expr * needle); + app * mk_fresh_const(char const* name, sort* s); literal mk_literal(expr* _e); app * mk_int(int n); From 3ae722025f44b2c6df646264743a06fe57bf6d04 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Sun, 7 May 2017 14:54:47 -0700 Subject: [PATCH 391/410] relaxing condition for assumptions, add theory-assumption to skolem functions Signed-off-by: Nikolaj Bjorner --- src/smt/smt_context.cpp | 9 ++++++++- src/smt/theory_str.cpp | 4 ++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/smt/smt_context.cpp b/src/smt/smt_context.cpp index 37a6d32b7..50b957331 100644 --- a/src/smt/smt_context.cpp +++ b/src/smt/smt_context.cpp @@ -3115,11 +3115,18 @@ namespace smt { } bool is_valid_assumption(ast_manager & m, expr * assumption) { + expr* arg; if (!m.is_bool(assumption)) return false; if (is_uninterp_const(assumption)) return true; - if (m.is_not(assumption) && is_uninterp_const(to_app(assumption)->get_arg(0))) + if (m.is_not(assumption, arg) && is_uninterp_const(arg)) + return true; + if (!is_app(assumption)) + return false; + if (to_app(assumption)->get_num_args() == 0) + return true; + if (m.is_not(assumption, arg) && is_app(arg) && to_app(arg)->get_num_args() == 0) return true; return false; } diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 835f3b553..128f93b11 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -7351,10 +7351,10 @@ namespace smt { void theory_str::add_theory_assumptions(expr_ref_vector & assumptions) { TRACE("str", tout << "add overlap assumption for theory_str" << std::endl;); - symbol strOverlap("!!TheoryStrOverlapAssumption!!"); + char* strOverlap = "!!TheoryStrOverlapAssumption!!"; seq_util m_sequtil(get_manager()); sort * s = get_manager().mk_bool_sort(); - m_theoryStrOverlapAssumption_term = expr_ref(get_manager().mk_const(strOverlap, s), get_manager()); + m_theoryStrOverlapAssumption_term = expr_ref(mk_fresh_const(strOverlap, s), get_manager()); assumptions.push_back(get_manager().mk_not(m_theoryStrOverlapAssumption_term)); } From 6b2a800c7fb14a504ece5fb0eae574c8cd7f1f4a Mon Sep 17 00:00:00 2001 From: Murphy Berzish Date: Sun, 7 May 2017 18:23:47 -0400 Subject: [PATCH 392/410] fix warnings: unused variables, string constants --- src/smt/theory_str.cpp | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 128f93b11..818aca29a 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -4844,7 +4844,6 @@ namespace smt { * Collect constant strings (from left to right) in an AST node. */ void theory_str::get_const_str_asts_in_node(expr * node, expr_ref_vector & astList) { - ast_manager & m = get_manager(); if (u.str.is_string(node)) { astList.push_back(node); //} else if (getNodeType(t, node) == my_Z3_Func) { @@ -5519,7 +5518,6 @@ namespace smt { // --------------------------------------------------------- context & ctx = get_context(); - ast_manager & m = get_manager(); // const strings: node is de-aliased if (u.str.is_string(node)) { @@ -7351,7 +7349,7 @@ namespace smt { void theory_str::add_theory_assumptions(expr_ref_vector & assumptions) { TRACE("str", tout << "add overlap assumption for theory_str" << std::endl;); - char* strOverlap = "!!TheoryStrOverlapAssumption!!"; + const char* strOverlap = "!!TheoryStrOverlapAssumption!!"; seq_util m_sequtil(get_manager()); sort * s = get_manager().mk_bool_sort(); m_theoryStrOverlapAssumption_term = expr_ref(mk_fresh_const(strOverlap, s), get_manager()); @@ -7359,8 +7357,6 @@ namespace smt { } lbool theory_str::validate_unsat_core(expr_ref_vector & unsat_core) { - bool assumptionFound = false; - app * target_term = to_app(get_manager().mk_not(m_theoryStrOverlapAssumption_term)); get_context().internalize(target_term, false); for (unsigned i = 0; i < unsat_core.size(); ++i) { @@ -7372,7 +7368,6 @@ namespace smt { e2 = get_context().get_enode(core_term); if (e1 == e2) { TRACE("str", tout << "overlap detected in unsat core, changing UNSAT to UNKNOWN" << std::endl;); - assumptionFound = true; return l_undef; } } @@ -7483,7 +7478,6 @@ namespace smt { } void theory_str::recursive_check_variable_scope(expr * ex) { - context & ctx = get_context(); ast_manager & m = get_manager(); if (is_app(ex)) { @@ -7551,7 +7545,7 @@ namespace smt { std::stack & val = cut_var_map[varItor->m_key]; while ((val.size() > 0) && (val.top()->level != 0) && (val.top()->level >= sLevel)) { TRACE("str", tout << "remove cut info for " << mk_pp(e, m) << std::endl; print_cut_var(e, tout);); - T_cut * aCut = val.top(); + // T_cut * aCut = val.top(); val.pop(); // dealloc(aCut); } @@ -8571,8 +8565,6 @@ namespace smt { return; } if (u.str.is_concat(aNode)) { - expr * arg0 = aNode->get_arg(0); - expr * arg1 = aNode->get_arg(1); if (concatSet.find(node) == concatSet.end()) { concatSet.insert(node); } @@ -8592,7 +8584,6 @@ namespace smt { TRACE("str", tout << "propagate_length_within_eqc: " << mk_ismt2_pp(var, m) << std::endl ;); - enode * n_eq_enode = ctx.get_enode(var); rational varLen; if (! get_len_value(var, varLen)) { bool hasLen = false; @@ -8686,7 +8677,6 @@ namespace smt { expr * var = *it; rational lenValue; expr_ref varlen (mk_strlen(var), m) ; - bool allLeafResolved = true; if (! get_value(varlen, lenValue)) { if (propagate_length_within_eqc(var)) { axiomAdded = true; @@ -8806,7 +8796,7 @@ namespace smt { bool concat_lhs_haseqc, concat_rhs_haseqc, var_haseqc; expr * concat_lhs_str = get_eqc_value(concat_lhs, concat_lhs_haseqc); expr * concat_rhs_str = get_eqc_value(concat_rhs, concat_rhs_haseqc); - expr * var_str = get_eqc_value(var, var_haseqc); + get_eqc_value(var, var_haseqc); if (concat_lhs_haseqc && concat_rhs_haseqc && !var_haseqc) { TRACE("str", tout << "backpropagate into " << mk_pp(var, m) << " = " << mk_pp(concat, m) << std::endl << "LHS ~= " << mk_pp(concat_lhs_str, m) << " RHS ~= " << mk_pp(concat_rhs_str, m) << std::endl;); @@ -10358,8 +10348,6 @@ namespace smt { } void theory_str::get_var_in_eqc(expr * n, std::set & varSet) { - context & ctx = get_context(); - expr * eqcNode = n; do { if (variable_set.find(eqcNode) != variable_set.end()) { @@ -10476,7 +10464,6 @@ namespace smt { void theory_str::get_eqc_allUnroll(expr * n, expr * &constStr, std::set & unrollFuncSet) { constStr = NULL; unrollFuncSet.clear(); - context & ctx = get_context(); expr * curr = n; do { From 99474eb44a950c380795874dd09c30d1fa97b7ec Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Mon, 8 May 2017 09:46:40 -0700 Subject: [PATCH 393/410] integrate Dan Liew's patch for language selection Signed-off-by: Nikolaj Bjorner --- CMakeLists.txt | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 19c56a413..de64a6179 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -325,6 +325,18 @@ if (("${TARGET_ARCHITECTURE}" STREQUAL "x86_64") OR ("${TARGET_ARCHITECTURE}" ST unset(SSE_FLAGS) endif() +################################################################################ +# C++ language version +################################################################################ +# FIXME: Use CMake's own mechanism for selecting language version +if (("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") OR ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")) + z3_add_cxx_flag("-std=c++11" REQUIRED) +else() + message(AUTHOR_WARNING "Not setting C++ language version for compiler") +endif() + + + # FIXME: Remove "x.." when CMP0054 is set to NEW if ("x${CMAKE_CXX_COMPILER_ID}" STREQUAL "xMSVC") # This is the default for MSVC already but to replicate the From 579b914d78a4d72cd5c433148e0d82af5ecbde64 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Mon, 8 May 2017 10:02:09 -0700 Subject: [PATCH 394/410] add configuration per Dan Liew's comment Signed-off-by: Nikolaj Bjorner --- CMakeLists.txt | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index de64a6179..7983b45bc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -212,6 +212,16 @@ endif() ################################################################################ include(${CMAKE_SOURCE_DIR}/cmake/z3_add_cxx_flag.cmake) +################################################################################ +# C++ language version +################################################################################ +# FIXME: Use CMake's own mechanism for selecting language version +if (("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") OR ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")) + z3_add_cxx_flag("-std=c++11" REQUIRED) +else() + message(AUTHOR_WARNING "Not setting C++ language version for compiler") +endif() + ################################################################################ # Platform detection ################################################################################ @@ -325,16 +335,6 @@ if (("${TARGET_ARCHITECTURE}" STREQUAL "x86_64") OR ("${TARGET_ARCHITECTURE}" ST unset(SSE_FLAGS) endif() -################################################################################ -# C++ language version -################################################################################ -# FIXME: Use CMake's own mechanism for selecting language version -if (("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") OR ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")) - z3_add_cxx_flag("-std=c++11" REQUIRED) -else() - message(AUTHOR_WARNING "Not setting C++ language version for compiler") -endif() - # FIXME: Remove "x.." when CMP0054 is set to NEW From d8c3b273d3c26386a7b0bf2c491e297e522f9cfe Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Mon, 8 May 2017 10:50:06 -0700 Subject: [PATCH 395/410] adding benign initialization Signed-off-by: Nikolaj Bjorner --- src/smt/theory_str.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 818aca29a..df1418aea 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -10059,7 +10059,7 @@ namespace smt { } TRACE("str", tout << "last bounds are [" << lastBounds.lowerBound << " | " << lastBounds.midPoint << " | " << lastBounds.upperBound << "]!" << lastBounds.windowSize << std::endl;); binary_search_info newBounds; - expr * newTester; + expr * newTester = 0; if (lastTesterConstant == "more") { // special case: if the midpoint, upper bound, and window size are all equal, // we double the window size and adjust the bounds From 42ea2d1ea5591b5d2ee01bd654422e824120f509 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Tue, 9 May 2017 09:06:41 -0700 Subject: [PATCH 396/410] LRA changes to rational Signed-off-by: Nikolaj Bjorner --- src/util/rational.h | 63 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/src/util/rational.h b/src/util/rational.h index ba447fca6..4fa3382ec 100644 --- a/src/util/rational.h +++ b/src/util/rational.h @@ -52,6 +52,9 @@ public: rational(mpz const & z) { m().set(m_val, z); } + rational(double z) { UNREACHABLE(); } + + explicit rational(char const * v) { m().set(m_val, v); } struct i64 {}; @@ -127,6 +130,12 @@ public: return *this; } + rational & operator=(int v) { + *this = rational(v); + return *this; + } + rational & operator=(double v) { UNREACHABLE(); return *this; } + friend inline rational numerator(rational const & r) { rational result; m().get_numerator(r.m_val, result.m_val); return result; } friend inline rational denominator(rational const & r) { rational result; m().get_denominator(r.m_val, result.m_val); return result; } @@ -136,6 +145,11 @@ public: return *this; } + rational & operator+=(int r) { + (*this) += rational(r); + return *this; + } + rational & operator-=(rational const & r) { m().sub(m_val, r.m_val, m_val); return *this; @@ -394,6 +408,7 @@ public: return num_bits; } + }; inline bool operator!=(rational const & r1, rational const & r2) { @@ -404,6 +419,10 @@ inline bool operator>(rational const & r1, rational const & r2) { return operator<(r2, r1); } +inline bool operator<(rational const & r1, int r2) { + return r1 < rational(r2); +} + inline bool operator<=(rational const & r1, rational const & r2) { return !operator>(r1, r2); } @@ -412,14 +431,43 @@ inline bool operator>=(rational const & r1, rational const & r2) { return !operator<(r1, r2); } +inline bool operator>(rational const & a, int b) { + return a > rational(b); +} + +inline bool operator!=(rational const & a, int b) { + return !(a == rational(b)); +} + +inline bool operator==(rational const & a, int b) { + return a == rational(b); +} + inline rational operator+(rational const & r1, rational const & r2) { return rational(r1) += r2; } +inline rational operator+(int r1, rational const & r2) { + return rational(r1) + r2; +} + +inline rational operator+(rational const & r1, int r2) { + return r1 + rational(r2); +} + + inline rational operator-(rational const & r1, rational const & r2) { return rational(r1) -= r2; } +inline rational operator-(rational const & r1, int r2) { + return r1 - rational(r2); +} + +inline rational operator-(int r1, rational const & r2) { + return rational(r1) - r2; +} + inline rational operator-(rational const & r) { rational result(r); result.neg(); @@ -430,10 +478,25 @@ inline rational operator*(rational const & r1, rational const & r2) { return rational(r1) *= r2; } +inline rational operator*(rational const & r1, int r2) { + return r1 * rational(r2); +} +inline rational operator*(int r1, rational const & r2) { + return rational(r1) * r2; +} + inline rational operator/(rational const & r1, rational const & r2) { return rational(r1) /= r2; } +inline rational operator/(rational const & r1, int r2) { + return r1 / rational(r2); +} + +inline rational operator/(int r1, rational const & r2) { + return rational(r1) / r2; +} + inline rational power(rational const & r, unsigned p) { return r.expt(p); } From 60725f5384d2c8bd68105a68870c3cfa183ba9e5 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Tue, 9 May 2017 09:13:27 -0700 Subject: [PATCH 397/410] use vector fixes from LRA branch Signed-off-by: Nikolaj Bjorner --- src/util/vector.h | 56 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 48 insertions(+), 8 deletions(-) diff --git a/src/util/vector.h b/src/util/vector.h index 7ffee2535..7e89a773c 100644 --- a/src/util/vector.h +++ b/src/util/vector.h @@ -117,6 +117,10 @@ public: } vector(SZ s) { + if (s == 0) { + m_data = 0; + return; + } SZ * mem = reinterpret_cast(memory::allocate(sizeof(T) * s + sizeof(SZ) * 2)); *mem = s; mem++; @@ -184,6 +188,8 @@ public: } } + void clear() { reset(); } + bool empty() const { return m_data == 0 || reinterpret_cast(m_data)[SIZE_IDX] == 0; } @@ -218,6 +224,33 @@ public: return m_data + size(); } + class reverse_iterator { + T* v; + public: + reverse_iterator(T* v):v(v) {} + + T operator*() { return *v; } + reverse_iterator operator++(int) { + reverse_iterator tmp = *this; + --v; + return tmp; + } + reverse_iterator& operator++() { + --v; + return *this; + } + + bool operator==(reverse_iterator const& other) const { + return other.v == v; + } + bool operator!=(reverse_iterator const& other) const { + return other.v != v; + } + }; + + reverse_iterator rbegin() { return reverse_iterator(end() - 1); } + reverse_iterator rend() { return reverse_iterator(begin() - 1); } + void set_end(iterator it) { if (m_data) { SZ new_sz = static_cast(it - m_data); @@ -362,8 +395,8 @@ public: void reverse() { SZ sz = size(); for (SZ i = 0; i < sz/2; ++i) { - std::swap(m_data[i], m_data[sz-i-1]); - } + std::swap(m_data[i], m_data[sz-i-1]); + } } void fill(T const & elem) { @@ -461,16 +494,23 @@ struct vector_hash : public vector_hash_tpl > template struct svector_hash : public vector_hash_tpl > {}; - -// Specialize vector to be inaccessible. +#include +// Specialize vector to be an instance of std::vector instead. // This will catch any regression of issue #564 and #420. -// Use std::vector instead. + template <> -class vector { -private: - vector(); +class vector : public std::vector { +public: + vector(vector const& other): std::vector(other) {} + vector(size_t sz, char const* s): std::vector(sz, s) {} + vector() {} + + void reset() { clear(); } + + }; + #endif /* VECTOR_H_ */ From 085d31dca2eec44f4a5a64368ab2a7f4d6fa332e Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Tue, 9 May 2017 09:18:59 -0700 Subject: [PATCH 398/410] mpq/mpz features from LRA Signed-off-by: Nikolaj Bjorner --- src/util/mpq.h | 6 +++++- src/util/mpz.h | 9 +++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/util/mpq.h b/src/util/mpq.h index 093cc0a44..474d38802 100644 --- a/src/util/mpq.h +++ b/src/util/mpq.h @@ -34,6 +34,8 @@ public: void swap(mpq & other) { m_num.swap(other.m_num); m_den.swap(other.m_den); } mpz const & numerator() const { return m_num; } mpz const & denominator() const { return m_den; } + + double get_double() const; }; inline void swap(mpq & m1, mpq & m2) { m1.swap(m2); } @@ -833,9 +835,11 @@ public: } bool is_even(mpz const & a) { return mpz_manager::is_even(a); } - +public: bool is_even(mpq const & a) { return is_int(a) && is_even(a.m_num); } + friend bool operator==(mpq const & a, mpq const & b) ; + friend bool operator>=(mpq const & a, mpq const & b); }; typedef mpq_manager synch_mpq_manager; diff --git a/src/util/mpz.h b/src/util/mpz.h index 9cb27ebff..2661cb5da 100644 --- a/src/util/mpz.h +++ b/src/util/mpz.h @@ -593,6 +593,15 @@ public: } } + bool lt(mpz const& a, int b) { + if (is_small(a)) { + return a.m_val < b; + } + else { + return lt(a, mpz(b)); + } + } + bool lt(mpz const & a, mpz const & b) { if (is_small(a) && is_small(b)) { return a.m_val < b.m_val; From 911b24784ad75b381f6be1c5a2ddaa5910fcec58 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Tue, 9 May 2017 10:46:11 -0700 Subject: [PATCH 399/410] merge LRA Signed-off-by: Nikolaj Bjorner --- CMakeLists.txt | 3 + contrib/cmake/src/CMakeLists.txt | 1 + contrib/cmake/src/shell/CMakeLists.txt | 1 + contrib/cmake/src/smt/CMakeLists.txt | 2 + contrib/cmake/src/test/CMakeLists.txt | 12 + scripts/mk_project.py | 3 +- scripts/mk_util.py | 10 +- src/ast/arith_decl_plugin.h | 4 + src/opt/opt_solver.cpp | 12 + src/shell/main.cpp | 10 +- src/smt/smt_setup.cpp | 11 +- src/smt/smt_setup.h | 1 + src/smt/smt_theory.cpp | 6 +- src/smt/smt_theory.h | 12 +- src/test/main.cpp | 1 + src/util/lp/binary_heap_priority_queue.h | 71 + src/util/lp/binary_heap_priority_queue.hpp | 193 ++ .../binary_heap_priority_queue_instances.cpp | 26 + src/util/lp/binary_heap_upair_queue.h | 50 + src/util/lp/binary_heap_upair_queue.hpp | 110 + .../lp/binary_heap_upair_queue_instances.cpp | 17 + src/util/lp/bound_analyzer_on_row.h | 333 +++ src/util/lp/bound_propagator.cpp | 47 + src/util/lp/bound_propagator.h | 27 + src/util/lp/breakpoint.h | 20 + src/util/lp/column_info.h | 235 ++ src/util/lp/column_namer.h | 82 + src/util/lp/core_solver_pretty_printer.h | 118 + src/util/lp/core_solver_pretty_printer.hpp | 377 +++ .../core_solver_pretty_printer_instances.cpp | 15 + src/util/lp/dense_matrix.h | 92 + src/util/lp/dense_matrix.hpp | 186 ++ src/util/lp/dense_matrix_instances.cpp | 24 + src/util/lp/eta_matrix.h | 83 + src/util/lp/eta_matrix.hpp | 136 ++ src/util/lp/eta_matrix_instances.cpp | 28 + src/util/lp/hash_helper.h | 39 + src/util/lp/implied_bound.h | 42 + src/util/lp/indexed_value.h | 55 + src/util/lp/indexed_vector.h | 169 ++ src/util/lp/indexed_vector.hpp | 110 + src/util/lp/indexed_vector_instances.cpp | 36 + src/util/lp/int_set.h | 66 + src/util/lp/iterator_on_column.h | 50 + src/util/lp/iterator_on_indexed_vector.h | 35 + src/util/lp/iterator_on_pivot_row.h | 42 + src/util/lp/iterator_on_row.h | 37 + src/util/lp/iterator_on_term_with_basis_var.h | 56 + src/util/lp/lar_constraints.h | 86 + src/util/lp/lar_core_solver.h | 802 +++++++ src/util/lp/lar_core_solver.hpp | 292 +++ src/util/lp/lar_core_solver_instances.cpp | 10 + src/util/lp/lar_solution_signature.h | 13 + src/util/lp/lar_solver.h | 2135 +++++++++++++++++ src/util/lp/lar_term.h | 64 + src/util/lp/linear_combination_iterator.h | 47 + src/util/lp/lp_core_solver_base.h | 683 ++++++ src/util/lp/lp_core_solver_base.hpp | 1007 ++++++++ src/util/lp/lp_core_solver_base_instances.cpp | 131 + src/util/lp/lp_dual_core_solver.h | 197 ++ src/util/lp/lp_dual_core_solver.hpp | 743 ++++++ src/util/lp/lp_dual_core_solver_instances.cpp | 29 + src/util/lp/lp_dual_simplex.h | 79 + src/util/lp/lp_dual_simplex.hpp | 362 +++ src/util/lp/lp_dual_simplex_instances.cpp | 9 + src/util/lp/lp_primal_core_solver.h | 986 ++++++++ src/util/lp/lp_primal_core_solver.hpp | 1374 +++++++++++ .../lp/lp_primal_core_solver_instances.cpp | 24 + src/util/lp/lp_primal_core_solver_tableau.hpp | 393 +++ src/util/lp/lp_primal_simplex.h | 96 + src/util/lp/lp_primal_simplex.hpp | 355 +++ src/util/lp/lp_primal_simplex_instances.cpp | 20 + src/util/lp/lp_settings.h | 339 +++ src/util/lp/lp_settings.hpp | 133 + src/util/lp/lp_settings_instances.cpp | 10 + src/util/lp/lp_solver.h | 253 ++ src/util/lp/lp_solver.hpp | 554 +++++ src/util/lp/lp_solver_instances.cpp | 40 + src/util/lp/lp_utils.cpp | 11 + src/util/lp/lp_utils.h | 141 ++ src/util/lp/lu.h | 359 +++ src/util/lp/lu.hpp | 940 ++++++++ src/util/lp/lu_instances.cpp | 63 + src/util/lp/matrix.h | 44 + src/util/lp/matrix.hpp | 105 + src/util/lp/matrix_instances.cpp | 16 + src/util/lp/mps_reader.h | 867 +++++++ src/util/lp/numeric_pair.h | 329 +++ src/util/lp/permutation_matrix.h | 173 ++ src/util/lp/permutation_matrix.hpp | 419 ++++ src/util/lp/permutation_matrix_instances.cpp | 60 + src/util/lp/quick_xplain.cpp | 138 ++ src/util/lp/quick_xplain.h | 33 + src/util/lp/random_updater.h | 77 + src/util/lp/random_updater.hpp | 205 ++ src/util/lp/random_updater_instances.cpp | 5 + src/util/lp/row_eta_matrix.h | 74 + src/util/lp/row_eta_matrix.hpp | 171 ++ src/util/lp/row_eta_matrix_instances.cpp | 33 + src/util/lp/scaler.h | 79 + src/util/lp/scaler.hpp | 254 ++ src/util/lp/scaler_instances.cpp | 7 + src/util/lp/signature_bound_evidence.h | 23 + src/util/lp/sparse_matrix.h | 417 ++++ src/util/lp/sparse_matrix.hpp | 1255 ++++++++++ src/util/lp/sparse_matrix_instances.cpp | 101 + src/util/lp/sparse_vector.h | 38 + src/util/lp/square_dense_submatrix.h | 210 ++ src/util/lp/square_dense_submatrix.hpp | 353 +++ .../lp/square_dense_submatrix_instances.cpp | 33 + src/util/lp/stacked_map.h | 179 ++ src/util/lp/stacked_unordered_set.h | 92 + src/util/lp/stacked_value.h | 65 + src/util/lp/stacked_vector.h | 167 ++ src/util/lp/static_matrix.h | 364 +++ src/util/lp/static_matrix.hpp | 409 ++++ src/util/lp/static_matrix_instances.cpp | 67 + src/util/lp/tail_matrix.h | 28 + src/util/lp/test_bound_analyzer.h | 260 ++ src/util/lp/ul_pair.h | 58 + 120 files changed, 23069 insertions(+), 15 deletions(-) create mode 100644 src/util/lp/binary_heap_priority_queue.h create mode 100644 src/util/lp/binary_heap_priority_queue.hpp create mode 100644 src/util/lp/binary_heap_priority_queue_instances.cpp create mode 100644 src/util/lp/binary_heap_upair_queue.h create mode 100644 src/util/lp/binary_heap_upair_queue.hpp create mode 100644 src/util/lp/binary_heap_upair_queue_instances.cpp create mode 100644 src/util/lp/bound_analyzer_on_row.h create mode 100644 src/util/lp/bound_propagator.cpp create mode 100644 src/util/lp/bound_propagator.h create mode 100644 src/util/lp/breakpoint.h create mode 100644 src/util/lp/column_info.h create mode 100644 src/util/lp/column_namer.h create mode 100644 src/util/lp/core_solver_pretty_printer.h create mode 100644 src/util/lp/core_solver_pretty_printer.hpp create mode 100644 src/util/lp/core_solver_pretty_printer_instances.cpp create mode 100644 src/util/lp/dense_matrix.h create mode 100644 src/util/lp/dense_matrix.hpp create mode 100644 src/util/lp/dense_matrix_instances.cpp create mode 100644 src/util/lp/eta_matrix.h create mode 100644 src/util/lp/eta_matrix.hpp create mode 100644 src/util/lp/eta_matrix_instances.cpp create mode 100644 src/util/lp/hash_helper.h create mode 100644 src/util/lp/implied_bound.h create mode 100644 src/util/lp/indexed_value.h create mode 100644 src/util/lp/indexed_vector.h create mode 100644 src/util/lp/indexed_vector.hpp create mode 100644 src/util/lp/indexed_vector_instances.cpp create mode 100644 src/util/lp/int_set.h create mode 100644 src/util/lp/iterator_on_column.h create mode 100644 src/util/lp/iterator_on_indexed_vector.h create mode 100644 src/util/lp/iterator_on_pivot_row.h create mode 100644 src/util/lp/iterator_on_row.h create mode 100644 src/util/lp/iterator_on_term_with_basis_var.h create mode 100644 src/util/lp/lar_constraints.h create mode 100644 src/util/lp/lar_core_solver.h create mode 100644 src/util/lp/lar_core_solver.hpp create mode 100644 src/util/lp/lar_core_solver_instances.cpp create mode 100644 src/util/lp/lar_solution_signature.h create mode 100644 src/util/lp/lar_solver.h create mode 100644 src/util/lp/lar_term.h create mode 100644 src/util/lp/linear_combination_iterator.h create mode 100644 src/util/lp/lp_core_solver_base.h create mode 100644 src/util/lp/lp_core_solver_base.hpp create mode 100644 src/util/lp/lp_core_solver_base_instances.cpp create mode 100644 src/util/lp/lp_dual_core_solver.h create mode 100644 src/util/lp/lp_dual_core_solver.hpp create mode 100644 src/util/lp/lp_dual_core_solver_instances.cpp create mode 100644 src/util/lp/lp_dual_simplex.h create mode 100644 src/util/lp/lp_dual_simplex.hpp create mode 100644 src/util/lp/lp_dual_simplex_instances.cpp create mode 100644 src/util/lp/lp_primal_core_solver.h create mode 100644 src/util/lp/lp_primal_core_solver.hpp create mode 100644 src/util/lp/lp_primal_core_solver_instances.cpp create mode 100644 src/util/lp/lp_primal_core_solver_tableau.hpp create mode 100644 src/util/lp/lp_primal_simplex.h create mode 100644 src/util/lp/lp_primal_simplex.hpp create mode 100644 src/util/lp/lp_primal_simplex_instances.cpp create mode 100644 src/util/lp/lp_settings.h create mode 100644 src/util/lp/lp_settings.hpp create mode 100644 src/util/lp/lp_settings_instances.cpp create mode 100644 src/util/lp/lp_solver.h create mode 100644 src/util/lp/lp_solver.hpp create mode 100644 src/util/lp/lp_solver_instances.cpp create mode 100644 src/util/lp/lp_utils.cpp create mode 100644 src/util/lp/lp_utils.h create mode 100644 src/util/lp/lu.h create mode 100644 src/util/lp/lu.hpp create mode 100644 src/util/lp/lu_instances.cpp create mode 100644 src/util/lp/matrix.h create mode 100644 src/util/lp/matrix.hpp create mode 100644 src/util/lp/matrix_instances.cpp create mode 100644 src/util/lp/mps_reader.h create mode 100644 src/util/lp/numeric_pair.h create mode 100644 src/util/lp/permutation_matrix.h create mode 100644 src/util/lp/permutation_matrix.hpp create mode 100644 src/util/lp/permutation_matrix_instances.cpp create mode 100644 src/util/lp/quick_xplain.cpp create mode 100644 src/util/lp/quick_xplain.h create mode 100644 src/util/lp/random_updater.h create mode 100644 src/util/lp/random_updater.hpp create mode 100644 src/util/lp/random_updater_instances.cpp create mode 100644 src/util/lp/row_eta_matrix.h create mode 100644 src/util/lp/row_eta_matrix.hpp create mode 100644 src/util/lp/row_eta_matrix_instances.cpp create mode 100644 src/util/lp/scaler.h create mode 100644 src/util/lp/scaler.hpp create mode 100644 src/util/lp/scaler_instances.cpp create mode 100644 src/util/lp/signature_bound_evidence.h create mode 100644 src/util/lp/sparse_matrix.h create mode 100644 src/util/lp/sparse_matrix.hpp create mode 100644 src/util/lp/sparse_matrix_instances.cpp create mode 100644 src/util/lp/sparse_vector.h create mode 100644 src/util/lp/square_dense_submatrix.h create mode 100644 src/util/lp/square_dense_submatrix.hpp create mode 100644 src/util/lp/square_dense_submatrix_instances.cpp create mode 100644 src/util/lp/stacked_map.h create mode 100644 src/util/lp/stacked_unordered_set.h create mode 100644 src/util/lp/stacked_value.h create mode 100644 src/util/lp/stacked_vector.h create mode 100644 src/util/lp/static_matrix.h create mode 100644 src/util/lp/static_matrix.hpp create mode 100644 src/util/lp/static_matrix_instances.cpp create mode 100644 src/util/lp/tail_matrix.h create mode 100644 src/util/lp/test_bound_analyzer.h create mode 100644 src/util/lp/ul_pair.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 7983b45bc..b25139c1b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -187,6 +187,7 @@ endif() # Note for some reason we have to leave off ``-D`` here otherwise # we get ``-D-DZ3DEBUG`` passed to the compiler list(APPEND Z3_COMPONENT_CXX_DEFINES $<$:Z3DEBUG>) +list(APPEND Z3_COMPONENT_CXX_DEFINES $<$:LEAN_DEBUG>) list(APPEND Z3_COMPONENT_CXX_DEFINES $<$:_EXTERNAL_RELEASE>) list(APPEND Z3_COMPONENT_CXX_DEFINES $<$:_EXTERNAL_RELEASE>) @@ -254,6 +255,7 @@ else() message(FATAL_ERROR "Platform \"${CMAKE_SYSTEM_NAME}\" not recognised") endif() +list(APPEND Z3_COMPONENT_EXTRA_INCLUDE_DIRS "${CMAKE_SOURCE_DIR}/src") ################################################################################ # GNU multiple precision library support @@ -297,6 +299,7 @@ if (USE_OPENMP) message(WARNING "OpenMP support was requested but your compiler doesn't support it") endif() endif() + if (OPENMP_FOUND) list(APPEND Z3_COMPONENT_CXX_FLAGS ${OpenMP_CXX_FLAGS}) # GCC and Clang need to have additional flags passed to the linker. diff --git a/contrib/cmake/src/CMakeLists.txt b/contrib/cmake/src/CMakeLists.txt index 66e34790a..dd440b34d 100644 --- a/contrib/cmake/src/CMakeLists.txt +++ b/contrib/cmake/src/CMakeLists.txt @@ -35,6 +35,7 @@ endforeach() # raised if you try to declare a component is dependent on another component # that has not yet been declared. add_subdirectory(util) +add_subdirectory(util/lp) add_subdirectory(math/polynomial) add_subdirectory(sat) add_subdirectory(nlsat) diff --git a/contrib/cmake/src/shell/CMakeLists.txt b/contrib/cmake/src/shell/CMakeLists.txt index c5d5ca880..278246341 100644 --- a/contrib/cmake/src/shell/CMakeLists.txt +++ b/contrib/cmake/src/shell/CMakeLists.txt @@ -27,6 +27,7 @@ add_executable(shell opt_frontend.cpp smtlib_frontend.cpp z3_log_frontend.cpp + lp_frontend.cpp # FIXME: shell should really link against libz3 but it can't due to requiring # use of some hidden symbols. Also libz3 has the ``api_dll`` component which # we don't want (I think). diff --git a/contrib/cmake/src/smt/CMakeLists.txt b/contrib/cmake/src/smt/CMakeLists.txt index c344e936f..41890dd05 100644 --- a/contrib/cmake/src/smt/CMakeLists.txt +++ b/contrib/cmake/src/smt/CMakeLists.txt @@ -55,6 +55,7 @@ z3_add_component(smt theory_dl.cpp theory_dummy.cpp theory_fpa.cpp + theory_lra.cpp theory_opt.cpp theory_pb.cpp theory_seq.cpp @@ -69,6 +70,7 @@ z3_add_component(smt euclid fpa grobner + lp macros normal_forms parser_util diff --git a/contrib/cmake/src/test/CMakeLists.txt b/contrib/cmake/src/test/CMakeLists.txt index 46781b2cc..ba9ae785d 100644 --- a/contrib/cmake/src/test/CMakeLists.txt +++ b/contrib/cmake/src/test/CMakeLists.txt @@ -117,6 +117,7 @@ add_executable(test-z3 upolynomial.cpp var_subst.cpp vector.cpp + lp.cpp ${z3_test_extra_object_files} ) z3_add_install_tactic_rule(${z3_test_deps}) @@ -128,3 +129,14 @@ target_link_libraries(test-z3 PRIVATE ${Z3_DEPENDENT_LIBS}) target_include_directories(test-z3 PRIVATE ${Z3_COMPONENT_EXTRA_INCLUDE_DIRS}) z3_append_linker_flag_list_to_target(test-z3 ${Z3_DEPENDENT_EXTRA_CXX_LINK_FLAGS}) z3_add_component_dependencies_to_target(test-z3 ${z3_test_expanded_deps}) + +add_executable(lp_tst lp_main.cpp lp.cpp $ $) +target_compile_definitions(lp_tst PRIVATE ${Z3_COMPONENT_CXX_DEFINES}) +target_compile_options(lp_tst PRIVATE ${Z3_COMPONENT_CXX_FLAGS}) +target_include_directories(lp_tst PRIVATE ${Z3_COMPONENT_EXTRA_INCLUDE_DIRS}) +target_link_libraries(lp_tst PRIVATE ${Z3_DEPENDENT_LIBS}) +z3_append_linker_flag_list_to_target(lp_tst ${Z3_DEPENDENT_EXTRA_CXX_LINK_FLAGS}) + + + + diff --git a/scripts/mk_project.py b/scripts/mk_project.py index 7ba88c1b3..8655346f2 100644 --- a/scripts/mk_project.py +++ b/scripts/mk_project.py @@ -11,6 +11,7 @@ from mk_util import * def init_project_def(): set_version(4, 5, 1, 0) add_lib('util', []) + add_lib('lp', ['util'], 'util/lp') add_lib('polynomial', ['util'], 'math/polynomial') add_lib('sat', ['util']) add_lib('nlsat', ['polynomial', 'sat']) @@ -52,7 +53,7 @@ def init_project_def(): add_lib('smt_params', ['ast', 'simplifier', 'pattern', 'bit_blaster'], 'smt/params') add_lib('proto_model', ['model', 'simplifier', 'smt_params'], 'smt/proto_model') add_lib('smt', ['bit_blaster', 'macros', 'normal_forms', 'cmd_context', 'proto_model', - 'substitution', 'grobner', 'euclid', 'simplex', 'proof_checker', 'pattern', 'parser_util', 'fpa']) + 'substitution', 'grobner', 'euclid', 'simplex', 'proof_checker', 'pattern', 'parser_util', 'fpa', 'lp']) add_lib('bv_tactics', ['tactic', 'bit_blaster', 'core_tactics'], 'tactic/bv') add_lib('fuzzing', ['ast'], 'test/fuzzing') add_lib('smt_tactic', ['smt'], 'smt/tactic') diff --git a/scripts/mk_util.py b/scripts/mk_util.py index d1bbd6ca4..edaffcd1e 100644 --- a/scripts/mk_util.py +++ b/scripts/mk_util.py @@ -774,8 +774,13 @@ def extract_c_includes(fname): linenum = 1 for line in f: m1 = std_inc_pat.match(line) - if m1: - result.append(m1.group(1)) + if m1: + root_file_name = m1.group(1) + slash_pos = root_file_name.rfind('/') + if slash_pos >= 0 and root_file_name.find("..") < 0 : #it is a hack for lp include files that behave as continued from "src" + print(root_file_name) + root_file_name = root_file_name[slash_pos+1:] + result.append(root_file_name) elif not system_inc_pat.match(line) and non_std_inc_pat.match(line): raise MKException("Invalid #include directive at '%s':%s" % (fname, line)) linenum = linenum + 1 @@ -999,6 +1004,7 @@ class Component: out.write('%s =' % include_defs) for dep in self.deps: out.write(' -I%s' % get_component(dep).to_src_dir) + out.write(' -I..\src') out.write('\n') mk_dir(os.path.join(BUILD_DIR, self.build_dir)) if VS_PAR and IS_WINDOWS: diff --git a/src/ast/arith_decl_plugin.h b/src/ast/arith_decl_plugin.h index 668eebcc9..beb227c33 100644 --- a/src/ast/arith_decl_plugin.h +++ b/src/ast/arith_decl_plugin.h @@ -305,6 +305,7 @@ public: MATCH_UNARY(is_uminus); MATCH_UNARY(is_to_real); MATCH_UNARY(is_to_int); + MATCH_UNARY(is_is_int); MATCH_BINARY(is_sub); MATCH_BINARY(is_add); MATCH_BINARY(is_mul); @@ -377,6 +378,9 @@ public: app * mk_real(int i) { return mk_numeral(rational(i), false); } + app * mk_real(rational const& r) { + return mk_numeral(r, false); + } app * mk_le(expr * arg1, expr * arg2) const { return m_manager.mk_app(m_afid, OP_LE, arg1, arg2); } app * mk_ge(expr * arg1, expr * arg2) const { return m_manager.mk_app(m_afid, OP_GE, arg1, arg2); } app * mk_lt(expr * arg1, expr * arg2) const { return m_manager.mk_app(m_afid, OP_LT, arg1, arg2); } diff --git a/src/opt/opt_solver.cpp b/src/opt/opt_solver.cpp index bc6462a18..458a5c540 100644 --- a/src/opt/opt_solver.cpp +++ b/src/opt/opt_solver.cpp @@ -26,6 +26,7 @@ Notes: #include "theory_diff_logic.h" #include "theory_dense_diff_logic.h" #include "theory_pb.h" +#include "theory_lra.h" #include "ast_pp.h" #include "ast_smt_pp.h" #include "pp_params.hpp" @@ -143,6 +144,9 @@ namespace opt { else if (typeid(smt::theory_dense_si&) == typeid(*arith_theory)) { return dynamic_cast(*arith_theory); } + else if (typeid(smt::theory_lra&) == typeid(*arith_theory)) { + return dynamic_cast(*arith_theory); + } else { UNREACHABLE(); return dynamic_cast(*arith_theory); @@ -401,6 +405,14 @@ namespace opt { return th.mk_ge(m_fm, v, val); } + + if (typeid(smt::theory_lra) == typeid(opt)) { + smt::theory_lra& th = dynamic_cast(opt); + SASSERT(val.is_finite()); + return th.mk_ge(m_fm, v, val.get_numeral()); + } + + // difference logic? if (typeid(smt::theory_dense_si) == typeid(opt) && val.get_infinitesimal().is_zero()) { smt::theory_dense_si& th = dynamic_cast(opt); diff --git a/src/shell/main.cpp b/src/shell/main.cpp index fddce4f69..b29586f8d 100644 --- a/src/shell/main.cpp +++ b/src/shell/main.cpp @@ -35,8 +35,9 @@ Revision History: #include"error_codes.h" #include"gparams.h" #include"env_params.h" +#include "lp_frontend.h" -typedef enum { IN_UNSPECIFIED, IN_SMTLIB, IN_SMTLIB_2, IN_DATALOG, IN_DIMACS, IN_WCNF, IN_OPB, IN_Z3_LOG } input_kind; +typedef enum { IN_UNSPECIFIED, IN_SMTLIB, IN_SMTLIB_2, IN_DATALOG, IN_DIMACS, IN_WCNF, IN_OPB, IN_Z3_LOG, IN_MPS } input_kind; std::string g_aux_input_file; char const * g_input_file = 0; @@ -342,6 +343,10 @@ int STD_CALL main(int argc, char ** argv) { else if (strcmp(ext, "smt") == 0) { g_input_kind = IN_SMTLIB; } + else if (strcmp(ext, "mps") == 0 || strcmp(ext, "sif") == 0 || + strcmp(ext, "MPS") == 0 || strcmp(ext, "SIF") == 0) { + g_input_kind = IN_MPS; + } } } switch (g_input_kind) { @@ -367,6 +372,9 @@ int STD_CALL main(int argc, char ** argv) { case IN_Z3_LOG: replay_z3_log(g_input_file); break; + case IN_MPS: + return_value = read_mps_file(g_input_file); + break; default: UNREACHABLE(); } diff --git a/src/smt/smt_setup.cpp b/src/smt/smt_setup.cpp index a4f2ee646..1abb3d010 100644 --- a/src/smt/smt_setup.cpp +++ b/src/smt/smt_setup.cpp @@ -20,6 +20,7 @@ Revision History: #include"smt_setup.h" #include"static_features.h" #include"theory_arith.h" +#include"theory_lra.h" #include"theory_dense_diff_logic.h" #include"theory_diff_logic.h" #include"theory_utvpi.h" @@ -442,7 +443,7 @@ namespace smt { m_params.m_arith_propagate_eqs = false; m_params.m_eliminate_term_ite = true; m_params.m_nnf_cnf = false; - setup_mi_arith(); + setup_r_arith(); } void setup::setup_QF_LRA(static_features const & st) { @@ -467,7 +468,7 @@ namespace smt { m_params.m_restart_adaptive = false; } m_params.m_arith_small_lemma_size = 32; - setup_mi_arith(); + setup_r_arith(); } void setup::setup_QF_LIA() { @@ -539,7 +540,7 @@ namespace smt { m_params.m_relevancy_lvl = 0; m_params.m_arith_reflect = false; m_params.m_nnf_cnf = false; - setup_mi_arith(); + setup_r_arith(); } void setup::setup_QF_BV() { @@ -718,6 +719,10 @@ namespace smt { m_context.register_plugin(alloc(smt::theory_i_arith, m_manager, m_params)); } + void setup::setup_r_arith() { + m_context.register_plugin(alloc(smt::theory_lra, m_manager, m_params)); + } + void setup::setup_mi_arith() { if (m_params.m_arith_mode == AS_OPTINF) { m_context.register_plugin(alloc(smt::theory_inf_arith, m_manager, m_params)); diff --git a/src/smt/smt_setup.h b/src/smt/smt_setup.h index 80d5d7d1b..cffc96bb8 100644 --- a/src/smt/smt_setup.h +++ b/src/smt/smt_setup.h @@ -99,6 +99,7 @@ namespace smt { void setup_card(); void setup_i_arith(); void setup_mi_arith(); + void setup_r_arith(); void setup_fpa(); void setup_str(); diff --git a/src/smt/smt_theory.cpp b/src/smt/smt_theory.cpp index fd853d6a6..2263699f9 100644 --- a/src/smt/smt_theory.cpp +++ b/src/smt/smt_theory.cpp @@ -54,7 +54,7 @@ namespace smt { } } - void theory::display_app(std::ostream & out, app * n) const { + std::ostream& theory::display_app(std::ostream & out, app * n) const { func_decl * d = n->get_decl(); if (n->get_num_args() == 0) { out << d->get_name(); @@ -73,9 +73,10 @@ namespace smt { else { out << "#" << n->get_id(); } + return out; } - void theory::display_flat_app(std::ostream & out, app * n) const { + std::ostream& theory::display_flat_app(std::ostream & out, app * n) const { func_decl * d = n->get_decl(); if (n->get_num_args() == 0) { out << d->get_name(); @@ -106,6 +107,7 @@ namespace smt { else { out << "#" << n->get_id(); } + return out; } bool theory::is_relevant_and_shared(enode * n) const { diff --git a/src/smt/smt_theory.h b/src/smt/smt_theory.h index 67091c601..c943a85d8 100644 --- a/src/smt/smt_theory.h +++ b/src/smt/smt_theory.h @@ -337,14 +337,14 @@ namespace smt { virtual void collect_statistics(::statistics & st) const { } - - void display_app(std::ostream & out, app * n) const; - - void display_flat_app(std::ostream & out, app * n) const; - void display_var_def(std::ostream & out, theory_var v) const { return display_app(out, get_enode(v)->get_owner()); } + std::ostream& display_app(std::ostream & out, app * n) const; - void display_var_flat_def(std::ostream & out, theory_var v) const { return display_flat_app(out, get_enode(v)->get_owner()); } + std::ostream& display_flat_app(std::ostream & out, app * n) const; + + std::ostream& display_var_def(std::ostream & out, theory_var v) const { return display_app(out, get_enode(v)->get_owner()); } + + std::ostream& display_var_flat_def(std::ostream & out, theory_var v) const { return display_flat_app(out, get_enode(v)->get_owner()); } /** \brief Assume eqs between variable that are equal with respect to the given table. diff --git a/src/test/main.cpp b/src/test/main.cpp index 15f92b2f7..c05488370 100644 --- a/src/test/main.cpp +++ b/src/test/main.cpp @@ -239,6 +239,7 @@ int main(int argc, char ** argv) { TST(pdr); TST_ARGV(ddnf); TST(model_evaluator); + TST_ARGV(lp); TST(get_consequences); TST(pb2bv); TST_ARGV(cnf_backbones); diff --git a/src/util/lp/binary_heap_priority_queue.h b/src/util/lp/binary_heap_priority_queue.h new file mode 100644 index 000000000..1a1f89592 --- /dev/null +++ b/src/util/lp/binary_heap_priority_queue.h @@ -0,0 +1,71 @@ + +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#pragma once +#include "util/vector.h" +#include "util/debug.h" +#include "util/lp/lp_utils.h" +namespace lean { +// the elements with the smallest priority are dequeued first +template +class binary_heap_priority_queue { + vector m_priorities; + + // indexing for A starts from 1 + vector m_heap; // keeps the elements of the queue + vector m_heap_inverse; // o = m_heap[m_heap_inverse[o]] + unsigned m_heap_size = 0; + + // is is the child place in heap + void swap_with_parent(unsigned i); + void put_at(unsigned i, unsigned h); + void decrease_priority(unsigned o, T newPriority); +public: +#ifdef LEAN_DEBUG + bool is_consistent() const; +#endif +public: + void remove(unsigned o); + unsigned size() const { return m_heap_size; } + binary_heap_priority_queue(): m_heap(1) {} // the empty constructror + // n is the initial queue capacity. + // The capacity will be enlarged two times automatically if needed + binary_heap_priority_queue(unsigned n); + + void clear() { + for (unsigned i = 0; i < m_heap_size; i++) { + unsigned o = m_heap[i+1]; + m_heap_inverse[o] = -1; + } + m_heap_size = 0; + } + + void resize(unsigned n); + void put_to_heap(unsigned i, unsigned o); + + void enqueue_new(unsigned o, const T& priority); + + // This method can work with an element that is already in the queue. + // In this case the priority will be changed and the queue adjusted. + void enqueue(unsigned o, const T & priority); + void change_priority_for_existing(unsigned o, const T & priority); + T get_priority(unsigned o) const { return m_priorities[o]; } + bool is_empty() const { return m_heap_size == 0; } + + /// return the first element of the queue and removes it from the queue + unsigned dequeue_and_get_priority(T & priority); + void fix_heap_under(unsigned i); + void put_the_last_at_the_top_and_fix_the_heap(); + /// return the first element of the queue and removes it from the queue + unsigned dequeue(); + unsigned peek() const { + lean_assert(m_heap_size > 0); + return m_heap[1]; + } +#ifdef LEAN_DEBUG + void print(std::ostream & out); +#endif +}; +} diff --git a/src/util/lp/binary_heap_priority_queue.hpp b/src/util/lp/binary_heap_priority_queue.hpp new file mode 100644 index 000000000..2ad65c536 --- /dev/null +++ b/src/util/lp/binary_heap_priority_queue.hpp @@ -0,0 +1,193 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include "util/vector.h" +#include "util/lp/binary_heap_priority_queue.h" +namespace lean { +// is is the child place in heap +template void binary_heap_priority_queue::swap_with_parent(unsigned i) { + unsigned parent = m_heap[i >> 1]; + put_at(i >> 1, m_heap[i]); + put_at(i, parent); +} + +template void binary_heap_priority_queue::put_at(unsigned i, unsigned h) { + m_heap[i] = h; + m_heap_inverse[h] = i; +} + +template void binary_heap_priority_queue::decrease_priority(unsigned o, T newPriority) { + m_priorities[o] = newPriority; + int i = m_heap_inverse[o]; + while (i > 1) { + if (m_priorities[m_heap[i]] < m_priorities[m_heap[i >> 1]]) + swap_with_parent(i); + else + break; + i >>= 1; + } +} + +#ifdef LEAN_DEBUG +template bool binary_heap_priority_queue::is_consistent() const { + for (int i = 0; i < m_heap_inverse.size(); i++) { + int i_index = m_heap_inverse[i]; + lean_assert(i_index <= static_cast(m_heap_size)); + lean_assert(i_index == -1 || m_heap[i_index] == i); + } + for (unsigned i = 1; i < m_heap_size; i++) { + unsigned ch = i << 1; + for (int k = 0; k < 2; k++) { + if (ch > m_heap_size) break; + if (!(m_priorities[m_heap[i]] <= m_priorities[m_heap[ch]])){ + return false; + } + ch++; + } + } + return true; +} +#endif +template void binary_heap_priority_queue::remove(unsigned o) { + T priority_of_o = m_priorities[o]; + int o_in_heap = m_heap_inverse[o]; + if (o_in_heap == -1) { + return; // nothing to do + } + lean_assert(static_cast(o_in_heap) <= m_heap_size); + if (static_cast(o_in_heap) < m_heap_size) { + put_at(o_in_heap, m_heap[m_heap_size--]); + if (m_priorities[m_heap[o_in_heap]] > priority_of_o) { + fix_heap_under(o_in_heap); + } else { // we need to propogate the m_heap[o_in_heap] up + unsigned i = o_in_heap; + while (i > 1) { + unsigned ip = i >> 1; + if (m_priorities[m_heap[i]] < m_priorities[m_heap[ip]]) + swap_with_parent(i); + else + break; + i = ip; + } + } + } else { + lean_assert(static_cast(o_in_heap) == m_heap_size); + m_heap_size--; + } + m_heap_inverse[o] = -1; + // lean_assert(is_consistent()); +} +// n is the initial queue capacity. +// The capacity will be enlarged two times automatically if needed +template binary_heap_priority_queue::binary_heap_priority_queue(unsigned n) : + m_priorities(n), + m_heap(n + 1), // because the indexing for A starts from 1 + m_heap_inverse(n, -1) +{ } + + +template void binary_heap_priority_queue::resize(unsigned n) { + m_priorities.resize(n); + m_heap.resize(n + 1); + m_heap_inverse.resize(n, -1); +} + +template void binary_heap_priority_queue::put_to_heap(unsigned i, unsigned o) { + m_heap[i] = o; + m_heap_inverse[o] = i; +} + +template void binary_heap_priority_queue::enqueue_new(unsigned o, const T& priority) { + m_heap_size++; + int i = m_heap_size; + lean_assert(o < m_priorities.size()); + m_priorities[o] = priority; + put_at(i, o); + while (i > 1 && m_priorities[m_heap[i >> 1]] > priority) { + swap_with_parent(i); + i >>= 1; + } +} +// This method can work with an element that is already in the queue. +// In this case the priority will be changed and the queue adjusted. +template void binary_heap_priority_queue::enqueue(unsigned o, const T & priority) { + if (o >= m_priorities.size()) { + resize(o << 1); // make the size twice larger + } + if (m_heap_inverse[o] == -1) + enqueue_new(o, priority); + else + change_priority_for_existing(o, priority); +} + +template void binary_heap_priority_queue::change_priority_for_existing(unsigned o, const T & priority) { + if (m_priorities[o] > priority) { + decrease_priority(o, priority); + } else { + m_priorities[o] = priority; + fix_heap_under(m_heap_inverse[o]); + } +} + + +/// return the first element of the queue and removes it from the queue +template unsigned binary_heap_priority_queue::dequeue_and_get_priority(T & priority) { + lean_assert(m_heap_size != 0); + int ret = m_heap[1]; + priority = m_priorities[ret]; + put_the_last_at_the_top_and_fix_the_heap(); + return ret; +} + +template void binary_heap_priority_queue::fix_heap_under(unsigned i) { + while (true) { + unsigned smallest = i; + unsigned l = i << 1; + if (l <= m_heap_size && m_priorities[m_heap[l]] < m_priorities[m_heap[i]]) + smallest = l; + unsigned r = l + 1; + if (r <= m_heap_size && m_priorities[m_heap[r]] < m_priorities[m_heap[smallest]]) + smallest = r; + if (smallest != i) + swap_with_parent(smallest); + else + break; + i = smallest; + } +} + +template void binary_heap_priority_queue::put_the_last_at_the_top_and_fix_the_heap() { + if (m_heap_size > 1) { + put_at(1, m_heap[m_heap_size--]); + fix_heap_under(1); + } else { + m_heap_size--; + } +} +/// return the first element of the queue and removes it from the queue +template unsigned binary_heap_priority_queue::dequeue() { + lean_assert(m_heap_size > 0); + int ret = m_heap[1]; + put_the_last_at_the_top_and_fix_the_heap(); + m_heap_inverse[ret] = -1; + return ret; +} +#ifdef LEAN_DEBUG +template void binary_heap_priority_queue::print(std::ostream & out) { + vector index; + vector prs; + while (size()) { + T prior; + int j = dequeue_and_get_priority(prior); + index.push_back(j); + prs.push_back(prior); + out << "(" << j << ", " << prior << ")"; + } + out << std::endl; + // restore the queue + for (int i = 0; i < index.size(); i++) + enqueue(index[i], prs[i]); +} +#endif +} diff --git a/src/util/lp/binary_heap_priority_queue_instances.cpp b/src/util/lp/binary_heap_priority_queue_instances.cpp new file mode 100644 index 000000000..567494d6f --- /dev/null +++ b/src/util/lp/binary_heap_priority_queue_instances.cpp @@ -0,0 +1,26 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include "util/lp/numeric_pair.h" +#include "util/lp/binary_heap_priority_queue.hpp" +namespace lean { +template binary_heap_priority_queue::binary_heap_priority_queue(unsigned int); +template unsigned binary_heap_priority_queue::dequeue(); +template void binary_heap_priority_queue::enqueue(unsigned int, int const&); +template void binary_heap_priority_queue::enqueue(unsigned int, double const&); +template void binary_heap_priority_queue::enqueue(unsigned int, mpq const&); +template void binary_heap_priority_queue::remove(unsigned int); +template unsigned binary_heap_priority_queue >::dequeue(); +template unsigned binary_heap_priority_queue::dequeue(); +template unsigned binary_heap_priority_queue::dequeue(); +template void binary_heap_priority_queue >::enqueue(unsigned int, numeric_pair const&); +template void binary_heap_priority_queue >::resize(unsigned int); +template void lean::binary_heap_priority_queue::resize(unsigned int); +template binary_heap_priority_queue::binary_heap_priority_queue(unsigned int); +template void binary_heap_priority_queue::resize(unsigned int); +template unsigned binary_heap_priority_queue::dequeue(); +template void binary_heap_priority_queue::enqueue(unsigned int, unsigned int const&); +template void binary_heap_priority_queue::remove(unsigned int); +template void lean::binary_heap_priority_queue::resize(unsigned int); +} diff --git a/src/util/lp/binary_heap_upair_queue.h b/src/util/lp/binary_heap_upair_queue.h new file mode 100644 index 000000000..d5df7affc --- /dev/null +++ b/src/util/lp/binary_heap_upair_queue.h @@ -0,0 +1,50 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ + +#pragma once +#include +#include +#include +#include "util/vector.h" +#include +#include +#include "util/lp/binary_heap_priority_queue.h" + + +typedef std::pair upair; + +namespace lean { +template +class binary_heap_upair_queue { + binary_heap_priority_queue m_q; + std::unordered_map m_pairs_to_index; + vector m_pairs; // inverse to index + vector m_available_spots; +public: + binary_heap_upair_queue(unsigned size); + + unsigned dequeue_available_spot(); + bool is_empty() const { return m_q.is_empty(); } + + unsigned size() const {return m_q.size(); } + + bool contains(unsigned i, unsigned j) const { return m_pairs_to_index.find(std::make_pair(i, j)) != m_pairs_to_index.end(); + } + + void remove(unsigned i, unsigned j); + bool ij_index_is_new(unsigned ij_index) const; + void enqueue(unsigned i, unsigned j, const T & priority); + void dequeue(unsigned & i, unsigned &j); + T get_priority(unsigned i, unsigned j) const; +#ifdef LEAN_DEBUG + bool pair_to_index_is_a_bijection() const; + bool available_spots_are_correct() const; + bool is_correct() const { + return m_q.is_consistent() && pair_to_index_is_a_bijection() && available_spots_are_correct(); + } +#endif + void resize(unsigned size) { m_q.resize(size); } +}; +} diff --git a/src/util/lp/binary_heap_upair_queue.hpp b/src/util/lp/binary_heap_upair_queue.hpp new file mode 100644 index 000000000..a48bdb5b7 --- /dev/null +++ b/src/util/lp/binary_heap_upair_queue.hpp @@ -0,0 +1,110 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ + +#include +#include "util/lp/lp_utils.h" +#include "util/lp/binary_heap_upair_queue.h" +namespace lean { +template binary_heap_upair_queue::binary_heap_upair_queue(unsigned size) : m_q(size), m_pairs(size) { + for (unsigned i = 0; i < size; i++) + m_available_spots.push_back(i); +} + +template unsigned +binary_heap_upair_queue::dequeue_available_spot() { + lean_assert(m_available_spots.empty() == false); + unsigned ret = m_available_spots.back(); + m_available_spots.pop_back(); + return ret; +} + +template void binary_heap_upair_queue::remove(unsigned i, unsigned j) { + upair p(i, j); + auto it = m_pairs_to_index.find(p); + if (it == m_pairs_to_index.end()) + return; // nothing to do + m_q.remove(it->second); + m_available_spots.push_back(it->second); + m_pairs_to_index.erase(it); +} + + +template bool binary_heap_upair_queue::ij_index_is_new(unsigned ij_index) const { + for (auto it : m_pairs_to_index) { + if (it.second == ij_index) + return false; + } + return true; +} + +template void binary_heap_upair_queue::enqueue(unsigned i, unsigned j, const T & priority) { + upair p(i, j); + auto it = m_pairs_to_index.find(p); + unsigned ij_index; + if (it == m_pairs_to_index.end()) { + // it is a new pair, let us find a spot for it + if (m_available_spots.empty()) { + // we ran out of empty spots + unsigned size_was = static_cast(m_pairs.size()); + unsigned new_size = size_was << 1; + for (unsigned i = size_was; i < new_size; i++) + m_available_spots.push_back(i); + m_pairs.resize(new_size); + } + ij_index = dequeue_available_spot(); + // lean_assert(ij_indexsecond; + } + m_q.enqueue(ij_index, priority); +} + +template void binary_heap_upair_queue::dequeue(unsigned & i, unsigned &j) { + lean_assert(!m_q.is_empty()); + unsigned ij_index = m_q.dequeue(); + upair & p = m_pairs[ij_index]; + i = p.first; + j = p.second; + m_available_spots.push_back(ij_index); + m_pairs_to_index.erase(p); +} + + +template T binary_heap_upair_queue::get_priority(unsigned i, unsigned j) const { + auto it = m_pairs_to_index.find(std::make_pair(i, j)); + if (it == m_pairs_to_index.end()) + return T(0xFFFFFF); // big number + return m_q.get_priority(it->second); +} + +#ifdef LEAN_DEBUG +template bool binary_heap_upair_queue::pair_to_index_is_a_bijection() const { + std::set tmp; + for (auto p : m_pairs_to_index) { + unsigned j = p.second; + unsigned size = tmp.size(); + tmp.insert(j); + if (tmp.size() == size) + return false; + } + return true; +} + +template bool binary_heap_upair_queue::available_spots_are_correct() const { + std::set tmp; + for (auto p : m_available_spots){ + tmp.insert(p); + } + if (tmp.size() != m_available_spots.size()) + return false; + for (auto it : m_pairs_to_index) + if (tmp.find(it.second) != tmp.end()) + return false; + return true; +} +#endif +} diff --git a/src/util/lp/binary_heap_upair_queue_instances.cpp b/src/util/lp/binary_heap_upair_queue_instances.cpp new file mode 100644 index 000000000..4c4603110 --- /dev/null +++ b/src/util/lp/binary_heap_upair_queue_instances.cpp @@ -0,0 +1,17 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include "util/lp/binary_heap_upair_queue.hpp" +namespace lean { +template binary_heap_upair_queue::binary_heap_upair_queue(unsigned int); +template binary_heap_upair_queue::binary_heap_upair_queue(unsigned int); +template unsigned binary_heap_upair_queue::dequeue_available_spot(); +template unsigned binary_heap_upair_queue::dequeue_available_spot(); +template void binary_heap_upair_queue::enqueue(unsigned int, unsigned int, int const&); +template void binary_heap_upair_queue::remove(unsigned int, unsigned int); +template void binary_heap_upair_queue::remove(unsigned int, unsigned int); +template void binary_heap_upair_queue::dequeue(unsigned int&, unsigned int&); +template void binary_heap_upair_queue::enqueue(unsigned int, unsigned int, unsigned int const&); +template void binary_heap_upair_queue::dequeue(unsigned int&, unsigned int&); +} diff --git a/src/util/lp/bound_analyzer_on_row.h b/src/util/lp/bound_analyzer_on_row.h new file mode 100644 index 000000000..7987c89e9 --- /dev/null +++ b/src/util/lp/bound_analyzer_on_row.h @@ -0,0 +1,333 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#pragma once +#include "util/vector.h" +#include "util/lp/linear_combination_iterator.h" +#include "implied_bound.h" +#include "test_bound_analyzer.h" +#include +#include "util/lp/bound_propagator.h" +// We have an equality : sum by j of row[j]*x[j] = rs +// We try to pin a var by pushing the total by using the variable bounds +// In a loop we drive the partial sum down, denoting the variables of this process by _u. +// In the same loop trying to pin variables by pushing the partial sum up, denoting the variable related to it by _l +namespace lean { + +class bound_analyzer_on_row { + + linear_combination_iterator & m_it; + unsigned m_row_or_term_index; + int m_column_of_u = -1; // index of an unlimited from above monoid + // -1 means that such a value is not found, -2 means that at least two of such monoids were found + int m_column_of_l = -1; // index of an unlimited from below monoid + impq m_rs; + bound_propagator & m_bp; +public : + // constructor + bound_analyzer_on_row( + linear_combination_iterator &it, + const numeric_pair& rs, + unsigned row_or_term_index, + bound_propagator & bp + ) + : + m_it(it), + m_row_or_term_index(row_or_term_index), + m_rs(rs), + m_bp(bp) + {} + + + unsigned j; + void analyze() { + + mpq a; unsigned j; + while (((m_column_of_l != -2) || (m_column_of_u != -2)) && m_it.next(a, j)) + analyze_bound_on_var_on_coeff(j, a); + + if (m_column_of_u >= 0) + limit_monoid_u_from_below(); + else if (m_column_of_u == -1) + limit_all_monoids_from_below(); + + if (m_column_of_l >= 0) + limit_monoid_l_from_above(); + else if (m_column_of_l == -1) + limit_all_monoids_from_above(); + } + + bool bound_is_available(unsigned j, bool low_bound) { + return (low_bound && low_bound_is_available(j)) || + (!low_bound && upper_bound_is_available(j)); + } + + bool upper_bound_is_available(unsigned j) const { + switch (m_bp.get_column_type(j)) + { + case column_type::fixed: + case column_type::boxed: + case column_type::upper_bound: + return true; + default: + return false; + } + } + + bool low_bound_is_available(unsigned j) const { + switch (m_bp.get_column_type(j)) + { + case column_type::fixed: + case column_type::boxed: + case column_type::low_bound: + return true; + default: + return false; + } + } + + const impq & ub(unsigned j) const { + lean_assert(upper_bound_is_available(j)); + return m_bp.get_upper_bound(j); + } + const impq & lb(unsigned j) const { + lean_assert(low_bound_is_available(j)); + return m_bp.get_low_bound(j); + } + + + const mpq & monoid_max_no_mult(bool a_is_pos, unsigned j, bool & strict) const { + if (a_is_pos) { + strict = !is_zero(ub(j).y); + return ub(j).x; + } + strict = !is_zero(lb(j).y); + return lb(j).x; + } + mpq monoid_max(const mpq & a, unsigned j) const { + if (is_pos(a)) { + return a * ub(j).x; + } + return a * lb(j).x; + } + mpq monoid_max(const mpq & a, unsigned j, bool & strict) const { + if (is_pos(a)) { + strict = !is_zero(ub(j).y); + return a * ub(j).x; + } + strict = !is_zero(lb(j).y); + return a * lb(j).x; + } + const mpq & monoid_min_no_mult(bool a_is_pos, unsigned j, bool & strict) const { + if (!a_is_pos) { + strict = !is_zero(ub(j).y); + return ub(j).x; + } + strict = !is_zero(lb(j).y); + return lb(j).x; + } + + mpq monoid_min(const mpq & a, unsigned j, bool& strict) const { + if (is_neg(a)) { + strict = !is_zero(ub(j).y); + return a * ub(j).x; + } + + strict = !is_zero(lb(j).y); + return a * lb(j).x; + } + + mpq monoid_min(const mpq & a, unsigned j) const { + if (is_neg(a)) { + return a * ub(j).x; + } + + return a * lb(j).x; + } + + + void limit_all_monoids_from_above() { + int strict = 0; + mpq total; + lean_assert(is_zero(total)); + m_it.reset(); + mpq a; unsigned j; + while (m_it.next(a, j)) { + bool str; + total -= monoid_min(a, j, str); + if (str) + strict++; + } + + m_it.reset(); + while (m_it.next(a, j)) { + bool str; + bool a_is_pos = is_pos(a); + mpq bound = total / a + monoid_min_no_mult(a_is_pos, j, str); + if (a_is_pos) { + limit_j(j, bound, true, false, strict - static_cast(str) > 0); + } + else { + limit_j(j, bound, false, true, strict - static_cast(str) > 0); + } + } + } + + void limit_all_monoids_from_below() { + int strict = 0; + mpq total; + lean_assert(is_zero(total)); + m_it.reset(); + mpq a; unsigned j; + while (m_it.next(a, j)) { + bool str; + total -= monoid_max(a, j, str); + if (str) + strict++; + } + m_it.reset(); + while (m_it.next(a, j)) { + bool str; + bool a_is_pos = is_pos(a); + mpq bound = total / a + monoid_max_no_mult(a_is_pos, j, str); + bool astrict = strict - static_cast(str) > 0; + if (a_is_pos) { + limit_j(j, bound, true, true, astrict); + } + else { + limit_j(j, bound, false, false, astrict); + } + } + } + + + void limit_monoid_u_from_below() { + // we are going to limit from below the monoid m_column_of_u, + // every other monoid is impossible to limit from below + mpq u_coeff, a; + unsigned j; + mpq bound = -m_rs.x; + m_it.reset(); + bool strict = false; + while (m_it.next(a, j)) { + if (j == static_cast(m_column_of_u)) { + u_coeff = a; + continue; + } + bool str; + bound -= monoid_max(a, j, str); + if (str) + strict = true; + } + + bound /= u_coeff; + + if (numeric_traits::is_pos(u_coeff)) { + limit_j(m_column_of_u, bound, true, true, strict); + } else { + limit_j(m_column_of_u, bound, false, false, strict); + } + } + + + void limit_monoid_l_from_above() { + // we are going to limit from above the monoid m_column_of_l, + // every other monoid is impossible to limit from above + mpq l_coeff, a; + unsigned j; + mpq bound = -m_rs.x; + bool strict = false; + m_it.reset(); + while (m_it.next(a, j)) { + if (j == static_cast(m_column_of_l)) { + l_coeff = a; + continue; + } + + bool str; + bound -= monoid_min(a, j, str); + if (str) + strict = true; + } + + bound /= l_coeff; + if (is_pos(l_coeff)) { + limit_j(m_column_of_l, bound, true, false, strict); + } else { + limit_j(m_column_of_l, bound, false, true, strict); + } + } + + // // it is the coefficent before the bounded column + // void provide_evidence(bool coeff_is_pos) { + // /* + // auto & be = m_ibounds.back(); + // bool low_bound = be.m_low_bound; + // if (!coeff_is_pos) + // low_bound = !low_bound; + // auto it = m_it.clone(); + // mpq a; unsigned j; + // while (it->next(a, j)) { + // if (be.m_j == j) continue; + // lean_assert(bound_is_available(j, is_neg(a) ? low_bound : !low_bound)); + // be.m_vector_of_bound_signatures.emplace_back(a, j, numeric_traits:: + // is_neg(a)? low_bound: !low_bound); + // } + // delete it; + // */ + // } + + void limit_j(unsigned j, const mpq& u, bool coeff_before_j_is_pos, bool is_low_bound, bool strict){ + m_bp.try_add_bound(u, j, is_low_bound, coeff_before_j_is_pos, m_row_or_term_index, strict); + } + + + void advance_u(unsigned j) { + if (m_column_of_u == -1) + m_column_of_u = j; + else + m_column_of_u = -2; + } + + void advance_l(unsigned j) { + if (m_column_of_l == -1) + m_column_of_l = j; + else + m_column_of_l = -2; + } + + void analyze_bound_on_var_on_coeff(int j, const mpq &a) { + switch (m_bp.get_column_type(j)) { + case column_type::low_bound: + if (numeric_traits::is_pos(a)) + advance_u(j); + else + advance_l(j); + break; + case column_type::upper_bound: + if(numeric_traits::is_neg(a)) + advance_u(j); + else + advance_l(j); + break; + case column_type::free_column: + advance_u(j); + advance_l(j); + break; + default: + break; + } + } + + static void analyze_row(linear_combination_iterator &it, + const numeric_pair& rs, + unsigned row_or_term_index, + bound_propagator & bp + ) { + bound_analyzer_on_row a(it, rs, row_or_term_index, bp); + a.analyze(); + } + +}; +} diff --git a/src/util/lp/bound_propagator.cpp b/src/util/lp/bound_propagator.cpp new file mode 100644 index 000000000..0d58ec2be --- /dev/null +++ b/src/util/lp/bound_propagator.cpp @@ -0,0 +1,47 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include "util/lp/lar_solver.h" +namespace lean { +bound_propagator::bound_propagator(lar_solver & ls): + m_lar_solver(ls) {} +column_type bound_propagator::get_column_type(unsigned j) const { + return m_lar_solver.m_mpq_lar_core_solver.m_column_types()[j]; +} +const impq & bound_propagator::get_low_bound(unsigned j) const { + return m_lar_solver.m_mpq_lar_core_solver.m_r_low_bounds()[j]; +} +const impq & bound_propagator::get_upper_bound(unsigned j) const { + return m_lar_solver.m_mpq_lar_core_solver.m_r_upper_bounds()[j]; +} +void bound_propagator::try_add_bound(const mpq & v, unsigned j, bool is_low, bool coeff_before_j_is_pos, unsigned row_or_term_index, bool strict) { + j = m_lar_solver.adjust_column_index_to_term_index(j); + lconstraint_kind kind = is_low? GE : LE; + if (strict) + kind = static_cast(kind / 2); + + if (!bound_is_interesting(j, kind, v)) + return; + unsigned k; // index to ibounds + if (is_low) { + if (try_get_val(m_improved_low_bounds, j, k)) { + auto & found_bound = m_ibounds[k]; + if (v > found_bound.m_bound || (v == found_bound.m_bound && found_bound.m_strict == false && strict)) + found_bound = implied_bound(v, j, is_low, coeff_before_j_is_pos, row_or_term_index, strict); + } else { + m_improved_low_bounds[j] = m_ibounds.size(); + m_ibounds.push_back(implied_bound(v, j, is_low, coeff_before_j_is_pos, row_or_term_index, strict)); + } + } else { // the upper bound case + if (try_get_val(m_improved_upper_bounds, j, k)) { + auto & found_bound = m_ibounds[k]; + if (v < found_bound.m_bound || (v == found_bound.m_bound && found_bound.m_strict == false && strict)) + found_bound = implied_bound(v, j, is_low, coeff_before_j_is_pos, row_or_term_index, strict); + } else { + m_improved_upper_bounds[j] = m_ibounds.size(); + m_ibounds.push_back(implied_bound(v, j, is_low, coeff_before_j_is_pos, row_or_term_index, strict)); + } + } +} +} diff --git a/src/util/lp/bound_propagator.h b/src/util/lp/bound_propagator.h new file mode 100644 index 000000000..92523d75f --- /dev/null +++ b/src/util/lp/bound_propagator.h @@ -0,0 +1,27 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#pragma once +#include "util/lp/lp_settings.h" +namespace lean { +class lar_solver; +class bound_propagator { + std::unordered_map m_improved_low_bounds; // these maps map a column index to the corresponding index in ibounds + std::unordered_map m_improved_upper_bounds; + lar_solver & m_lar_solver; +public: + vector m_ibounds; +public: + bound_propagator(lar_solver & ls); + column_type get_column_type(unsigned) const; + const impq & get_low_bound(unsigned) const; + const impq & get_upper_bound(unsigned) const; + void try_add_bound(const mpq & v, unsigned j, bool is_low, bool coeff_before_j_is_pos, unsigned row_or_term_index, bool strict); + virtual bool bound_is_interesting(unsigned vi, + lean::lconstraint_kind kind, + const rational & bval) {return true;} + unsigned number_of_found_bounds() const { return m_ibounds.size(); } + virtual void consume(mpq const& v, unsigned j) { std::cout << "doh\n"; } +}; +} diff --git a/src/util/lp/breakpoint.h b/src/util/lp/breakpoint.h new file mode 100644 index 000000000..e5454db0e --- /dev/null +++ b/src/util/lp/breakpoint.h @@ -0,0 +1,20 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ + +#pragma once + +namespace lean { +enum breakpoint_type { + low_break, upper_break, fixed_break +}; +template +struct breakpoint { + unsigned m_j; // the basic column + breakpoint_type m_type; + X m_delta; + breakpoint(){} + breakpoint(unsigned j, X delta, breakpoint_type type):m_j(j), m_type(type), m_delta(delta) {} +}; +} diff --git a/src/util/lp/column_info.h b/src/util/lp/column_info.h new file mode 100644 index 000000000..7e44ccf43 --- /dev/null +++ b/src/util/lp/column_info.h @@ -0,0 +1,235 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ + +#pragma once +#include "util/vector.h" +#include +#include +#include +#include "util/lp/lp_settings.h" +namespace lean { +inline bool is_valid(unsigned j) { return static_cast(j) >= 0;} + +template +class column_info { + std::string m_name; + bool m_low_bound_is_set = false; + bool m_low_bound_is_strict = false; + bool m_upper_bound_is_set = false; + bool m_upper_bound_is_strict = false; + T m_low_bound; + T m_upper_bound; + T m_cost = numeric_traits::zero(); + T m_fixed_value; + bool m_is_fixed = false; + unsigned m_column_index = static_cast(-1); +public: + bool operator==(const column_info & c) const { + return m_name == c.m_name && + m_low_bound_is_set == c.m_low_bound_is_set && + m_low_bound_is_strict == c.m_low_bound_is_strict && + m_upper_bound_is_set == c.m_upper_bound_is_set&& + m_upper_bound_is_strict == c.m_upper_bound_is_strict&& + (!m_low_bound_is_set || m_low_bound == c.m_low_bound) && + (!m_upper_bound_is_set || m_upper_bound == c.m_upper_bound) && + m_cost == c.m_cost&& + m_is_fixed == c.m_is_fixed && + (!m_is_fixed || m_fixed_value == c.m_fixed_value) && + m_column_index == c.m_column_index; + } + bool operator!=(const column_info & c) const { return !((*this) == c); } + void set_column_index(unsigned j) { + m_column_index = j; + } + // the default constructor + column_info() {} + + column_info(unsigned column_index) : m_column_index(column_index) { + } + + column_info(const column_info & ci) { + m_name = ci.m_name; + m_low_bound_is_set = ci.m_low_bound_is_set; + m_low_bound_is_strict = ci.m_low_bound_is_strict; + m_upper_bound_is_set = ci.m_upper_bound_is_set; + m_upper_bound_is_strict = ci.m_upper_bound_is_strict; + m_low_bound = ci.m_low_bound; + m_upper_bound = ci.m_upper_bound; + m_cost = ci.m_cost; + m_fixed_value = ci.m_fixed_value; + m_is_fixed = ci.m_is_fixed; + m_column_index = ci.m_column_index; + } + + unsigned get_column_index() const { + return m_column_index; + } + + column_type get_column_type() const { + return m_is_fixed? column_type::fixed : (m_low_bound_is_set? (m_upper_bound_is_set? column_type::boxed : column_type::low_bound) : (m_upper_bound_is_set? column_type::upper_bound: column_type::free_column)); + } + + column_type get_column_type_no_flipping() const { + if (m_is_fixed) { + return column_type::fixed; + } + + if (m_low_bound_is_set) { + return m_upper_bound_is_set? column_type::boxed: column_type::low_bound; + } + // we are flipping the bounds! + return m_upper_bound_is_set? column_type::upper_bound + : column_type::free_column; + } + + T get_low_bound() const { + lean_assert(m_low_bound_is_set); + return m_low_bound; + } + T get_upper_bound() const { + lean_assert(m_upper_bound_is_set); + return m_upper_bound; + } + + bool low_bound_is_set() const { + return m_low_bound_is_set; + } + + bool upper_bound_is_set() const { + return m_upper_bound_is_set; + } + + T get_shift() { + if (is_fixed()) { + return m_fixed_value; + } + if (is_flipped()){ + return m_upper_bound; + } + return m_low_bound_is_set? m_low_bound : numeric_traits::zero(); + } + + bool is_flipped() { + return m_upper_bound_is_set && !m_low_bound_is_set; + } + + bool adjusted_low_bound_is_set() { + return !is_flipped()? low_bound_is_set(): upper_bound_is_set(); + } + + bool adjusted_upper_bound_is_set() { + return !is_flipped()? upper_bound_is_set(): low_bound_is_set(); + } + + T get_adjusted_upper_bound() { + return get_upper_bound() - get_low_bound(); + } + + bool is_fixed() const { + return m_is_fixed; + } + + bool is_free() { + return !m_low_bound_is_set && !m_upper_bound_is_set; + } + + void set_fixed_value(T v) { + m_is_fixed = true; + m_fixed_value = v; + } + + T get_fixed_value() const { + lean_assert(m_is_fixed); + return m_fixed_value; + } + + T get_cost() const { + return m_cost; + } + + void set_cost(T const & cost) { + m_cost = cost; + } + + void set_name(std::string const & s) { + m_name = s; + } + + std::string get_name() const { + return m_name; + } + + void set_low_bound(T const & l) { + m_low_bound = l; + m_low_bound_is_set = true; + } + + void set_upper_bound(T const & l) { + m_upper_bound = l; + m_upper_bound_is_set = true; + } + + void unset_low_bound() { + m_low_bound_is_set = false; + } + + void unset_upper_bound() { + m_upper_bound_is_set = false; + } + + void unset_fixed() { + m_is_fixed = false; + } + + bool low_bound_holds(T v) { + return !low_bound_is_set() || v >= m_low_bound -T(0.0000001); + } + + bool upper_bound_holds(T v) { + return !upper_bound_is_set() || v <= m_upper_bound + T(0.000001); + } + + bool bounds_hold(T v) { + return low_bound_holds(v) && upper_bound_holds(v); + } + + bool adjusted_bounds_hold(T v) { + return adjusted_low_bound_holds(v) && adjusted_upper_bound_holds(v); + } + + bool adjusted_low_bound_holds(T v) { + return !adjusted_low_bound_is_set() || v >= -T(0.0000001); + } + + bool adjusted_upper_bound_holds(T v) { + return !adjusted_upper_bound_is_set() || v <= get_adjusted_upper_bound() + T(0.000001); + } + bool is_infeasible() { + if ((!upper_bound_is_set()) || (!low_bound_is_set())) + return false; + // ok, both bounds are set + bool at_least_one_is_strict = upper_bound_is_strict() || low_bound_is_strict(); + if (!at_least_one_is_strict) + return get_upper_bound() < get_low_bound(); + // at least on bound is strict + return get_upper_bound() <= get_low_bound(); // the equality is impossible + } + bool low_bound_is_strict() const { + return m_low_bound_is_strict; + } + + void set_low_bound_strict(bool val) { + m_low_bound_is_strict = val; + } + + bool upper_bound_is_strict() const { + return m_upper_bound_is_strict; + } + + void set_upper_bound_strict(bool val) { + m_upper_bound_is_strict = val; + } +}; +} diff --git a/src/util/lp/column_namer.h b/src/util/lp/column_namer.h new file mode 100644 index 000000000..1a10a5a23 --- /dev/null +++ b/src/util/lp/column_namer.h @@ -0,0 +1,82 @@ +#pragma once +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include +#include "util/lp/linear_combination_iterator.h" +namespace lean { +class column_namer { +public: + virtual std::string get_column_name(unsigned j) const = 0; + template + void print_linear_iterator(linear_combination_iterator* it, std::ostream & out) const { + vector> coeff; + T a; + unsigned i; + while (it->next(a, i)) { + coeff.emplace_back(a, i); + } + print_linear_combination_of_column_indices(coeff, out); + } + template + void print_linear_iterator_indices_only(linear_combination_iterator* it, std::ostream & out) const { + vector> coeff; + T a; + unsigned i; + while (it->next(a, i)) { + coeff.emplace_back(a, i); + } + print_linear_combination_of_column_indices_only(coeff, out); + } + + template + void print_linear_combination_of_column_indices_only(const vector> & coeffs, std::ostream & out) const { + bool first = true; + for (const auto & it : coeffs) { + auto val = it.first; + if (first) { + first = false; + } else { + if (numeric_traits::is_pos(val)) { + out << " + "; + } else { + out << " - "; + val = -val; + } + } + if (val == -numeric_traits::one()) + out << " - "; + else if (val != numeric_traits::one()) + out << T_to_string(val); + + out << "_" << it.second; + } + } + + template + void print_linear_combination_of_column_indices(const vector> & coeffs, std::ostream & out) const { + bool first = true; + for (const auto & it : coeffs) { + auto val = it.first; + if (first) { + first = false; + } else { + if (numeric_traits::is_pos(val)) { + out << " + "; + } else { + out << " - "; + val = -val; + } + } + if (val == -numeric_traits::one()) + out << " - "; + else if (val != numeric_traits::one()) + out << val; + + out << get_column_name(it.second); + } + } + +}; +} diff --git a/src/util/lp/core_solver_pretty_printer.h b/src/util/lp/core_solver_pretty_printer.h new file mode 100644 index 000000000..6dbeb28cf --- /dev/null +++ b/src/util/lp/core_solver_pretty_printer.h @@ -0,0 +1,118 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#pragma once +#include +#include +#include +#include "util/vector.h" +#include +#include "util/lp/lp_settings.h" +#include "util/lp/indexed_vector.h" +namespace lean { +template class lp_core_solver_base; // forward definition + +template +class core_solver_pretty_printer { + std::ostream & m_out; + template using vector = vector; + typedef std::string string; + lp_core_solver_base & m_core_solver; + vector m_column_widths; + vector> m_A; + vector> m_signs; + vector m_costs; + vector m_cost_signs; + vector m_lows; // low bounds + vector m_upps; // upper bounds + vector m_lows_signs; + vector m_upps_signs; + unsigned m_rs_width; + vector m_rs; + unsigned m_title_width; + std::string m_cost_title; + std::string m_basis_heading_title; + std::string m_x_title; + std::string m_low_bounds_title = "low"; + std::string m_upp_bounds_title = "upp"; + std::string m_exact_norm_title = "exact cn"; + std::string m_approx_norm_title = "approx cn"; + + + unsigned ncols() { return m_core_solver.m_A.column_count(); } + unsigned nrows() { return m_core_solver.m_A.row_count(); } + unsigned m_artificial_start = std::numeric_limits::max(); + indexed_vector m_w_buff; + indexed_vector m_ed_buff; + vector m_exact_column_norms; + +public: + core_solver_pretty_printer(lp_core_solver_base & core_solver, std::ostream & out); + + void init_costs(); + + ~core_solver_pretty_printer(); + void init_rs_width(); + + T current_column_norm(); + + void init_m_A_and_signs(); + + void init_column_widths(); + + void adjust_width_with_low_bound(unsigned column, unsigned & w); + void adjust_width_with_upper_bound(unsigned column, unsigned & w); + + void adjust_width_with_bounds(unsigned column, unsigned & w); + + void adjust_width_with_basis_heading(unsigned column, unsigned & w) { + w = std::max(w, (unsigned)T_to_string(m_core_solver.m_basis_heading[column]).size()); + } + + unsigned get_column_width(unsigned column); + + unsigned regular_cell_width(unsigned row, unsigned column, std::string name) { + return regular_cell_string(row, column, name).size(); + } + + std::string regular_cell_string(unsigned row, unsigned column, std::string name); + + + void set_coeff(vector& row, vector & row_signs, unsigned col, const T & t, string name); + + void print_x(); + + std::string get_low_bound_string(unsigned j); + + std::string get_upp_bound_string(unsigned j); + + + void print_lows(); + + void print_upps(); + + string get_exact_column_norm_string(unsigned col) { + return T_to_string(m_exact_column_norms[col]); + } + + void print_exact_norms(); + + void print_approx_norms(); + + void print(); + + void print_basis_heading(); + + void print_bottom_line() { + m_out << "----------------------" << std::endl; + } + + void print_cost(); + + void print_given_rows(vector & row, vector & signs, X rst); + + void print_row(unsigned i); + +}; +} diff --git a/src/util/lp/core_solver_pretty_printer.hpp b/src/util/lp/core_solver_pretty_printer.hpp new file mode 100644 index 000000000..aa963b220 --- /dev/null +++ b/src/util/lp/core_solver_pretty_printer.hpp @@ -0,0 +1,377 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include +#include +#include +#include "util/lp/lp_utils.h" +#include "util/lp/lp_core_solver_base.h" +#include "util/lp/core_solver_pretty_printer.h" +#include "util/lp/numeric_pair.h" +namespace lean { + + +template +core_solver_pretty_printer::core_solver_pretty_printer(lp_core_solver_base & core_solver, std::ostream & out): + m_out(out), + m_core_solver(core_solver), + m_A(core_solver.m_A.row_count(), vector(core_solver.m_A.column_count(), "")), + m_signs(core_solver.m_A.row_count(), vector(core_solver.m_A.column_count(), " ")), + m_costs(ncols(), ""), + m_cost_signs(ncols(), " "), + m_rs(ncols(), zero_of_type()), + m_w_buff(core_solver.m_w), + m_ed_buff(core_solver.m_ed) { + m_column_widths.resize(core_solver.m_A.column_count(), 0), + init_m_A_and_signs(); + init_costs(); + init_column_widths(); + init_rs_width(); + m_cost_title = "costs"; + m_basis_heading_title = "heading"; + m_x_title = "x*"; + m_title_width = static_cast(std::max(std::max(m_cost_title.size(), std::max(m_basis_heading_title.size(), m_x_title.size())), m_approx_norm_title.size())); +} + +template void core_solver_pretty_printer::init_costs() { + if (!m_core_solver.use_tableau()) { + vector local_y(m_core_solver.m_m()); + m_core_solver.solve_yB(local_y); + for (unsigned i = 0; i < ncols(); i++) { + if (m_core_solver.m_basis_heading[i] < 0) { + T t = m_core_solver.m_costs[i] - m_core_solver.m_A.dot_product_with_column(local_y, i); + set_coeff(m_costs, m_cost_signs, i, t, m_core_solver.column_name(i)); + } + } + } else { + for (unsigned i = 0; i < ncols(); i++) { + if (m_core_solver.m_basis_heading[i] < 0) { + set_coeff(m_costs, m_cost_signs, i, m_core_solver.m_d[i], m_core_solver.column_name(i)); + } + } + } +} + +template core_solver_pretty_printer::~core_solver_pretty_printer() { + m_core_solver.m_w = m_w_buff; + m_core_solver.m_ed = m_ed_buff; +} +template void core_solver_pretty_printer::init_rs_width() { + m_rs_width = static_cast(T_to_string(m_core_solver.get_cost()).size()); + for (unsigned i = 0; i < nrows(); i++) { + unsigned wt = static_cast(T_to_string(m_rs[i]).size()); + if (wt > m_rs_width) { + m_rs_width = wt; + } + } +} + +template T core_solver_pretty_printer::current_column_norm() { + T ret = zero_of_type(); + for (auto i : m_core_solver.m_ed.m_index) + ret += m_core_solver.m_ed[i] * m_core_solver.m_ed[i]; + return ret; +} + +template void core_solver_pretty_printer::init_m_A_and_signs() { + if (numeric_traits::precise() && m_core_solver.m_settings.use_tableau()) { + for (unsigned column = 0; column < ncols(); column++) { + vector t(nrows(), zero_of_type()); + for (const auto & c : m_core_solver.m_A.m_columns[column]){ + t[c.m_i] = m_core_solver.m_A.get_val(c); + } + + string name = m_core_solver.column_name(column); + for (unsigned row = 0; row < nrows(); row ++) { + set_coeff( + m_A[row], + m_signs[row], + column, + t[row], + name); + m_rs[row] += t[row] * m_core_solver.m_x[column]; + } + } + } else { + for (unsigned column = 0; column < ncols(); column++) { + m_core_solver.solve_Bd(column); // puts the result into m_core_solver.m_ed + string name = m_core_solver.column_name(column); + for (unsigned row = 0; row < nrows(); row ++) { + set_coeff( + m_A[row], + m_signs[row], + column, + m_core_solver.m_ed[row], + name); + m_rs[row] += m_core_solver.m_ed[row] * m_core_solver.m_x[column]; + } + if (!m_core_solver.use_tableau()) + m_exact_column_norms.push_back(current_column_norm() + T(1)); // a conversion missing 1 -> T + } + } +} + +template void core_solver_pretty_printer::init_column_widths() { + for (unsigned i = 0; i < ncols(); i++) { + m_column_widths[i] = get_column_width(i); + } +} + +template void core_solver_pretty_printer::adjust_width_with_low_bound(unsigned column, unsigned & w) { + if (!m_core_solver.low_bounds_are_set()) return; + w = std::max(w, (unsigned)T_to_string(m_core_solver.low_bound_value(column)).size()); +} +template void core_solver_pretty_printer::adjust_width_with_upper_bound(unsigned column, unsigned & w) { + w = std::max(w, (unsigned)T_to_string(m_core_solver.upper_bound_value(column)).size()); +} + +template void core_solver_pretty_printer::adjust_width_with_bounds(unsigned column, unsigned & w) { + switch (m_core_solver.get_column_type(column)) { + case column_type::fixed: + case column_type::boxed: + adjust_width_with_low_bound(column, w); + adjust_width_with_upper_bound(column, w); + break; + case column_type::low_bound: + adjust_width_with_low_bound(column, w); + break; + case column_type::upper_bound: + adjust_width_with_upper_bound(column, w); + break; + case column_type::free_column: + break; + default: + lean_assert(false); + break; + } +} + + +template unsigned core_solver_pretty_printer:: get_column_width(unsigned column) { + unsigned w = static_cast(std::max((size_t)m_costs[column].size(), T_to_string(m_core_solver.m_x[column]).size())); + adjust_width_with_bounds(column, w); + adjust_width_with_basis_heading(column, w); + for (unsigned i = 0; i < nrows(); i++) { + unsigned cellw = static_cast(m_A[i][column].size()); + if (cellw > w) { + w = cellw; + } + } + if (!m_core_solver.use_tableau()) { + w = std::max(w, (unsigned)T_to_string(m_exact_column_norms[column]).size()); + if (m_core_solver.m_column_norms.size() > 0) + w = std::max(w, (unsigned)T_to_string(m_core_solver.m_column_norms[column]).size()); + } + return w; +} + +template std::string core_solver_pretty_printer::regular_cell_string(unsigned row, unsigned /* column */, std::string name) { + T t = fabs(m_core_solver.m_ed[row]); + if ( t == 1) return name; + return T_to_string(t) + name; +} + + +template void core_solver_pretty_printer::set_coeff(vector& row, vector & row_signs, unsigned col, const T & t, string name) { + if (numeric_traits::is_zero(t)) { + return; + } + if (col > 0) { + if (t > 0) { + row_signs[col] = "+"; + row[col] = t != 1? T_to_string(t) + name : name; + } else { + row_signs[col] = "-"; + row[col] = t != -1? T_to_string(-t) + name: name; + } + } else { // col == 0 + if (t == -1) { + row[col] = "-" + name; + } else if (t == 1) { + row[col] = name; + } else { + row[col] = T_to_string(t) + name; + } + } +} + +template void core_solver_pretty_printer::print_x() { + if (ncols() == 0) { + return; + } + + int blanks = m_title_width + 1 - static_cast(m_x_title.size()); + m_out << m_x_title; + print_blanks(blanks, m_out); + + auto bh = m_core_solver.m_x; + for (unsigned i = 0; i < ncols(); i++) { + string s = T_to_string(bh[i]); + int blanks = m_column_widths[i] - static_cast(s.size()); + print_blanks(blanks, m_out); + m_out << s << " "; // the column interval + } + m_out << std::endl; +} + +template std::string core_solver_pretty_printer::get_low_bound_string(unsigned j) { + switch (m_core_solver.get_column_type(j)){ + case column_type::boxed: + case column_type::low_bound: + case column_type::fixed: + if (m_core_solver.low_bounds_are_set()) + return T_to_string(m_core_solver.low_bound_value(j)); + else + return std::string("0"); + break; + default: + return std::string(); + } +} + +template std::string core_solver_pretty_printer::get_upp_bound_string(unsigned j) { + switch (m_core_solver.get_column_type(j)){ + case column_type::boxed: + case column_type::upper_bound: + case column_type::fixed: + return T_to_string(m_core_solver.upper_bound_value(j)); + break; + default: + return std::string(); + } +} + + +template void core_solver_pretty_printer::print_lows() { + if (ncols() == 0) { + return; + } + int blanks = m_title_width + 1 - static_cast(m_low_bounds_title.size()); + m_out << m_low_bounds_title; + print_blanks(blanks, m_out); + + for (unsigned i = 0; i < ncols(); i++) { + string s = get_low_bound_string(i); + int blanks = m_column_widths[i] - static_cast(s.size()); + print_blanks(blanks, m_out); + m_out << s << " "; // the column interval + } + m_out << std::endl; +} + +template void core_solver_pretty_printer::print_upps() { + if (ncols() == 0) { + return; + } + int blanks = m_title_width + 1 - static_cast(m_upp_bounds_title.size()); + m_out << m_upp_bounds_title; + print_blanks(blanks, m_out); + + for (unsigned i = 0; i < ncols(); i++) { + string s = get_upp_bound_string(i); + int blanks = m_column_widths[i] - static_cast(s.size()); + print_blanks(blanks, m_out); + m_out << s << " "; // the column interval + } + m_out << std::endl; +} + +template void core_solver_pretty_printer::print_exact_norms() { + if (m_core_solver.use_tableau()) return; + int blanks = m_title_width + 1 - static_cast(m_exact_norm_title.size()); + m_out << m_exact_norm_title; + print_blanks(blanks, m_out); + for (unsigned i = 0; i < ncols(); i++) { + string s = get_exact_column_norm_string(i); + int blanks = m_column_widths[i] - static_cast(s.size()); + print_blanks(blanks, m_out); + m_out << s << " "; + } + m_out << std::endl; +} + +template void core_solver_pretty_printer::print_approx_norms() { + if (m_core_solver.use_tableau()) return; + int blanks = m_title_width + 1 - static_cast(m_approx_norm_title.size()); + m_out << m_approx_norm_title; + print_blanks(blanks, m_out); + for (unsigned i = 0; i < ncols(); i++) { + string s = T_to_string(m_core_solver.m_column_norms[i]); + int blanks = m_column_widths[i] - static_cast(s.size()); + print_blanks(blanks, m_out); + m_out << s << " "; + } + m_out << std::endl; +} + +template void core_solver_pretty_printer::print() { + for (unsigned i = 0; i < nrows(); i++) { + print_row(i); + } + print_bottom_line(); + print_cost(); + print_x(); + print_basis_heading(); + print_lows(); + print_upps(); + print_exact_norms(); + if (m_core_solver.m_column_norms.size() > 0) + print_approx_norms(); + m_out << std::endl; +} + +template void core_solver_pretty_printer::print_basis_heading() { + int blanks = m_title_width + 1 - static_cast(m_basis_heading_title.size()); + m_out << m_basis_heading_title; + print_blanks(blanks, m_out); + + if (ncols() == 0) { + return; + } + auto bh = m_core_solver.m_basis_heading; + for (unsigned i = 0; i < ncols(); i++) { + string s = T_to_string(bh[i]); + int blanks = m_column_widths[i] - static_cast(s.size()); + print_blanks(blanks, m_out); + m_out << s << " "; // the column interval + } + m_out << std::endl; +} + +template void core_solver_pretty_printer::print_cost() { + int blanks = m_title_width + 1 - static_cast(m_cost_title.size()); + m_out << m_cost_title; + print_blanks(blanks, m_out); + print_given_rows(m_costs, m_cost_signs, m_core_solver.get_cost()); +} + +template void core_solver_pretty_printer::print_given_rows(vector & row, vector & signs, X rst) { + for (unsigned col = 0; col < row.size(); col++) { + unsigned width = m_column_widths[col]; + string s = row[col]; + int number_of_blanks = width - static_cast(s.size()); + lean_assert(number_of_blanks >= 0); + print_blanks(number_of_blanks, m_out); + m_out << s << ' '; + if (col < row.size() - 1) { + m_out << signs[col + 1] << ' '; + } + } + m_out << '='; + + string rs = T_to_string(rst); + int nb = m_rs_width - static_cast(rs.size()); + lean_assert(nb >= 0); + print_blanks(nb + 1, m_out); + m_out << rs << std::endl; +} + +template void core_solver_pretty_printer::print_row(unsigned i){ + print_blanks(m_title_width + 1, m_out); + auto row = m_A[i]; + auto sign_row = m_signs[i]; + auto rs = m_rs[i]; + print_given_rows(row, sign_row, rs); +} +} diff --git a/src/util/lp/core_solver_pretty_printer_instances.cpp b/src/util/lp/core_solver_pretty_printer_instances.cpp new file mode 100644 index 000000000..cfa72f725 --- /dev/null +++ b/src/util/lp/core_solver_pretty_printer_instances.cpp @@ -0,0 +1,15 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include "util/lp/numeric_pair.h" +#include "util/lp/core_solver_pretty_printer.hpp" +template lean::core_solver_pretty_printer::core_solver_pretty_printer(lean::lp_core_solver_base &, std::ostream & out); +template void lean::core_solver_pretty_printer::print(); +template lean::core_solver_pretty_printer::~core_solver_pretty_printer(); +template lean::core_solver_pretty_printer::core_solver_pretty_printer(lean::lp_core_solver_base &, std::ostream & out); +template void lean::core_solver_pretty_printer::print(); +template lean::core_solver_pretty_printer::~core_solver_pretty_printer(); +template lean::core_solver_pretty_printer >::core_solver_pretty_printer(lean::lp_core_solver_base > &, std::ostream & out); +template lean::core_solver_pretty_printer >::~core_solver_pretty_printer(); +template void lean::core_solver_pretty_printer >::print(); diff --git a/src/util/lp/dense_matrix.h b/src/util/lp/dense_matrix.h new file mode 100644 index 000000000..233f74016 --- /dev/null +++ b/src/util/lp/dense_matrix.h @@ -0,0 +1,92 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#pragma once +#ifdef LEAN_DEBUG +#include "util/vector.h" +#include "util/lp/matrix.h" +namespace lean { +// used for debugging purposes only +template +class dense_matrix: public matrix { +public: + struct ref { + unsigned m_i; + dense_matrix & m_s; + ref(unsigned i, dense_matrix & s) :m_i(i * s.m_n), m_s(s){} + T & operator[] (unsigned j) { + return m_s.m_values[m_i + j]; + } + const T & operator[] (unsigned j) const { + return m_s.m_v[m_i + j]; + } + }; + ref operator[] (unsigned i) { + return ref(i, *this); + } + unsigned m_m; // number of rows + unsigned m_n; // number of const + vector m_values; + dense_matrix(unsigned m, unsigned n); + + dense_matrix operator*=(matrix const & a) { + lean_assert(column_count() == a.row_count()); + dense_matrix c(row_count(), a.column_count()); + for (unsigned i = 0; i < row_count(); i++) { + for (unsigned j = 0; j < a.column_count(); j++) { + T v = numeric_traits::zero(); + for (unsigned k = 0; k < a.column_count(); k++) { + v += get_elem(i, k) * a(k, j); + } + c.set_elem(i, j, v); + } + } + *this = c; + return *this; + } + + dense_matrix & operator=(matrix const & other); + + dense_matrix & operator=(dense_matrix const & other); + + dense_matrix(matrix const * other); + void apply_from_right(T * w); + + void apply_from_right(vector & w); + + T * apply_from_left_with_different_dims(vector & w); + void apply_from_left(vector & w , lp_settings & ) { apply_from_left(w); } + + void apply_from_left(vector & w); + + void apply_from_left(X * w, lp_settings & ); + + void apply_from_left_to_X(vector & w, lp_settings & ); + + virtual void set_number_of_rows(unsigned /*m*/) {} + virtual void set_number_of_columns(unsigned /*n*/) { } + + T get_elem(unsigned i, unsigned j) const { return m_values[i * m_n + j]; } + + unsigned row_count() const { return m_m; } + unsigned column_count() const { return m_n; } + + void set_elem(unsigned i, unsigned j, const T& val) { m_values[i * m_n + j] = val; } + + // This method pivots row i to row i0 by muliplying row i by + // alpha and adding it to row i0. + void pivot_row_to_row(unsigned i, const T& alpha, unsigned i0, + const double & pivot_epsilon); + + void swap_columns(unsigned a, unsigned b); + + void swap_rows(unsigned a, unsigned b); + + void multiply_row_by_constant(unsigned row, T & t); + +}; +template +dense_matrix operator* (matrix & a, matrix & b); +} +#endif diff --git a/src/util/lp/dense_matrix.hpp b/src/util/lp/dense_matrix.hpp new file mode 100644 index 000000000..e42d9e3a4 --- /dev/null +++ b/src/util/lp/dense_matrix.hpp @@ -0,0 +1,186 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include "util/lp/lp_settings.h" +#ifdef LEAN_DEBUG +#include "util/vector.h" +#include "util/lp/numeric_pair.h" +#include "util/lp/dense_matrix.h" +namespace lean { +template void print_vector(const vector & t, std::ostream & out); +template dense_matrix::dense_matrix(unsigned m, unsigned n) : m_m(m), m_n(n), m_values(m * n, numeric_traits::zero()) { +} + +template dense_matrix& +dense_matrix::operator=(matrix const & other){ + if ( this == & other) + return *this; + m_values = new T[m_m * m_n]; + for (unsigned i = 0; i < m_m; i ++) + for (unsigned j = 0; j < m_n; j++) + m_values[i * m_n + j] = other.get_elem(i, j); + return *this; +} + +template dense_matrix& +dense_matrix::operator=(dense_matrix const & other){ + if ( this == & other) + return *this; + m_m = other.m_m; + m_n = other.m_n; + m_values.resize(m_m * m_n); + for (unsigned i = 0; i < m_m; i ++) + for (unsigned j = 0; j < m_n; j++) + m_values[i * m_n + j] = other.get_elem(i, j); + return *this; +} + +template dense_matrix::dense_matrix(matrix const * other) : + m_m(other->row_count()), + m_n(other->column_count()) { + m_values.resize(m_m*m_n); + for (unsigned i = 0; i < m_m; i++) + for (unsigned j = 0; j < m_n; j++) + m_values[i * m_n + j] = other->get_elem(i, j); +} + +template void dense_matrix::apply_from_right(T * w) { + T * t = new T[m_m]; + for (int i = 0; i < m_m; i ++) { + T v = numeric_traits::zero(); + for (int j = 0; j < m_m; j++) { + v += w[j]* get_elem(j, i); + } + t[i] = v; + } + + for (int i = 0; i < m_m; i++) { + w[i] = t[i]; + } + delete [] t; +} + +template void dense_matrix::apply_from_right(vector & w) { + vector t(m_m, numeric_traits::zero()); + for (unsigned i = 0; i < m_m; i ++) { + auto & v = t[i]; + for (unsigned j = 0; j < m_m; j++) + v += w[j]* get_elem(j, i); + } + + for (unsigned i = 0; i < m_m; i++) + w[i] = t[i]; +} + +template T* dense_matrix:: +apply_from_left_with_different_dims(vector & w) { + T * t = new T[m_m]; + for (int i = 0; i < m_m; i ++) { + T v = numeric_traits::zero(); + for (int j = 0; j < m_n; j++) { + v += w[j]* get_elem(i, j); + } + t[i] = v; + } + + return t; +} + +template void dense_matrix::apply_from_left(vector & w) { + T * t = new T[m_m]; + for (unsigned i = 0; i < m_m; i ++) { + T v = numeric_traits::zero(); + for (unsigned j = 0; j < m_m; j++) { + v += w[j]* get_elem(i, j); + } + t[i] = v; + } + + for (unsigned i = 0; i < m_m; i ++) { + w[i] = t[i]; + } + delete [] t; +} + +template void dense_matrix::apply_from_left(X * w, lp_settings & ) { + T * t = new T[m_m]; + for (int i = 0; i < m_m; i ++) { + T v = numeric_traits::zero(); + for (int j = 0; j < m_m; j++) { + v += w[j]* get_elem(i, j); + } + t[i] = v; + } + + for (int i = 0; i < m_m; i ++) { + w[i] = t[i]; + } + delete [] t; +} + +template void dense_matrix::apply_from_left_to_X(vector & w, lp_settings & ) { + vector t(m_m); + for (int i = 0; i < m_m; i ++) { + X v = zero_of_type(); + for (int j = 0; j < m_m; j++) { + v += w[j]* get_elem(i, j); + } + t[i] = v; + } + + for (int i = 0; i < m_m; i ++) { + w[i] = t[i]; + } +} + +// This method pivots row i to row i0 by muliplying row i by +// alpha and adding it to row i0. +template void dense_matrix::pivot_row_to_row(unsigned i, const T& alpha, unsigned i0, + const double & pivot_epsilon) { + for (unsigned j = 0; j < m_n; j++) { + m_values[i0 * m_n + j] += m_values[i * m_n + j] * alpha; + if (fabs(m_values[i0 + m_n + j]) < pivot_epsilon) { + m_values[i0 + m_n + j] = numeric_traits::zero();; + } + } +} + +template void dense_matrix::swap_columns(unsigned a, unsigned b) { + for (unsigned i = 0; i < m_m; i++) { + T t = get_elem(i, a); + set_elem(i, a, get_elem(i, b)); + set_elem(i, b, t); + } +} + +template void dense_matrix::swap_rows(unsigned a, unsigned b) { + for (unsigned i = 0; i < m_n; i++) { + T t = get_elem(a, i); + set_elem(a, i, get_elem(b, i)); + set_elem(b, i, t); + } +} + +template void dense_matrix::multiply_row_by_constant(unsigned row, T & t) { + for (unsigned i = 0; i < m_n; i++) { + set_elem(row, i, t * get_elem(row, i)); + } +} + +template +dense_matrix operator* (matrix & a, matrix & b){ + lean_assert(a.column_count() == b.row_count()); + dense_matrix ret(a.row_count(), b.column_count()); + for (unsigned i = 0; i < ret.m_m; i++) + for (unsigned j = 0; j< ret.m_n; j++) { + T v = numeric_traits::zero(); + for (unsigned k = 0; k < a.column_count(); k ++){ + v += (a.get_elem(i, k) * b.get_elem(k, j)); + } + ret.set_elem(i, j, v); + } + return ret; +} +} +#endif diff --git a/src/util/lp/dense_matrix_instances.cpp b/src/util/lp/dense_matrix_instances.cpp new file mode 100644 index 000000000..1e931211d --- /dev/null +++ b/src/util/lp/dense_matrix_instances.cpp @@ -0,0 +1,24 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include "util/lp/lp_settings.h" +#include "util/lp/dense_matrix.hpp" +#ifdef LEAN_DEBUG +#include "util/vector.h" +template lean::dense_matrix lean::operator*(lean::matrix&, lean::matrix&); +template void lean::dense_matrix::apply_from_left(vector &); +template lean::dense_matrix::dense_matrix(lean::matrix const*); +template lean::dense_matrix::dense_matrix(unsigned int, unsigned int); +template lean::dense_matrix& lean::dense_matrix::operator=(lean::dense_matrix const&); +template lean::dense_matrix >::dense_matrix(lean::matrix > const*); +template void lean::dense_matrix >::apply_from_left(vector&); +template lean::dense_matrix lean::operator*(lean::matrix&, lean::matrix&); +template lean::dense_matrix & lean::dense_matrix::operator=(lean::dense_matrix const&); +template lean::dense_matrix >::dense_matrix(unsigned int, unsigned int); +template lean::dense_matrix >& lean::dense_matrix >::operator=(lean::dense_matrix > const&); +template lean::dense_matrix > lean::operator* >(lean::matrix >&, lean::matrix >&); +template void lean::dense_matrix >::apply_from_right( vector< lean::mpq> &); +template void lean::dense_matrix::apply_from_right(class vector &); +template void lean::dense_matrix::apply_from_left(vector&); +#endif diff --git a/src/util/lp/eta_matrix.h b/src/util/lp/eta_matrix.h new file mode 100644 index 000000000..51b015066 --- /dev/null +++ b/src/util/lp/eta_matrix.h @@ -0,0 +1,83 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ + +#pragma once +#include "util/vector.h" +#include "util/lp/tail_matrix.h" +#include "util/lp/permutation_matrix.h" +namespace lean { + +// This is the sum of a unit matrix and a one-column matrix +template +class eta_matrix + : public tail_matrix { +#ifdef LEAN_DEBUG + unsigned m_length; +#endif + unsigned m_column_index; +public: + sparse_vector m_column_vector; + T m_diagonal_element; +#ifdef LEAN_DEBUG + eta_matrix(unsigned column_index, unsigned length): +#else + eta_matrix(unsigned column_index): +#endif + +#ifdef LEAN_DEBUG + m_length(length), +#endif + m_column_index(column_index) {} + + bool is_dense() const { return false; } + + void print(std::ostream & out) { + print_matrix(*this, out); + } + + bool is_unit() { + return m_column_vector.size() == 0 && m_diagonal_element == 1; + } + + bool set_diagonal_element(T const & diagonal_element) { + m_diagonal_element = diagonal_element; + return !lp_settings::is_eps_small_general(diagonal_element, 1e-12); + } + + const T & get_diagonal_element() const { + return m_diagonal_element; + } + + void apply_from_left(vector & w, lp_settings & ); + + template + void apply_from_left_local(indexed_vector & w, lp_settings & settings); + + void apply_from_left_to_T(indexed_vector & w, lp_settings & settings) { + apply_from_left_local(w, settings); + } + + + void push_back(unsigned row_index, T val ) { + lean_assert(row_index != m_column_index); + m_column_vector.push_back(row_index, val); + } + + void apply_from_right(vector & w); + void apply_from_right(indexed_vector & w); + + T get_elem(unsigned i, unsigned j) const; +#ifdef LEAN_DEBUG + unsigned row_count() const { return m_length; } + unsigned column_count() const { return m_length; } + void set_number_of_rows(unsigned m) { m_length = m; } + void set_number_of_columns(unsigned n) { m_length = n; } +#endif + void divide_by_diagonal_element() { + m_column_vector.divide(m_diagonal_element); + } + void conjugate_by_permutation(permutation_matrix & p); +}; +} diff --git a/src/util/lp/eta_matrix.hpp b/src/util/lp/eta_matrix.hpp new file mode 100644 index 000000000..142a408d1 --- /dev/null +++ b/src/util/lp/eta_matrix.hpp @@ -0,0 +1,136 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ + +#pragma once +#include "util/vector.h" +#include "util/lp/eta_matrix.h" +namespace lean { + +// This is the sum of a unit matrix and a one-column matrix +template +void eta_matrix::apply_from_left(vector & w, lp_settings & ) { + auto & w_at_column_index = w[m_column_index]; + for (auto & it : m_column_vector.m_data) { + w[it.first] += w_at_column_index * it.second; + } + w_at_column_index /= m_diagonal_element; +} +template +template +void eta_matrix:: +apply_from_left_local(indexed_vector & w, lp_settings & settings) { + const L w_at_column_index = w[m_column_index]; + if (is_zero(w_at_column_index)) return; + + if (settings.abs_val_is_smaller_than_drop_tolerance(w[m_column_index] /= m_diagonal_element)) { + w[m_column_index] = zero_of_type(); + w.erase_from_index(m_column_index); + } + + for (auto & it : m_column_vector.m_data) { + unsigned i = it.first; + if (is_zero(w[i])) { + L v = w[i] = w_at_column_index * it.second; + if (settings.abs_val_is_smaller_than_drop_tolerance(v)) { + w[i] = zero_of_type(); + continue; + } + w.m_index.push_back(i); + } else { + L v = w[i] += w_at_column_index * it.second; + if (settings.abs_val_is_smaller_than_drop_tolerance(v)) { + w[i] = zero_of_type(); + w.erase_from_index(i); + } + } + } +} +template +void eta_matrix::apply_from_right(vector & w) { +#ifdef LEAN_DEBUG + // dense_matrix deb(*this); + // auto clone_w = clone_vector(w, get_number_of_rows()); + // deb.apply_from_right(clone_w); +#endif + T t = w[m_column_index] / m_diagonal_element; + for (auto & it : m_column_vector.m_data) { + t += w[it.first] * it.second; + } + w[m_column_index] = t; +#ifdef LEAN_DEBUG + // lean_assert(vectors_are_equal(clone_w, w, get_number_of_rows())); + // delete clone_w; +#endif +} +template +void eta_matrix::apply_from_right(indexed_vector & w) { + if (w.m_index.size() == 0) + return; +#ifdef LEAN_DEBUG + // vector wcopy(w.m_data); + // apply_from_right(wcopy); +#endif + T & t = w[m_column_index]; + t /= m_diagonal_element; + bool was_in_index = (!numeric_traits::is_zero(t)); + + for (auto & it : m_column_vector.m_data) { + t += w[it.first] * it.second; + } + + if (numeric_traits::precise() ) { + if (!numeric_traits::is_zero(t)) { + if (!was_in_index) + w.m_index.push_back(m_column_index); + } else { + if (was_in_index) + w.erase_from_index(m_column_index); + } + } else { + if (!lp_settings::is_eps_small_general(t, 1e-14)) { + if (!was_in_index) + w.m_index.push_back(m_column_index); + } else { + if (was_in_index) + w.erase_from_index(m_column_index); + t = zero_of_type(); + } + } + +#ifdef LEAN_DEBUG + // lean_assert(w.is_OK()); + // lean_assert(vectors_are_equal(wcopy, w.m_data)); +#endif +} +#ifdef LEAN_DEBUG +template +T eta_matrix::get_elem(unsigned i, unsigned j) const { + if (j == m_column_index){ + if (i == j) { + return 1 / m_diagonal_element; + } + return m_column_vector[i]; + } + + return i == j ? numeric_traits::one() : numeric_traits::zero(); +} +#endif +template +void eta_matrix::conjugate_by_permutation(permutation_matrix & p) { + // this = p * this * p(-1) +#ifdef LEAN_DEBUG + // auto rev = p.get_reverse(); + // auto deb = ((*this) * rev); + // deb = p * deb; +#endif + m_column_index = p.get_rev(m_column_index); + for (auto & pair : m_column_vector.m_data) { + pair.first = p.get_rev(pair.first); + } +#ifdef LEAN_DEBUG + // lean_assert(deb == *this); +#endif +} +} diff --git a/src/util/lp/eta_matrix_instances.cpp b/src/util/lp/eta_matrix_instances.cpp new file mode 100644 index 000000000..d57d43fed --- /dev/null +++ b/src/util/lp/eta_matrix_instances.cpp @@ -0,0 +1,28 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include +#include "util/vector.h" +#include "util/lp/numeric_pair.h" +#include "util/lp/eta_matrix.hpp" +#ifdef LEAN_DEBUG +template double lean::eta_matrix::get_elem(unsigned int, unsigned int) const; +template lean::mpq lean::eta_matrix::get_elem(unsigned int, unsigned int) const; +template lean::mpq lean::eta_matrix >::get_elem(unsigned int, unsigned int) const; +#endif +template void lean::eta_matrix::apply_from_left(vector&, lean::lp_settings&); +template void lean::eta_matrix::apply_from_right(vector&); +template void lean::eta_matrix::conjugate_by_permutation(lean::permutation_matrix&); +template void lean::eta_matrix::apply_from_left(vector&, lean::lp_settings&); +template void lean::eta_matrix::apply_from_right(vector&); +template void lean::eta_matrix::conjugate_by_permutation(lean::permutation_matrix&); +template void lean::eta_matrix >::apply_from_left(vector >&, lean::lp_settings&); +template void lean::eta_matrix >::apply_from_right(vector&); +template void lean::eta_matrix >::conjugate_by_permutation(lean::permutation_matrix >&); +template void lean::eta_matrix::apply_from_left_local(lean::indexed_vector&, lean::lp_settings&); +template void lean::eta_matrix::apply_from_left_local(lean::indexed_vector&, lean::lp_settings&); +template void lean::eta_matrix >::apply_from_left_local(lean::indexed_vector&, lean::lp_settings&); +template void lean::eta_matrix >::apply_from_right(lean::indexed_vector&); +template void lean::eta_matrix::apply_from_right(lean::indexed_vector&); +template void lean::eta_matrix::apply_from_right(lean::indexed_vector&); diff --git a/src/util/lp/hash_helper.h b/src/util/lp/hash_helper.h new file mode 100644 index 000000000..6fe31d5cd --- /dev/null +++ b/src/util/lp/hash_helper.h @@ -0,0 +1,39 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#pragma once +#include +#include +#include "util/numerics/mpq.h" +#ifdef __CLANG__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wmismatched-tags" +#endif +namespace std { +template<> +struct hash { + inline size_t operator()(const lean::mpq & v) const { + return v.hash(); + } +}; +} + +template +inline void hash_combine(std::size_t & seed, const T & v) { + seed ^= std::hash()(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); +} + +namespace std { +template struct hash> { + inline size_t operator()(const pair & v) const { + size_t seed = 0; + hash_combine(seed, v.first); + hash_combine(seed, v.second); + return seed; + } +}; +} +#ifdef __CLANG__ +#pragma clang diagnostic pop +#endif diff --git a/src/util/lp/implied_bound.h b/src/util/lp/implied_bound.h new file mode 100644 index 000000000..9583e3cd8 --- /dev/null +++ b/src/util/lp/implied_bound.h @@ -0,0 +1,42 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#pragma once +#include "util/lp/lp_settings.h" +#include "util/lp/lar_constraints.h" +namespace lean { +struct implied_bound { + mpq m_bound; + unsigned m_j; // the column for which the bound has been found + bool m_is_low_bound; + bool m_coeff_before_j_is_pos; + unsigned m_row_or_term_index; + bool m_strict; + + lconstraint_kind kind() const { + lconstraint_kind k = m_is_low_bound? GE : LE; + if (m_strict) + k = static_cast(k / 2); + return k; + } + bool operator==(const implied_bound & o) const { + return m_j == o.m_j && m_is_low_bound == o.m_is_low_bound && m_bound == o.m_bound && + m_coeff_before_j_is_pos == o.m_coeff_before_j_is_pos && + m_row_or_term_index == o.m_row_or_term_index && m_strict == o.m_strict; + } + implied_bound(){} + implied_bound(const mpq & a, + unsigned j, + bool low_bound, + bool coeff_before_j_is_pos, + unsigned row_or_term_index, + bool strict): + m_bound(a), + m_j(j), + m_is_low_bound(low_bound), + m_coeff_before_j_is_pos(coeff_before_j_is_pos), + m_row_or_term_index(row_or_term_index), + m_strict(strict) {} +}; +} diff --git a/src/util/lp/indexed_value.h b/src/util/lp/indexed_value.h new file mode 100644 index 000000000..7963dfdf9 --- /dev/null +++ b/src/util/lp/indexed_value.h @@ -0,0 +1,55 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#pragma once + +namespace lean { +template +class indexed_value { +public: + T m_value; + // the idea is that m_index for a row element gives its column, and for a column element its row + unsigned m_index; + // m_other point is the offset of the corresponding element in its vector : for a row element it point to the column element offset, + // for a column element it points to the row element offset + unsigned m_other; + indexed_value() {} + indexed_value(T v, unsigned i) : m_value(v), m_index(i) {} + indexed_value(T v, unsigned i, unsigned other) : + m_value(v), m_index(i), m_other(other) { + } + + indexed_value(const indexed_value & iv) { + m_value = iv.m_value; + m_index = iv.m_index; + m_other = iv.m_other; + } + + indexed_value & operator=(const indexed_value & right_side) { + m_value = right_side.m_value; + m_index = right_side.m_index; + m_other = right_side.m_other; + return *this; + } + + const T & value() const { + return m_value; + } + + void set_value(T val) { + m_value = val; + } +}; +#ifdef LEAN_DEBUG +template +bool check_vector_for_small_values(indexed_vector & w, lp_settings & settings) { + for (unsigned i : w.m_index) { + const X & v = w[i]; + if ((!is_zero(v)) && settings.abs_val_is_smaller_than_drop_tolerance(v)) + return false; + } + return true; +} +#endif +} diff --git a/src/util/lp/indexed_vector.h b/src/util/lp/indexed_vector.h new file mode 100644 index 000000000..6e6a6009b --- /dev/null +++ b/src/util/lp/indexed_vector.h @@ -0,0 +1,169 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ + +#pragma once +#include "util/vector.h" +#include "util/debug.h" +#include +#include +#include "util/lp/lp_utils.h" +#include "util/lp/lp_settings.h" +#include +namespace lean { + +template void print_vector(const vector & t, std::ostream & out); +template void print_vector(const buffer & t, std::ostream & out); +template void print_sparse_vector(const vector & t, std::ostream & out); + +void print_vector(const vector & t, std::ostream & out); +template +class indexed_vector { +public: + // m_index points to non-zero elements of m_data + vector m_data; + vector m_index; + indexed_vector(unsigned data_size) { + m_data.resize(data_size, numeric_traits::zero()); + } + + indexed_vector& operator=(const indexed_vector& y) { + for (unsigned i: m_index) + m_data[i] = zero_of_type(); + + m_index = y.m_index; + + m_data.resize(y.data_size()); + for (unsigned i : m_index) + m_data[i] = y[i]; + return *this; + } + + bool operator==(const indexed_vector& y) const { + std::unordered_set y_index; + for (unsigned i : y.m_index) + y_index.insert(i); + + std::unordered_set this_index; + for (unsigned i : m_index) + this_index.insert(i); + + for (unsigned i : y.m_index) { + if (this_index.find(i) == this_index.end()) + return false; + } + + for (unsigned i : m_index) { + if (y_index.find(i) == y_index.end()) + return false; + } + + return vectors_are_equal(m_data, m_data); + + } + + indexed_vector() {} + + void resize(unsigned data_size); + unsigned data_size() const { + return m_data.size(); + } + + unsigned size() { + return m_index.size(); + } + + void set_value(const T& value, unsigned index); + void set_value_as_in_dictionary(unsigned index) { + lean_assert(index < m_data.size()); + T & loc = m_data[index]; + if (is_zero(loc)) { + m_index.push_back(index); + loc = one_of_type(); // use as a characteristic function + } + } + + + void clear(); + void clear_all(); + const T& operator[] (unsigned i) const { + return m_data[i]; + } + + T& operator[] (unsigned i) { + return m_data[i]; + } + + void clean_up() { +#if 0==1 + for (unsigned k = 0; k < m_index.size(); k++) { + unsigned i = m_index[k]; + T & v = m_data[i]; + if (lp_settings::is_eps_small_general(v, 1e-14)) { + v = zero_of_type(); + m_index.erase(m_index.begin() + k--); + } + } +#endif + vector index_copy; + for (unsigned i : m_index) { + T & v = m_data[i]; + if (!lp_settings::is_eps_small_general(v, 1e-14)) { + index_copy.push_back(i); + } else if (!numeric_traits::is_zero(v)) { + v = zero_of_type(); + } + } + m_index = index_copy; + } + + + void erase_from_index(unsigned j); + + void add_value_at_index_with_drop_tolerance(unsigned j, const T& val_to_add) { + T & v = m_data[j]; + bool was_zero = is_zero(v); + v += val_to_add; + if (lp_settings::is_eps_small_general(v, 1e-14)) { + v = zero_of_type(); + if (!was_zero) { + erase_from_index(j); + } + } else { + if (was_zero) + m_index.push_back(j); + } + } + + void add_value_at_index(unsigned j, const T& val_to_add) { + T & v = m_data[j]; + bool was_zero = is_zero(v); + v += val_to_add; + if (is_zero(v)) { + if (!was_zero) + erase_from_index(j); + } else { + if (was_zero) + m_index.push_back(j); + } + } + + void restore_index_and_clean_from_data() { + m_index.resize(0); + for (unsigned i = 0; i < m_data.size(); i++) { + T & v = m_data[i]; + if (lp_settings::is_eps_small_general(v, 1e-14)) { + v = zero_of_type(); + } else { + m_index.push_back(i); + } + } + } + +#ifdef LEAN_DEBUG + bool is_OK() const; + void print(std::ostream & out); +#endif +}; +} diff --git a/src/util/lp/indexed_vector.hpp b/src/util/lp/indexed_vector.hpp new file mode 100644 index 000000000..64e329adc --- /dev/null +++ b/src/util/lp/indexed_vector.hpp @@ -0,0 +1,110 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include "util/vector.h" +#include "util/lp/indexed_vector.h" +#include "util/lp/lp_settings.h" +namespace lean { + +template +void print_vector(const vector & t, std::ostream & out) { + for (unsigned i = 0; i < t.size(); i++) + out << t[i] << " "; + out << std::endl; +} + + +template +void print_vector(const buffer & t, std::ostream & out) { + for (unsigned i = 0; i < t.size(); i++) + out << t[i] << " "; + out << std::endl; +} + +template +void print_sparse_vector(const vector & t, std::ostream & out) { + for (unsigned i = 0; i < t.size(); i++) { + if (is_zero(t[i]))continue; + out << "[" << i << "] = " << t[i] << ", "; + } + out << std::endl; +} + +void print_vector(const vector & t, std::ostream & out) { + for (unsigned i = 0; i < t.size(); i++) + out << t[i].get_double() << std::setprecision(3) << " "; + out << std::endl; +} + +template +void indexed_vector::resize(unsigned data_size) { + clear(); + m_data.resize(data_size, numeric_traits::zero()); + lean_assert(is_OK()); +} + +template +void indexed_vector::set_value(const T& value, unsigned index) { + m_data[index] = value; + lean_assert(std::find(m_index.begin(), m_index.end(), index) == m_index.end()); + m_index.push_back(index); +} + +template +void indexed_vector::clear() { + for (unsigned i : m_index) + m_data[i] = numeric_traits::zero(); + m_index.resize(0); +} +template +void indexed_vector::clear_all() { + unsigned i = m_data.size(); + while (i--) m_data[i] = numeric_traits::zero(); + m_index.resize(0); +} +template +void indexed_vector::erase_from_index(unsigned j) { + auto it = std::find(m_index.begin(), m_index.end(), j); + if (it != m_index.end()) + m_index.erase(it); +} + +#ifdef LEAN_DEBUG +template +bool indexed_vector::is_OK() const { + return true; + const double drop_eps = 1e-14; + for (unsigned i = 0; i < m_data.size(); i++) { + if (!is_zero(m_data[i]) && lp_settings::is_eps_small_general(m_data[i], drop_eps)) { + return false; + } + if (lp_settings::is_eps_small_general(m_data[i], drop_eps) != (std::find(m_index.begin(), m_index.end(), i) == m_index.end())) { + return false; + } + } + + std::unordered_set s; + for (unsigned i : m_index) { + //no duplicates!!! + if (s.find(i) != s.end()) + return false; + s.insert(i); + if (i >= m_data.size()) + return false; + } + + return true; +} +template +void indexed_vector::print(std::ostream & out) { + out << "m_index " << std::endl; + for (unsigned i = 0; i < m_index.size(); i++) { + out << m_index[i] << " "; + } + out << std::endl; + print_vector(m_data, out); +} +#endif + +} diff --git a/src/util/lp/indexed_vector_instances.cpp b/src/util/lp/indexed_vector_instances.cpp new file mode 100644 index 000000000..6f17a894f --- /dev/null +++ b/src/util/lp/indexed_vector_instances.cpp @@ -0,0 +1,36 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include "util/vector.h" +#include "util/lp/indexed_vector.hpp" +namespace lean { +template void indexed_vector::clear(); +template void indexed_vector::clear_all(); +template void indexed_vector::erase_from_index(unsigned int); +template void indexed_vector::set_value(const double&, unsigned int); +template void indexed_vector::clear(); +template void indexed_vector::clear(); +template void indexed_vector::clear_all(); +template void indexed_vector::erase_from_index(unsigned int); +template void indexed_vector::resize(unsigned int); +template void indexed_vector::resize(unsigned int); +template void indexed_vector::set_value(const mpq&, unsigned int); +template void indexed_vector::set_value(const unsigned&, unsigned int); +#ifdef LEAN_DEBUG +template bool indexed_vector::is_OK() const; +template bool indexed_vector::is_OK() const; +template bool indexed_vector >::is_OK() const; +template void lean::indexed_vector< lean::mpq>::print(std::basic_ostream > &); +template void lean::indexed_vector::print(std::basic_ostream > &); +template void lean::indexed_vector >::print(std::ostream&); +#endif +} +template void lean::print_vector(vector const&, std::ostream&); +template void lean::print_vector(vector const&, std::ostream&); +template void lean::print_vector(vector const&, std::ostream&); +template void lean::print_vector >(vector> const&, std::ostream&); +template void lean::indexed_vector::resize(unsigned int); +template void lean::print_vector< lean::mpq>(vector< lean::mpq> const &, std::basic_ostream > &); +template void lean::print_vector >(vector> const&, std::ostream&); +template void lean::indexed_vector >::erase_from_index(unsigned int); diff --git a/src/util/lp/int_set.h b/src/util/lp/int_set.h new file mode 100644 index 000000000..0619facd8 --- /dev/null +++ b/src/util/lp/int_set.h @@ -0,0 +1,66 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#pragma once +#include "util/vector.h" +#include "util/lp/indexed_vector.h" +#include +namespace lean { +// serves at a set of non-negative integers smaller than the set size +class int_set { + vector m_data; +public: + vector m_index; + int_set(unsigned size): m_data(size, -1) {} + int_set() {} + bool contains(unsigned j) const { + if (j >= m_data.size()) + return false; + return m_data[j] >= 0; + } + void insert(unsigned j) { + lean_assert(j < m_data.size()); + if (contains(j)) return; + m_data[j] = m_index.size(); + m_index.push_back(j); + } + void erase(unsigned j) { + if (!contains(j)) return; + unsigned pos_j = m_data[j]; + unsigned last_pos = m_index.size() - 1; + int last_j = m_index[last_pos]; + if (last_pos != pos_j) { + // move last to j spot + m_data[last_j] = pos_j; + m_index[pos_j] = last_j; + } + m_index.pop_back(); + m_data[j] = -1; + } + + void resize(unsigned size) { + m_data.resize(size, -1); + } + + void increase_size_by_one() { + resize(m_data.size() + 1); + } + + unsigned data_size() const { return m_data.size(); } + unsigned size() const { return m_index.size();} + bool is_empty() const { return size() == 0; } + void clear() { + for (unsigned j : m_index) + m_data[j] = -1; + m_index.resize(0); + } + void print(std::ostream & out ) const { + for (unsigned j : m_index) { + out << j << " "; + } + out << std::endl; + } + +}; +} diff --git a/src/util/lp/iterator_on_column.h b/src/util/lp/iterator_on_column.h new file mode 100644 index 000000000..5ad925d3e --- /dev/null +++ b/src/util/lp/iterator_on_column.h @@ -0,0 +1,50 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#pragma once +#include "util/lp/linear_combination_iterator.h" +#include "util/lp/static_matrix.h" +#include "util/lp/lar_term.h" +namespace lean { +template +struct iterator_on_column:linear_combination_iterator { + const vector& m_column; // the offset in term coeffs + const static_matrix & m_A; + int m_i = -1; // the initial offset in the column + unsigned size() const { return m_column.size(); } + iterator_on_column(const vector& column, const static_matrix & A) // the offset in term coeffs + : + m_column(column), + m_A(A), + m_i(-1) {} + + bool next(mpq & a, unsigned & i) { + if (++m_i >= static_cast(m_column.size())) + return false; + + const column_cell& c = m_column[m_i]; + a = m_A.get_val(c); + i = c.m_i; + return true; + } + + bool next(unsigned & i) { + if (++m_i >= static_cast(m_column.size())) + return false; + + const column_cell& c = m_column[m_i]; + i = c.m_i; + return true; + } + + void reset() { + m_i = -1; + } + + linear_combination_iterator * clone() { + iterator_on_column * r = new iterator_on_column(m_column, m_A); + return r; + } +}; +} diff --git a/src/util/lp/iterator_on_indexed_vector.h b/src/util/lp/iterator_on_indexed_vector.h new file mode 100644 index 000000000..c19c08e64 --- /dev/null +++ b/src/util/lp/iterator_on_indexed_vector.h @@ -0,0 +1,35 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#pragma once +#include "util/lp/linear_combination_iterator.h" +namespace lean { +template +struct iterator_on_indexed_vector:linear_combination_iterator { + const indexed_vector & m_v; + unsigned m_offset = 0; + iterator_on_indexed_vector(const indexed_vector & v) : m_v(v){} + unsigned size() const { return m_v.m_index.size(); } + bool next(T & a, unsigned & i) { + if (m_offset >= m_v.m_index.size()) + return false; + i = m_v.m_index[m_offset++]; + a = m_v.m_data[i]; + return true; + } + + bool next(unsigned & i) { + if (m_offset >= m_v.m_index.size()) + return false; + i = m_v.m_index[m_offset++]; + return true; + } + void reset() { + m_offset = 0; + } + linear_combination_iterator* clone() { + return new iterator_on_indexed_vector(m_v); + } +}; +} diff --git a/src/util/lp/iterator_on_pivot_row.h b/src/util/lp/iterator_on_pivot_row.h new file mode 100644 index 000000000..19c0c7df1 --- /dev/null +++ b/src/util/lp/iterator_on_pivot_row.h @@ -0,0 +1,42 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#pragma once +#include "util/lp/iterator_on_indexed_vector.h" +namespace lean { +template +struct iterator_on_pivot_row:linear_combination_iterator { + bool m_basis_returned = false; + const indexed_vector & m_v; + unsigned m_basis_j; + iterator_on_indexed_vector m_it; + unsigned size() const { return m_it.size(); } + iterator_on_pivot_row(const indexed_vector & v, unsigned basis_j) : m_v(v), m_basis_j(basis_j), m_it(v) {} + bool next(T & a, unsigned & i) { + if (m_basis_returned == false) { + m_basis_returned = true; + a = one_of_type(); + i = m_basis_j; + return true; + } + return m_it.next(a, i); + } + bool next(unsigned & i) { + if (m_basis_returned == false) { + m_basis_returned = true; + i = m_basis_j; + return true; + } + return m_it.next(i); + } + void reset() { + m_basis_returned = false; + m_it.reset(); + } + linear_combination_iterator * clone() { + iterator_on_pivot_row * r = new iterator_on_pivot_row(m_v, m_basis_j); + return r; + } +}; +} diff --git a/src/util/lp/iterator_on_row.h b/src/util/lp/iterator_on_row.h new file mode 100644 index 000000000..212768db9 --- /dev/null +++ b/src/util/lp/iterator_on_row.h @@ -0,0 +1,37 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#pragma once +#include "util/lp/linear_combination_iterator.h" +namespace lean { +template +struct iterator_on_row:linear_combination_iterator { + const vector> & m_row; + unsigned m_i= 0; // offset + iterator_on_row(const vector> & row) : m_row(row) + {} + unsigned size() const { return m_row.size(); } + bool next(T & a, unsigned & i) { + if (m_i == m_row.size()) + return false; + auto &c = m_row[m_i++]; + i = c.m_j; + a = c.get_val(); + return true; + } + bool next(unsigned & i) { + if (m_i == m_row.size()) + return false; + auto &c = m_row[m_i++]; + i = c.m_j; + return true; + } + void reset() { + m_i = 0; + } + linear_combination_iterator* clone() { + return new iterator_on_row(m_row); + } +}; +} diff --git a/src/util/lp/iterator_on_term_with_basis_var.h b/src/util/lp/iterator_on_term_with_basis_var.h new file mode 100644 index 000000000..9279dd637 --- /dev/null +++ b/src/util/lp/iterator_on_term_with_basis_var.h @@ -0,0 +1,56 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#pragma once +#include "util/lp/linear_combination_iterator.h" +#include "util/lp/numeric_pair.h" +#include "util/lp/lar_term.h" +namespace lean { +struct iterator_on_term_with_basis_var:linear_combination_iterator { + std::unordered_map::const_iterator m_i; // the offset in term coeffs + bool m_term_j_returned = false; + const lar_term & m_term; + unsigned m_term_j; + unsigned size() const {return static_cast(m_term.m_coeffs.size() + 1);} + iterator_on_term_with_basis_var(const lar_term & t, unsigned term_j) : + m_i(t.m_coeffs.begin()), + m_term(t), + m_term_j(term_j) {} + + bool next(mpq & a, unsigned & i) { + if (m_term_j_returned == false) { + m_term_j_returned = true; + a = - one_of_type(); + i = m_term_j; + return true; + } + if (m_i == m_term.m_coeffs.end()) + return false; + i = m_i->first; + a = m_i->second; + m_i++; + return true; + } + bool next(unsigned & i) { + if (m_term_j_returned == false) { + m_term_j_returned = true; + i = m_term_j; + return true; + } + if (m_i == m_term.m_coeffs.end()) + return false; + i = m_i->first; + m_i++; + return true; + } + void reset() { + m_term_j_returned = false; + m_i = m_term.m_coeffs.begin(); + } + linear_combination_iterator * clone() { + iterator_on_term_with_basis_var * r = new iterator_on_term_with_basis_var(m_term, m_term_j); + return r; + } +}; +} diff --git a/src/util/lp/lar_constraints.h b/src/util/lp/lar_constraints.h new file mode 100644 index 000000000..ee0864a4e --- /dev/null +++ b/src/util/lp/lar_constraints.h @@ -0,0 +1,86 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ + +#pragma once +#include "util/vector.h" +#include +#include +#include +#include +#include "util/lp/lp_utils.h" +#include "util/lp/ul_pair.h" +#include "util/lp/lar_term.h" +namespace lean { +inline lconstraint_kind flip_kind(lconstraint_kind t) { + return static_cast( - static_cast(t)); +} + +inline std::string lconstraint_kind_string(lconstraint_kind t) { + switch (t) { + case LE: return std::string("<="); + case LT: return std::string("<"); + case GE: return std::string(">="); + case GT: return std::string(">"); + case EQ: return std::string("="); + } + lean_unreachable(); + return std::string(); // it is unreachable +} + +class lar_base_constraint { +public: + lconstraint_kind m_kind; + mpq m_right_side; + virtual vector> get_left_side_coefficients() const = 0; + lar_base_constraint() {} + lar_base_constraint(lconstraint_kind kind, const mpq& right_side) :m_kind(kind), m_right_side(right_side) {} + + virtual unsigned size() const = 0; + virtual ~lar_base_constraint(){} + virtual mpq get_free_coeff_of_left_side() const { return zero_of_type();} +}; + +struct lar_var_constraint: public lar_base_constraint { + unsigned m_j; + vector> get_left_side_coefficients() const { + vector> ret; + ret.push_back(std::make_pair(one_of_type(), m_j)); + return ret; + } + unsigned size() const { return 1;} + lar_var_constraint(unsigned j, lconstraint_kind kind, const mpq& right_side) : lar_base_constraint(kind, right_side), m_j(j) { } +}; + + +struct lar_term_constraint: public lar_base_constraint { + const lar_term * m_term; + vector> get_left_side_coefficients() const { + return m_term->coeffs_as_vector(); + } + unsigned size() const { return m_term->size();} + lar_term_constraint(const lar_term *t, lconstraint_kind kind, const mpq& right_side) : lar_base_constraint(kind, right_side), m_term(t) { } + virtual mpq get_free_coeff_of_left_side() const { return m_term->m_v;} + +}; + + +class lar_constraint : public lar_base_constraint { +public: + vector> m_coeffs; + lar_constraint() {} + lar_constraint(const vector> & left_side, lconstraint_kind kind, const mpq & right_side) + : lar_base_constraint(kind, right_side), m_coeffs(left_side) {} + + lar_constraint(const lar_base_constraint & c) { + lean_assert(false); // should not be called : todo! + } + + unsigned size() const { + return static_cast(m_coeffs.size()); + } + + vector> get_left_side_coefficients() const { return m_coeffs; } +}; +} diff --git a/src/util/lp/lar_core_solver.h b/src/util/lp/lar_core_solver.h new file mode 100644 index 000000000..b3117a2d7 --- /dev/null +++ b/src/util/lp/lar_core_solver.h @@ -0,0 +1,802 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#pragma once +#include "util/vector.h" +#include +#include +#include "util/lp/lp_core_solver_base.h" +#include +#include "util/lp/indexed_vector.h" +#include "util/lp/binary_heap_priority_queue.h" +#include "util/lp/breakpoint.h" +#include "util/lp/stacked_unordered_set.h" +#include "util/lp/lp_primal_core_solver.h" +#include "util/lp/stacked_vector.h" +#include "util/lp/lar_solution_signature.h" +#include "util/lp/iterator_on_column.h" +#include "util/lp/iterator_on_indexed_vector.h" +#include "util/lp/stacked_value.h" +namespace lean { + +class lar_core_solver { + // m_sign_of_entering is set to 1 if the entering variable needs + // to grow and is set to -1 otherwise + int m_sign_of_entering_delta; + vector> m_infeasible_linear_combination; + int m_infeasible_sum_sign = 0; // todo: get rid of this field + vector> m_right_sides_dummy; + vector m_costs_dummy; + vector m_d_right_sides_dummy; + vector m_d_costs_dummy; +public: + stacked_value m_stacked_simplex_strategy; + stacked_vector m_column_types; + // r - solver fields, for rational numbers + vector> m_r_x; // the solution + stacked_vector> m_r_low_bounds; + stacked_vector> m_r_upper_bounds; + static_matrix> m_r_A; + stacked_vector m_r_pushed_basis; + vector m_r_basis; + vector m_r_nbasis; + vector m_r_heading; + stacked_vector m_r_columns_nz; + stacked_vector m_r_rows_nz; + + // d - solver fields, for doubles + vector m_d_x; // the solution in doubles + vector m_d_low_bounds; + vector m_d_upper_bounds; + static_matrix m_d_A; + stacked_vector m_d_pushed_basis; + vector m_d_basis; + vector m_d_nbasis; + vector m_d_heading; + + + lp_primal_core_solver> m_r_solver; // solver in rational numbers + + lp_primal_core_solver m_d_solver; // solver in doubles + + lar_core_solver( + lp_settings & settings, + const column_namer & column_names + ); + + lp_settings & settings() { return m_r_solver.m_settings;} + const lp_settings & settings() const { return m_r_solver.m_settings;} + + int get_infeasible_sum_sign() const { return m_infeasible_sum_sign; } + + const vector> & get_infeasibility_info(int & inf_sign) const { + inf_sign = m_infeasible_sum_sign; + return m_infeasible_linear_combination; + } + + void fill_not_improvable_zero_sum_from_inf_row(); + + column_type get_column_type(unsigned j) { return m_column_types[j];} + + void init_costs(bool first_time); + + void init_cost_for_column(unsigned j); + + // returns m_sign_of_alpha_r + int column_is_out_of_bounds(unsigned j); + + void calculate_pivot_row(unsigned i); + + void print_pivot_row(std::ostream & out, unsigned row_index) const { // remove later debug !!!! + for (unsigned j : m_r_solver.m_pivot_row.m_index) { + if (numeric_traits::is_pos(m_r_solver.m_pivot_row.m_data[j])) + out << "+"; + out << m_r_solver.m_pivot_row.m_data[j] << m_r_solver.column_name(j) << " "; + } + + out << " +" << m_r_solver.column_name(m_r_solver.m_basis[row_index]) << std::endl; + + for (unsigned j : m_r_solver.m_pivot_row.m_index) { + m_r_solver.print_column_bound_info(j, out); + } + m_r_solver.print_column_bound_info(m_r_solver.m_basis[row_index], out); + + } + + + void advance_on_sorted_breakpoints(unsigned entering); + + void change_slope_on_breakpoint(unsigned entering, breakpoint> * b, mpq & slope_at_entering); + + bool row_is_infeasible(unsigned row); + + bool row_is_evidence(unsigned row); + + bool find_evidence_row(); + + void prefix_r(); + + void prefix_d(); + + unsigned m_m() const { + return m_r_A.row_count(); + } + + unsigned m_n() const { + return m_r_A.column_count(); + } + + bool is_tiny() const { return this->m_m() < 10 && this->m_n() < 20; } + + bool is_empty() const { return this->m_m() == 0 && this->m_n() == 0; } + + template + int get_sign(const L & v) { + return v > zero_of_type() ? 1 : (v < zero_of_type() ? -1 : 0); + } + + + + void fill_evidence(unsigned row); + + + + void solve(); + + bool low_bounds_are_set() const { return true; } + + const indexed_vector & get_pivot_row() const { + return m_r_solver.m_pivot_row; + } + + void fill_not_improvable_zero_sum(); + + void pop_basis(unsigned k) { + if (!settings().use_tableau()) { + m_r_pushed_basis.pop(k); + m_r_basis = m_r_pushed_basis(); + m_r_solver.init_basis_heading_and_non_basic_columns_vector(); + m_d_pushed_basis.pop(k); + m_d_basis = m_d_pushed_basis(); + m_d_solver.init_basis_heading_and_non_basic_columns_vector(); + } else { + m_d_basis = m_r_basis; + m_d_nbasis = m_r_nbasis; + m_d_heading = m_r_heading; + } + } + + void push() { + lean_assert(m_r_solver.basis_heading_is_correct()); + lean_assert(!need_to_presolve_with_double_solver() || m_d_solver.basis_heading_is_correct()); + lean_assert(m_column_types.size() == m_r_A.column_count()); + m_stacked_simplex_strategy = settings().simplex_strategy(); + m_stacked_simplex_strategy.push(); + m_column_types.push(); + // rational + if (!settings().use_tableau()) + m_r_A.push(); + m_r_low_bounds.push(); + m_r_upper_bounds.push(); + if (!settings().use_tableau()) { + push_vector(m_r_pushed_basis, m_r_basis); + push_vector(m_r_columns_nz, m_r_solver.m_columns_nz); + push_vector(m_r_rows_nz, m_r_solver.m_rows_nz); + } + + m_d_A.push(); + if (!settings().use_tableau()) + push_vector(m_d_pushed_basis, m_d_basis); + } + + template + void push_vector(stacked_vector & pushed_vector, const vector & vector) { + lean_assert(pushed_vector.size() <= vector.size()); + for (unsigned i = 0; i < vector.size();i++) { + if (i == pushed_vector.size()) { + pushed_vector.push_back(vector[i]); + } else { + pushed_vector[i] = vector[i]; + } + } + pushed_vector.push(); + } + + void pop_markowitz_counts(unsigned k) { + m_r_columns_nz.pop(k); + m_r_rows_nz.pop(k); + m_r_solver.m_columns_nz.resize(m_r_columns_nz.size()); + m_r_solver.m_rows_nz.resize(m_r_rows_nz.size()); + for (unsigned i = 0; i < m_r_columns_nz.size(); i++) + m_r_solver.m_columns_nz[i] = m_r_columns_nz[i]; + for (unsigned i = 0; i < m_r_rows_nz.size(); i++) + m_r_solver.m_rows_nz[i] = m_r_rows_nz[i]; + } + + + void pop(unsigned k) { + m_stacked_simplex_strategy.pop(k); + bool use_tableau = m_stacked_simplex_strategy() != simplex_strategy_enum::no_tableau; + // rationals + if (!settings().use_tableau()) + m_r_A.pop(k); + m_r_low_bounds.pop(k); + m_r_upper_bounds.pop(k); + m_column_types.pop(k); + + if (m_r_solver.m_factorization != nullptr) { + delete m_r_solver.m_factorization; + m_r_solver.m_factorization = nullptr; + } + m_r_x.resize(m_r_A.column_count()); + m_r_solver.m_costs.resize(m_r_A.column_count()); + m_r_solver.m_d.resize(m_r_A.column_count()); + if(!use_tableau) + pop_markowitz_counts(k); + m_d_A.pop(k); + if (m_d_solver.m_factorization != nullptr) { + delete m_d_solver.m_factorization; + m_d_solver.m_factorization = nullptr; + } + + m_d_x.resize(m_d_A.column_count()); + pop_basis(k); + + lean_assert(m_r_solver.basis_heading_is_correct()); + lean_assert(!need_to_presolve_with_double_solver() || m_d_solver.basis_heading_is_correct()); + } + + bool need_to_presolve_with_double_solver() const { + return settings().presolve_with_double_solver_for_lar && !settings().use_tableau(); + } + + template + bool is_zero_vector(const vector & b) { + for (const L & m: b) + if (!is_zero(m)) return false; + return true; + } + + + bool update_xj_and_get_delta(unsigned j, non_basic_column_value_position pos_type, numeric_pair & delta) { + auto & x = m_r_x[j]; + switch (pos_type) { + case at_low_bound: + if (x == m_r_solver.m_low_bounds[j]) + return false; + delta = m_r_solver.m_low_bounds[j] - x; + m_r_solver.m_x[j] = m_r_solver.m_low_bounds[j]; + break; + case at_fixed: + case at_upper_bound: + if (x == m_r_solver.m_upper_bounds[j]) + return false; + delta = m_r_solver.m_upper_bounds[j] - x; + x = m_r_solver.m_upper_bounds[j]; + break; + case free_of_bounds: { + return false; + } + case not_at_bound: + switch (m_column_types[j]) { + case column_type::free_column: + return false; + case column_type::upper_bound: + delta = m_r_solver.m_upper_bounds[j] - x; + x = m_r_solver.m_upper_bounds[j]; + break; + case column_type::low_bound: + delta = m_r_solver.m_low_bounds[j] - x; + x = m_r_solver.m_low_bounds[j]; + break; + case column_type::boxed: + if (x > m_r_solver.m_upper_bounds[j]) { + delta = m_r_solver.m_upper_bounds[j] - x; + x += m_r_solver.m_upper_bounds[j]; + } else { + delta = m_r_solver.m_low_bounds[j] - x; + x = m_r_solver.m_low_bounds[j]; + } + break; + case column_type::fixed: + delta = m_r_solver.m_low_bounds[j] - x; + x = m_r_solver.m_low_bounds[j]; + break; + + default: + lean_assert(false); + } + break; + default: + lean_unreachable(); + } + m_r_solver.remove_column_from_inf_set(j); + return true; + } + + + + void prepare_solver_x_with_signature_tableau(const lar_solution_signature & signature) { + lean_assert(m_r_solver.inf_set_is_correct()); + for (auto &t : signature) { + unsigned j = t.first; + if (m_r_heading[j] >= 0) + continue; + auto pos_type = t.second; + numeric_pair delta; + if (!update_xj_and_get_delta(j, pos_type, delta)) + continue; + for (const auto & cc : m_r_solver.m_A.m_columns[j]){ + unsigned i = cc.m_i; + unsigned jb = m_r_solver.m_basis[i]; + m_r_solver.m_x[jb] -= delta * m_r_solver.m_A.get_val(cc); + m_r_solver.update_column_in_inf_set(jb); + } + lean_assert(m_r_solver.A_mult_x_is_off() == false); + } + lean_assert(m_r_solver.inf_set_is_correct()); + } + + + template + void prepare_solver_x_with_signature(const lar_solution_signature & signature, lp_primal_core_solver & s) { + for (auto &t : signature) { + unsigned j = t.first; + lean_assert(m_r_heading[j] < 0); + auto pos_type = t.second; + switch (pos_type) { + case at_low_bound: + s.m_x[j] = s.m_low_bounds[j]; + break; + case at_fixed: + case at_upper_bound: + s.m_x[j] = s.m_upper_bounds[j]; + break; + case free_of_bounds: { + s.m_x[j] = zero_of_type(); + continue; + } + case not_at_bound: + switch (m_column_types[j]) { + case column_type::free_column: + lean_assert(false); // unreachable + case column_type::upper_bound: + s.m_x[j] = s.m_upper_bounds[j]; + break; + case column_type::low_bound: + s.m_x[j] = s.m_low_bounds[j]; + break; + case column_type::boxed: + if (my_random() % 2) { + s.m_x[j] = s.m_low_bounds[j]; + } else { + s.m_x[j] = s.m_upper_bounds[j]; + } + break; + case column_type::fixed: + s.m_x[j] = s.m_low_bounds[j]; + break; + default: + lean_assert(false); + } + break; + default: + lean_unreachable(); + } + } + + lean_assert(is_zero_vector(s.m_b)); + s.solve_Ax_eq_b(); + } + + template + void catch_up_in_lu_in_reverse(const vector & trace_of_basis_change, lp_primal_core_solver & cs) { + // recover the previous working basis + for (unsigned i = trace_of_basis_change.size(); i > 0; i-= 2) { + unsigned entering = trace_of_basis_change[i-1]; + unsigned leaving = trace_of_basis_change[i-2]; + cs.change_basis_unconditionally(entering, leaving); + } + cs.init_lu(); + } + + //basis_heading is the basis heading of the solver owning trace_of_basis_change + // here we compact the trace as we go to avoid unnecessary column changes + template + void catch_up_in_lu(const vector & trace_of_basis_change, const vector & basis_heading, lp_primal_core_solver & cs) { + if (cs.m_factorization == nullptr || cs.m_factorization->m_refactor_counter + trace_of_basis_change.size()/2 >= 200) { + for (unsigned i = 0; i < trace_of_basis_change.size(); i+= 2) { + unsigned entering = trace_of_basis_change[i]; + unsigned leaving = trace_of_basis_change[i+1]; + cs.change_basis_unconditionally(entering, leaving); + } + if (cs.m_factorization != nullptr) + delete cs.m_factorization; + cs.m_factorization = nullptr; + } else { + indexed_vector w(cs.m_A.row_count()); + // the queues of delayed indices + std::queue entr_q, leav_q; + auto * l = cs.m_factorization; + lean_assert(l->get_status() == LU_status::OK); + for (unsigned i = 0; i < trace_of_basis_change.size(); i+= 2) { + unsigned entering = trace_of_basis_change[i]; + unsigned leaving = trace_of_basis_change[i+1]; + bool good_e = basis_heading[entering] >= 0 && cs.m_basis_heading[entering] < 0; + bool good_l = basis_heading[leaving] < 0 && cs.m_basis_heading[leaving] >= 0; + if (!good_e && !good_l) continue; + if (good_e && !good_l) { + while (!leav_q.empty() && cs.m_basis_heading[leav_q.front()] < 0) + leav_q.pop(); + if (!leav_q.empty()) { + leaving = leav_q.front(); + leav_q.pop(); + } else { + entr_q.push(entering); + continue; + } + } else if (!good_e && good_l) { + while (!entr_q.empty() && cs.m_basis_heading[entr_q.front()] >= 0) + entr_q.pop(); + if (!entr_q.empty()) { + entering = entr_q.front(); + entr_q.pop(); + } else { + leav_q.push(leaving); + continue; + } + } + lean_assert(cs.m_basis_heading[entering] < 0); + lean_assert(cs.m_basis_heading[leaving] >= 0); + if (l->get_status() == LU_status::OK) { + l->prepare_entering(entering, w); // to init vector w + l->replace_column(zero_of_type(), w, cs.m_basis_heading[leaving]); + } + cs.change_basis_unconditionally(entering, leaving); + } + if (l->get_status() != LU_status::OK) { + delete l; + cs.m_factorization = nullptr; + } + } + if (cs.m_factorization == nullptr) { + if (numeric_traits::precise()) + init_factorization(cs.m_factorization, cs.m_A, cs.m_basis, settings()); + } + } + + bool no_r_lu() const { + return m_r_solver.m_factorization == nullptr || m_r_solver.m_factorization->get_status() == LU_status::Degenerated; + } + + void solve_on_signature_tableau(const lar_solution_signature & signature, const vector & changes_of_basis) { + r_basis_is_OK(); + lean_assert(settings().use_tableau()); + bool r = catch_up_in_lu_tableau(changes_of_basis, m_d_solver.m_basis_heading); + + if (!r) { // it is the case where m_d_solver gives a degenerated basis + prepare_solver_x_with_signature_tableau(signature); // still are going to use the signature partially + m_r_solver.find_feasible_solution(); + m_d_basis = m_r_basis; + m_d_heading = m_r_heading; + m_d_nbasis = m_r_nbasis; + delete m_d_solver.m_factorization; + m_d_solver.m_factorization = nullptr; + } else { + prepare_solver_x_with_signature_tableau(signature); + m_r_solver.start_tracing_basis_changes(); + m_r_solver.find_feasible_solution(); + if (settings().get_cancel_flag()) + return; + m_r_solver.stop_tracing_basis_changes(); + // and now catch up in the double solver + lean_assert(m_r_solver.total_iterations() >= m_r_solver.m_trace_of_basis_change_vector.size() /2); + catch_up_in_lu(m_r_solver.m_trace_of_basis_change_vector, m_r_solver.m_basis_heading, m_d_solver); + } + lean_assert(r_basis_is_OK()); + } + + bool adjust_x_of_column(unsigned j) { + /* + if (m_r_solver.m_basis_heading[j] >= 0) { + return false; + } + + if (m_r_solver.column_is_feasible(j)) { + return false; + } + + m_r_solver.snap_column_to_bound_tableau(j); + lean_assert(m_r_solver.column_is_feasible(j)); + m_r_solver.m_inf_set.erase(j); + */ + lean_assert(false); + return true; + } + + + bool catch_up_in_lu_tableau(const vector & trace_of_basis_change, const vector & basis_heading) { + lean_assert(r_basis_is_OK()); + // the queues of delayed indices + std::queue entr_q, leav_q; + for (unsigned i = 0; i < trace_of_basis_change.size(); i+= 2) { + unsigned entering = trace_of_basis_change[i]; + unsigned leaving = trace_of_basis_change[i+1]; + bool good_e = basis_heading[entering] >= 0 && m_r_solver.m_basis_heading[entering] < 0; + bool good_l = basis_heading[leaving] < 0 && m_r_solver.m_basis_heading[leaving] >= 0; + if (!good_e && !good_l) continue; + if (good_e && !good_l) { + while (!leav_q.empty() && m_r_solver.m_basis_heading[leav_q.front()] < 0) + leav_q.pop(); + if (!leav_q.empty()) { + leaving = leav_q.front(); + leav_q.pop(); + } else { + entr_q.push(entering); + continue; + } + } else if (!good_e && good_l) { + while (!entr_q.empty() && m_r_solver.m_basis_heading[entr_q.front()] >= 0) + entr_q.pop(); + if (!entr_q.empty()) { + entering = entr_q.front(); + entr_q.pop(); + } else { + leav_q.push(leaving); + continue; + } + } + lean_assert(m_r_solver.m_basis_heading[entering] < 0); + lean_assert(m_r_solver.m_basis_heading[leaving] >= 0); + m_r_solver.change_basis_unconditionally(entering, leaving); + if(!m_r_solver.pivot_column_tableau(entering, m_r_solver.m_basis_heading[entering])) { + // unroll the last step + m_r_solver.change_basis_unconditionally(leaving, entering); +#ifdef LEAN_DEBUG + bool t = +#endif + m_r_solver.pivot_column_tableau(leaving, m_r_solver.m_basis_heading[leaving]); +#ifdef LEAN_DEBUG + lean_assert(t); +#endif + return false; + } + } + lean_assert(r_basis_is_OK()); + return true; + } + + + bool r_basis_is_OK() const { +#ifdef LEAN_DEBUG + if (!m_r_solver.m_settings.use_tableau()) + return true; + for (unsigned j : m_r_solver.m_basis) { + lean_assert(m_r_solver.m_A.m_columns[j].size() == 1); + lean_assert(m_r_solver.m_A.get_val(m_r_solver.m_A.m_columns[j][0]) == one_of_type()); + } + for (unsigned j =0; j < m_r_solver.m_basis_heading.size(); j++) { + if (m_r_solver.m_basis_heading[j] >= 0) continue; + if (m_r_solver.m_column_types[j] == column_type::fixed) continue; + lean_assert(static_cast(- m_r_solver.m_basis_heading[j] - 1) < m_r_solver.m_column_types.size()); + lean_assert( m_r_solver.m_basis_heading[j] <= -1); + } +#endif + return true; + } + + void solve_on_signature(const lar_solution_signature & signature, const vector & changes_of_basis) { + lean_assert(!settings().use_tableau()); + if (m_r_solver.m_factorization == nullptr) { + for (unsigned j = 0; j < changes_of_basis.size(); j+=2) { + unsigned entering = changes_of_basis[j]; + unsigned leaving = changes_of_basis[j + 1]; + m_r_solver.change_basis_unconditionally(entering, leaving); + } + init_factorization(m_r_solver.m_factorization, m_r_A, m_r_basis, settings()); + } else { + catch_up_in_lu(changes_of_basis, m_d_solver.m_basis_heading, m_r_solver); + } + + if (no_r_lu()) { // it is the case where m_d_solver gives a degenerated basis, we need to roll back + std::cout << "no_r_lu" << std::endl; + catch_up_in_lu_in_reverse(changes_of_basis, m_r_solver); + m_r_solver.find_feasible_solution(); + m_d_basis = m_r_basis; + m_d_heading = m_r_heading; + m_d_nbasis = m_r_nbasis; + delete m_d_solver.m_factorization; + m_d_solver.m_factorization = nullptr; + } else { + prepare_solver_x_with_signature(signature, m_r_solver); + m_r_solver.start_tracing_basis_changes(); + m_r_solver.find_feasible_solution(); + if (settings().get_cancel_flag()) + return; + m_r_solver.stop_tracing_basis_changes(); + // and now catch up in the double solver + lean_assert(m_r_solver.total_iterations() >= m_r_solver.m_trace_of_basis_change_vector.size() /2); + catch_up_in_lu(m_r_solver.m_trace_of_basis_change_vector, m_r_solver.m_basis_heading, m_d_solver); + } + } + + void create_double_matrix(static_matrix & A) { + for (unsigned i = 0; i < m_r_A.row_count(); i++) { + auto & row = m_r_A.m_rows[i]; + for (row_cell & c : row) { + A.set(i, c.m_j, c.get_val().get_double()); + } + } + } + + void fill_basis_d( + vector& basis_d, + vector& heading_d, + vector& nbasis_d){ + basis_d = m_r_basis; + heading_d = m_r_heading; + nbasis_d = m_r_nbasis; + } + + template + void extract_signature_from_lp_core_solver(const lp_primal_core_solver & solver, lar_solution_signature & signature) { + signature.clear(); + lean_assert(signature.size() == 0); + for (unsigned j = 0; j < solver.m_basis_heading.size(); j++) { + if (solver.m_basis_heading[j] < 0) { + signature[j] = solver.get_non_basic_column_value_position(j); + } + } + } + + void get_bounds_for_double_solver() { + unsigned n = m_n(); + m_d_low_bounds.resize(n); + m_d_upper_bounds.resize(n); + double delta = find_delta_for_strict_boxed_bounds().get_double(); + if (delta > 0.000001) + delta = 0.000001; + for (unsigned j = 0; j < n; j++) { + if (low_bound_is_set(j)) { + const auto & lb = m_r_solver.m_low_bounds[j]; + m_d_low_bounds[j] = lb.x.get_double() + delta * lb.y.get_double(); + } + if (upper_bound_is_set(j)) { + const auto & ub = m_r_solver.m_upper_bounds[j]; + m_d_upper_bounds[j] = ub.x.get_double() + delta * ub.y.get_double(); + lean_assert(!low_bound_is_set(j) || (m_d_upper_bounds[j] >= m_d_low_bounds[j])); + } + } + } + + void scale_problem_for_doubles( + static_matrix& A, + vector & low_bounds, + vector & upper_bounds) { + vector column_scale_vector; + vector right_side_vector(A.column_count()); + settings().reps_in_scaler = 5; + scaler scaler(right_side_vector, + A, + settings().scaling_minimum, + settings().scaling_maximum, + column_scale_vector, + settings()); + if (! scaler.scale()) { + // the scale did not succeed, unscaling + A.clear(); + create_double_matrix(A); + } else { + for (unsigned j = 0; j < A.column_count(); j++) { + if (m_r_solver.column_has_upper_bound(j)) { + upper_bounds[j] /= column_scale_vector[j]; + } + if (m_r_solver.column_has_low_bound(j)) { + low_bounds[j] /= column_scale_vector[j]; + } + } + } + + } + // returns the trace of basis changes + vector find_solution_signature_with_doubles(lar_solution_signature & signature) { + if (m_d_solver.m_factorization == nullptr || m_d_solver.m_factorization->get_status() != LU_status::OK) { + vector ret; + return ret; + } + get_bounds_for_double_solver(); + + extract_signature_from_lp_core_solver(m_r_solver, signature); + prepare_solver_x_with_signature(signature, m_d_solver); + m_d_solver.start_tracing_basis_changes(); + m_d_solver.find_feasible_solution(); + if (settings().get_cancel_flag()) + return vector(); + + m_d_solver.stop_tracing_basis_changes(); + extract_signature_from_lp_core_solver(m_d_solver, signature); + return m_d_solver.m_trace_of_basis_change_vector; + } + + + bool low_bound_is_set(unsigned j) const { + switch (m_column_types[j]) { + case column_type::free_column: + case column_type::upper_bound: + return false; + case column_type::low_bound: + case column_type::boxed: + case column_type::fixed: + return true; + default: + lean_assert(false); + } + return false; + } + + bool upper_bound_is_set(unsigned j) const { + switch (m_column_types[j]) { + case column_type::free_column: + case column_type::low_bound: + return false; + case column_type::upper_bound: + case column_type::boxed: + case column_type::fixed: + return true; + default: + lean_assert(false); + } + return false; + } + + void update_delta(mpq& delta, numeric_pair const& l, numeric_pair const& u) const { + lean_assert(l <= u); + if (l.x < u.x && l.y > u.y) { + mpq delta1 = (u.x - l.x) / (l.y - u.y); + if (delta1 < delta) { + delta = delta1; + } + } + lean_assert(l.x + delta * l.y <= u.x + delta * u.y); + } + + + mpq find_delta_for_strict_boxed_bounds() const{ + mpq delta = numeric_traits::one(); + for (unsigned j = 0; j < m_r_A.column_count(); j++ ) { + if (m_column_types()[j] != column_type::boxed) + continue; + update_delta(delta, m_r_low_bounds[j], m_r_upper_bounds[j]); + + } + return delta; + } + + + mpq find_delta_for_strict_bounds() const{ + mpq delta = numeric_traits::one(); + for (unsigned j = 0; j < m_r_A.column_count(); j++ ) { + if (low_bound_is_set(j)) + update_delta(delta, m_r_low_bounds[j], m_r_x[j]); + if (upper_bound_is_set(j)) + update_delta(delta, m_r_x[j], m_r_upper_bounds[j]); + } + return delta; + } + + void init_column_row_nz_for_r_solver() { + m_r_solver.init_column_row_non_zeroes(); + } + + linear_combination_iterator * get_column_iterator(unsigned j) { + if (settings().use_tableau()) { + return new iterator_on_column>(m_r_solver.m_A.m_columns[j], m_r_solver.m_A); + } else { + m_r_solver.solve_Bd(j); + return new iterator_on_indexed_vector(m_r_solver.m_ed); + } + } + +}; +} diff --git a/src/util/lp/lar_core_solver.hpp b/src/util/lp/lar_core_solver.hpp new file mode 100644 index 000000000..ded6762c5 --- /dev/null +++ b/src/util/lp/lar_core_solver.hpp @@ -0,0 +1,292 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include +#include "util/vector.h" +#include "util/lp/lar_core_solver.h" +#include "util/lp/lar_solution_signature.h" +namespace lean { +lar_core_solver::lar_core_solver( + lp_settings & settings, + const column_namer & column_names +): + m_r_solver(m_r_A, + m_right_sides_dummy, + m_r_x, + m_r_basis, + m_r_nbasis, + m_r_heading, + m_costs_dummy, + m_column_types(), + m_r_low_bounds(), + m_r_upper_bounds(), + settings, + column_names), + m_d_solver(m_d_A, + m_d_right_sides_dummy, + m_d_x, + m_d_basis, + m_d_nbasis, + m_d_heading, + m_d_costs_dummy, + m_column_types(), + m_d_low_bounds, + m_d_upper_bounds, + settings, + column_names){} + +void lar_core_solver::init_costs(bool first_time) { + lean_assert(false); // should not be called + // lean_assert(this->m_x.size() >= this->m_n()); + // lean_assert(this->m_column_types.size() >= this->m_n()); + // if (first_time) + // this->m_costs.resize(this->m_n()); + // X inf = this->m_infeasibility; + // this->m_infeasibility = zero_of_type(); + // for (unsigned j = this->m_n(); j--;) + // init_cost_for_column(j); + // if (!(first_time || inf >= this->m_infeasibility)) { + // LP_OUT(this->m_settings, "iter = " << this->total_iterations() << std::endl); + // LP_OUT(this->m_settings, "inf was " << T_to_string(inf) << " and now " << T_to_string(this->m_infeasibility) << std::endl); + // lean_assert(false); + // } + // if (inf == this->m_infeasibility) + // this->m_iters_with_no_cost_growing++; +} + + +void lar_core_solver::init_cost_for_column(unsigned j) { + /* + // If j is a breakpoint column, then we set the cost zero. + // When anylyzing an entering column candidate we update the cost of the breakpoints columns to get the left or the right derivative if the infeasibility function + const numeric_pair & x = this->m_x[j]; + // set zero cost for each non-basis column + if (this->m_basis_heading[j] < 0) { + this->m_costs[j] = numeric_traits::zero(); + return; + } + // j is a basis column + switch (this->m_column_types[j]) { + case fixed: + case column_type::boxed: + if (x > this->m_upper_bounds[j]) { + this->m_costs[j] = 1; + this->m_infeasibility += x - this->m_upper_bounds[j]; + } else if (x < this->m_low_bounds[j]) { + this->m_infeasibility += this->m_low_bounds[j] - x; + this->m_costs[j] = -1; + } else { + this->m_costs[j] = numeric_traits::zero(); + } + break; + case low_bound: + if (x < this->m_low_bounds[j]) { + this->m_costs[j] = -1; + this->m_infeasibility += this->m_low_bounds[j] - x; + } else { + this->m_costs[j] = numeric_traits::zero(); + } + break; + case upper_bound: + if (x > this->m_upper_bounds[j]) { + this->m_costs[j] = 1; + this->m_infeasibility += x - this->m_upper_bounds[j]; + } else { + this->m_costs[j] = numeric_traits::zero(); + } + break; + case free_column: + this->m_costs[j] = numeric_traits::zero(); + break; + default: + lean_assert(false); + break; + }*/ +} + + +// returns m_sign_of_alpha_r +int lar_core_solver::column_is_out_of_bounds(unsigned j) { + /* + switch (this->m_column_type[j]) { + case fixed: + case column_type::boxed: + if (this->x_below_low_bound(j)) { + return -1; + } + if (this->x_above_upper_bound(j)) { + return 1; + } + return 0; + case low_bound: + if (this->x_below_low_bound(j)) { + return -1; + } + return 0; + case upper_bound: + if (this->x_above_upper_bound(j)) { + return 1; + } + return 0; + default: + return 0; + break; + }*/ + lean_assert(false); + return true; +} + + + +void lar_core_solver::calculate_pivot_row(unsigned i) { + lean_assert(!m_r_solver.use_tableau()); + lean_assert(m_r_solver.m_pivot_row.is_OK()); + m_r_solver.m_pivot_row_of_B_1.clear(); + m_r_solver.m_pivot_row_of_B_1.resize(m_r_solver.m_m()); + m_r_solver.m_pivot_row.clear(); + m_r_solver.m_pivot_row.resize(m_r_solver.m_n()); + if (m_r_solver.m_settings.use_tableau()) { + unsigned basis_j = m_r_solver.m_basis[i]; + for (auto & c : m_r_solver.m_A.m_rows[i]) { + if (c.m_j != basis_j) + m_r_solver.m_pivot_row.set_value(c.get_val(), c.m_j); + } + return; + } + + m_r_solver.calculate_pivot_row_of_B_1(i); + m_r_solver.calculate_pivot_row_when_pivot_row_of_B1_is_ready(i); +} + + + + void lar_core_solver::prefix_r() { + if (!m_r_solver.m_settings.use_tableau()) { + m_r_solver.m_copy_of_xB.resize(m_r_solver.m_n()); + m_r_solver.m_ed.resize(m_r_solver.m_m()); + m_r_solver.m_pivot_row.resize(m_r_solver.m_n()); + m_r_solver.m_pivot_row_of_B_1.resize(m_r_solver.m_m()); + m_r_solver.m_w.resize(m_r_solver.m_m()); + m_r_solver.m_y.resize(m_r_solver.m_m()); + m_r_solver.m_rows_nz.resize(m_r_solver.m_m(), 0); + m_r_solver.m_columns_nz.resize(m_r_solver.m_n(), 0); + init_column_row_nz_for_r_solver(); + } + + m_r_solver.m_b.resize(m_r_solver.m_m()); + if (m_r_solver.m_settings.simplex_strategy() != simplex_strategy_enum::tableau_rows) { + if(m_r_solver.m_settings.use_breakpoints_in_feasibility_search) + m_r_solver.m_breakpoint_indices_queue.resize(m_r_solver.m_n()); + m_r_solver.m_costs.resize(m_r_solver.m_n()); + m_r_solver.m_d.resize(m_r_solver.m_n()); + m_r_solver.m_using_infeas_costs = true; + } +} + + void lar_core_solver::prefix_d() { + m_d_solver.m_b.resize(m_d_solver.m_m()); + m_d_solver.m_breakpoint_indices_queue.resize(m_d_solver.m_n()); + m_d_solver.m_copy_of_xB.resize(m_d_solver.m_n()); + m_d_solver.m_costs.resize(m_d_solver.m_n()); + m_d_solver.m_d.resize(m_d_solver.m_n()); + m_d_solver.m_ed.resize(m_d_solver.m_m()); + m_d_solver.m_pivot_row.resize(m_d_solver.m_n()); + m_d_solver.m_pivot_row_of_B_1.resize(m_d_solver.m_m()); + m_d_solver.m_w.resize(m_d_solver.m_m()); + m_d_solver.m_y.resize(m_d_solver.m_m()); + m_d_solver.m_steepest_edge_coefficients.resize(m_d_solver.m_n()); + m_d_solver.m_column_norms.clear(); + m_d_solver.m_column_norms.resize(m_d_solver.m_n(), 2); + m_d_solver.m_inf_set.clear(); + m_d_solver.m_inf_set.resize(m_d_solver.m_n()); +} + +void lar_core_solver::fill_not_improvable_zero_sum_from_inf_row() { + lean_assert(m_r_solver.A_mult_x_is_off() == false); + unsigned bj = m_r_basis[m_r_solver.m_inf_row_index_for_tableau]; + m_infeasible_sum_sign = m_r_solver.inf_sign_of_column(bj); + m_infeasible_linear_combination.clear(); + for (auto & rc : m_r_solver.m_A.m_rows[m_r_solver.m_inf_row_index_for_tableau]) { + m_infeasible_linear_combination.push_back(std::make_pair( rc.get_val(), rc.m_j)); + } +} + +void lar_core_solver::fill_not_improvable_zero_sum() { + if (m_r_solver.m_settings.simplex_strategy() == simplex_strategy_enum::tableau_rows) { + fill_not_improvable_zero_sum_from_inf_row(); + return; + } + // reusing the existing mechanism for row_feasibility_loop + m_infeasible_sum_sign = m_r_solver.m_settings.use_breakpoints_in_feasibility_search? -1 : 1; + m_infeasible_linear_combination.clear(); + for (auto j : m_r_solver.m_basis) { + const mpq & cost_j = m_r_solver.m_costs[j]; + if (!numeric_traits::is_zero(cost_j)) { + m_infeasible_linear_combination.push_back(std::make_pair(cost_j, j)); + } + } + // m_costs are expressed by m_d ( additional costs), substructing the latter gives 0 + for (unsigned j = 0; j < m_r_solver.m_n(); j++) { + if (m_r_solver.m_basis_heading[j] >= 0) continue; + const mpq & d_j = m_r_solver.m_d[j]; + if (!numeric_traits::is_zero(d_j)) { + m_infeasible_linear_combination.push_back(std::make_pair(-d_j, j)); + } + } +} + + +void lar_core_solver::solve() { + lean_assert(m_r_solver.non_basic_columns_are_set_correctly()); + lean_assert(m_r_solver.inf_set_is_correct()); + if (m_r_solver.current_x_is_feasible() && m_r_solver.m_look_for_feasible_solution_only) { + m_r_solver.set_status(OPTIMAL); + return; + } + ++settings().st().m_need_to_solve_inf; + lean_assert(!m_r_solver.A_mult_x_is_off()); + lean_assert((!settings().use_tableau()) || r_basis_is_OK()); + if (need_to_presolve_with_double_solver()) { + prefix_d(); + lar_solution_signature solution_signature; + vector changes_of_basis = find_solution_signature_with_doubles(solution_signature); + if (m_d_solver.get_status() == TIME_EXHAUSTED) { + m_r_solver.set_status(TIME_EXHAUSTED); + return; + } + if (settings().use_tableau()) + solve_on_signature_tableau(solution_signature, changes_of_basis); + else + solve_on_signature(solution_signature, changes_of_basis); + lean_assert(!settings().use_tableau() || r_basis_is_OK()); + } else { + if (!settings().use_tableau()) { + bool snapped = m_r_solver.snap_non_basic_x_to_bound(); + lean_assert(m_r_solver.non_basic_columns_are_set_correctly()); + if (snapped) + m_r_solver.solve_Ax_eq_b(); + } + if (m_r_solver.m_look_for_feasible_solution_only) + m_r_solver.find_feasible_solution(); + else + m_r_solver.solve(); + lean_assert(!settings().use_tableau() || r_basis_is_OK()); + } + if (m_r_solver.get_status() == INFEASIBLE) { + fill_not_improvable_zero_sum(); + } else if (m_r_solver.get_status() != UNBOUNDED) { + m_r_solver.set_status(OPTIMAL); + } + lean_assert(r_basis_is_OK()); + lean_assert(m_r_solver.non_basic_columns_are_set_correctly()); + lean_assert(m_r_solver.inf_set_is_correct()); +} + + +} + diff --git a/src/util/lp/lar_core_solver_instances.cpp b/src/util/lp/lar_core_solver_instances.cpp new file mode 100644 index 000000000..432d1a939 --- /dev/null +++ b/src/util/lp/lar_core_solver_instances.cpp @@ -0,0 +1,10 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include +#include +#include +#include "util/vector.h" +#include +#include "util/lp/lar_core_solver.hpp" diff --git a/src/util/lp/lar_solution_signature.h b/src/util/lp/lar_solution_signature.h new file mode 100644 index 000000000..2c4169c81 --- /dev/null +++ b/src/util/lp/lar_solution_signature.h @@ -0,0 +1,13 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ + +#pragma once +#include "util/vector.h" +#include "util/debug.h" +#include "util/lp/lp_settings.h" +#include +namespace lean { +typedef std::unordered_map lar_solution_signature; +} diff --git a/src/util/lp/lar_solver.h b/src/util/lp/lar_solver.h new file mode 100644 index 000000000..1fe5c6ca1 --- /dev/null +++ b/src/util/lp/lar_solver.h @@ -0,0 +1,2135 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#pragma once +#include "util/vector.h" +#include +#include "util/debug.h" +#include "util/buffer.h" +#include +#include +#include +#include "util/lp/lar_constraints.h" +#include +#include "util/lp/lar_core_solver.h" +#include +#include "util/lp/numeric_pair.h" +#include "util/lp/scaler.h" +#include "util/lp/lp_primal_core_solver.h" +#include "util/lp/random_updater.h" +#include +#include "util/lp/stacked_map.h" +#include "util/lp/stacked_value.h" +#include "util/lp/stacked_vector.h" +#include "util/lp/stacked_unordered_set.h" +#include "util/lp/iterator_on_pivot_row.h" +#include "util/lp/implied_bound.h" +#include "util/lp/bound_analyzer_on_row.h" +#include "util/lp/iterator_on_term_with_basis_var.h" +#include "util/lp/iterator_on_row.h" +#include "util/lp/quick_xplain.h" +namespace lean { +template +struct conversion_helper { + static V get_low_bound(const column_info & ci) { + return V(ci.get_low_bound(), ci.low_bound_is_strict()? 1 : 0); + } + + static V get_upper_bound(const column_info & ci) { + return V(ci.get_upper_bound(), ci.upper_bound_is_strict()? -1 : 0); + } +}; + +template<> +struct conversion_helper { + static double conversion_helper ::get_upper_bound(const column_info & ci) { + if (!ci.upper_bound_is_strict()) + return ci.get_upper_bound().get_double(); + double eps = 0.00001; + if (!ci.low_bound_is_set()) + return ci.get_upper_bound().get_double() - eps; + eps = std::min((ci.get_upper_bound() - ci.get_low_bound()).get_double() / 1000, eps); + return ci.get_upper_bound().get_double() - eps; + } + + static double get_low_bound(const column_info & ci) { + if (!ci.low_bound_is_strict()) + return ci.get_low_bound().get_double(); + double eps = 0.00001; + if (!ci.upper_bound_is_set()) + return ci.get_low_bound().get_double() + eps; + eps = std::min((ci.get_upper_bound() - ci.get_low_bound()).get_double() / 1000, eps); + return ci.get_low_bound().get_double() + eps; + } + +}; + +struct constraint_index_and_column_struct { + int m_ci = -1; + int m_j = -1; + constraint_index_and_column_struct() {} + constraint_index_and_column_struct(unsigned ci, unsigned j): + m_ci(static_cast(ci)), + m_j(static_cast(j)) + {} + bool operator==(const constraint_index_and_column_struct & a) const { return a.m_ci == m_ci && a.m_j == m_j; } + bool operator!=(const constraint_index_and_column_struct & a) const { return ! (*this == a);} +}; + +class lar_solver : public column_namer { + //////////////////// fields ////////////////////////// + lp_settings m_settings; + stacked_value m_status = OPTIMAL; + std::unordered_map m_ext_vars_to_columns; + vector m_columns_to_ext_vars_or_term_indices; + stacked_vector m_vars_to_ul_pairs; + vector m_constraints; + stacked_value m_constraint_count; + indexed_vector m_incoming_buffer; + // the set of column indices j such that bounds have changed for j + int_set m_columns_with_changed_bound; + int_set m_rows_with_changed_bounds; + int_set m_basic_columns_with_changed_cost; + stacked_value m_infeasible_column_index = -1; // such can be found at the initialization step + stacked_value m_term_count; +public: // debug remove later + vector m_terms; +private: + vector m_orig_terms; + const var_index m_terms_start_index = 1000000; + indexed_vector m_column_buffer; + std::function m_column_type_function = [this] (unsigned j) {return m_mpq_lar_core_solver.m_column_types()[j];}; + +public: + lar_core_solver m_mpq_lar_core_solver; + unsigned constraint_count() const { + return m_constraints.size(); + } + const lar_base_constraint& get_constraint(unsigned ci) const { + return *(m_constraints[ci]); + } + + ////////////////// methods //////////////////////////////// + static_matrix> & A_r() { return m_mpq_lar_core_solver.m_r_A;} + static_matrix> const & A_r() const { return m_mpq_lar_core_solver.m_r_A;} + static_matrix & A_d() { return m_mpq_lar_core_solver.m_d_A;} + static_matrix const & A_d() const { return m_mpq_lar_core_solver.m_d_A;} + + static bool valid_index(unsigned j){ return static_cast(j) >= 0;} + + +public: + lp_settings & settings() { return m_settings;} + + lp_settings const & settings() const { return m_settings;} + + void clear() {lean_assert(false); // not implemented + } + + + lar_solver() : m_mpq_lar_core_solver( + m_settings, + *this + ) {} + + void set_propagate_bounds_on_pivoted_rows_mode(bool v) { + m_mpq_lar_core_solver.m_r_solver.m_pivoted_rows = v? (& m_rows_with_changed_bounds) : nullptr; + } + + virtual ~lar_solver(){ + for (auto c : m_constraints) + delete c; + for (auto t : m_terms) + delete t; + for (auto t : m_orig_terms) + delete t; + } + + var_index add_var(unsigned ext_j) { + var_index i; + lean_assert (ext_j < m_terms_start_index); + + if (ext_j >= m_terms_start_index) + throw 0; // todo : what is the right was to exit? + + if (try_get_val(m_ext_vars_to_columns, ext_j, i)) { + return i; + } + lean_assert(m_vars_to_ul_pairs.size() == A_r().column_count()); + i = A_r().column_count(); + m_vars_to_ul_pairs.push_back (ul_pair(static_cast(-1))); + register_new_ext_var_index(ext_j); + add_non_basic_var_to_core_fields(); + lean_assert(sizes_are_correct()); + return i; + } + + numeric_pair const& get_value(var_index vi) const { return m_mpq_lar_core_solver.m_r_x[vi]; } + + bool is_term(var_index j) const { + return j >= m_terms_start_index && j - m_terms_start_index < m_terms.size(); + } + + unsigned adjust_term_index(unsigned j) const { + lean_assert(is_term(j)); + return j - m_terms_start_index; + } + + + bool need_to_presolve_with_doubles() const { return m_mpq_lar_core_solver.need_to_presolve_with_double_solver(); } + + void add_row_from_term_no_constraint(const lar_term * term) { + // j will be a new variable + unsigned j = A_r().column_count(); + ul_pair ul(j); + m_vars_to_ul_pairs.push_back(ul); + add_basic_var_to_core_fields(); + if (use_tableau()) { + auto it = iterator_on_term_with_basis_var(*term, j); + A_r().fill_last_row_with_pivoting(it, + m_mpq_lar_core_solver.m_r_solver.m_basis_heading); + m_mpq_lar_core_solver.m_r_solver.m_b.resize(A_r().column_count(), zero_of_type()); + } else { + fill_last_row_of_A_r(A_r(), term); + } + m_mpq_lar_core_solver.m_r_x[j] = get_basic_var_value_from_row_directly(A_r().row_count() - 1); + if (need_to_presolve_with_doubles()) + fill_last_row_of_A_d(A_d(), term); + } + + void add_constraint_from_term_and_create_new_column_row(unsigned term_j, const lar_term* term, + lconstraint_kind kind, const mpq & right_side) { + + lean_assert(A_r().column_count() == m_mpq_lar_core_solver.m_r_solver.m_costs.size()); + // j will be a new variable + unsigned j = A_r().column_count(); + ul_pair ul(j); + m_vars_to_ul_pairs.push_back(ul); + add_basic_var_to_core_fields(); + if (!m_settings.use_tableau()) { + fill_last_row_of_A_r(A_r(), term); + } + else { + auto it = iterator_on_term_with_basis_var(*term, j); + A_r().fill_last_row_with_pivoting(it, + m_mpq_lar_core_solver.m_r_solver.m_basis_heading); + m_mpq_lar_core_solver.m_r_solver.m_b.resize(A_r().column_count(), zero_of_type()); + } + m_mpq_lar_core_solver.m_r_x[A_r().column_count() - 1] = get_basic_var_value_from_row_directly(A_r().row_count() - 1); + fill_last_row_of_A_d(A_d(), term); + register_new_ext_var_index(term_j); + + // m_constraints.size() is the index of the constrained that is about to be added + update_column_type_and_bound(j, kind, right_side - term->m_v, m_constraints.size()); + m_constraints.push_back(new lar_term_constraint(term, kind, right_side)); + lean_assert(A_r().column_count() == m_mpq_lar_core_solver.m_r_solver.m_costs.size()); + } + + void add_var_bound_on_constraint_for_term(var_index j, lconstraint_kind kind, const mpq & right_side, constraint_index ci) { + lean_assert(is_term(j)); + unsigned adjusted_term_index = adjust_term_index(j); + unsigned term_j; + if (try_get_val(m_ext_vars_to_columns, j, term_j)) { + mpq rs = right_side - m_orig_terms[adjusted_term_index]->m_v; + m_constraints.push_back(new lar_term_constraint(m_orig_terms[adjusted_term_index], kind, right_side)); + update_column_type_and_bound(term_j, kind, rs, ci); + } + else { + add_constraint_from_term_and_create_new_column_row(j, m_orig_terms[adjusted_term_index], kind, right_side); + } + } + + + void add_row_for_term(const lar_term * term) { + lean_assert(sizes_are_correct()); + add_row_from_term_no_constraint(term); + lean_assert(sizes_are_correct()); + } + + bool sizes_are_correct() const { + lean_assert(!m_mpq_lar_core_solver.need_to_presolve_with_double_solver() || A_r().column_count() == A_d().column_count()); + lean_assert(A_r().column_count() == m_mpq_lar_core_solver.m_r_solver.m_column_types.size()); + lean_assert(A_r().column_count() == m_mpq_lar_core_solver.m_r_solver.m_costs.size()); + lean_assert(A_r().column_count() == m_mpq_lar_core_solver.m_r_x.size()); + return true; + } + + constraint_index add_var_bound(var_index j, lconstraint_kind kind, const mpq & right_side) { + lean_assert(sizes_are_correct()); + constraint_index ci = m_constraints.size(); + if (!is_term(j)) { // j is a var + auto vc = new lar_var_constraint(j, kind, right_side); + m_constraints.push_back(vc); + update_column_type_and_bound(j, kind, right_side, ci); + } else { + add_var_bound_on_constraint_for_term(j, kind, right_side, ci); + } + lean_assert(sizes_are_correct()); + return ci; + } + + + void print_implied_bound(const implied_bound& be, std::ostream & out) const { + out << "implied bound\n"; + unsigned v = be.m_j; + if (is_term(v)) { + out << "it is a term number " << be.m_j << std::endl; + print_term(*m_orig_terms[be.m_j - m_terms_start_index], out); + } + else { + out << get_column_name(v); + } + out << " " << lconstraint_kind_string(be.kind()) << " " << be.m_bound << std::endl; + // for (auto & p : be.m_explanation) { + // out << p.first << " : "; + // print_constraint(p.second, out); + // } + + // m_mpq_lar_core_solver.m_r_solver.print_column_info(be.m_j< m_terms_start_index? be.m_j : adjust_term_index(be.m_j), out); + out << "end of implied bound" << std::endl; + } + + bool implied_bound_is_correctly_explained(implied_bound const & be, const vector> & explanation) const { + std::unordered_map coeff_map; + auto rs_of_evidence = zero_of_type(); + unsigned n_of_G = 0, n_of_L = 0; + bool strict = false; + for (auto & it : explanation) { + mpq coeff = it.first; + constraint_index con_ind = it.second; + const auto & constr = *m_constraints[con_ind]; + lconstraint_kind kind = coeff.is_pos() ? constr.m_kind : flip_kind(constr.m_kind); + register_in_map(coeff_map, constr, coeff); + if (kind == GT || kind == LT) + strict = true; + if (kind == GE || kind == GT) n_of_G++; + else if (kind == LE || kind == LT) n_of_L++; + rs_of_evidence += coeff*constr.m_right_side; + } + lean_assert(n_of_G == 0 || n_of_L == 0); + lconstraint_kind kind = n_of_G ? GE : (n_of_L ? LE : EQ); + if (strict) + kind = static_cast((static_cast(kind) / 2)); + + if (!is_term(be.m_j)) { + if (coeff_map.size() != 1) + return false; + auto it = coeff_map.find(be.m_j); + if (it == coeff_map.end()) return false; + mpq ratio = it->second; + if (ratio < zero_of_type()) { + kind = static_cast(-kind); + } + rs_of_evidence /= ratio; + } else { + const lar_term * t = m_orig_terms[adjust_term_index(be.m_j)]; + const auto first_coeff = *t->m_coeffs.begin(); + unsigned j = first_coeff.first; + auto it = coeff_map.find(j); + if (it == coeff_map.end()) + return false; + mpq ratio = it->second / first_coeff.second; + for (auto & p : t->m_coeffs) { + it = coeff_map.find(p.first); + if (it == coeff_map.end()) + return false; + if (p.second * ratio != it->second) + return false; + } + if (ratio < zero_of_type()) { + kind = static_cast(-kind); + } + rs_of_evidence /= ratio; + rs_of_evidence += t->m_v * ratio; + } + + return kind == be.kind() && rs_of_evidence == be.m_bound; + } + + + void analyze_new_bounds_on_row( + unsigned row_index, + bound_propagator & bp) { + lean_assert(!use_tableau()); + iterator_on_pivot_row it(m_mpq_lar_core_solver.get_pivot_row(), m_mpq_lar_core_solver.m_r_basis[row_index]); + + bound_analyzer_on_row ra_pos(it, + zero_of_type>(), + row_index, + bp + ); + ra_pos.analyze(); + } + + void analyze_new_bounds_on_row_tableau( + unsigned row_index, + bound_propagator & bp + ) { + + if (A_r().m_rows[row_index].size() > settings().max_row_length_for_bound_propagation) + return; + iterator_on_row it(A_r().m_rows[row_index]); + lean_assert(use_tableau()); + bound_analyzer_on_row::analyze_row(it, + zero_of_type>(), + row_index, + bp + ); + } + + + void substitute_basis_var_in_terms_for_row(unsigned i) { + // todo : create a map from term basic vars to the rows where they are used + unsigned basis_j = m_mpq_lar_core_solver.m_r_solver.m_basis[i]; + for (unsigned k = 0; k < m_terms.size(); k++) { + if (term_is_used_as_row(k)) + continue; + if (!m_terms[k]->contains(basis_j)) + continue; + m_terms[k]->subst(basis_j, m_mpq_lar_core_solver.m_r_solver.m_pivot_row); + } + } + + void calculate_implied_bounds_for_row(unsigned i, bound_propagator & bp) { + if(use_tableau()) { + analyze_new_bounds_on_row_tableau(i, bp); + } else { + m_mpq_lar_core_solver.calculate_pivot_row(i); + substitute_basis_var_in_terms_for_row(i); + analyze_new_bounds_on_row(i, bp); + } + } + + /* + void process_new_implied_evidence_for_low_bound( + implied_bound_explanation& implied_evidence, // not pushed yet + vector & bound_evidences, + std::unordered_map & improved_low_bounds) { + + unsigned existing_index; + if (try_get_val(improved_low_bounds, implied_evidence.m_j, existing_index)) { + // we are improving the existent bound + bound_evidences[existing_index] = fill_bound_evidence(implied_evidence); + } else { + improved_low_bounds[implied_evidence.m_j] = bound_evidences.size(); + bound_evidences.push_back(fill_bound_evidence(implied_evidence)); + } + } + + void fill_bound_evidence_on_term(implied_bound & ie, implied_bound& be) { + lean_assert(false); + } + void fill_implied_bound_on_row(implied_bound & ie, implied_bound& be) { + iterator_on_row it(A_r().m_rows[ie.m_row_or_term_index]); + mpq a; unsigned j; + bool toggle = ie.m_coeff_before_j_is_pos; + if (!ie.m_is_low_bound) + toggle = !toggle; + while (it.next(a, j)) { + if (j == ie.m_j) continue; + const ul_pair & ul = m_vars_to_ul_pairs[j]; + + if (is_neg(a)) { // so the monoid has a positive coeff on the right side + constraint_index witness = toggle ? ul.m_low_bound_witness : ul.m_upper_bound_witness; + lean_assert(is_valid(witness)); + be.m_explanation.emplace_back(a, witness); + } + } + } + */ + /* + implied_bound fill_implied_bound_for_low_bound(implied_bound& ie) { + implied_bound be(ie.m_j, ie.m_bound.y.is_zero() ? GE : GT, ie.m_bound.x); + if (is_term(ie.m_row_or_term_index)) { + fill_implied_bound_for_low_bound_on_term(ie, be); + } + else { + fill_implied_bound_for_low_bound_on_row(ie, be); + } + return be; + } + + implied_bound fill_implied_bound_for_upper_bound(implied_bound& implied_evidence) { + lean_assert(false); + + be.m_j = implied_evidence.m_j; + be.m_bound = implied_evidence.m_bound.x; + be.m_kind = implied_evidence.m_bound.y.is_zero() ? LE : LT; + for (auto t : implied_evidence.m_vector_of_bound_signatures) { + const ul_pair & ul = m_vars_to_ul_pairs[t.m_column_index]; + constraint_index witness = t.m_low_bound ? ul.m_low_bound_witness : ul.m_upper_bound_witness; + lean_assert(is_valid(witness)); + be.m_explanation.emplace_back(t.m_coeff, witness); + } + + } + */ + /* + void process_new_implied_evidence_for_upper_bound( + implied_bound& implied_evidence, + vector & implied_bounds, + std::unordered_map & improved_upper_bounds) { + unsigned existing_index; + if (try_get_val(improved_upper_bounds, implied_evidence.m_j, existing_index)) { + implied_bound & be = implied_bounds[existing_index]; + be.m_explanation.clear(); + // we are improving the existent bound improve the existing bound + be = fill_implied_bound_for_upper_bound(implied_evidence); + } else { + improved_upper_bounds[implied_evidence.m_j] = implied_bounds.size(); + implied_bounds.push_back(fill_implied_bound_for_upper_bound(implied_evidence)); + } + } + */ + // implied_bound * get_existing_ + + linear_combination_iterator * create_new_iter_from_term(unsigned term_index) const { + lean_assert(false); // not implemented + return nullptr; + // new linear_combination_iterator_on_vector(m_terms[adjust_term_index(term_index)]->coeffs_as_vector()); + } + + unsigned adjust_column_index_to_term_index(unsigned j) const { + unsigned ext_var_or_term = m_columns_to_ext_vars_or_term_indices[j]; + return ext_var_or_term < m_terms_start_index ? j : ext_var_or_term; + } + + void propagate_bounds_on_a_term(const lar_term& t, bound_propagator & bp, unsigned term_offset) { + lean_assert(false); // not implemented + } + + + void explain_implied_bound(implied_bound & ib, bound_propagator & bp) { + unsigned i = ib.m_row_or_term_index; + int bound_sign = ib.m_is_low_bound? 1: -1; + int j_sign = (ib.m_coeff_before_j_is_pos ? 1 :-1) * bound_sign; + unsigned m_j = ib.m_j; + if (is_term(m_j)) { + m_j = m_ext_vars_to_columns[m_j]; + } + for (auto const& r : A_r().m_rows[i]) { + unsigned j = r.m_j; + mpq const& a = r.get_val(); + if (j == m_j) continue; + if (is_term(j)) { + j = m_ext_vars_to_columns[j]; + } + int a_sign = is_pos(a)? 1: -1; + int sign = j_sign * a_sign; + const ul_pair & ul = m_vars_to_ul_pairs[j]; + auto witness = sign > 0? ul.upper_bound_witness(): ul.low_bound_witness(); + lean_assert(is_valid(witness)); + bp.consume(a, witness); + } + // lean_assert(implied_bound_is_correctly_explained(ib, explanation)); + } + + + bool term_is_used_as_row(unsigned term) const { + lean_assert(is_term(term)); + return contains(m_ext_vars_to_columns, term); + } + + void propagate_bounds_on_terms(bound_propagator & bp) { + for (unsigned i = 0; i < m_terms.size(); i++) { + if (term_is_used_as_row(i + m_terms_start_index)) + continue; // this term is used a left side of a constraint, + // it was processed as a touched row if needed + propagate_bounds_on_a_term(*m_terms[i], bp, i); + } + } + + + // goes over touched rows and tries to induce bounds + void propagate_bounds_for_touched_rows(bound_propagator & bp) { + if (!use_tableau()) + return; // ! todo : enable bound propagaion here. The current bug is that after the pop + // the changed terms become incorrect! + + for (unsigned i : m_rows_with_changed_bounds.m_index) { + calculate_implied_bounds_for_row(i, bp); + } + m_rows_with_changed_bounds.clear(); + if (!use_tableau()) { + propagate_bounds_on_terms(bp); + } + } + + lp_status get_status() const { return m_status;} + + void set_status(lp_status s) {m_status = s;} + + lp_status find_feasible_solution() { + m_mpq_lar_core_solver.m_r_solver.m_look_for_feasible_solution_only = true; + return solve(); + } + + lp_status solve() { + if (m_status == INFEASIBLE) { + return m_status; + } + solve_with_core_solver(); + if (m_status != INFEASIBLE) { + if (m_settings.bound_propagation()) + detect_rows_with_changed_bounds(); + } + + m_columns_with_changed_bound.clear(); + return m_status; + } + + void fill_explanation_from_infeasible_column(vector> & evidence) const{ + + // this is the case when the lower bound is in conflict with the upper one + const ul_pair & ul = m_vars_to_ul_pairs[m_infeasible_column_index]; + evidence.push_back(std::make_pair(numeric_traits::one(), ul.upper_bound_witness())); + evidence.push_back(std::make_pair(-numeric_traits::one(), ul.low_bound_witness())); + } + + + unsigned get_total_iterations() const { return m_mpq_lar_core_solver.m_r_solver.total_iterations(); } + // see http://research.microsoft.com/projects/z3/smt07.pdf + // This method searches for a feasible solution with as many different values of variables, reverenced in vars, as it can find + // Attention, after a call to this method the non-basic variables don't necesserarly stick to their bounds anymore + vector get_list_of_all_var_indices() const { + vector ret; + for (unsigned j = 0; j < m_mpq_lar_core_solver.m_r_heading.size(); j++) + ret.push_back(j); + return ret; + } + void push() { + lean_assert(sizes_are_correct()); + m_status.push(); + m_vars_to_ul_pairs.push(); + m_infeasible_column_index.push(); + m_mpq_lar_core_solver.push(); + m_term_count = m_terms.size(); + m_term_count.push(); + m_constraint_count = m_constraints.size(); + m_constraint_count.push(); + lean_assert(sizes_are_correct()); + } + + static void clean_large_elements_after_pop(unsigned n, int_set& set) { + vector to_remove; + for (unsigned j: set.m_index) + if (j >= n) + to_remove.push_back(j); + for (unsigned j : to_remove) + set.erase(j); + } + + static void shrink_inf_set_after_pop(unsigned n, int_set & set) { + clean_large_elements_after_pop(n, set); + set.resize(n); + } + + + void pop(unsigned k) { + lean_assert(sizes_are_correct()); + int n_was = static_cast(m_ext_vars_to_columns.size()); + m_status.pop(k); + m_infeasible_column_index.pop(k); + unsigned n = m_vars_to_ul_pairs.peek_size(k); + for (unsigned j = n_was; j-- > n;) + m_ext_vars_to_columns.erase(m_columns_to_ext_vars_or_term_indices[j]); + m_columns_to_ext_vars_or_term_indices.resize(n); + if (m_settings.use_tableau()) { + pop_tableau(); + } + m_vars_to_ul_pairs.pop(k); + + m_mpq_lar_core_solver.pop(k); + clean_large_elements_after_pop(n, m_columns_with_changed_bound); + unsigned m = A_r().row_count(); + clean_large_elements_after_pop(m, m_rows_with_changed_bounds); + clean_inf_set_of_r_solver_after_pop(); + lean_assert(!use_tableau() || m_mpq_lar_core_solver.m_r_solver.reduced_costs_are_correct_tableau()); + + + lean_assert(ax_is_correct()); + lean_assert(m_mpq_lar_core_solver.m_r_solver.inf_set_is_correct()); + m_constraint_count.pop(k); + for (unsigned i = m_constraint_count; i < m_constraints.size(); i++) + delete m_constraints[i]; + + m_constraints.resize(m_constraint_count); + m_term_count.pop(k); + for (unsigned i = m_term_count; i < m_terms.size(); i++) { + delete m_terms[i]; + delete m_orig_terms[i]; + } + m_terms.resize(m_term_count); + m_orig_terms.resize(m_term_count); + lean_assert(sizes_are_correct()); + lean_assert((!m_settings.use_tableau()) || m_mpq_lar_core_solver.m_r_solver.reduced_costs_are_correct_tableau()); + } + + vector get_all_constraint_indices() const { + vector ret; + constraint_index i = 0; + while ( i < m_constraints.size()) + ret.push_back(i++); + return ret; + } + + bool maximize_term_on_tableau(const vector> & term, + impq &term_max) { + m_mpq_lar_core_solver.solve(); + if (m_mpq_lar_core_solver.m_r_solver.get_status() == UNBOUNDED) + return false; + + term_max = 0; + for (auto & p : term) + term_max += p.first * m_mpq_lar_core_solver.m_r_x[p.second]; + + return true; + } + + bool costs_are_zeros_for_r_solver() const { + for (unsigned j = 0; j < m_mpq_lar_core_solver.m_r_solver.m_costs.size(); j++) { + lean_assert(is_zero(m_mpq_lar_core_solver.m_r_solver.m_costs[j])); + } + return true; + } + bool reduced_costs_are_zeroes_for_r_solver() const { + for (unsigned j = 0; j < m_mpq_lar_core_solver.m_r_solver.m_d.size(); j++) { + lean_assert(is_zero(m_mpq_lar_core_solver.m_r_solver.m_d[j])); + } + return true; + } + + void set_costs_to_zero(const vector> & term) { + auto & rslv = m_mpq_lar_core_solver.m_r_solver; + auto & jset = m_mpq_lar_core_solver.m_r_solver.m_inf_set; // hijack this set that should be empty right now + lean_assert(jset.m_index.size()==0); + + for (auto & p : term) { + unsigned j = p.second; + rslv.m_costs[j] = zero_of_type(); + int i = rslv.m_basis_heading[j]; + if (i < 0) + jset.insert(j); + else { + for (auto & rc : A_r().m_rows[i]) + jset.insert(rc.m_j); + } + } + + for (unsigned j : jset.m_index) + rslv.m_d[j] = zero_of_type(); + + jset.clear(); + + lean_assert(reduced_costs_are_zeroes_for_r_solver()); + lean_assert(costs_are_zeros_for_r_solver()); + } + + void prepare_costs_for_r_solver(const vector> & term) { + + auto & rslv = m_mpq_lar_core_solver.m_r_solver; + rslv.m_using_infeas_costs = false; + lean_assert(costs_are_zeros_for_r_solver()); + lean_assert(reduced_costs_are_zeroes_for_r_solver()); + rslv.m_costs.resize(A_r().column_count(), zero_of_type()); + for (auto & p : term) { + unsigned j = p.second; + rslv.m_costs[j] = p.first; + if (rslv.m_basis_heading[j] < 0) + rslv.m_d[j] += p.first; + else + rslv.update_reduced_cost_for_basic_column_cost_change(- p.first, j); + } + lean_assert(rslv.reduced_costs_are_correct_tableau()); + } + + bool maximize_term_on_corrected_r_solver(const vector> & term, + impq &term_max) { + settings().backup_costs = false; + switch (settings().m_simplex_strategy) { + case simplex_strategy_enum::tableau_rows: + prepare_costs_for_r_solver(term); + settings().m_simplex_strategy = simplex_strategy_enum::tableau_costs; + { + bool ret = maximize_term_on_tableau(term, term_max); + settings().m_simplex_strategy = simplex_strategy_enum::tableau_rows; + set_costs_to_zero(term); + m_mpq_lar_core_solver.m_r_solver.set_status(OPTIMAL); + return ret; + } + case simplex_strategy_enum::tableau_costs: + prepare_costs_for_r_solver(term); + { + bool ret = maximize_term_on_tableau(term, term_max); + set_costs_to_zero(term); + m_mpq_lar_core_solver.m_r_solver.set_status(OPTIMAL); + return ret; + } + + case simplex_strategy_enum::no_tableau: + lean_assert(false); // not implemented + return false; + default: + lean_unreachable(); // wrong mode + } + return false; + } + // starting from a given feasible state look for the maximum of the term + // return true if found and false if unbounded + bool maximize_term(const vector> & term, + impq &term_max) { + lean_assert(m_mpq_lar_core_solver.m_r_solver.current_x_is_feasible()); + m_mpq_lar_core_solver.m_r_solver.m_look_for_feasible_solution_only = false; + return maximize_term_on_corrected_r_solver(term, term_max); + } + + + + var_index add_term(const vector> & coeffs, + const mpq &m_v) { + + lean_assert(A_r().column_count() == m_mpq_lar_core_solver.m_r_solver.m_costs.size()); + m_terms.push_back(new lar_term(coeffs, m_v)); + m_orig_terms.push_back(new lar_term(coeffs, m_v)); + unsigned adjusted_term_index = m_terms.size() - 1; + if (use_tableau() && !coeffs.empty()) { + register_new_ext_var_index(m_terms_start_index + adjusted_term_index); + add_row_for_term(m_orig_terms.back()); + if (m_settings.bound_propagation()) + m_rows_with_changed_bounds.insert(A_r().row_count() - 1); + } + lean_assert(m_ext_vars_to_columns.size() == A_r().column_count()); + lean_assert(A_r().column_count() == m_mpq_lar_core_solver.m_r_solver.m_costs.size()); + return m_terms_start_index + adjusted_term_index; + } + + const lar_term & get_term(unsigned j) const { + lean_assert(j >= m_terms_start_index); + return *m_terms[j - m_terms_start_index]; + } + + void pop_core_solver_params() { + pop_core_solver_params(1); + } + + void pop_core_solver_params(unsigned k) { + A_r().pop(k); + A_d().pop(k); + } + + void add_new_var_to_core_fields_for_mpq(bool register_in_basis) { + unsigned j = A_r().column_count(); + A_r().add_column(); + lean_assert(m_mpq_lar_core_solver.m_r_x.size() == j); + // lean_assert(m_mpq_lar_core_solver.m_r_low_bounds.size() == j && m_mpq_lar_core_solver.m_r_upper_bounds.size() == j); // restore later + m_mpq_lar_core_solver.m_r_x.resize(j + 1); + m_mpq_lar_core_solver.m_r_low_bounds.increase_size_by_one(); + m_mpq_lar_core_solver.m_r_upper_bounds.increase_size_by_one(); + m_mpq_lar_core_solver.m_r_solver.m_inf_set.increase_size_by_one(); + m_mpq_lar_core_solver.m_r_solver.m_costs.resize(j + 1); + m_mpq_lar_core_solver.m_r_solver.m_d.resize(j + 1); + lean_assert(m_mpq_lar_core_solver.m_r_heading.size() == j); // as A().column_count() on the entry to the method + if (register_in_basis) { + A_r().add_row(); + m_mpq_lar_core_solver.m_r_heading.push_back(m_mpq_lar_core_solver.m_r_basis.size()); + m_mpq_lar_core_solver.m_r_basis.push_back(j); + if (m_settings.bound_propagation()) + m_rows_with_changed_bounds.insert(A_r().row_count() - 1); + } else { + m_mpq_lar_core_solver.m_r_heading.push_back(- static_cast(m_mpq_lar_core_solver.m_r_nbasis.size()) - 1); + m_mpq_lar_core_solver.m_r_nbasis.push_back(j); + } + } + + void add_new_var_to_core_fields_for_doubles(bool register_in_basis) { + unsigned j = A_d().column_count(); + A_d().add_column(); + lean_assert(m_mpq_lar_core_solver.m_d_x.size() == j); + // lean_assert(m_mpq_lar_core_solver.m_d_low_bounds.size() == j && m_mpq_lar_core_solver.m_d_upper_bounds.size() == j); // restore later + m_mpq_lar_core_solver.m_d_x.resize(j + 1 ); + m_mpq_lar_core_solver.m_d_low_bounds.resize(j + 1); + m_mpq_lar_core_solver.m_d_upper_bounds.resize(j + 1); + lean_assert(m_mpq_lar_core_solver.m_d_heading.size() == j); // as A().column_count() on the entry to the method + if (register_in_basis) { + A_d().add_row(); + m_mpq_lar_core_solver.m_d_heading.push_back(m_mpq_lar_core_solver.m_d_basis.size()); + m_mpq_lar_core_solver.m_d_basis.push_back(j); + }else { + m_mpq_lar_core_solver.m_d_heading.push_back(- static_cast(m_mpq_lar_core_solver.m_d_nbasis.size()) - 1); + m_mpq_lar_core_solver.m_d_nbasis.push_back(j); + } + } + + void add_basic_var_to_core_fields() { + bool need_to_presolve_with_doubles = m_mpq_lar_core_solver.need_to_presolve_with_double_solver(); + lean_assert(!need_to_presolve_with_doubles || A_r().column_count() == A_d().column_count()); + m_mpq_lar_core_solver.m_column_types.push_back(column_type::free_column); + m_columns_with_changed_bound.increase_size_by_one(); + m_rows_with_changed_bounds.increase_size_by_one(); + add_new_var_to_core_fields_for_mpq(true); + if (need_to_presolve_with_doubles) + add_new_var_to_core_fields_for_doubles(true); + } + + void add_non_basic_var_to_core_fields() { + lean_assert(!m_mpq_lar_core_solver.need_to_presolve_with_double_solver() || A_r().column_count() == A_d().column_count()); + m_mpq_lar_core_solver.m_column_types.push_back(column_type::free_column); + m_columns_with_changed_bound.increase_size_by_one(); + add_new_var_to_core_fields_for_mpq(false); + if (m_mpq_lar_core_solver.need_to_presolve_with_double_solver()) + add_new_var_to_core_fields_for_doubles(false); + } + + void register_new_ext_var_index(unsigned s) { + lean_assert(!contains(m_ext_vars_to_columns, s)); + unsigned j = static_cast(m_ext_vars_to_columns.size()); + m_ext_vars_to_columns[s] = j; + lean_assert(m_columns_to_ext_vars_or_term_indices.size() == j); + m_columns_to_ext_vars_or_term_indices.push_back(s); + } + + + + void set_upper_bound_witness(var_index j, constraint_index ci) { + ul_pair ul = m_vars_to_ul_pairs[j]; + ul.upper_bound_witness() = ci; + m_vars_to_ul_pairs[j] = ul; + } + + void set_low_bound_witness(var_index j, constraint_index ci) { + ul_pair ul = m_vars_to_ul_pairs[j]; + ul.low_bound_witness() = ci; + m_vars_to_ul_pairs[j] = ul; + } + + void update_free_column_type_and_bound(var_index j, lconstraint_kind kind, const mpq & right_side, constraint_index constr_ind) { + mpq y_of_bound(0); + switch (kind) { + case LT: + y_of_bound = -1; + case LE: + m_mpq_lar_core_solver.m_column_types[j] = column_type::upper_bound; + lean_assert(m_mpq_lar_core_solver.m_column_types()[j] == column_type::upper_bound); + lean_assert(m_mpq_lar_core_solver.m_r_upper_bounds.size() > j); + { + auto up = numeric_pair(right_side, y_of_bound); + m_mpq_lar_core_solver.m_r_upper_bounds[j] = up; + } + set_upper_bound_witness(j, constr_ind); + break; + case GT: + y_of_bound = 1; + case GE: + m_mpq_lar_core_solver.m_column_types[j] = column_type::low_bound; + lean_assert(m_mpq_lar_core_solver.m_r_upper_bounds.size() > j); + { + auto low = numeric_pair(right_side, y_of_bound); + m_mpq_lar_core_solver.m_r_low_bounds[j] = low; + } + set_low_bound_witness(j, constr_ind); + break; + case EQ: + m_mpq_lar_core_solver.m_column_types[j] = column_type::fixed; + m_mpq_lar_core_solver.m_r_low_bounds[j] = m_mpq_lar_core_solver.m_r_upper_bounds[j] = numeric_pair(right_side, zero_of_type()); + set_upper_bound_witness(j, constr_ind); + set_low_bound_witness(j, constr_ind); + break; + + default: + lean_unreachable(); + + } + m_columns_with_changed_bound.insert(j); + } + + void update_upper_bound_column_type_and_bound(var_index j, lconstraint_kind kind, const mpq & right_side, constraint_index ci) { + lean_assert(m_mpq_lar_core_solver.m_column_types()[j] == column_type::upper_bound); + mpq y_of_bound(0); + switch (kind) { + case LT: + y_of_bound = -1; + case LE: + { + auto up = numeric_pair(right_side, y_of_bound); + if (up < m_mpq_lar_core_solver.m_r_upper_bounds()[j]) { + m_mpq_lar_core_solver.m_r_upper_bounds[j] = up; + set_upper_bound_witness(j, ci); + m_columns_with_changed_bound.insert(j); + } + } + break; + case GT: + y_of_bound = 1; + case GE: + m_mpq_lar_core_solver.m_column_types[j] = column_type::boxed; + { + auto low = numeric_pair(right_side, y_of_bound); + m_mpq_lar_core_solver.m_r_low_bounds[j] = low; + set_low_bound_witness(j, ci); + m_columns_with_changed_bound.insert(j); + if (low > m_mpq_lar_core_solver.m_r_upper_bounds[j]) { + m_status = INFEASIBLE; + m_infeasible_column_index = j; + } else { + m_mpq_lar_core_solver.m_column_types[j] = m_mpq_lar_core_solver.m_r_low_bounds()[j] < m_mpq_lar_core_solver.m_r_upper_bounds()[j]? column_type::boxed : column_type::fixed; + } + } + break; + case EQ: + { + auto v = numeric_pair(right_side, zero_of_type()); + if (v > m_mpq_lar_core_solver.m_r_upper_bounds[j]) { + m_status = INFEASIBLE; + set_low_bound_witness(j, ci); + m_infeasible_column_index = j; + } else { + m_mpq_lar_core_solver.m_r_low_bounds[j] = m_mpq_lar_core_solver.m_r_upper_bounds[j] = v; + m_columns_with_changed_bound.insert(j); + set_low_bound_witness(j, ci); + set_upper_bound_witness(j, ci); + m_mpq_lar_core_solver.m_column_types[j] = column_type::fixed; + } + break; + } + break; + + default: + lean_unreachable(); + + } + } + + void update_boxed_column_type_and_bound(var_index j, lconstraint_kind kind, const mpq & right_side, constraint_index ci) { + lean_assert(m_status == INFEASIBLE || (m_mpq_lar_core_solver.m_column_types()[j] == column_type::boxed && m_mpq_lar_core_solver.m_r_low_bounds()[j] < m_mpq_lar_core_solver.m_r_upper_bounds()[j])); + mpq y_of_bound(0); + switch (kind) { + case LT: + y_of_bound = -1; + case LE: + { + auto up = numeric_pair(right_side, y_of_bound); + if (up < m_mpq_lar_core_solver.m_r_upper_bounds[j]) { + m_mpq_lar_core_solver.m_r_upper_bounds[j] = up; + set_upper_bound_witness(j, ci); + m_columns_with_changed_bound.insert(j); + } + + if (up < m_mpq_lar_core_solver.m_r_low_bounds[j]) { + m_status = INFEASIBLE; + lean_assert(false); + m_infeasible_column_index = j; + } else { + if (m_mpq_lar_core_solver.m_r_low_bounds()[j] == m_mpq_lar_core_solver.m_r_upper_bounds()[j]) + m_mpq_lar_core_solver.m_column_types[j] = column_type::fixed; + } + } + break; + case GT: + y_of_bound = 1; + case GE: + { + auto low = numeric_pair(right_side, y_of_bound); + if (low > m_mpq_lar_core_solver.m_r_low_bounds[j]) { + m_mpq_lar_core_solver.m_r_low_bounds[j] = low; + m_columns_with_changed_bound.insert(j); + set_low_bound_witness(j, ci); + } + if (low > m_mpq_lar_core_solver.m_r_upper_bounds[j]) { + m_status = INFEASIBLE; + m_infeasible_column_index = j; + } else if ( low == m_mpq_lar_core_solver.m_r_upper_bounds[j]) { + m_mpq_lar_core_solver.m_column_types[j] = column_type::fixed; + } + } + break; + case EQ: + { + auto v = numeric_pair(right_side, zero_of_type()); + if (v < m_mpq_lar_core_solver.m_r_low_bounds[j]) { + m_status = INFEASIBLE; + m_infeasible_column_index = j; + set_upper_bound_witness(j, ci); + } else if (v > m_mpq_lar_core_solver.m_r_upper_bounds[j]) { + m_status = INFEASIBLE; + m_infeasible_column_index = j; + set_low_bound_witness(j, ci); + } else { + m_mpq_lar_core_solver.m_r_low_bounds[j] = m_mpq_lar_core_solver.m_r_upper_bounds[j] = v; + set_low_bound_witness(j, ci); + set_upper_bound_witness(j, ci); + m_mpq_lar_core_solver.m_column_types[j] = column_type::fixed; + m_columns_with_changed_bound.insert(j); + } + + break; + } + + default: + lean_unreachable(); + + } + } + void update_low_bound_column_type_and_bound(var_index j, lconstraint_kind kind, const mpq & right_side, constraint_index ci) { + lean_assert(m_mpq_lar_core_solver.m_column_types()[j] == column_type::low_bound); + mpq y_of_bound(0); + switch (kind) { + case LT: + y_of_bound = -1; + case LE: + { + auto up = numeric_pair(right_side, y_of_bound); + m_mpq_lar_core_solver.m_r_upper_bounds[j] = up; + set_upper_bound_witness(j, ci); + m_columns_with_changed_bound.insert(j); + + if (up < m_mpq_lar_core_solver.m_r_low_bounds[j]) { + m_status = INFEASIBLE; + m_infeasible_column_index = j; + } else { + m_mpq_lar_core_solver.m_column_types[j] = m_mpq_lar_core_solver.m_r_low_bounds()[j] < m_mpq_lar_core_solver.m_r_upper_bounds()[j]? column_type::boxed : column_type::fixed; + } + } + break; + case GT: + y_of_bound = 1; + case GE: + { + auto low = numeric_pair(right_side, y_of_bound); + if (low > m_mpq_lar_core_solver.m_r_low_bounds[j]) { + m_mpq_lar_core_solver.m_r_low_bounds[j] = low; + m_columns_with_changed_bound.insert(j); + set_low_bound_witness(j, ci); + } + } + break; + case EQ: + { + auto v = numeric_pair(right_side, zero_of_type()); + if (v < m_mpq_lar_core_solver.m_r_low_bounds[j]) { + m_status = INFEASIBLE; + m_infeasible_column_index = j; + set_upper_bound_witness(j, ci); + } else { + m_mpq_lar_core_solver.m_r_low_bounds[j] = m_mpq_lar_core_solver.m_r_upper_bounds[j] = v; + set_low_bound_witness(j, ci); + set_upper_bound_witness(j, ci); + m_mpq_lar_core_solver.m_column_types[j] = column_type::fixed; + } + m_columns_with_changed_bound.insert(j); + break; + } + + default: + lean_unreachable(); + + } + } + + void update_fixed_column_type_and_bound(var_index j, lconstraint_kind kind, const mpq & right_side, constraint_index ci) { + lean_assert(m_status == INFEASIBLE || (m_mpq_lar_core_solver.m_column_types()[j] == column_type::fixed && m_mpq_lar_core_solver.m_r_low_bounds()[j] == m_mpq_lar_core_solver.m_r_upper_bounds()[j])); + lean_assert(m_status == INFEASIBLE || (m_mpq_lar_core_solver.m_r_low_bounds()[j].y.is_zero() && m_mpq_lar_core_solver.m_r_upper_bounds()[j].y.is_zero())); + auto v = numeric_pair(right_side, mpq(0)); + + mpq y_of_bound(0); + switch (kind) { + case LT: + if (v <= m_mpq_lar_core_solver.m_r_low_bounds[j]) { + m_status = INFEASIBLE; + m_infeasible_column_index = j; + set_upper_bound_witness(j, ci); + } + break; + case LE: + { + if (v < m_mpq_lar_core_solver.m_r_low_bounds[j]) { + m_status = INFEASIBLE; + m_infeasible_column_index = j; + set_upper_bound_witness(j, ci); + } + } + break; + case GT: + { + if (v >= m_mpq_lar_core_solver.m_r_upper_bounds[j]) { + m_status = INFEASIBLE; + m_infeasible_column_index =j; + set_low_bound_witness(j, ci); + } + } + break; + case GE: + { + if (v > m_mpq_lar_core_solver.m_r_upper_bounds[j]) { + m_status = INFEASIBLE; + m_infeasible_column_index = j; + set_low_bound_witness(j, ci); + } + } + break; + case EQ: + { + if (v < m_mpq_lar_core_solver.m_r_low_bounds[j]) { + m_status = INFEASIBLE; + m_infeasible_column_index = j; + set_upper_bound_witness(j, ci); + } else if (v > m_mpq_lar_core_solver.m_r_upper_bounds[j]) { + m_status = INFEASIBLE; + m_infeasible_column_index = j; + set_low_bound_witness(j, ci); + } + break; + } + + default: + lean_unreachable(); + + } + } + + void update_column_type_and_bound(var_index j, lconstraint_kind kind, const mpq & right_side, constraint_index constr_index) { + switch(m_mpq_lar_core_solver.m_column_types[j]) { + case column_type::free_column: + update_free_column_type_and_bound(j, kind, right_side, constr_index); + break; + case column_type::boxed: + update_boxed_column_type_and_bound(j, kind, right_side, constr_index); + break; + case column_type::low_bound: + update_low_bound_column_type_and_bound(j, kind, right_side, constr_index); + break; + case column_type::upper_bound: + update_upper_bound_column_type_and_bound(j, kind, right_side, constr_index); + break; + case column_type::fixed: + update_fixed_column_type_and_bound(j, kind, right_side, constr_index); + break; + default: + lean_assert(false); // cannot be here + } + } + + + void substitute_terms(const mpq & mult, + const vector>& left_side_with_terms, + vector> &left_side, mpq & right_side) const { + for (auto & t : left_side_with_terms) { + if (t.second < m_terms_start_index) { + lean_assert(t.second < A_r().column_count()); + left_side.push_back(std::pair(mult * t.first, t.second)); + } else { + const lar_term & term = * m_terms[adjust_term_index(t.second)]; + substitute_terms(mult * t.first, left_side_with_terms, left_side, right_side); + right_side -= mult * term.m_v; + } + } + } + + + void detect_rows_of_bound_change_column_for_nbasic_column(unsigned j) { + if (A_r().row_count() != m_column_buffer.data_size()) + m_column_buffer.resize(A_r().row_count()); + else + m_column_buffer.clear(); + lean_assert(m_column_buffer.size() == 0 && m_column_buffer.is_OK()); + + m_mpq_lar_core_solver.m_r_solver.solve_Bd(j, m_column_buffer); + for (unsigned i : m_column_buffer.m_index) + m_rows_with_changed_bounds.insert(i); + } + + + + void detect_rows_of_bound_change_column_for_nbasic_column_tableau(unsigned j) { + for (auto & rc : m_mpq_lar_core_solver.m_r_A.m_columns[j]) + m_rows_with_changed_bounds.insert(rc.m_i); + } + + bool use_tableau() const { return m_settings.use_tableau(); } + + bool use_tableau_costs() const { return m_settings.simplex_strategy() == simplex_strategy_enum::tableau_costs; } + + + void detect_rows_of_column_with_bound_change(unsigned j) { + if (m_mpq_lar_core_solver.m_r_heading[j] >= 0) { // it is a basic column + // just mark the row at touched and exit + m_rows_with_changed_bounds.insert(m_mpq_lar_core_solver.m_r_heading[j]); + return; + } + + if (use_tableau()) + detect_rows_of_bound_change_column_for_nbasic_column_tableau(j); + else + detect_rows_of_bound_change_column_for_nbasic_column(j); + } + + void adjust_x_of_column(unsigned j) { + lean_assert(false); + } + + bool row_is_correct(unsigned i) const { + numeric_pair r = zero_of_type>(); + for (const auto & c : A_r().m_rows[i]) + r += c.m_value * m_mpq_lar_core_solver.m_r_x[c.m_j]; + return is_zero(r); + } + + bool ax_is_correct() const { + for (unsigned i = 0; i < A_r().row_count(); i++) { + if (!row_is_correct(i)) + return false; + } + return true; + } + + bool tableau_with_costs() const { + return m_settings.simplex_strategy() == simplex_strategy_enum::tableau_costs; + } + + bool costs_are_used() const { + return m_settings.simplex_strategy() != simplex_strategy_enum::tableau_rows; + } + + void change_basic_x_by_delta_on_column(unsigned j, const numeric_pair & delta) { + if (use_tableau()) { + for (const auto & c : A_r().m_columns[j]) { + unsigned bj = m_mpq_lar_core_solver.m_r_basis[c.m_i]; + m_mpq_lar_core_solver.m_r_x[bj] -= A_r().get_val(c) * delta; + if (tableau_with_costs()) { + m_basic_columns_with_changed_cost.insert(bj); + } + m_mpq_lar_core_solver.m_r_solver.update_column_in_inf_set(bj); + } + } else { + m_column_buffer.clear(); + m_column_buffer.resize(A_r().row_count()); + m_mpq_lar_core_solver.m_r_solver.solve_Bd(j, m_column_buffer); + for (unsigned i : m_column_buffer.m_index) { + unsigned bj = m_mpq_lar_core_solver.m_r_basis[i]; + m_mpq_lar_core_solver.m_r_x[bj] -= m_column_buffer[i] * delta; + m_mpq_lar_core_solver.m_r_solver.update_column_in_inf_set(bj); + } + } + } + + void update_x_and_inf_costs_for_column_with_changed_bounds(unsigned j) { + if (m_mpq_lar_core_solver.m_r_heading[j] >= 0) { + if (costs_are_used()) { + bool was_infeas = m_mpq_lar_core_solver.m_r_solver.m_inf_set.contains(j); + m_mpq_lar_core_solver.m_r_solver.update_column_in_inf_set(j); + if (was_infeas != m_mpq_lar_core_solver.m_r_solver.m_inf_set.contains(j)) + m_basic_columns_with_changed_cost.insert(j); + } else { + m_mpq_lar_core_solver.m_r_solver.update_column_in_inf_set(j); + } + } else { + numeric_pair delta; + if (m_mpq_lar_core_solver.m_r_solver.make_column_feasible(j, delta)) + change_basic_x_by_delta_on_column(j, delta); + } + } + + + void detect_rows_with_changed_bounds_for_column(unsigned j) { + if (m_mpq_lar_core_solver.m_r_heading[j] >= 0) { + m_rows_with_changed_bounds.insert(m_mpq_lar_core_solver.m_r_heading[j]); + return; + } + + if (use_tableau()) + detect_rows_of_bound_change_column_for_nbasic_column_tableau(j); + else + detect_rows_of_bound_change_column_for_nbasic_column(j); + } + + void detect_rows_with_changed_bounds() { + for (auto j : m_columns_with_changed_bound.m_index) + detect_rows_with_changed_bounds_for_column(j); + } + + void update_x_and_inf_costs_for_columns_with_changed_bounds() { + for (auto j : m_columns_with_changed_bound.m_index) + update_x_and_inf_costs_for_column_with_changed_bounds(j); + } + + void update_x_and_inf_costs_for_columns_with_changed_bounds_tableau() { + lean_assert(ax_is_correct()); + for (auto j : m_columns_with_changed_bound.m_index) + update_x_and_inf_costs_for_column_with_changed_bounds(j); + + if (tableau_with_costs()) { + for (unsigned j : m_basic_columns_with_changed_cost.m_index) + m_mpq_lar_core_solver.m_r_solver.update_inf_cost_for_column_tableau(j); + lean_assert(m_mpq_lar_core_solver.m_r_solver.reduced_costs_are_correct_tableau()); + } + } + + + void solve_with_core_solver() { + if (!use_tableau()) + add_last_rows_to_lu(m_mpq_lar_core_solver.m_r_solver); + if (m_mpq_lar_core_solver.need_to_presolve_with_double_solver()) { + add_last_rows_to_lu(m_mpq_lar_core_solver.m_d_solver); + } + m_mpq_lar_core_solver.prefix_r(); + if (costs_are_used()) { + m_basic_columns_with_changed_cost.clear(); + m_basic_columns_with_changed_cost.resize(m_mpq_lar_core_solver.m_r_x.size()); + } + if (use_tableau()) + update_x_and_inf_costs_for_columns_with_changed_bounds_tableau(); + else + update_x_and_inf_costs_for_columns_with_changed_bounds(); + m_mpq_lar_core_solver.solve(); + set_status(m_mpq_lar_core_solver.m_r_solver.get_status()); + lean_assert(m_status != OPTIMAL || all_constraints_hold()); + } + + + numeric_pair get_basic_var_value_from_row_directly(unsigned i) { + numeric_pair r = zero_of_type>(); + + unsigned bj = m_mpq_lar_core_solver.m_r_solver.m_basis[i]; + for (const auto & c: A_r().m_rows[i]) { + if (c.m_j == bj) continue; + const auto & x = m_mpq_lar_core_solver.m_r_x[c.m_j]; + if (!is_zero(x)) + r -= c.m_value * x; + } + return r; + } + + + + numeric_pair get_basic_var_value_from_row(unsigned i) { + if (settings().use_tableau()) { + return get_basic_var_value_from_row_directly(i); + } + + numeric_pair r = zero_of_type>(); + m_mpq_lar_core_solver.calculate_pivot_row(i); + for (unsigned j : m_mpq_lar_core_solver.m_r_solver.m_pivot_row.m_index) { + lean_assert(m_mpq_lar_core_solver.m_r_solver.m_basis_heading[j] < 0); + r -= m_mpq_lar_core_solver.m_r_solver.m_pivot_row.m_data[j] * m_mpq_lar_core_solver.m_r_x[j]; + } + return r; + } + + template + void add_last_rows_to_lu(lp_primal_core_solver & s) { + auto & f = s.m_factorization; + if (f != nullptr) { + auto columns_to_replace = f->get_set_of_columns_to_replace_for_add_last_rows(s.m_basis_heading); + if (f->m_refactor_counter + columns_to_replace.size() >= 200 || f->has_dense_submatrix()) { + delete f; + f = nullptr; + } else { + f->add_last_rows_to_B(s.m_basis_heading, columns_to_replace); + } + } + if (f == nullptr) { + init_factorization(f, s.m_A, s.m_basis, m_settings); + if (f->get_status() != LU_status::OK) { + delete f; + f = nullptr; + } + } + + } + + bool x_is_correct() const { + if (m_mpq_lar_core_solver.m_r_x.size() != A_r().column_count()) { + // std::cout << "the size is off " << m_r_solver.m_x.size() << ", " << A().column_count() << std::endl; + return false; + } + for (unsigned i = 0; i < A_r().row_count(); i++) { + numeric_pair delta = A_r().dot_product_with_row(i, m_mpq_lar_core_solver.m_r_x); + if (!delta.is_zero()) { + // std::cout << "x is off ("; + // std::cout << "m_b[" << i << "] = " << m_b[i] << " "; + // std::cout << "left side = " << A().dot_product_with_row(i, m_r_solver.m_x) << ' '; + // std::cout << "delta = " << delta << ' '; + // std::cout << "iters = " << total_iterations() << ")" << std::endl; + // std::cout << "row " << i << " is off" << std::endl; + return false; + } + } + return true;; + + } + + bool var_is_registered(var_index vj) const { + if (vj >= m_terms_start_index) { + if (vj - m_terms_start_index >= m_terms.size()) + return false; + } else if ( vj >= A_r().column_count()) { + return false; + } + return true; + } + + unsigned constraint_stack_size() const { + return m_constraint_count.stack_size(); + } + + void fill_last_row_of_A_r(static_matrix> & A, const lar_term * ls) { + lean_assert(A.row_count() > 0); + lean_assert(A.column_count() > 0); + unsigned last_row = A.row_count() - 1; + lean_assert(A.m_rows[last_row].size() == 0); + for (auto & t : ls->m_coeffs) { + lean_assert(!is_zero(t.second)); + var_index j = t.first; + A.set(last_row, j, - t.second); + } + unsigned basis_j = A.column_count() - 1; + A.set(last_row, basis_j, mpq(1)); + } + // this fills the last row of A_d and sets the basis column: -1 in the last column of the row + void fill_last_row_of_A_d(static_matrix & A, const lar_term* ls) { + lean_assert(A.row_count() > 0); + lean_assert(A.column_count() > 0); + unsigned last_row = A.row_count() - 1; + lean_assert(A.m_rows[last_row].empty()); + + for (auto & t : ls->m_coeffs) { + lean_assert(!is_zero(t.second)); + var_index j = t.first; + A.set(last_row, j, - t.second.get_double()); + } + + unsigned basis_j = A.column_count() - 1; + A.set(last_row, basis_j, - 1 ); + } + + + template + void create_matrix_A(static_matrix & matr) { + lean_assert(false); // not implemented + /* + unsigned m = number_or_nontrivial_left_sides(); + unsigned n = m_vec_of_canonic_left_sides.size(); + if (matr.row_count() == m && matr.column_count() == n) + return; + matr.init_empty_matrix(m, n); + copy_from_mpq_matrix(matr); + */ + } + + template + void copy_from_mpq_matrix(static_matrix & matr) { + lean_assert(matr.row_count() == A_r().row_count()); + lean_assert(matr.column_count() == A_r().column_count()); + for (unsigned i = 0; i < matr.row_count(); i++) { + for (auto & it : A_r().m_rows[i]) { + matr.set(i, it.m_j, convert_struct::convert(it.get_val())); + } + } + } + + + bool try_to_set_fixed(column_info & ci) { + if (ci.upper_bound_is_set() && ci.low_bound_is_set() && ci.get_upper_bound() == ci.get_low_bound() && !ci.is_fixed()) { + ci.set_fixed_value(ci.get_upper_bound()); + return true; + } + return false; + } + + column_type get_column_type(const column_info & ci) { + auto ret = ci.get_column_type_no_flipping(); + if (ret == column_type::boxed) { // changing boxed to fixed because of the no span + if (ci.get_low_bound() == ci.get_upper_bound()) + ret = column_type::fixed; + } + return ret; + } + + std::string get_column_name(unsigned j) const { + if (j >= m_terms_start_index) + return std::string("_t") + T_to_string(j); + if (j >= m_columns_to_ext_vars_or_term_indices.size()) + return std::string("_s") + T_to_string(j); + + return std::string("v") + T_to_string(m_columns_to_ext_vars_or_term_indices[j]); + } + + bool all_constrained_variables_are_registered(const vector>& left_side) { + for (auto it : left_side) { + if (! var_is_registered(it.second)) + return false; + } + return true; + } + + constraint_index add_constraint(const vector>& left_side_with_terms, lconstraint_kind kind_par, const mpq& right_side_parm) { + /* + mpq rs = right_side_parm; + vector> left_side; + substitute_terms(one_of_type(), left_side_with_terms, left_side, rs); + lean_assert(left_side.size() > 0); + lean_assert(all_constrained_variables_are_registered(left_side)); + lar_constraint original_constr(left_side, kind_par, rs); + unsigned j; // j is the index of the basic variables corresponding to the left side + canonic_left_side ls = create_or_fetch_canonic_left_side(left_side, j); + mpq ratio = find_ratio_of_original_constraint_to_normalized(ls, original_constr); + auto kind = ratio.is_neg() ? flip_kind(kind_par) : kind_par; + rs/= ratio; + lar_normalized_constraint normalized_constraint(ls, ratio, kind, rs, original_constr); + m_constraints.push_back(normalized_constraint); + constraint_index constr_ind = m_constraints.size() - 1; + update_column_type_and_bound(j, kind, rs, constr_ind); + return constr_ind; + */ + lean_assert(false); // not implemented + return 0; + } + + bool all_constraints_hold() const { + if (m_settings.get_cancel_flag()) + return true; + std::unordered_map var_map; + get_model(var_map); + + for (unsigned i = 0; i < m_constraints.size(); i++) { + if (!constraint_holds(*m_constraints[i], var_map)) { + print_constraint(i, std::cout); + return false; + } + } + return true; + } + + bool constraint_holds(const lar_base_constraint & constr, std::unordered_map & var_map) const { + mpq left_side_val = get_left_side_val(constr, var_map); + switch (constr.m_kind) { + case LE: return left_side_val <= constr.m_right_side; + case LT: return left_side_val < constr.m_right_side; + case GE: return left_side_val >= constr.m_right_side; + case GT: return left_side_val > constr.m_right_side; + case EQ: return left_side_val == constr.m_right_side; + default: + lean_unreachable(); + } + return false; // it is unreachable + } + + + + + + + + bool the_relations_are_of_same_type(const vector> & evidence, lconstraint_kind & the_kind_of_sum) const { + unsigned n_of_G = 0, n_of_L = 0; + bool strict = false; + for (auto & it : evidence) { + mpq coeff = it.first; + constraint_index con_ind = it.second; + lconstraint_kind kind = coeff.is_pos() ? + m_constraints[con_ind]->m_kind : + flip_kind(m_constraints[con_ind]->m_kind); + if (kind == GT || kind == LT) + strict = true; + if (kind == GE || kind == GT) n_of_G++; + else if (kind == LE || kind == LT) n_of_L++; + } + the_kind_of_sum = n_of_G ? GE : (n_of_L ? LE : EQ); + if (strict) + the_kind_of_sum = static_cast((static_cast(the_kind_of_sum) / 2)); + + return n_of_G == 0 || n_of_L == 0; + } + + static void register_in_map(std::unordered_map & coeffs, const lar_base_constraint & cn, const mpq & a) { + for (auto & it : cn.get_left_side_coefficients()) { + unsigned j = it.second; + auto p = coeffs.find(j); + if (p == coeffs.end()) + coeffs[j] = it.first * a; + else { + p->second += it.first * a; + if (p->second.is_zero()) + coeffs.erase(p); + } + } + } + bool the_left_sides_sum_to_zero(const vector> & evidence) const { + std::unordered_map coeff_map; + for (auto & it : evidence) { + mpq coeff = it.first; + constraint_index con_ind = it.second; + lean_assert(con_ind < m_constraints.size()); + register_in_map(coeff_map, *m_constraints[con_ind], coeff); + } + + if (!coeff_map.empty()) { + std::cout << "left side = "; + vector> t; + for (auto & it : coeff_map) { + t.push_back(std::make_pair(it.second, it.first)); + } + print_linear_combination_of_column_indices(t, std::cout); + std::cout << std::endl; + return false; + } + + return true; + } + + bool the_right_sides_do_not_sum_to_zero(const vector> & evidence) { + mpq ret = numeric_traits::zero(); + for (auto & it : evidence) { + mpq coeff = it.first; + constraint_index con_ind = it.second; + lean_assert(con_ind < m_constraints.size()); + const lar_constraint & constr = *m_constraints[con_ind]; + ret += constr.m_right_side * coeff; + } + return !numeric_traits::is_zero(ret); + } + + bool explanation_is_correct(const vector>& explanation) const { +#ifdef LEAN_DEBUG + lconstraint_kind kind; + lean_assert(the_relations_are_of_same_type(explanation, kind)); + lean_assert(the_left_sides_sum_to_zero(explanation)); + mpq rs = sum_of_right_sides_of_explanation(explanation); + switch (kind) { + case LE: lean_assert(rs < zero_of_type()); + break; + case LT: lean_assert(rs <= zero_of_type()); + break; + case GE: lean_assert(rs > zero_of_type()); + break; + case GT: lean_assert(rs >= zero_of_type()); + break; + case EQ: lean_assert(rs != zero_of_type()); + break; + default: + lean_assert(false); + return false; + } +#endif + return true; + } + + bool inf_explanation_is_correct() const { +#ifdef LEAN_DEBUG + vector> explanation; + get_infeasibility_explanation(explanation); + return explanation_is_correct(explanation); +#endif + return true; + } + + mpq sum_of_right_sides_of_explanation(const vector> & explanation) const { + mpq ret = numeric_traits::zero(); + for (auto & it : explanation) { + mpq coeff = it.first; + constraint_index con_ind = it.second; + lean_assert(con_ind < m_constraints.size()); + ret += (m_constraints[con_ind]->m_right_side - m_constraints[con_ind]->get_free_coeff_of_left_side()) * coeff; + } + return ret; + } + + // template + // void prepare_core_solver_fields_with_signature(static_matrix & A, vector & x, + // vector & low_bound, + // vector & upper_bound, const lar_solution_signature & signature) { + // create_matrix_A_r(A); + // fill_bounds_for_core_solver(low_bound, upper_bound); + // if (m_status == INFEASIBLE) { + // lean_assert(false); // not implemented + // } + + // resize_and_init_x_with_signature(x, low_bound, upper_bound, signature); + // lean_assert(A.column_count() == x.size()); + // } + + // void find_solution_signature_with_doubles(lar_solution_signature & signature) { + // static_matrix A; + // vector x, low_bounds, upper_bounds; + // lean_assert(false); // not implemented + // // prepare_core_solver_fields(A, x, low_bounds, upper_bounds); + // vector column_scale_vector; + // vector right_side_vector(A.row_count(), 0); + + // scaler scaler(right_side_vector, + // A, + // m_settings.scaling_minimum, + // m_settings.scaling_maximum, + // column_scale_vector, + // m_settings); + // if (!scaler.scale()) { + // // the scale did not succeed, unscaling + // A.clear(); + // create_matrix_A_r(A); + // for (auto & s : column_scale_vector) + // s = 1.0; + // } + // vector costs(A.column_count()); + // auto core_solver = lp_primal_core_solver(A, + // right_side_vector, + // x, + // m_mpq_lar_core_solver.m_basis, + // m_mpq_lar_core_solver.m_nbasis, + // m_mpq_lar_core_solver.m_heading, + // costs, + // m_mpq_lar_core_solver.m_column_types(), + // low_bounds, + // upper_bounds, + // m_settings, + // *this); + // core_solver.find_feasible_solution(); + // extract_signature_from_lp_core_solver(core_solver, signature); + // } + + bool has_lower_bound(var_index var, constraint_index& ci, mpq& value, bool& is_strict) { + + if (var >= m_vars_to_ul_pairs.size()) { + // TBD: bounds on terms could also be used, caller may have to track these. + return false; + } + const ul_pair & ul = m_vars_to_ul_pairs[var]; + ci = ul.low_bound_witness(); + if (ci != static_cast(-1)) { + auto& p = m_mpq_lar_core_solver.m_r_low_bounds()[var]; + value = p.x; + is_strict = p.y.is_pos(); + return true; + } + else { + return false; + } + } + + bool has_upper_bound(var_index var, constraint_index& ci, mpq& value, bool& is_strict) { + + if (var >= m_vars_to_ul_pairs.size()) { + // TBD: bounds on terms could also be used, caller may have to track these. + return false; + } + const ul_pair & ul = m_vars_to_ul_pairs[var]; + ci = ul.upper_bound_witness(); + if (ci != static_cast(-1)) { + auto& p = m_mpq_lar_core_solver.m_r_upper_bounds()[var]; + value = p.x; + is_strict = p.y.is_neg(); + return true; + } + else { + return false; + } + } + + + void get_infeasibility_explanation(vector> & explanation) const { + explanation.clear(); + if (m_infeasible_column_index != -1) { + fill_explanation_from_infeasible_column(explanation); + return; + } + if (m_mpq_lar_core_solver.get_infeasible_sum_sign() == 0) { + return; + } + // the infeasibility sign + int inf_sign; + auto inf_row = m_mpq_lar_core_solver.get_infeasibility_info(inf_sign); + get_infeasibility_explanation_for_inf_sign(explanation, inf_row, inf_sign); + lean_assert(explanation_is_correct(explanation)); + } + + void get_infeasibility_explanation_for_inf_sign( + vector> & explanation, + const vector> & inf_row, + int inf_sign) const { + + for (auto & it : inf_row) { + mpq coeff = it.first; + unsigned j = it.second; + + int adj_sign = coeff.is_pos() ? inf_sign : -inf_sign; + const ul_pair & ul = m_vars_to_ul_pairs[j]; + + constraint_index bound_constr_i = adj_sign < 0 ? ul.upper_bound_witness() : ul.low_bound_witness(); + lean_assert(bound_constr_i < m_constraints.size()); + explanation.push_back(std::make_pair(coeff, bound_constr_i)); + } + } + + + + void get_model(std::unordered_map & variable_values) const { + lean_assert(m_status == OPTIMAL); + mpq delta = m_mpq_lar_core_solver.find_delta_for_strict_bounds(); + for (unsigned i = 0; i < m_mpq_lar_core_solver.m_r_x.size(); i++ ) { + const numeric_pair & rp = m_mpq_lar_core_solver.m_r_x[i]; + variable_values[i] = rp.x + delta * rp.y; + } + } + + + std::string get_variable_name(var_index vi) const { + return get_column_name(vi); + } + + // ********** print region start + void print_constraint(constraint_index ci, std::ostream & out) const { + if (ci >= m_constraints.size()) { + out << "constraint " << T_to_string(ci) << " is not found"; + out << std::endl; + return; + } + + print_constraint(m_constraints[ci], out); + } + + void print_constraints(std::ostream& out) const { + for (auto c : m_constraints) { + print_constraint(c, out); + } + } + + void print_terms(std::ostream& out) const { + for (auto it : m_terms) { + print_term(*it, out); + out << "\n"; + } + } + + void print_left_side_of_constraint(const lar_base_constraint * c, std::ostream & out) const { + print_linear_combination_of_column_indices(c->get_left_side_coefficients(), out); + mpq free_coeff = c->get_free_coeff_of_left_side(); + if (!is_zero(free_coeff)) + out << " + " << free_coeff; + + } + + void print_term(lar_term const& term, std::ostream & out) const { + if (!numeric_traits::is_zero(term.m_v)) { + out << term.m_v << " + "; + } + print_linear_combination_of_column_indices(term.coeffs_as_vector(), out); + } + + mpq get_left_side_val(const lar_base_constraint & cns, const std::unordered_map & var_map) const { + mpq ret = cns.get_free_coeff_of_left_side(); + for (auto & it : cns.get_left_side_coefficients()) { + var_index j = it.second; + auto vi = var_map.find(j); + lean_assert(vi != var_map.end()); + ret += it.first * vi->second; + } + return ret; + } + + void print_constraint(const lar_base_constraint * c, std::ostream & out) const { + print_left_side_of_constraint(c, out); + out << " " << lconstraint_kind_string(c->m_kind) << " " << c->m_right_side << std::endl; + } + + void fill_var_set_for_random_update(unsigned sz, var_index const * vars, vector& column_list) { + for (unsigned i = 0; i < sz; i++) { + var_index var = vars[i]; + if (var >= m_terms_start_index) { // handle the term + for (auto & it : m_terms[var - m_terms_start_index]->m_coeffs) { + column_list.push_back(it.first); + } + } else { + column_list.push_back(var); + } + } + } + + void random_update(unsigned sz, var_index const * vars) { + vector column_list; + fill_var_set_for_random_update(sz, vars, column_list); + random_updater ru(m_mpq_lar_core_solver, column_list); + ru.update(); + } + + + void try_pivot_fixed_vars_from_basis() { + m_mpq_lar_core_solver.m_r_solver.pivot_fixed_vars_from_basis(); + } + + void pop() { + pop(1); + } + + + bool column_represents_row_in_tableau(unsigned j) { + return m_vars_to_ul_pairs()[j].m_i != static_cast(-1); + } + + void make_sure_that_the_bottom_right_elem_not_zero_in_tableau(unsigned i, unsigned j) { + // i, j - is the indices of the bottom-right element of the tableau + lean_assert(A_r().row_count() == i + 1 && A_r().column_count() == j + 1); + auto & last_column = A_r().m_columns[j]; + int non_zero_column_cell_index = -1; + for (unsigned k = last_column.size(); k-- > 0;){ + auto & cc = last_column[k]; + if (cc.m_i == i) + return; + non_zero_column_cell_index = k; + } + + lean_assert(non_zero_column_cell_index != -1); + lean_assert(static_cast(non_zero_column_cell_index) != i); + m_mpq_lar_core_solver.m_r_solver.transpose_rows_tableau(last_column[non_zero_column_cell_index].m_i, i); + } + + void remove_last_row_and_column_from_tableau(unsigned j) { + lean_assert(A_r().column_count() == m_mpq_lar_core_solver.m_r_solver.m_costs.size()); + auto & slv = m_mpq_lar_core_solver.m_r_solver; + unsigned i = A_r().row_count() - 1; //last row index + make_sure_that_the_bottom_right_elem_not_zero_in_tableau(i, j); + if (slv.m_basis_heading[j] < 0) { + slv.pivot_column_tableau(j, i); + } + + auto & last_row = A_r().m_rows[i]; + mpq &cost_j = m_mpq_lar_core_solver.m_r_solver.m_costs[j]; + bool cost_is_nz = !is_zero(cost_j); + for (unsigned k = last_row.size(); k-- > 0;) { + auto &rc = last_row[k]; + if (cost_is_nz) { + m_mpq_lar_core_solver.m_r_solver.m_d[rc.m_j] += cost_j*rc.get_val(); + } + + A_r().remove_element(last_row, rc); + } + lean_assert(last_row.size() == 0); + lean_assert(A_r().m_columns[j].size() == 0); + A_r().m_rows.pop_back(); + A_r().m_columns.pop_back(); + slv.m_b.pop_back(); + } + + void remove_last_column_from_tableau(unsigned j) { + lean_assert(j == A_r().column_count() - 1); + // the last column has to be empty + lean_assert(A_r().m_columns[j].size() == 0); + A_r().m_columns.pop_back(); + } + + void remove_last_column_from_basis_tableau(unsigned j) { + auto& rslv = m_mpq_lar_core_solver.m_r_solver; + int i = rslv.m_basis_heading[j]; + if (i >= 0) { // j is a basic var + int last_pos = static_cast(rslv.m_basis.size()) - 1; + lean_assert(last_pos >= 0); + if (i != last_pos) { + unsigned j_at_last_pos = rslv.m_basis[last_pos]; + rslv.m_basis[i] = j_at_last_pos; + rslv.m_basis_heading[j_at_last_pos] = i; + } + rslv.m_basis.pop_back(); // remove j from the basis + } else { + int last_pos = static_cast(rslv.m_nbasis.size()) - 1; + lean_assert(last_pos >= 0); + i = - 1 - i; + if (i != last_pos) { + unsigned j_at_last_pos = rslv.m_nbasis[last_pos]; + rslv.m_nbasis[i] = j_at_last_pos; + rslv.m_basis_heading[j_at_last_pos] = - i - 1; + } + rslv.m_nbasis.pop_back(); // remove j from the basis + } + rslv.m_basis_heading.pop_back(); + lean_assert(rslv.m_basis.size() == A_r().row_count()); + lean_assert(rslv.basis_heading_is_correct()); + } + + void remove_column_from_tableau(unsigned j) { + auto& rslv = m_mpq_lar_core_solver.m_r_solver; + lean_assert(j == A_r().column_count() - 1); + lean_assert(A_r().column_count() == m_mpq_lar_core_solver.m_r_solver.m_costs.size()); + if (column_represents_row_in_tableau(j)) { + remove_last_row_and_column_from_tableau(j); + if (rslv.m_basis_heading[j] < 0) + rslv.change_basis_unconditionally(j, rslv.m_basis[A_r().row_count()]); // A_r().row_count() is the index of the last row in the basis still + } + else { + remove_last_column_from_tableau(j); + } + rslv.m_x.pop_back(); + rslv.m_d.pop_back(); + rslv.m_costs.pop_back(); + + remove_last_column_from_basis_tableau(j); + lean_assert(m_mpq_lar_core_solver.r_basis_is_OK()); + lean_assert(A_r().column_count() == m_mpq_lar_core_solver.m_r_solver.m_costs.size()); + } + + + void pop_tableau() { + lean_assert(m_mpq_lar_core_solver.m_r_solver.m_costs.size() == A_r().column_count()); + + lean_assert(m_mpq_lar_core_solver.m_r_solver.m_basis.size() == A_r().row_count()); + lean_assert(m_mpq_lar_core_solver.m_r_solver.basis_heading_is_correct()); + // We remove last variables starting from m_column_names.size() to m_vec_of_canonic_left_sides.size(). + // At this moment m_column_names is already popped + for (unsigned j = A_r().column_count(); j-- > m_columns_to_ext_vars_or_term_indices.size();) + remove_column_from_tableau(j); + lean_assert(m_mpq_lar_core_solver.m_r_solver.m_costs.size() == A_r().column_count()); + lean_assert(m_mpq_lar_core_solver.m_r_solver.m_basis.size() == A_r().row_count()); + lean_assert(m_mpq_lar_core_solver.m_r_solver.basis_heading_is_correct()); + } + + + + + void clean_inf_set_of_r_solver_after_pop() { + vector became_feas; + clean_large_elements_after_pop(A_r().column_count(), m_mpq_lar_core_solver.m_r_solver.m_inf_set); + std::unordered_set basic_columns_with_changed_cost; + auto inf_index_copy = m_mpq_lar_core_solver.m_r_solver.m_inf_set.m_index; + for (auto j: inf_index_copy) { + if (m_mpq_lar_core_solver.m_r_heading[j] >= 0) { + continue; + } + // some basic columns might become non-basic - these columns need to be made feasible + numeric_pair delta; + if (m_mpq_lar_core_solver.m_r_solver.make_column_feasible(j, delta)) + change_basic_x_by_delta_on_column(j, delta); + became_feas.push_back(j); + } + + for (unsigned j : became_feas) { + lean_assert(m_mpq_lar_core_solver.m_r_solver.m_basis_heading[j] < 0); + m_mpq_lar_core_solver.m_r_solver.m_d[j] -= m_mpq_lar_core_solver.m_r_solver.m_costs[j]; + m_mpq_lar_core_solver.m_r_solver.m_costs[j] = zero_of_type(); + m_mpq_lar_core_solver.m_r_solver.m_inf_set.erase(j); + } + became_feas.clear(); + for (unsigned j : m_mpq_lar_core_solver.m_r_solver.m_inf_set.m_index) { + lean_assert(m_mpq_lar_core_solver.m_r_heading[j] >= 0); + if (m_mpq_lar_core_solver.m_r_solver.column_is_feasible(j)) + became_feas.push_back(j); + } + for (unsigned j : became_feas) + m_mpq_lar_core_solver.m_r_solver.m_inf_set.erase(j); + + + if (use_tableau_costs()) { + for (unsigned j : became_feas) + m_mpq_lar_core_solver.m_r_solver.update_inf_cost_for_column_tableau(j); + for (unsigned j : basic_columns_with_changed_cost) + m_mpq_lar_core_solver.m_r_solver.update_inf_cost_for_column_tableau(j); + lean_assert(m_mpq_lar_core_solver.m_r_solver.reduced_costs_are_correct_tableau()); + } + } + + + void shrink_explanation_to_minimum(vector> & explanation) const { + // implementing quickXplain + quick_xplain::run(explanation, *this); + lean_assert(this->explanation_is_correct(explanation)); + } + + +}; +} diff --git a/src/util/lp/lar_term.h b/src/util/lp/lar_term.h new file mode 100644 index 000000000..0e715ad0b --- /dev/null +++ b/src/util/lp/lar_term.h @@ -0,0 +1,64 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#pragma once +#include "util/lp/indexed_vector.h" +namespace lean { +struct lar_term { + // the term evaluates to sum of m_coeffs + m_v + std::unordered_map m_coeffs; + mpq m_v; + lar_term() {} + void add_to_map(unsigned j, const mpq& c) { + auto it = m_coeffs.find(j); + if (it == m_coeffs.end()) { + m_coeffs.emplace(j, c); + } else { + it->second += c; + if (is_zero(it->second)) + m_coeffs.erase(it); + } + } + + unsigned size() const { return static_cast(m_coeffs.size()); } + + const std::unordered_map & coeffs() const { + return m_coeffs; + } + + lar_term(const vector>& coeffs, + const mpq & v) : m_v(v) { + for (const auto & p : coeffs) { + add_to_map(p.second, p.first); + } + } + bool operator==(const lar_term & a) const { return false; } // take care not to create identical terms + bool operator!=(const lar_term & a) const { return ! (*this == a);} + // some terms get used in add constraint + // it is the same as the offset in the m_constraints + + vector> coeffs_as_vector() const { + vector> ret; + for (const auto & p : m_coeffs) { + ret.push_back(std::make_pair(p.second, p.first)); + } + return ret; + } + + // j is the basic variable to substitute + void subst(unsigned j, indexed_vector & li) { + auto it = m_coeffs.find(j); + if (it == m_coeffs.end()) return; + const mpq & b = it->second; + for (unsigned it_j :li.m_index) { + add_to_map(it_j, - b * li.m_data[it_j]); + } + m_coeffs.erase(it); + } + + bool contains(unsigned j) const { + return m_coeffs.find(j) != m_coeffs.end(); + } +}; +} diff --git a/src/util/lp/linear_combination_iterator.h b/src/util/lp/linear_combination_iterator.h new file mode 100644 index 000000000..9a1a2de77 --- /dev/null +++ b/src/util/lp/linear_combination_iterator.h @@ -0,0 +1,47 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#pragma once +namespace lean { +template +struct linear_combination_iterator { + virtual bool next(T & a, unsigned & i) = 0; + virtual bool next(unsigned & i) = 0; + virtual void reset() = 0; + virtual linear_combination_iterator * clone() = 0; + virtual ~linear_combination_iterator(){} + virtual unsigned size() const = 0; +}; +template +struct linear_combination_iterator_on_vector : linear_combination_iterator { + vector> & m_vector; + int m_offset = 0; + bool next(T & a, unsigned & i) { + if(m_offset >= m_vector.size()) + return false; + auto & p = m_vector[m_offset]; + a = p.first; + i = p.second; + m_offset++; + return true; + } + + bool next(unsigned & i) { + if(m_offset >= m_vector.size()) + return false; + auto & p = m_vector[m_offset]; + i = p.second; + m_offset++; + return true; + } + + void reset() {m_offset = 0;} + linear_combination_iterator * clone() { + return new linear_combination_iterator_on_vector(m_vector); + } + linear_combination_iterator_on_vector(vector> & vec): m_vector(vec) {} + unsigned size() const { return m_vector.size(); } +}; + +} diff --git a/src/util/lp/lp_core_solver_base.h b/src/util/lp/lp_core_solver_base.h new file mode 100644 index 000000000..d8618f32a --- /dev/null +++ b/src/util/lp/lp_core_solver_base.h @@ -0,0 +1,683 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#pragma once +#include +#include "util/vector.h" +#include +#include "util/lp/lp_utils.h" +#include "util/lp/core_solver_pretty_printer.h" +#include "util/lp/numeric_pair.h" +#include "util/lp/static_matrix.h" +#include "util/lp/lu.h" +#include "util/lp/permutation_matrix.h" +#include "util/lp/column_namer.h" +namespace lean { + +template // X represents the type of the x variable and the bounds +class lp_core_solver_base { + unsigned m_total_iterations = 0; + unsigned inc_total_iterations() { ++m_settings.st().m_total_iterations; return m_total_iterations++; } +private: + lp_status m_status; +public: + bool current_x_is_feasible() const { return m_inf_set.size() == 0; } + bool current_x_is_infeasible() const { return m_inf_set.size() != 0; } + int_set m_inf_set; + bool m_using_infeas_costs = false; + + + vector m_columns_nz; // m_columns_nz[i] keeps an approximate value of non zeroes the i-th column + vector m_rows_nz; // m_rows_nz[i] keeps an approximate value of non zeroes in the i-th row + indexed_vector m_pivot_row_of_B_1; // the pivot row of the reverse of B + indexed_vector m_pivot_row; // this is the real pivot row of the simplex tableu + static_matrix & m_A; // the matrix A + vector & m_b; // the right side + vector & m_basis; + vector& m_nbasis; + vector& m_basis_heading; + vector & m_x; // a feasible solution, the fist time set in the constructor + vector & m_costs; + lp_settings & m_settings; + vector m_y; // the buffer for yB = cb + // a device that is able to solve Bx=c, xB=d, and change the basis + lu * m_factorization = nullptr; + const column_namer & m_column_names; + indexed_vector m_w; // the vector featuring in 24.3 of the Chvatal book + vector m_d; // the vector of reduced costs + indexed_vector m_ed; // the solution of B*m_ed = a + unsigned m_iters_with_no_cost_growing = 0; + const vector & m_column_types; + const vector & m_low_bounds; + const vector & m_upper_bounds; + vector m_column_norms; // the approximate squares of column norms that help choosing a profitable column + vector m_copy_of_xB; + unsigned m_basis_sort_counter = 0; + vector m_steepest_edge_coefficients; + vector m_trace_of_basis_change_vector; // the even positions are entering, the odd positions are leaving + bool m_tracing_basis_changes = false; + int_set* m_pivoted_rows = nullptr; + bool m_look_for_feasible_solution_only = false; + void start_tracing_basis_changes() { + m_trace_of_basis_change_vector.resize(0); + m_tracing_basis_changes = true; + } + + void stop_tracing_basis_changes() { + m_tracing_basis_changes = false; + } + + void trace_basis_change(unsigned entering, unsigned leaving) { + unsigned size = m_trace_of_basis_change_vector.size(); + if (size >= 2 && m_trace_of_basis_change_vector[size-2] == leaving + && m_trace_of_basis_change_vector[size -1] == entering) { + m_trace_of_basis_change_vector.pop_back(); + m_trace_of_basis_change_vector.pop_back(); + } else { + m_trace_of_basis_change_vector.push_back(entering); + m_trace_of_basis_change_vector.push_back(leaving); + } + } + + unsigned m_m() const { return m_A.row_count(); } // it is the length of basis. The matrix m_A has m_m rows and the dimension of the matrix A is m_m + unsigned m_n() const { return m_A.column_count(); } // the number of columns in the matrix m_A + + lp_core_solver_base(static_matrix & A, + vector & b, // the right side vector + vector & basis, + vector & nbasis, + vector & heading, + vector & x, + vector & costs, + lp_settings & settings, + const column_namer& column_names, + const vector & column_types, + const vector & low_bound_values, + const vector & upper_bound_values); + + void allocate_basis_heading(); + void init(); + + virtual ~lp_core_solver_base() { + if (m_factorization != nullptr) + delete m_factorization; + } + + vector & non_basis() { + return m_nbasis; + } + + const vector & non_basis() const { return m_nbasis; } + + + + void set_status(lp_status status) { + m_status = status; + } + lp_status get_status() const{ + return m_status; + } + + void fill_cb(T * y); + + void fill_cb(vector & y); + + void solve_yB(vector & y); + + void solve_Bd(unsigned entering); + + void solve_Bd(unsigned entering, indexed_vector & column); + + void pretty_print(std::ostream & out); + + void save_state(T * w_buffer, T * d_buffer); + + void restore_state(T * w_buffer, T * d_buffer); + + X get_cost() { + return dot_product(m_costs, m_x); + } + + void copy_m_w(T * buffer); + + void restore_m_w(T * buffer); + + // needed for debugging + void copy_m_ed(T * buffer); + + void restore_m_ed(T * buffer); + + bool A_mult_x_is_off() const; + + bool A_mult_x_is_off_on_index(const vector & index) const; + // from page 182 of Istvan Maros's book + void calculate_pivot_row_of_B_1(unsigned pivot_row); + + void calculate_pivot_row_when_pivot_row_of_B1_is_ready(unsigned pivot_row); + + void update_x(unsigned entering, const X & delta); + + const T & get_var_value(unsigned j) const { + return m_x[j]; + } + + void print_statistics(char const* str, X cost, std::ostream & message_stream); + + bool print_statistics_with_iterations_and_check_that_the_time_is_over(std::ostream & message_stream); + + bool print_statistics_with_iterations_and_nonzeroes_and_cost_and_check_that_the_time_is_over(char const* str, std::ostream & message_stream); + + bool print_statistics_with_cost_and_check_that_the_time_is_over(X cost, std::ostream & message_stream); + + unsigned total_iterations() const { return m_total_iterations; } + + void set_total_iterations(unsigned s) { m_total_iterations = s; } + + void set_non_basic_x_to_correct_bounds(); + + bool at_bound(const X &x, const X & bound) const { + return !below_bound(x, bound) && !above_bound(x, bound); + } + + + bool need_to_pivot_to_basis_tableau() const { + lean_assert(m_A.is_correct()); + unsigned m = m_A.row_count(); + for (unsigned i = 0; i < m; i++) { + unsigned bj = m_basis[i]; + lean_assert(m_A.m_columns[bj].size() > 0); + if (m_A.m_columns[bj].size() > 1 || m_A.get_val(m_A.m_columns[bj][0]) != one_of_type()) return true; + } + return false; + } + + bool reduced_costs_are_correct_tableau() const { + if (m_settings.simplex_strategy() == simplex_strategy_enum::tableau_rows) + return true; + lean_assert(m_A.is_correct()); + if (m_using_infeas_costs) { + if (infeasibility_costs_are_correct() == false) { + std::cout << "infeasibility_costs_are_correct() does not hold" << std::endl; + return false; + } + } + + unsigned n = m_A.column_count(); + for (unsigned j = 0; j < n; j++) { + if (m_basis_heading[j] >= 0) { + if (!is_zero(m_d[j])) { + + std::cout << "case a\n"; + print_column_info(j, std::cout); + return false; + } + } else { + auto d = m_costs[j]; + for (auto & cc : this->m_A.m_columns[j]) { + d -= this->m_costs[this->m_basis[cc.m_i]] * this->m_A.get_val(cc); + } + if (m_d[j] != d) { + std::cout << "case b\n"; + print_column_info(j, std::cout); + return false; + } + } + } + return true; + } + + bool below_bound(const X & x, const X & bound) const { + if (precise()) return x < bound; + return below_bound_numeric(x, bound, m_settings.primal_feasibility_tolerance); + } + + bool above_bound(const X & x, const X & bound) const { + if (precise()) return x > bound; + return above_bound_numeric(x, bound, m_settings.primal_feasibility_tolerance); + } + + bool x_below_low_bound(unsigned p) const { + return below_bound(m_x[p], m_low_bounds[p]); + } + + bool infeasibility_costs_are_correct() const; + bool infeasibility_cost_is_correct_for_column(unsigned j) const; + + bool x_above_low_bound(unsigned p) const { + return above_bound(m_x[p], m_low_bounds[p]); + } + + bool x_below_upper_bound(unsigned p) const { + return below_bound(m_x[p], m_upper_bounds[p]); + } + + + bool x_above_upper_bound(unsigned p) const { + return above_bound(m_x[p], m_upper_bounds[p]); + } + bool x_is_at_low_bound(unsigned j) const { + return at_bound(m_x[j], m_low_bounds[j]); + } + bool x_is_at_upper_bound(unsigned j) const { + return at_bound(m_x[j], m_upper_bounds[j]); + } + + bool x_is_at_bound(unsigned j) const { + return x_is_at_low_bound(j) || x_is_at_upper_bound(j); + } + bool column_is_feasible(unsigned j) const; + + bool calc_current_x_is_feasible_include_non_basis() const; + + bool inf_set_is_correct() const; + + bool column_is_dual_feasible(unsigned j) const; + + bool d_is_not_negative(unsigned j) const; + + bool d_is_not_positive(unsigned j) const; + + + bool time_is_over(); + + void rs_minus_Anx(vector & rs); + + bool find_x_by_solving(); + + bool update_basis_and_x(int entering, int leaving, X const & tt); + + bool basis_has_no_doubles() const; + + bool non_basis_has_no_doubles() const; + + bool basis_is_correctly_represented_in_heading() const ; + bool non_basis_is_correctly_represented_in_heading() const ; + + bool basis_heading_is_correct() const; + + void restore_x_and_refactor(int entering, int leaving, X const & t); + + void restore_x(unsigned entering, X const & t); + + void fill_reduced_costs_from_m_y_by_rows(); + + void copy_rs_to_xB(vector & rs); + virtual bool low_bounds_are_set() const { return false; } + X low_bound_value(unsigned j) const { return m_low_bounds[j]; } + X upper_bound_value(unsigned j) const { return m_upper_bounds[j]; } + + column_type get_column_type(unsigned j) const {return m_column_types[j]; } + + bool pivot_row_element_is_too_small_for_ratio_test(unsigned j) { + return m_settings.abs_val_is_smaller_than_pivot_tolerance(m_pivot_row[j]); + } + + X bound_span(unsigned j) const { + return m_upper_bounds[j] - m_low_bounds[j]; + } + + std::string column_name(unsigned column) const; + + void copy_right_side(vector & rs); + + void add_delta_to_xB(vector & del); + + void find_error_in_BxB(vector& rs); + + // recalculates the projection of x to B, such that Ax = b, whereab is the right side + void solve_Ax_eq_b(); + + bool snap_non_basic_x_to_bound() { + bool ret = false; + for (unsigned j : non_basis()) + ret = snap_column_to_bound(j) || ret; + return ret; + } + + + + bool snap_column_to_bound(unsigned j) { + switch (m_column_types[j]) { + case column_type::fixed: + if (x_is_at_bound(j)) + break; + m_x[j] = m_low_bounds[j]; + return true; + case column_type::boxed: + if (x_is_at_bound(j)) + break; // we should preserve x if possible + // snap randomly + if (my_random() % 2 == 1) + m_x[j] = m_low_bounds[j]; + else + m_x[j] = m_upper_bounds[j]; + return true; + case column_type::low_bound: + if (x_is_at_low_bound(j)) + break; + m_x[j] = m_low_bounds[j]; + return true; + case column_type::upper_bound: + if (x_is_at_upper_bound(j)) + break; + m_x[j] = m_upper_bounds[j]; + return true; + default: + break; + } + return false; + } + + bool make_column_feasible(unsigned j, numeric_pair & delta) { + lean_assert(m_basis_heading[j] < 0); + auto & x = m_x[j]; + switch (m_column_types[j]) { + case column_type::fixed: + lean_assert(m_low_bounds[j] == m_upper_bounds[j]); + if (x != m_low_bounds[j]) { + delta = m_low_bounds[j] - x; + x = m_low_bounds[j]; + return true; + } + break; + case column_type::boxed: + if (x < m_low_bounds[j]) { + delta = m_low_bounds[j] - x; + x = m_low_bounds[j]; + return true; + } + if (x > m_upper_bounds[j]) { + delta = m_upper_bounds[j] - x; + x = m_upper_bounds[j]; + return true; + } + break; + case column_type::low_bound: + if (x < m_low_bounds[j]) { + delta = m_low_bounds[j] - x; + x = m_low_bounds[j]; + return true; + } + break; + case column_type::upper_bound: + if (x > m_upper_bounds[j]) { + delta = m_upper_bounds[j] - x; + x = m_upper_bounds[j]; + return true; + } + break; + case column_type::free_column: + break; + default: + lean_assert(false); + break; + } + return false; + } + + + void snap_non_basic_x_to_bound_and_free_to_zeroes(); + void snap_xN_to_bounds_and_fill_xB(); + + void snap_xN_to_bounds_and_free_columns_to_zeroes(); + + void init_reduced_costs_for_one_iteration(); + + non_basic_column_value_position get_non_basic_column_value_position(unsigned j) const; + + void init_lu(); + int pivots_in_column_and_row_are_different(int entering, int leaving) const; + void pivot_fixed_vars_from_basis(); + bool pivot_for_tableau_on_basis(); + bool pivot_row_for_tableau_on_basis(unsigned row); + void init_basic_part_of_basis_heading() { + unsigned m = m_basis.size(); + for (unsigned i = 0; i < m; i++) { + unsigned column = m_basis[i]; + m_basis_heading[column] = i; + } + } + + void init_non_basic_part_of_basis_heading() { + this->m_nbasis.clear(); + for (int j = m_basis_heading.size(); j--;){ + if (m_basis_heading[j] < 0) { + m_nbasis.push_back(j); + // the index of column j in m_nbasis is (- basis_heading[j] - 1) + m_basis_heading[j] = - static_cast(m_nbasis.size()); + } + } + } + + void init_basis_heading_and_non_basic_columns_vector() { + m_basis_heading.resize(0); + m_basis_heading.resize(m_n(), -1); + init_basic_part_of_basis_heading(); + init_non_basic_part_of_basis_heading(); + } + + void change_basis_unconditionally(unsigned entering, unsigned leaving) { + lean_assert(m_basis_heading[entering] < 0); + int place_in_non_basis = -1 - m_basis_heading[entering]; + if (static_cast(place_in_non_basis) >= m_nbasis.size()) { + // entering variable in not in m_nbasis, we need to put it back; + m_basis_heading[entering] = place_in_non_basis = m_nbasis.size(); + m_nbasis.push_back(entering); + } + + int place_in_basis = m_basis_heading[leaving]; + m_basis_heading[entering] = place_in_basis; + m_basis[place_in_basis] = entering; + m_basis_heading[leaving] = -place_in_non_basis - 1; + m_nbasis[place_in_non_basis] = leaving; + if (m_tracing_basis_changes) + trace_basis_change(entering, leaving); + + } + + void change_basis(unsigned entering, unsigned leaving) { + lean_assert(m_basis_heading[entering] < 0); + + int place_in_basis = m_basis_heading[leaving]; + int place_in_non_basis = - m_basis_heading[entering] - 1; + m_basis_heading[entering] = place_in_basis; + m_basis[place_in_basis] = entering; + + m_basis_heading[leaving] = -place_in_non_basis - 1; + m_nbasis[place_in_non_basis] = leaving; + + if (m_tracing_basis_changes) + trace_basis_change(entering, leaving); + } + + void restore_basis_change(unsigned entering, unsigned leaving) { + if (m_basis_heading[entering] < 0) { + return; // the basis has not been changed + } + change_basis_unconditionally(leaving, entering); + } + + bool non_basic_column_is_set_correctly(unsigned j) const { + if (j >= this->m_n()) + return false; + switch (this->m_column_types[j]) { + case column_type::fixed: + case column_type::boxed: + if (!this->x_is_at_bound(j)) + return false; + break; + case column_type::low_bound: + if (!this->x_is_at_low_bound(j)) + return false; + break; + case column_type::upper_bound: + if (!this->x_is_at_upper_bound(j)) + return false; + break; + case column_type::free_column: + break; + default: + lean_assert(false); + break; + } + return true; + } + bool non_basic_columns_are_set_correctly() const { + for (unsigned j : this->m_nbasis) + if (!column_is_feasible(j)) { + print_column_info(j, std::cout); + return false; + } + return true; + } + + void print_column_bound_info(unsigned j, std::ostream & out) const { + out << column_name(j) << " type = " << column_type_to_string(m_column_types[j]) << std::endl; + switch (m_column_types[j]) { + case column_type::fixed: + case column_type::boxed: + out << "(" << m_low_bounds[j] << ", " << m_upper_bounds[j] << ")" << std::endl; + break; + case column_type::low_bound: + out << m_low_bounds[j] << std::endl; + break; + case column_type::upper_bound: + out << m_upper_bounds[j] << std::endl; + break; + default: + break; + } + } + + void print_column_info(unsigned j, std::ostream & out) const { + out << "column_index = " << j << ", name = "<< column_name(j) << " type = " << column_type_to_string(m_column_types[j]) << std::endl; + switch (m_column_types[j]) { + case column_type::fixed: + case column_type::boxed: + out << "(" << m_low_bounds[j] << ", " << m_upper_bounds[j] << ")" << std::endl; + break; + case column_type::low_bound: + out << m_low_bounds[j] << std::endl; + break; + case column_type::upper_bound: + out << m_upper_bounds[j] << std::endl; + break; + case column_type::free_column: + break; + default: + lean_assert(false); + } + std::cout << "basis heading = " << m_basis_heading[j] << std::endl; + std::cout << "x = " << m_x[j] << std::endl; + /* + std::cout << "cost = " << m_costs[j] << std::endl; + std:: cout << "m_d = " << m_d[j] << std::endl;*/ + } + + bool column_is_free(unsigned j) { return this->m_column_type[j] == free; } + + bool column_has_upper_bound(unsigned j) { + switch(m_column_types[j]) { + case column_type::free_column: + case column_type::low_bound: + return false; + default: + return true; + } + } + + bool bounds_for_boxed_are_set_correctly() const { + for (unsigned j = 0; j < m_column_types.size(); j++) { + if (m_column_types[j] != column_type::boxed) continue; + if (m_low_bounds[j] > m_upper_bounds[j]) + return false; + } + return true; + } + + bool column_has_low_bound(unsigned j) { + switch(m_column_types[j]) { + case column_type::free_column: + case column_type::upper_bound: + return false; + default: + return true; + } + } + + // only check for basic columns + bool calc_current_x_is_feasible() const { + unsigned i = this->m_m(); + while (i--) { + if (!column_is_feasible(m_basis[i])) + return false; + } + return true; + } + + int find_pivot_index_in_row(unsigned i, const vector & col) const { + for (const auto & c: col) { + if (c.m_i == i) + return c.m_offset; + } + return -1; + } + + void transpose_rows_tableau(unsigned i, unsigned ii); + + void pivot_to_reduced_costs_tableau(unsigned i, unsigned j); + + bool pivot_column_tableau(unsigned j, unsigned row_index); + bool divide_row_by_pivot(unsigned pivot_row, unsigned pivot_col); + + bool precise() const { return numeric_traits::precise(); } + + simplex_strategy_enum simplex_strategy() const { return + m_settings.simplex_strategy(); + } + + bool use_tableau() const { return m_settings.use_tableau(); } + + template + static void swap(vector &v, unsigned i, unsigned j) { + auto t = v[i]; + v[i] = v[j]; + v[j] = t; + } + + // called when transposing row i and ii + void transpose_basis(unsigned i, unsigned ii) { + swap(m_basis, i, ii); + swap(m_basis_heading, m_basis[i], m_basis[ii]); + } + + bool column_is_in_inf_set(unsigned j) const { + return m_inf_set.contains(j); + } + + void update_column_in_inf_set(unsigned j) { + if (column_is_feasible(j)) { + m_inf_set.erase(j); + } else { + m_inf_set.insert(j); + } + } + void insert_column_into_inf_set(unsigned j) { + m_inf_set.insert(j); + lean_assert(!column_is_feasible(j)); + } + void remove_column_from_inf_set(unsigned j) { + m_inf_set.erase(j); + lean_assert(column_is_feasible(j)); + } + bool costs_on_nbasis_are_zeros() const { + lean_assert(this->basis_heading_is_correct()); + for (unsigned j = 0; j < this->m_n(); j++) { + if (this->m_basis_heading[j] < 0) + lean_assert(is_zero(this->m_costs[j])); + } + return true; +} +}; +} diff --git a/src/util/lp/lp_core_solver_base.hpp b/src/util/lp/lp_core_solver_base.hpp new file mode 100644 index 000000000..34b2d0b68 --- /dev/null +++ b/src/util/lp/lp_core_solver_base.hpp @@ -0,0 +1,1007 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include +#include +#include "util/vector.h" +#include "util/lp/lp_utils.h" +#include "util/lp/lp_core_solver_base.h" +namespace lean { + +template lp_core_solver_base:: +lp_core_solver_base(static_matrix & A, + vector & b, // the right side vector + vector & basis, + vector & nbasis, + vector & heading, + vector & x, + vector & costs, + lp_settings & settings, + const column_namer& column_names, + const vector & column_types, + const vector & low_bound_values, + const vector & upper_bound_values): + m_status(FEASIBLE), + m_inf_set(A.column_count()), + m_pivot_row_of_B_1(A.row_count()), + m_pivot_row(A.column_count()), + m_A(A), + m_b(b), + m_basis(basis), + m_nbasis(nbasis), + m_basis_heading(heading), + m_x(x), + m_costs(costs), + m_settings(settings), + m_y(m_m()), + m_factorization(nullptr), + m_column_names(column_names), + m_w(m_m()), + m_d(m_n()), + m_ed(m_m()), + m_column_types(column_types), + m_low_bounds(low_bound_values), + m_upper_bounds(upper_bound_values), + m_column_norms(m_n()), + m_copy_of_xB(m_m()), + m_steepest_edge_coefficients(A.column_count()) { + lean_assert(bounds_for_boxed_are_set_correctly()); + init(); + init_basis_heading_and_non_basic_columns_vector(); +} + +template void lp_core_solver_base:: +allocate_basis_heading() { // the rest of initilization will be handled by the factorization class + init_basis_heading_and_non_basic_columns_vector(); + lean_assert(basis_heading_is_correct()); +} +template void lp_core_solver_base:: +init() { + my_random_init(m_settings.random_seed); + allocate_basis_heading(); + if (!use_tableau()) + init_factorization(m_factorization, m_A, m_basis, m_settings); +} + +template bool lp_core_solver_base:: +pivot_for_tableau_on_basis() { + m_d = m_costs; // we will be pivoting to m_d as well + unsigned m = m_A.row_count(); + for (unsigned i = 0; i < m; i++) + if (!pivot_column_tableau(m_basis[i], i)) + return false; + return true; +} + +// i is the pivot row, and j is the pivot column +template void lp_core_solver_base:: +pivot_to_reduced_costs_tableau(unsigned i, unsigned j) { + if (j >= m_d.size()) + return; + T &a = m_d[j]; + if (is_zero(a)) + return; + for (const row_cell & r: m_A.m_rows[i]) + if (r.m_j != j) + m_d[r.m_j] -= a * r.get_val(); + + a = zero_of_type(); // zero the pivot column's m_d finally +} + + +template void lp_core_solver_base:: +fill_cb(T * y){ + for (unsigned i = 0; i < m_m(); i++) { + y[i] = m_costs[m_basis[i]]; + } +} + + +template void lp_core_solver_base:: +fill_cb(vector & y){ + for (unsigned i = 0; i < m_m(); i++) { + y[i] = m_costs[m_basis[i]]; + } +} + +template void lp_core_solver_base:: +solve_yB(vector & y) { + fill_cb(y); // now y = cB, that is the projection of costs to basis + m_factorization->solve_yB_with_error_check(y, m_basis); +} + +// template void lp_core_solver_base:: +// update_index_of_ed() { +// m_index_of_ed.clear(); +// unsigned i = static_cast(m_ed.size()); +// while (i--) { +// if (!is_zero(m_ed[i])) +// m_index_of_ed.push_back(i); +// } +// } +template void lp_core_solver_base::solve_Bd(unsigned entering, indexed_vector & column) { + lean_assert(!m_settings.use_tableau()); + if (m_factorization == nullptr) { + init_factorization(m_factorization, m_A, m_basis, m_settings); + } + m_factorization->solve_Bd_faster(entering, column); +} + + +template void lp_core_solver_base:: +solve_Bd(unsigned entering) { + lean_assert(m_ed.is_OK()); + m_factorization->solve_Bd(entering, m_ed, m_w); + if (this->precise()) + m_columns_nz[entering] = m_ed.m_index.size(); + lean_assert(m_ed.is_OK()); + lean_assert(m_w.is_OK()); +#ifdef LEAN_DEBUG + // auto B = get_B(*m_factorization, m_basis); + // vector a(m_m()); + // m_A.copy_column_to_vector(entering, a); + // vector cd(m_ed.m_data); + // B.apply_from_left(cd, m_settings); + // lean_assert(vectors_are_equal(cd , a)); +#endif +} + +template void lp_core_solver_base:: +pretty_print(std::ostream & out) { + core_solver_pretty_printer pp(*this, out); + pp.print(); +} + +template void lp_core_solver_base:: +save_state(T * w_buffer, T * d_buffer) { + copy_m_w(w_buffer); + copy_m_ed(d_buffer); +} + +template void lp_core_solver_base:: +restore_state(T * w_buffer, T * d_buffer) { + restore_m_w(w_buffer); + restore_m_ed(d_buffer); +} + +template void lp_core_solver_base:: +copy_m_w(T * buffer) { + unsigned i = m_m(); + while (i --) { + buffer[i] = m_w[i]; + } +} + +template void lp_core_solver_base:: +restore_m_w(T * buffer) { + m_w.m_index.clear(); + unsigned i = m_m(); + while (i--) { + if (!is_zero(m_w[i] = buffer[i])) + m_w.m_index.push_back(i); + } +} + +// needed for debugging +template void lp_core_solver_base:: +copy_m_ed(T * buffer) { + unsigned i = m_m(); + while (i --) { + buffer[i] = m_ed[i]; + } +} + +template void lp_core_solver_base:: +restore_m_ed(T * buffer) { + unsigned i = m_m(); + while (i --) { + m_ed[i] = buffer[i]; + } +} + +template bool lp_core_solver_base:: +A_mult_x_is_off() const { + lean_assert(m_x.size() == m_A.column_count()); + if (numeric_traits::precise()) { + for (unsigned i = 0; i < m_m(); i++) { + X delta = m_b[i] - m_A.dot_product_with_row(i, m_x); + if (delta != numeric_traits::zero()) { + std::cout << "precise x is off ("; + std::cout << "m_b[" << i << "] = " << m_b[i] << " "; + std::cout << "left side = " << m_A.dot_product_with_row(i, m_x) << ' '; + std::cout << "delta = " << delta << ' '; + std::cout << "iters = " << total_iterations() << ")" << std::endl; + return true; + } + } + return false; + } + T feps = convert_struct::convert(m_settings.refactor_tolerance); + X one = convert_struct::convert(1.0); + for (unsigned i = 0; i < m_m(); i++) { + X delta = abs(m_b[i] - m_A.dot_product_with_row(i, m_x)); + X eps = feps * (one + T(0.1) * abs(m_b[i])); + + if (delta > eps) { +#if 0 + LP_OUT(m_settings, "x is off (" + << "m_b[" << i << "] = " << m_b[i] << " " + << "left side = " << m_A.dot_product_with_row(i, m_x) << ' ' + << "delta = " << delta << ' ' + << "iters = " << total_iterations() << ")" << std::endl); +#endif + return true; + } + } + return false; +} +template bool lp_core_solver_base:: +A_mult_x_is_off_on_index(const vector & index) const { + lean_assert(m_x.size() == m_A.column_count()); + if (numeric_traits::precise()) return false; +#if RUN_A_MULT_X_IS_OFF_FOR_PRECESE + for (unsigned i : index) { + X delta = m_b[i] - m_A.dot_product_with_row(i, m_x); + if (delta != numeric_traits::zero()) { + // std::cout << "x is off ("; + // std::cout << "m_b[" << i << "] = " << m_b[i] << " "; + // std::cout << "left side = " << m_A.dot_product_with_row(i, m_x) << ' '; + // std::cout << "delta = " << delta << ' '; + // std::cout << "iters = " << total_iterations() << ")" << std::endl; + return true; + } + } + return false; +#endif + // todo(levnach) run on m_ed.m_index only !!!!! + T feps = convert_struct::convert(m_settings.refactor_tolerance); + X one = convert_struct::convert(1.0); + for (unsigned i : index) { + X delta = abs(m_b[i] - m_A.dot_product_with_row(i, m_x)); + X eps = feps * (one + T(0.1) * abs(m_b[i])); + + if (delta > eps) { +#if 0 + LP_OUT(m_settings, "x is off (" + << "m_b[" << i << "] = " << m_b[i] << " " + << "left side = " << m_A.dot_product_with_row(i, m_x) << ' ' + << "delta = " << delta << ' ' + << "iters = " << total_iterations() << ")" << std::endl); +#endif + return true; + } + } + return false; +} + +// from page 182 of Istvan Maros's book +template void lp_core_solver_base:: +calculate_pivot_row_of_B_1(unsigned pivot_row) { + lean_assert(! use_tableau()); + lean_assert(m_pivot_row_of_B_1.is_OK()); + m_pivot_row_of_B_1.clear(); + m_pivot_row_of_B_1.set_value(numeric_traits::one(), pivot_row); + lean_assert(m_pivot_row_of_B_1.is_OK()); + m_factorization->solve_yB_with_error_check_indexed(m_pivot_row_of_B_1, m_basis_heading, m_basis, m_settings); + lean_assert(m_pivot_row_of_B_1.is_OK()); +} + + +template void lp_core_solver_base:: +calculate_pivot_row_when_pivot_row_of_B1_is_ready(unsigned pivot_row) { + m_pivot_row.clear(); + + for (unsigned i : m_pivot_row_of_B_1.m_index) { + const T & pi_1 = m_pivot_row_of_B_1[i]; + if (numeric_traits::is_zero(pi_1)) { + continue; + } + for (auto & c : m_A.m_rows[i]) { + unsigned j = c.m_j; + if (m_basis_heading[j] < 0) { + m_pivot_row.add_value_at_index_with_drop_tolerance(j, c.get_val() * pi_1); + } + } + } + if (precise()) { + m_rows_nz[pivot_row] = m_pivot_row.m_index.size(); + } +} + +template void lp_core_solver_base:: +update_x(unsigned entering, const X& delta) { + m_x[entering] += delta; + if (!use_tableau()) + for (unsigned i : m_ed.m_index) { + if (!numeric_traits::precise()) + m_copy_of_xB[i] = m_x[m_basis[i]]; + m_x[m_basis[i]] -= delta * m_ed[i]; + } + else + for (const auto & c : m_A.m_columns[entering]) { + unsigned i = c.m_i; + m_x[m_basis[i]] -= delta * m_A.get_val(c); + } +} + + +template void lp_core_solver_base:: +print_statistics(char const* str, X cost, std::ostream & out) { + if (str!= nullptr) + out << str << " "; + out << "iterations = " << (total_iterations() - 1) << ", cost = " << T_to_string(cost) + << ", nonzeros = " << (m_factorization != nullptr? m_factorization->get_number_of_nonzeroes() : m_A.number_of_non_zeroes()) << std::endl; +} + +template bool lp_core_solver_base:: +print_statistics_with_iterations_and_check_that_the_time_is_over(std::ostream & str) { + unsigned total_iterations = inc_total_iterations(); + if (m_settings.report_frequency != 0) { + if (m_settings.print_statistics && (total_iterations % m_settings.report_frequency == 0)) { + print_statistics("", X(), str); + } + } + return time_is_over(); +} + +template bool lp_core_solver_base:: +print_statistics_with_iterations_and_nonzeroes_and_cost_and_check_that_the_time_is_over(char const* str, std::ostream & out) { + unsigned total_iterations = inc_total_iterations(); + if (m_settings.report_frequency != 0) + if (m_settings.print_statistics && (total_iterations % m_settings.report_frequency == 0)) { + print_statistics(str, get_cost(), out); + } + return time_is_over(); +} + +template bool lp_core_solver_base:: +print_statistics_with_cost_and_check_that_the_time_is_over(X cost, std::ostream & out) { + unsigned total_iterations = inc_total_iterations(); + if (m_settings.report_frequency != 0) + if (m_settings.print_statistics && (total_iterations % m_settings.report_frequency == 0)) { + print_statistics("", cost, out); + } + return time_is_over(); +} + +template void lp_core_solver_base:: +set_non_basic_x_to_correct_bounds() { + for (unsigned j : non_basis()) { + switch (m_column_types[j]) { + case column_type::boxed: + m_x[j] = m_d[j] < 0? m_upper_bounds[j]: m_low_bounds[j]; + break; + case column_type::low_bound: + m_x[j] = m_low_bounds[j]; + lean_assert(column_is_dual_feasible(j)); + break; + case column_type::upper_bound: + m_x[j] = m_upper_bounds[j]; + lean_assert(column_is_dual_feasible(j)); + break; + default: + break; + } + } +} +template bool lp_core_solver_base:: +column_is_dual_feasible(unsigned j) const { + switch (m_column_types[j]) { + case column_type::fixed: + case column_type::boxed: + return (x_is_at_low_bound(j) && d_is_not_negative(j)) || + (x_is_at_upper_bound(j) && d_is_not_positive(j)); + case column_type::low_bound: + return x_is_at_low_bound(j) && d_is_not_negative(j); + case column_type::upper_bound: + LP_OUT(m_settings, "upper_bound type should be switched to low_bound" << std::endl); + lean_assert(false); // impossible case + case column_type::free_column: + return numeric_traits::is_zero(m_d[j]); + default: + LP_OUT(m_settings, "column = " << j << std::endl); + LP_OUT(m_settings, "unexpected column type = " << column_type_to_string(m_column_types[j]) << std::endl); + lean_unreachable(); + } + lean_unreachable(); + return false; +} +template bool lp_core_solver_base:: +d_is_not_negative(unsigned j) const { + if (numeric_traits::precise()) { + return m_d[j] >= numeric_traits::zero(); + } + return m_d[j] > -T(0.00001); +} + +template bool lp_core_solver_base:: +d_is_not_positive(unsigned j) const { + if (numeric_traits::precise()) { + return m_d[j] <= numeric_traits::zero(); + } + return m_d[j] < T(0.00001); +} + + +template bool lp_core_solver_base:: +time_is_over() { + if (m_settings.get_cancel_flag()) { + m_status = lp_status::TIME_EXHAUSTED; + return true; + } + else { + return false; + } +} + +template void lp_core_solver_base:: +rs_minus_Anx(vector & rs) { + unsigned row = m_m(); + while (row--) { + auto &rsv = rs[row] = m_b[row]; + for (auto & it : m_A.m_rows[row]) { + unsigned j = it.m_j; + if (m_basis_heading[j] < 0) { + rsv -= m_x[j] * it.get_val(); + } + } + } +} + +template bool lp_core_solver_base:: +find_x_by_solving() { + solve_Ax_eq_b(); + bool ret= !A_mult_x_is_off(); + return ret; +} + +template bool lp_core_solver_base::column_is_feasible(unsigned j) const { + const X& x = this->m_x[j]; + switch (this->m_column_types[j]) { + case column_type::fixed: + case column_type::boxed: + if (this->above_bound(x, this->m_upper_bounds[j])) { + return false; + } else if (this->below_bound(x, this->m_low_bounds[j])) { + return false; + } else { + return true; + } + break; + case column_type::low_bound: + if (this->below_bound(x, this->m_low_bounds[j])) { + return false; + } else { + return true; + } + break; + case column_type::upper_bound: + if (this->above_bound(x, this->m_upper_bounds[j])) { + return false; + } else { + return true; + } + break; + case column_type::free_column: + return true; + break; + default: + lean_unreachable(); + } + return false; // it is unreachable +} + +template bool lp_core_solver_base::calc_current_x_is_feasible_include_non_basis() const { + unsigned j = this->m_n(); + while (j--) { + if (!column_is_feasible(j)) { + return false; + } + } + return true; +} + +template bool lp_core_solver_base::inf_set_is_correct() const { + unsigned j = this->m_n(); + while (j--) { + bool belongs_to_set = m_inf_set.contains(j); + bool is_feas = column_is_feasible(j); + + if (is_feas == belongs_to_set) { + print_column_info(j, std::cout); + std::cout << "belongs_to_set = " << belongs_to_set << std::endl; + std::cout <<( is_feas? "feas":"inf") << std::endl; + return false; + } + } + return true; +} + +template bool lp_core_solver_base:: +update_basis_and_x(int entering, int leaving, X const & tt) { + + if (!is_zero(tt)) { + update_x(entering, tt); + if ((!numeric_traits::precise()) && A_mult_x_is_off_on_index(m_ed.m_index) && !find_x_by_solving()) { + init_factorization(m_factorization, m_A, m_basis, m_settings); + if (!find_x_by_solving()) { + restore_x(entering, tt); + lean_assert(!A_mult_x_is_off()); + init_factorization(m_factorization, m_A, m_basis, m_settings); + m_iters_with_no_cost_growing++; + if (m_factorization->get_status() != LU_status::OK) { + std::stringstream s; + s << "failing refactor on off_result for entering = " << entering << ", leaving = " << leaving << " total_iterations = " << total_iterations(); + throw_exception(s.str()); + } + return false; + } + } + } + + bool refactor = m_factorization->need_to_refactor(); + if (!refactor) { + const T & pivot = this->m_pivot_row[entering]; // m_ed[m_factorization->basis_heading(leaving)] is the same but the one that we are using is more precise + m_factorization->replace_column(pivot, m_w, m_basis_heading[leaving]); + if (m_factorization->get_status() == LU_status::OK) { + change_basis(entering, leaving); + return true; + } + } + // need to refactor == true + change_basis(entering, leaving); + init_lu(); + if (m_factorization->get_status() != LU_status::OK) { + if (m_look_for_feasible_solution_only && !precise()) { + m_status = UNSTABLE; + delete m_factorization; + m_factorization = nullptr; + return false; + } + // LP_OUT(m_settings, "failing refactor for entering = " << entering << ", leaving = " << leaving << " total_iterations = " << total_iterations() << std::endl); + restore_x_and_refactor(entering, leaving, tt); + if (m_status == FLOATING_POINT_ERROR) + return false; + lean_assert(!A_mult_x_is_off()); + m_iters_with_no_cost_growing++; + // LP_OUT(m_settings, "rolled back after failing of init_factorization()" << std::endl); + m_status = UNSTABLE; + return false; + } + return true; +} + + +template bool lp_core_solver_base:: +divide_row_by_pivot(unsigned pivot_row, unsigned pivot_col) { + lean_assert(numeric_traits::precise()); + int pivot_index = -1; + auto & row = m_A.m_rows[pivot_row]; + unsigned size = row.size(); + for (unsigned j = 0; j < size; j++) { + if (row[j].m_j == pivot_col) { + pivot_index = static_cast(j); + break; + } + } + if (pivot_index == -1) + return false; + auto & pivot_cell = row[pivot_index]; + if (is_zero(pivot_cell.m_value)) + return false; + + this->m_b[pivot_row] /= pivot_cell.m_value; + for (unsigned j = 0; j < size; j++) { + if (row[j].m_j != pivot_col) { + row[j].m_value /= pivot_cell.m_value; + } + } + pivot_cell.m_value = one_of_type(); + return true; +} +template bool lp_core_solver_base:: +pivot_column_tableau(unsigned j, unsigned piv_row_index) { + if (!divide_row_by_pivot(piv_row_index, j)) + return false; + auto &column = m_A.m_columns[j]; + int pivot_col_cell_index = -1; + for (unsigned k = 0; k < column.size(); k++) { + if (column[k].m_i == piv_row_index) { + pivot_col_cell_index = k; + break; + } + } + if (pivot_col_cell_index < 0) + return false; + + if (pivot_col_cell_index != 0) { + lean_assert(column.size() > 1); + // swap the pivot column cell with the head cell + auto c = column[0]; + column[0] = column[pivot_col_cell_index]; + column[pivot_col_cell_index] = c; + + m_A.m_rows[piv_row_index][column[0].m_offset].m_offset = 0; + m_A.m_rows[c.m_i][c.m_offset].m_offset = pivot_col_cell_index; + } + while (column.size() > 1) { + auto & c = column.back(); + lean_assert(c.m_i != piv_row_index); + if(! m_A.pivot_row_to_row_given_cell(piv_row_index, c, j)) { + return false; + } + if (m_pivoted_rows!= nullptr) + m_pivoted_rows->insert(c.m_i); + } + + if (m_settings.simplex_strategy() == simplex_strategy_enum::tableau_costs) + pivot_to_reduced_costs_tableau(piv_row_index, j); + return true; +} + + +template bool lp_core_solver_base:: +basis_has_no_doubles() const { + std::set bm; + for (unsigned i = 0; i < m_m(); i++) { + bm.insert(m_basis[i]); + } + return bm.size() == m_m(); +} + +template bool lp_core_solver_base:: +non_basis_has_no_doubles() const { + std::set bm; + for (auto j : m_nbasis) { + bm.insert(j); + } + return bm.size() == m_nbasis.size(); +} + +template bool lp_core_solver_base:: +basis_is_correctly_represented_in_heading() const { + for (unsigned i = 0; i < m_m(); i++) { + if (m_basis_heading[m_basis[i]] != static_cast(i)) + return false; + } + return true; +} +template bool lp_core_solver_base:: +non_basis_is_correctly_represented_in_heading() const { + for (unsigned i = 0; i < m_nbasis.size(); i++) { + if (m_basis_heading[m_nbasis[i]] != - static_cast(i) - 1) + return false; + } + for (unsigned j = 0; j < m_A.column_count(); j++) { + if (m_basis_heading[j] >= 0) { + lean_assert(static_cast(m_basis_heading[j]) < m_A.row_count() && m_basis[m_basis_heading[j]] == j); + } + } + return true; +} + +template bool lp_core_solver_base:: + basis_heading_is_correct() const { + lean_assert(m_basis_heading.size() == m_A.column_count()); + lean_assert(m_basis.size() == m_A.row_count()); + lean_assert(m_nbasis.size() <= m_A.column_count() - m_A.row_count()); // for the dual the size of non basis can be smaller + if (!basis_has_no_doubles()) { + // std::cout << "basis_has_no_doubles" << std::endl; + return false; + } + + if (!non_basis_has_no_doubles()) { + // std::cout << "non_basis_has_no_doubles" << std::endl; + return false; + } + + if (!basis_is_correctly_represented_in_heading()) { + // std::cout << "basis_is_correctly_represented_in_heading" << std::endl; + return false; + } + + if (!non_basis_is_correctly_represented_in_heading()) { + // std::cout << "non_basis_is_correctly_represented_in_heading" << std::endl; + return false; + } + + + return true; +} + +template void lp_core_solver_base:: +restore_x_and_refactor(int entering, int leaving, X const & t) { + this->restore_basis_change(entering, leaving); + restore_x(entering, t); + init_factorization(m_factorization, m_A, m_basis, m_settings); + if (m_factorization->get_status() == LU_status::Degenerated) { + LP_OUT(m_settings, "cannot refactor" << std::endl); + m_status = lp_status::FLOATING_POINT_ERROR; + return; + } + // solve_Ax_eq_b(); + if (A_mult_x_is_off()) { + LP_OUT(m_settings, "cannot restore solution" << std::endl); + m_status = lp_status::FLOATING_POINT_ERROR; + return; + } +} + +template void lp_core_solver_base:: +restore_x(unsigned entering, X const & t) { + if (is_zero(t)) return; + m_x[entering] -= t; + for (unsigned i : m_ed.m_index) { + m_x[m_basis[i]] = m_copy_of_xB[i]; + } +} + +template void lp_core_solver_base:: +fill_reduced_costs_from_m_y_by_rows() { + unsigned j = m_n(); + while (j--) { + if (m_basis_heading[j] < 0) + m_d[j] = m_costs[j]; + else + m_d[j] = numeric_traits::zero(); + } + + unsigned i = m_m(); + while (i--) { + const T & y = m_y[i]; + if (is_zero(y)) continue; + for (row_cell & it : m_A.m_rows[i]) { + j = it.m_j; + if (m_basis_heading[j] < 0) { + m_d[j] -= y * it.get_val(); + } + } + } +} + +template void lp_core_solver_base:: +copy_rs_to_xB(vector & rs) { + unsigned j = m_m(); + while (j--) { + m_x[m_basis[j]] = rs[j]; + } +} + +template std::string lp_core_solver_base:: +column_name(unsigned column) const { + return m_column_names.get_column_name(column); +} + +template void lp_core_solver_base:: +copy_right_side(vector & rs) { + unsigned i = m_m(); + while (i --) { + rs[i] = m_b[i]; + } +} + +template void lp_core_solver_base:: +add_delta_to_xB(vector & del) { + unsigned i = m_m(); + while (i--) { + this->m_x[this->m_basis[i]] -= del[i]; + } +} + +template void lp_core_solver_base:: +find_error_in_BxB(vector& rs){ + unsigned row = m_m(); + while (row--) { + auto &rsv = rs[row]; + for (auto & it : m_A.m_rows[row]) { + unsigned j = it.m_j; + if (m_basis_heading[j] >= 0) { + rsv -= m_x[j] * it.get_val(); + } + } + } +} + +// recalculates the projection of x to B, such that Ax = b +template void lp_core_solver_base:: +solve_Ax_eq_b() { + if (numeric_traits::precise()) { + vector rs(m_m()); + rs_minus_Anx(rs); + m_factorization->solve_By(rs); + copy_rs_to_xB(rs); + } else { + vector rs(m_m()); + rs_minus_Anx(rs); + vector rrs = rs; // another copy of rs + m_factorization->solve_By(rs); + copy_rs_to_xB(rs); + find_error_in_BxB(rrs); + m_factorization->solve_By(rrs); + add_delta_to_xB(rrs); + } +} + + + + +template void lp_core_solver_base:: +snap_non_basic_x_to_bound_and_free_to_zeroes() { + for (unsigned j : non_basis()) { + lean_assert(j < m_x.size()); + switch (m_column_types[j]) { + case column_type::fixed: + case column_type::boxed: + case column_type::low_bound: + m_x[j] = m_low_bounds[j]; + break; + case column_type::upper_bound: + m_x[j] = m_upper_bounds[j]; + break; + default: + m_x[j] = zero_of_type(); + break; + } + } +} +template void lp_core_solver_base:: +snap_xN_to_bounds_and_fill_xB() { + snap_non_basic_x_to_bound(); + solve_Ax_eq_b(); +} + +template void lp_core_solver_base:: +snap_xN_to_bounds_and_free_columns_to_zeroes() { + snap_non_basic_x_to_bound_and_free_to_zeroes(); + solve_Ax_eq_b(); +} + +template void lp_core_solver_base:: +init_reduced_costs_for_one_iteration() { + solve_yB(m_y); + fill_reduced_costs_from_m_y_by_rows(); +} + +template non_basic_column_value_position lp_core_solver_base:: +get_non_basic_column_value_position(unsigned j) const { + switch (m_column_types[j]) { + case column_type::fixed: + return x_is_at_low_bound(j)? at_fixed : not_at_bound; + case column_type::free_column: + return free_of_bounds; + case column_type::boxed: + return x_is_at_low_bound(j)? at_low_bound :( + x_is_at_upper_bound(j)? at_upper_bound: + not_at_bound + ); + case column_type::low_bound: + return x_is_at_low_bound(j)? at_low_bound : not_at_bound; + case column_type::upper_bound: + return x_is_at_upper_bound(j)? at_upper_bound : not_at_bound; + default: + lean_unreachable(); + } + lean_unreachable(); + return at_low_bound; +} + +template void lp_core_solver_base::init_lu() { + init_factorization(this->m_factorization, this->m_A, this->m_basis, this->m_settings); +} + +template int lp_core_solver_base::pivots_in_column_and_row_are_different(int entering, int leaving) const { + const T & column_p = this->m_ed[this->m_basis_heading[leaving]]; + const T & row_p = this->m_pivot_row[entering]; + if (is_zero(column_p) || is_zero(row_p)) return true; // pivots cannot be zero + // the pivots have to have the same sign + if (column_p < 0) { + if (row_p > 0) + return 2; + } else { // column_p > 0 + if (row_p < 0) + return 2; + } + T diff_normalized = abs((column_p - row_p) / (numeric_traits::one() + abs(row_p))); + if ( !this->m_settings.abs_val_is_smaller_than_harris_tolerance(diff_normalized / T(10))) + return 1; + return 0; +} +template void lp_core_solver_base::transpose_rows_tableau(unsigned i, unsigned j) { + transpose_basis(i, j); + m_A.transpose_rows(i, j); +} + +template void lp_core_solver_base::pivot_fixed_vars_from_basis() { + // run over basis and non-basis at the same time + indexed_vector w(m_basis.size()); // the buffer + unsigned i = 0; // points to basis + unsigned j = 0; // points to nonbasis + for (; i < m_basis.size() && j < m_nbasis.size(); i++) { + unsigned ii = m_basis[i]; + unsigned jj; + + if (get_column_type(ii) != column_type::fixed) continue; + while (j < m_nbasis.size()) { + for (; j < m_nbasis.size(); j++) { + jj = m_nbasis[j]; + if (get_column_type(jj) != column_type::fixed) + break; + } + if (j >= m_nbasis.size()) + break; + j++; + if (m_factorization->need_to_refactor()) { + change_basis(jj, ii); + init_lu(); + } else { + m_factorization->prepare_entering(jj, w); // to init vector w + m_factorization->replace_column(zero_of_type(), w, m_basis_heading[ii]); + change_basis(jj, ii); + } + if (m_factorization->get_status() != LU_status::OK) { + change_basis(ii, jj); + init_lu(); + } else { + break; + } + } + lean_assert(m_factorization->get_status()== LU_status::OK); + } +} + +template bool +lp_core_solver_base::infeasibility_costs_are_correct() const { + if (! this->m_using_infeas_costs) + return true; + lean_assert(costs_on_nbasis_are_zeros()); + for (unsigned j :this->m_basis) { + if (!infeasibility_cost_is_correct_for_column(j)) { + std::cout << "infeasibility_cost_is_correct_for_column does not hold\n"; + print_column_info(j, std::cout); + return false; + } + if (!is_zero(m_d[j])) { + std::cout << "m_d is not zero\n"; + print_column_info(j, std::cout); + return false; + } + } + return true; +} + +template bool +lp_core_solver_base::infeasibility_cost_is_correct_for_column(unsigned j) const { + T r = (!this->m_settings.use_breakpoints_in_feasibility_search)? -one_of_type(): one_of_type(); + + switch (this->m_column_types[j]) { + case column_type::fixed: + case column_type::boxed: + if (this->x_above_upper_bound(j)) { + return (this->m_costs[j] == r); + } + if (this->x_below_low_bound(j)) { + return (this->m_costs[j] == -r); + } + return is_zero(this->m_costs[j]); + + case column_type::low_bound: + if (this->x_below_low_bound(j)) { + return this->m_costs[j] == -r; + } + return is_zero(this->m_costs[j]); + + case column_type::upper_bound: + if (this->x_above_upper_bound(j)) { + return this->m_costs[j] == r; + } + return is_zero(this->m_costs[j]); + case column_type::free_column: + return is_zero(this->m_costs[j]); + default: + lean_assert(false); + return true; + } +} + +} diff --git a/src/util/lp/lp_core_solver_base_instances.cpp b/src/util/lp/lp_core_solver_base_instances.cpp new file mode 100644 index 000000000..17dcb87db --- /dev/null +++ b/src/util/lp/lp_core_solver_base_instances.cpp @@ -0,0 +1,131 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include +#include +#include +#include "util/vector.h" +#include +#include "util/lp/lp_core_solver_base.hpp" +template bool lean::lp_core_solver_base::A_mult_x_is_off() const; +template bool lean::lp_core_solver_base::A_mult_x_is_off_on_index(const vector &) const; +template bool lean::lp_core_solver_base::basis_heading_is_correct() const; +template void lean::lp_core_solver_base::calculate_pivot_row_of_B_1(unsigned int); +template void lean::lp_core_solver_base::calculate_pivot_row_when_pivot_row_of_B1_is_ready(unsigned); +template bool lean::lp_core_solver_base::column_is_dual_feasible(unsigned int) const; +template void lean::lp_core_solver_base::fill_reduced_costs_from_m_y_by_rows(); +template bool lean::lp_core_solver_base::find_x_by_solving(); +template lean::non_basic_column_value_position lean::lp_core_solver_base::get_non_basic_column_value_position(unsigned int) const; +template lean::non_basic_column_value_position lean::lp_core_solver_base >::get_non_basic_column_value_position(unsigned int) const; +template lean::non_basic_column_value_position lean::lp_core_solver_base::get_non_basic_column_value_position(unsigned int) const; +template void lean::lp_core_solver_base::init_reduced_costs_for_one_iteration(); +template lean::lp_core_solver_base::lp_core_solver_base( + lean::static_matrix&, vector&, + vector&, + vector &, vector &, + vector&, + vector&, + lean::lp_settings&, const column_namer&, const vector&, + const vector&, + const vector&); + +template bool lean::lp_core_solver_base::print_statistics_with_iterations_and_nonzeroes_and_cost_and_check_that_the_time_is_over(char const*, std::ostream &); +template bool lean::lp_core_solver_base >::print_statistics_with_iterations_and_nonzeroes_and_cost_and_check_that_the_time_is_over(char const*, std::ostream &); +template void lean::lp_core_solver_base::restore_x(unsigned int, double const&); +template void lean::lp_core_solver_base::set_non_basic_x_to_correct_bounds(); +template void lean::lp_core_solver_base::snap_xN_to_bounds_and_free_columns_to_zeroes(); +template void lean::lp_core_solver_base >::snap_xN_to_bounds_and_free_columns_to_zeroes(); +template void lean::lp_core_solver_base::solve_Ax_eq_b(); +template void lean::lp_core_solver_base::solve_Bd(unsigned int); +template void lean::lp_core_solver_base>::solve_Bd(unsigned int, indexed_vector&); +template void lean::lp_core_solver_base::solve_yB(vector&); +template bool lean::lp_core_solver_base::update_basis_and_x(int, int, double const&); +template void lean::lp_core_solver_base::update_x(unsigned int, const double&); +template bool lean::lp_core_solver_base::A_mult_x_is_off() const; +template bool lean::lp_core_solver_base::A_mult_x_is_off_on_index(const vector &) const; +template bool lean::lp_core_solver_base::basis_heading_is_correct() const ; +template void lean::lp_core_solver_base::calculate_pivot_row_of_B_1(unsigned int); +template void lean::lp_core_solver_base::calculate_pivot_row_when_pivot_row_of_B1_is_ready(unsigned); +template bool lean::lp_core_solver_base::column_is_dual_feasible(unsigned int) const; +template void lean::lp_core_solver_base::fill_reduced_costs_from_m_y_by_rows(); +template bool lean::lp_core_solver_base::find_x_by_solving(); +template void lean::lp_core_solver_base::init_reduced_costs_for_one_iteration(); +template bool lean::lp_core_solver_base::print_statistics_with_iterations_and_nonzeroes_and_cost_and_check_that_the_time_is_over(char const*, std::ostream &); +template void lean::lp_core_solver_base::restore_x(unsigned int, lean::mpq const&); +template void lean::lp_core_solver_base::set_non_basic_x_to_correct_bounds(); +template void lean::lp_core_solver_base::solve_Ax_eq_b(); +template void lean::lp_core_solver_base::solve_Bd(unsigned int); +template void lean::lp_core_solver_base::solve_yB(vector&); +template bool lean::lp_core_solver_base::update_basis_and_x(int, int, lean::mpq const&); +template void lean::lp_core_solver_base::update_x(unsigned int, const lean::mpq&); +template void lean::lp_core_solver_base >::calculate_pivot_row_of_B_1(unsigned int); +template void lean::lp_core_solver_base >::calculate_pivot_row_when_pivot_row_of_B1_is_ready(unsigned); +template void lean::lp_core_solver_base >::init(); +template void lean::lp_core_solver_base >::init_basis_heading_and_non_basic_columns_vector(); +template void lean::lp_core_solver_base >::init_reduced_costs_for_one_iteration(); +template lean::lp_core_solver_base >::lp_core_solver_base(lean::static_matrix >&, vector >&, vector&, vector &, vector &, vector >&, vector&, lean::lp_settings&, const column_namer&, const vector&, + const vector >&, + const vector >&); +template bool lean::lp_core_solver_base >::print_statistics_with_cost_and_check_that_the_time_is_over(lean::numeric_pair, std::ostream&); +template void lean::lp_core_solver_base >::snap_xN_to_bounds_and_fill_xB(); +template void lean::lp_core_solver_base >::solve_Bd(unsigned int); +template bool lean::lp_core_solver_base >::update_basis_and_x(int, int, lean::numeric_pair const&); +template void lean::lp_core_solver_base >::update_x(unsigned int, const lean::numeric_pair&); +template lean::lp_core_solver_base::lp_core_solver_base( + lean::static_matrix&, + vector&, + vector&, + vector &, vector &, + vector&, + vector&, + lean::lp_settings&, + const column_namer&, + const vector&, + const vector&, + const vector&); +template bool lean::lp_core_solver_base >::print_statistics_with_iterations_and_check_that_the_time_is_over(std::ostream &); +template std::string lean::lp_core_solver_base::column_name(unsigned int) const; +template void lean::lp_core_solver_base::pretty_print(std::ostream & out); +template void lean::lp_core_solver_base::restore_state(double*, double*); +template void lean::lp_core_solver_base::save_state(double*, double*); +template std::string lean::lp_core_solver_base::column_name(unsigned int) const; +template void lean::lp_core_solver_base::pretty_print(std::ostream & out); +template void lean::lp_core_solver_base::restore_state(lean::mpq*, lean::mpq*); +template void lean::lp_core_solver_base::save_state(lean::mpq*, lean::mpq*); +template std::string lean::lp_core_solver_base >::column_name(unsigned int) const; +template void lean::lp_core_solver_base >::pretty_print(std::ostream & out); +template void lean::lp_core_solver_base >::restore_state(lean::mpq*, lean::mpq*); +template void lean::lp_core_solver_base >::save_state(lean::mpq*, lean::mpq*); +template void lean::lp_core_solver_base >::solve_yB(vector&); +template void lean::lp_core_solver_base::init_lu(); +template void lean::lp_core_solver_base::init_lu(); +template int lean::lp_core_solver_base::pivots_in_column_and_row_are_different(int, int) const; +template int lean::lp_core_solver_base >::pivots_in_column_and_row_are_different(int, int) const; +template int lean::lp_core_solver_base::pivots_in_column_and_row_are_different(int, int) const; +template bool lean::lp_core_solver_base::calc_current_x_is_feasible_include_non_basis(void)const; +template bool lean::lp_core_solver_base::calc_current_x_is_feasible_include_non_basis(void)const; +template bool lean::lp_core_solver_base >::calc_current_x_is_feasible_include_non_basis() const; +template void lean::lp_core_solver_base >::pivot_fixed_vars_from_basis(); +template bool lean::lp_core_solver_base::column_is_feasible(unsigned int) const; +template bool lean::lp_core_solver_base::column_is_feasible(unsigned int) const; +// template void lean::lp_core_solver_base >::print_linear_combination_of_column_indices(vector, std::allocator > > const&, std::ostream&) const; +template bool lean::lp_core_solver_base >::column_is_feasible(unsigned int) const; +template bool lean::lp_core_solver_base >::snap_non_basic_x_to_bound(); +template void lean::lp_core_solver_base >::init_lu(); +template bool lean::lp_core_solver_base >::A_mult_x_is_off_on_index(vector const&) const; +template bool lean::lp_core_solver_base >::find_x_by_solving(); +template void lean::lp_core_solver_base >::restore_x(unsigned int, lean::numeric_pair const&); +template bool lean::lp_core_solver_base::pivot_for_tableau_on_basis(); +template bool lean::lp_core_solver_base::pivot_for_tableau_on_basis(); +template bool lean::lp_core_solver_base>::pivot_for_tableau_on_basis(); +template bool lean::lp_core_solver_base>::pivot_column_tableau(unsigned int, unsigned int); +template bool lean::lp_core_solver_base::pivot_column_tableau(unsigned int, unsigned int); +template bool lean::lp_core_solver_base::pivot_column_tableau(unsigned int, unsigned int); +template void lean::lp_core_solver_base >::transpose_rows_tableau(unsigned int, unsigned int); +template bool lean::lp_core_solver_base >::inf_set_is_correct() const; +template bool lean::lp_core_solver_base::inf_set_is_correct() const; +template bool lean::lp_core_solver_base::inf_set_is_correct() const; +template bool lean::lp_core_solver_base >::infeasibility_costs_are_correct() const; +template bool lean::lp_core_solver_base::infeasibility_costs_are_correct() const; +template bool lean::lp_core_solver_base::infeasibility_costs_are_correct() const; diff --git a/src/util/lp/lp_dual_core_solver.h b/src/util/lp/lp_dual_core_solver.h new file mode 100644 index 000000000..b873cb711 --- /dev/null +++ b/src/util/lp/lp_dual_core_solver.h @@ -0,0 +1,197 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#pragma once +#include "util/lp/static_matrix.h" +#include "util/lp/lp_core_solver_base.h" +#include +#include +#include +#include +#include "util/vector.h" + +namespace lean { +template +class lp_dual_core_solver:public lp_core_solver_base { +public: + vector & m_can_enter_basis; + int m_r; // the row of the leaving column + int m_p; // leaving column; that is m_p = m_basis[m_r] + T m_delta; // the offset of the leaving basis variable + int m_sign_of_alpha_r; // see page 27 + T m_theta_D; + T m_theta_P; + int m_q; + // todo : replace by a vector later + std::set m_breakpoint_set; // it is F in "Progress in the dual simplex method ..." + std::set m_flipped_boxed; + std::set m_tight_set; // it is the set of all breakpoints that become tight when m_q becomes tight + vector m_a_wave; + vector m_betas; // m_betas[i] is approximately a square of the norm of the i-th row of the reverse of B + T m_harris_tolerance; + std::set m_forbidden_rows; + + lp_dual_core_solver(static_matrix & A, + vector & can_enter_basis, + vector & b, // the right side vector + vector & x, // the number of elements in x needs to be at least as large as the number of columns in A + vector & basis, + vector & nbasis, + vector & heading, + vector & costs, + vector & column_type_array, + vector & low_bound_values, + vector & upper_bound_values, + lp_settings & settings, + const column_namer & column_names): + lp_core_solver_base(A, + b, + basis, + nbasis, + heading, + x, + costs, + settings, + column_names, + column_type_array, + low_bound_values, + upper_bound_values), + m_can_enter_basis(can_enter_basis), + m_a_wave(this->m_m()), + m_betas(this->m_m()) { + m_harris_tolerance = numeric_traits::precise()? numeric_traits::zero() : T(this->m_settings.harris_feasibility_tolerance); + this->solve_yB(this->m_y); + this->init_basic_part_of_basis_heading(); + fill_non_basis_with_only_able_to_enter_columns(); + } + + void init_a_wave_by_zeros(); + + void fill_non_basis_with_only_able_to_enter_columns() { + auto & nb = this->m_nbasis; + nb.reset(); + unsigned j = this->m_n(); + while (j--) { + if (this->m_basis_heading[j] >= 0 || !m_can_enter_basis[j]) continue; + nb.push_back(j); + this->m_basis_heading[j] = - static_cast(nb.size()); + } + } + + void restore_non_basis(); + + bool update_basis(int entering, int leaving); + + void recalculate_xB_and_d(); + + void recalculate_d(); + + void init_betas(); + + void adjust_xb_for_changed_xn_and_init_betas(); + + void start_with_initial_basis_and_make_it_dual_feasible(); + + bool done(); + + T get_edge_steepness_for_low_bound(unsigned p); + + T get_edge_steepness_for_upper_bound(unsigned p); + + T pricing_for_row(unsigned i); + + void pricing_loop(unsigned number_of_rows_to_try, unsigned offset_in_rows); + + bool advance_on_known_p(); + + int define_sign_of_alpha_r(); + + bool can_be_breakpoint(unsigned j); + + void fill_breakpoint_set(); + + void DSE_FTran(); + T get_delta(); + + void restore_d(); + + bool d_is_correct(); + + void xb_minus_delta_p_pivot_column(); + + void update_betas(); + + void apply_flips(); + + void snap_xN_column_to_bounds(unsigned j); + + void snap_xN_to_bounds(); + + void init_beta_precisely(unsigned i); + + void init_betas_precisely(); + + // step 7 of the algorithm from Progress + bool basis_change_and_update(); + + void revert_to_previous_basis(); + + non_basic_column_value_position m_entering_boundary_position; + bool update_basis_and_x_local(int entering, int leaving, X const & tt); + void recover_leaving(); + + bool problem_is_dual_feasible() const; + + bool snap_runaway_nonbasic_column(unsigned); + + bool snap_runaway_nonbasic_columns(); + + unsigned get_number_of_rows_to_try_for_leaving(); + + void update_a_wave(const T & del, unsigned j) { + this->m_A.add_column_to_vector(del, j, & m_a_wave[0]); + } + + bool delta_keeps_the_sign(int initial_delta_sign, const T & delta); + + void set_status_to_tentative_dual_unbounded_or_dual_unbounded(); + + // it is positive if going from low bound to upper bound and negative if going from upper bound to low bound + T signed_span_of_boxed(unsigned j) { + return this->x_is_at_low_bound(j)? this->bound_span(j): - this->bound_span(j); + } + + void add_tight_breakpoints_and_q_to_flipped_set(); + + T delta_lost_on_flips_of_tight_breakpoints(); + + bool tight_breakpoinst_are_all_boxed(); + + T calculate_harris_delta_on_breakpoint_set(); + + void fill_tight_set_on_harris_delta(const T & harris_delta ); + + void find_q_on_tight_set(); + + void find_q_and_tight_set(); + + void erase_tight_breakpoints_and_q_from_breakpoint_set(); + + bool ratio_test(); + + void process_flipped(); + void update_d_and_xB(); + + void calculate_beta_r_precisely(); + // see "Progress in the dual simplex method for large scale LP problems: practical dual phase 1 algorithms" + + void update_xb_after_bound_flips(); + + void one_iteration(); + + void solve(); + + bool low_bounds_are_set() const { return true; } +}; +} diff --git a/src/util/lp/lp_dual_core_solver.hpp b/src/util/lp/lp_dual_core_solver.hpp new file mode 100644 index 000000000..92a84e238 --- /dev/null +++ b/src/util/lp/lp_dual_core_solver.hpp @@ -0,0 +1,743 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include +#include +#include "util/vector.h" +#include "util/lp/lp_dual_core_solver.h" + +namespace lean { + +template void lp_dual_core_solver::init_a_wave_by_zeros() { + unsigned j = this->m_m(); + while (j--) { + m_a_wave[j] = numeric_traits::zero(); + } +} + +template void lp_dual_core_solver::restore_non_basis() { + auto & nb = this->m_nbasis; + nb.reset(); + unsigned j = this->m_n(); + while (j--) { + if (this->m_basis_heading[j] >= 0 ) continue; + if (m_can_enter_basis[j]) { + lean_assert(std::find(nb.begin(), nb.end(), j) == nb.end()); + nb.push_back(j); + this->m_basis_heading[j] = - static_cast(nb.size()); + } + } +} + +template bool lp_dual_core_solver::update_basis(int entering, int leaving) { + // the second argument is the element of the entering column from the pivot row - its value should be equal to the low diagonal element of the bump after all pivoting is done + if (this->m_refactor_counter++ < 200) { + this->m_factorization->replace_column(this->m_ed[this->m_factorization->basis_heading(leaving)], this->m_w); + if (this->m_factorization->get_status() == LU_status::OK) { + this->m_factorization->change_basis(entering, leaving); + return true; + } + } + // need to refactor + this->m_factorization->change_basis(entering, leaving); + init_factorization(this->m_factorization, this->m_A, this->m_basis, this->m_basis_heading, this->m_settings); + this->m_refactor_counter = 0; + if (this->m_factorization->get_status() != LU_status::OK) { + LP_OUT(this->m_settings, "failing refactor for entering = " << entering << ", leaving = " << leaving << " total_iterations = " << this->total_iterations() << std::endl); + this->m_iters_with_no_cost_growing++; + return false; + } + return true; +} + +template void lp_dual_core_solver::recalculate_xB_and_d() { + this->solve_Ax_eq_b(); + recalculate_d(); +} + +template void lp_dual_core_solver::recalculate_d() { + this->solve_yB(this->m_y); + this->fill_reduced_costs_from_m_y_by_rows(); +} + +template void lp_dual_core_solver::init_betas() { + // todo : look at page 194 of Progress in the dual simplex algorithm for solving large scale LP problems : techniques for a fast and stable implementation + // the current implementation is not good enough: todo + unsigned i = this->m_m(); + while (i--) { + m_betas[i] = 1; + } +} + +template void lp_dual_core_solver::adjust_xb_for_changed_xn_and_init_betas() { + this->solve_Ax_eq_b(); + init_betas(); +} + +template void lp_dual_core_solver::start_with_initial_basis_and_make_it_dual_feasible() { + this->set_non_basic_x_to_correct_bounds(); // It is not an efficient version, see 3.29, + // however this version does not require that m_x is the solution of Ax = 0 beforehand + adjust_xb_for_changed_xn_and_init_betas(); +} + +template bool lp_dual_core_solver::done() { + if (this->get_status() == OPTIMAL) { + return true; + } + if (this->total_iterations() > this->m_settings.max_total_number_of_iterations) { // debug !!!! + this->set_status(ITERATIONS_EXHAUSTED); + return true; + } + return false; // todo, need to be more cases +} + +template T lp_dual_core_solver::get_edge_steepness_for_low_bound(unsigned p) { + lean_assert(this->m_basis_heading[p] >= 0 && static_cast(this->m_basis_heading[p]) < this->m_m()); + T del = this->m_x[p] - this->m_low_bounds[p]; + del *= del; + return del / this->m_betas[this->m_basis_heading[p]]; +} + +template T lp_dual_core_solver::get_edge_steepness_for_upper_bound(unsigned p) { + lean_assert(this->m_basis_heading[p] >= 0 && static_cast(this->m_basis_heading[p]) < this->m_m()); + T del = this->m_x[p] - this->m_upper_bounds[p]; + del *= del; + return del / this->m_betas[this->m_basis_heading[p]]; +} + +template T lp_dual_core_solver::pricing_for_row(unsigned i) { + unsigned p = this->m_basis[i]; + switch (this->m_column_types[p]) { + case column_type::fixed: + case column_type::boxed: + if (this->x_below_low_bound(p)) { + T del = get_edge_steepness_for_low_bound(p); + return del; + } + if (this->x_above_upper_bound(p)) { + T del = get_edge_steepness_for_upper_bound(p); + return del; + } + return numeric_traits::zero(); + case column_type::low_bound: + if (this->x_below_low_bound(p)) { + T del = get_edge_steepness_for_low_bound(p); + return del; + } + return numeric_traits::zero(); + break; + case column_type::upper_bound: + if (this->x_above_upper_bound(p)) { + T del = get_edge_steepness_for_upper_bound(p); + return del; + } + return numeric_traits::zero(); + break; + case column_type::free_column: + lean_assert(numeric_traits::is_zero(this->m_d[p])); + return numeric_traits::zero(); + default: + lean_unreachable(); + } + lean_unreachable(); + return numeric_traits::zero(); +} + +template void lp_dual_core_solver::pricing_loop(unsigned number_of_rows_to_try, unsigned offset_in_rows) { + m_r = -1; + T steepest_edge_max = numeric_traits::zero(); + unsigned initial_offset_in_rows = offset_in_rows; + unsigned i = offset_in_rows; + unsigned rows_left = number_of_rows_to_try; + do { + if (m_forbidden_rows.find(i) != m_forbidden_rows.end()) { + if (++i == this->m_m()) { + i = 0; + } + continue; + } + T se = pricing_for_row(i); + if (se > steepest_edge_max) { + steepest_edge_max = se; + m_r = i; + if (rows_left > 0) { + rows_left--; + } + } + if (++i == this->m_m()) { + i = 0; + } + } while (i != initial_offset_in_rows && rows_left); + if (m_r == -1) { + if (this->get_status() != UNSTABLE) { + this->set_status(OPTIMAL); + } + } else { + m_p = this->m_basis[m_r]; + m_delta = get_delta(); + if (advance_on_known_p()){ + m_forbidden_rows.clear(); + return; + } + // failure in advance_on_known_p + if (this->get_status() == FLOATING_POINT_ERROR) { + return; + } + this->set_status(UNSTABLE); + m_forbidden_rows.insert(m_r); + } +} + + // this calculation is needed for the steepest edge update, + // it hijackes m_pivot_row_of_B_1 for this purpose since we will need it anymore to the end of the cycle +template void lp_dual_core_solver::DSE_FTran() { // todo, see algorithm 7 from page 35 + this->m_factorization->solve_By_for_T_indexed_only(this->m_pivot_row_of_B_1, this->m_settings); +} + +template bool lp_dual_core_solver::advance_on_known_p() { + if (done()) { + return true; + } + this->calculate_pivot_row_of_B_1(m_r); + this->calculate_pivot_row_when_pivot_row_of_B1_is_ready(m_r); + if (!ratio_test()) { + return true; + } + calculate_beta_r_precisely(); + this->solve_Bd(m_q); // FTRAN + int pivot_compare_result = this->pivots_in_column_and_row_are_different(m_q, m_p); + if (!pivot_compare_result){;} + else if (pivot_compare_result == 2) { // the sign is changed, cannot continue + lean_unreachable(); // not implemented yet + } else { + lean_assert(pivot_compare_result == 1); + this->init_lu(); + } + DSE_FTran(); + return basis_change_and_update(); +} + +template int lp_dual_core_solver::define_sign_of_alpha_r() { + switch (this->m_column_types[m_p]) { + case column_type::boxed: + case column_type::fixed: + if (this->x_below_low_bound(m_p)) { + return -1; + } + if (this->x_above_upper_bound(m_p)) { + return 1; + } + lean_unreachable(); + case column_type::low_bound: + if (this->x_below_low_bound(m_p)) { + return -1; + } + lean_unreachable(); + case column_type::upper_bound: + if (this->x_above_upper_bound(m_p)) { + return 1; + } + lean_unreachable(); + default: + lean_unreachable(); + } + lean_unreachable(); + return 0; +} + +template bool lp_dual_core_solver::can_be_breakpoint(unsigned j) { + if (this->pivot_row_element_is_too_small_for_ratio_test(j)) return false; + switch (this->m_column_types[j]) { + case column_type::low_bound: + lean_assert(this->m_settings.abs_val_is_smaller_than_harris_tolerance(this->m_x[j] - this->m_low_bounds[j])); + return m_sign_of_alpha_r * this->m_pivot_row[j] > 0; + case column_type::upper_bound: + lean_assert(this->m_settings.abs_val_is_smaller_than_harris_tolerance(this->m_x[j] - this->m_upper_bounds[j])); + return m_sign_of_alpha_r * this->m_pivot_row[j] < 0; + case column_type::boxed: + { + bool low_bound = this->x_is_at_low_bound(j); + bool grawing = m_sign_of_alpha_r * this->m_pivot_row[j] > 0; + return low_bound == grawing; + } + case column_type::fixed: // is always dual feasible so we ingore it + return false; + case column_type::free_column: + return true; + default: + return false; + } +} + +template void lp_dual_core_solver::fill_breakpoint_set() { + m_breakpoint_set.clear(); + for (unsigned j : this->non_basis()) { + if (can_be_breakpoint(j)) { + m_breakpoint_set.insert(j); + } + } +} + +// template void lp_dual_core_solver::FTran() { +// this->solve_Bd(m_q); +// } + +template T lp_dual_core_solver::get_delta() { + switch (this->m_column_types[m_p]) { + case column_type::boxed: + if (this->x_below_low_bound(m_p)) { + return this->m_x[m_p] - this->m_low_bounds[m_p]; + } + if (this->x_above_upper_bound(m_p)) { + return this->m_x[m_p] - this->m_upper_bounds[m_p]; + } + lean_unreachable(); + case column_type::low_bound: + if (this->x_below_low_bound(m_p)) { + return this->m_x[m_p] - this->m_low_bounds[m_p]; + } + lean_unreachable(); + case column_type::upper_bound: + if (this->x_above_upper_bound(m_p)) { + return get_edge_steepness_for_upper_bound(m_p); + } + lean_unreachable(); + case column_type::fixed: + return this->m_x[m_p] - this->m_upper_bounds[m_p]; + default: + lean_unreachable(); + } + lean_unreachable(); + return zero_of_type(); +} + +template void lp_dual_core_solver::restore_d() { + this->m_d[m_p] = numeric_traits::zero(); + for (auto j : this->non_basis()) { + this->m_d[j] += m_theta_D * this->m_pivot_row[j]; + } +} + +template bool lp_dual_core_solver::d_is_correct() { + this->solve_yB(this->m_y); + for (auto j : this->non_basis()) { + T d = this->m_costs[j] - this->m_A.dot_product_with_column(this->m_y, j); + if (numeric_traits::get_double(abs(d - this->m_d[j])) >= 0.001) { + LP_OUT(this->m_settings, "total_iterations = " << this->total_iterations() << std::endl + << "d[" << j << "] = " << this->m_d[j] << " but should be " << d << std::endl); + return false; + } + } + return true; +} + +template void lp_dual_core_solver::xb_minus_delta_p_pivot_column() { + unsigned i = this->m_m(); + while (i--) { + this->m_x[this->m_basis[i]] -= m_theta_P * this->m_ed[i]; + } +} + +template void lp_dual_core_solver::update_betas() { // page 194 of Progress ... todo - once in a while betas have to be reinitialized + T one_over_arq = numeric_traits::one() / this->m_pivot_row[m_q]; + T beta_r = this->m_betas[m_r] = std::max(T(0.0001), (m_betas[m_r] * one_over_arq) * one_over_arq); + T k = -2 * one_over_arq; + unsigned i = this->m_m(); + while (i--) { + if (static_cast(i) == m_r) continue; + T a = this->m_ed[i]; + m_betas[i] += a * (a * beta_r + k * this->m_pivot_row_of_B_1[i]); + if (m_betas[i] < T(0.0001)) + m_betas[i] = T(0.0001); + } +} + +template void lp_dual_core_solver::apply_flips() { + for (unsigned j : m_flipped_boxed) { + lean_assert(this->x_is_at_bound(j)); + if (this->x_is_at_low_bound(j)) { + this->m_x[j] = this->m_upper_bounds[j]; + } else { + this->m_x[j] = this->m_low_bounds[j]; + } + } +} + +template void lp_dual_core_solver::snap_xN_column_to_bounds(unsigned j) { + switch (this->m_column_type[j]) { + case column_type::fixed: + this->m_x[j] = this->m_low_bounds[j]; + break; + case column_type::boxed: + if (this->x_is_at_low_bound(j)) { + this->m_x[j] = this->m_low_bounds[j]; + } else { + this->m_x[j] = this->m_upper_bounds[j]; + } + break; + case column_type::low_bound: + this->m_x[j] = this->m_low_bounds[j]; + break; + case column_type::upper_bound: + this->m_x[j] = this->m_upper_bounds[j]; + break; + case column_type::free_column: + break; + default: + lean_unreachable(); + } +} + +template void lp_dual_core_solver::snap_xN_to_bounds() { + for (auto j : this->non_basis()) { + snap_xN_column_to_bounds(j); + } +} + +template void lp_dual_core_solver::init_beta_precisely(unsigned i) { + vector vec(this->m_m(), numeric_traits::zero()); + vec[i] = numeric_traits::one(); + this->m_factorization->solve_yB_with_error_check(vec, this->m_basis); + T beta = numeric_traits::zero(); + for (T & v : vec) { + beta += v * v; + } + this->m_betas[i] =beta; +} + +template void lp_dual_core_solver::init_betas_precisely() { + unsigned i = this->m_m(); + while (i--) { + init_beta_precisely(i); + } +} + +// step 7 of the algorithm from Progress +template bool lp_dual_core_solver::basis_change_and_update() { + update_betas(); + update_d_and_xB(); + // m_theta_P = m_delta / this->m_ed[m_r]; + m_theta_P = m_delta / this->m_pivot_row[m_q]; + // xb_minus_delta_p_pivot_column(); + apply_flips(); + if (!this->update_basis_and_x(m_q, m_p, m_theta_P)) { + init_betas_precisely(); + return false; + } + + if (snap_runaway_nonbasic_column(m_p)) { + if (!this->find_x_by_solving()) { + revert_to_previous_basis(); + this->m_iters_with_no_cost_growing++; + return false; + } + } + + if (!problem_is_dual_feasible()) { + // todo : shift the costs!!!! + revert_to_previous_basis(); + this->m_iters_with_no_cost_growing++; + return false; + } + + lean_assert(d_is_correct()); + return true; +} + +template void lp_dual_core_solver::recover_leaving() { + switch (m_entering_boundary_position) { + case at_low_bound: + case at_fixed: + this->m_x[m_q] = this->m_low_bounds[m_q]; + break; + case at_upper_bound: + this->m_x[m_q] = this->m_upper_bounds[m_q]; + break; + case free_of_bounds: + this->m_x[m_q] = zero_of_type(); + default: + lean_unreachable(); + } +} + +template void lp_dual_core_solver::revert_to_previous_basis() { + LP_OUT(this->m_settings, "revert to previous basis on ( " << m_p << ", " << m_q << ")" << std::endl); + this->change_basis_unconditionally(m_p, m_q); + init_factorization(this->m_factorization, this->m_A, this->m_basis, this->m_settings); + if (this->m_factorization->get_status() != LU_status::OK) { + this->set_status(FLOATING_POINT_ERROR); // complete failure + return; + } + recover_leaving(); + if (!this->find_x_by_solving()) { + this->set_status(FLOATING_POINT_ERROR); + return; + } + recalculate_xB_and_d(); + init_betas_precisely(); +} + +// returns true if the column has been snapped +template bool lp_dual_core_solver::snap_runaway_nonbasic_column(unsigned j) { + switch (this->m_column_types[j]) { + case column_type::fixed: + case column_type::low_bound: + if (!this->x_is_at_low_bound(j)) { + this->m_x[j] = this->m_low_bounds[j]; + return true; + } + break; + case column_type::boxed: + { + bool closer_to_low_bound = abs(this->m_low_bounds[j] - this->m_x[j]) < abs(this->m_upper_bounds[j] - this->m_x[j]); + if (closer_to_low_bound) { + if (!this->x_is_at_low_bound(j)) { + this->m_x[j] = this->m_low_bounds[j]; + return true; + } + } else { + if (!this->x_is_at_upper_bound(j)) { + this->m_x[j] = this->m_low_bounds[j]; + return true; + } + } + } + break; + case column_type::upper_bound: + if (!this->x_is_at_upper_bound(j)) { + this->m_x[j] = this->m_upper_bounds[j]; + return true; + } + break; + default: + break; + } + return false; +} + + +template bool lp_dual_core_solver::problem_is_dual_feasible() const { + for (unsigned j : this->non_basis()){ + if (!this->column_is_dual_feasible(j)) { + // std::cout << "column " << j << " is not dual feasible" << std::endl; + // std::cout << "m_d[" << j << "] = " << this->m_d[j] << std::endl; + // std::cout << "x[" << j << "] = " << this->m_x[j] << std::endl; + // std::cout << "type = " << column_type_to_string(this->m_column_type[j]) << std::endl; + // std::cout << "bounds = " << this->m_low_bounds[j] << "," << this->m_upper_bounds[j] << std::endl; + // std::cout << "total_iterations = " << this->total_iterations() << std::endl; + return false; + } + } + return true; +} + +template unsigned lp_dual_core_solver::get_number_of_rows_to_try_for_leaving() { + unsigned s = this->m_m(); + if (this->m_m() > 300) { + s = (unsigned)((s / 100.0) * this->m_settings.percent_of_entering_to_check); + } + return my_random() % s + 1; +} + +template bool lp_dual_core_solver::delta_keeps_the_sign(int initial_delta_sign, const T & delta) { + if (numeric_traits::precise()) + return ((delta > numeric_traits::zero()) && (initial_delta_sign == 1)) || + ((delta < numeric_traits::zero()) && (initial_delta_sign == -1)); + + double del = numeric_traits::get_double(delta); + return ( (del > this->m_settings.zero_tolerance) && (initial_delta_sign == 1)) || + ((del < - this->m_settings.zero_tolerance) && (initial_delta_sign == -1)); +} + +template void lp_dual_core_solver::set_status_to_tentative_dual_unbounded_or_dual_unbounded() { + if (this->get_status() == TENTATIVE_DUAL_UNBOUNDED) { + this->set_status(DUAL_UNBOUNDED); + } else { + this->set_status(TENTATIVE_DUAL_UNBOUNDED); + } +} + +template void lp_dual_core_solver::add_tight_breakpoints_and_q_to_flipped_set() { + m_flipped_boxed.insert(m_q); + for (auto j : m_tight_set) { + m_flipped_boxed.insert(j); + } +} + +template T lp_dual_core_solver::delta_lost_on_flips_of_tight_breakpoints() { + T ret = abs(this->bound_span(m_q) * this->m_pivot_row[m_q]); + for (auto j : m_tight_set) { + ret += abs(this->bound_span(j) * this->m_pivot_row[j]); + } + return ret; +} + +template bool lp_dual_core_solver::tight_breakpoinst_are_all_boxed() { + if (this->m_column_types[m_q] != column_type::boxed) return false; + for (auto j : m_tight_set) { + if (this->m_column_types[j] != column_type::boxed) return false; + } + return true; +} + +template T lp_dual_core_solver::calculate_harris_delta_on_breakpoint_set() { + bool first_time = true; + T ret = zero_of_type(); + lean_assert(m_breakpoint_set.size() > 0); + for (auto j : m_breakpoint_set) { + T t; + if (this->x_is_at_low_bound(j)) { + t = abs((std::max(this->m_d[j], numeric_traits::zero()) + m_harris_tolerance) / this->m_pivot_row[j]); + } else { + t = abs((std::min(this->m_d[j], numeric_traits::zero()) - m_harris_tolerance) / this->m_pivot_row[j]); + } + if (first_time) { + ret = t; + first_time = false; + } else if (t < ret) { + ret = t; + } + } + return ret; +} + +template void lp_dual_core_solver::fill_tight_set_on_harris_delta(const T & harris_delta ){ + m_tight_set.clear(); + for (auto j : m_breakpoint_set) { + if (this->x_is_at_low_bound(j)) { + if (abs(std::max(this->m_d[j], numeric_traits::zero()) / this->m_pivot_row[j]) <= harris_delta){ + m_tight_set.insert(j); + } + } else { + if (abs(std::min(this->m_d[j], numeric_traits::zero() ) / this->m_pivot_row[j]) <= harris_delta){ + m_tight_set.insert(j); + } + } + } +} + +template void lp_dual_core_solver::find_q_on_tight_set() { + m_q = -1; + T max_pivot; + for (auto j : m_tight_set) { + T r = abs(this->m_pivot_row[j]); + if (m_q != -1) { + if (r > max_pivot) { + max_pivot = r; + m_q = j; + } + } else { + max_pivot = r; + m_q = j; + } + } + m_tight_set.erase(m_q); + lean_assert(m_q != -1); +} + +template void lp_dual_core_solver::find_q_and_tight_set() { + T harris_del = calculate_harris_delta_on_breakpoint_set(); + fill_tight_set_on_harris_delta(harris_del); + find_q_on_tight_set(); + m_entering_boundary_position = this->get_non_basic_column_value_position(m_q); +} + +template void lp_dual_core_solver::erase_tight_breakpoints_and_q_from_breakpoint_set() { + m_breakpoint_set.erase(m_q); + for (auto j : m_tight_set) { + m_breakpoint_set.erase(j); + } +} + +template bool lp_dual_core_solver::ratio_test() { + m_sign_of_alpha_r = define_sign_of_alpha_r(); + fill_breakpoint_set(); + m_flipped_boxed.clear(); + int initial_delta_sign = m_delta >= numeric_traits::zero()? 1: -1; + do { + if (m_breakpoint_set.size() == 0) { + set_status_to_tentative_dual_unbounded_or_dual_unbounded(); + return false; + } + this->set_status(FEASIBLE); + find_q_and_tight_set(); + if (!tight_breakpoinst_are_all_boxed()) break; + T del = m_delta - delta_lost_on_flips_of_tight_breakpoints() * initial_delta_sign; + if (!delta_keeps_the_sign(initial_delta_sign, del)) break; + if (m_tight_set.size() + 1 == m_breakpoint_set.size()) { + break; // deciding not to flip since we might get stuck without finding m_q, the column entering the basis + } + // we can flip m_q together with the tight set and look for another breakpoint candidate for m_q and another tight set + add_tight_breakpoints_and_q_to_flipped_set(); + m_delta = del; + erase_tight_breakpoints_and_q_from_breakpoint_set(); + } while (true); + m_theta_D = this->m_d[m_q] / this->m_pivot_row[m_q]; + return true; +} + +template void lp_dual_core_solver::process_flipped() { + init_a_wave_by_zeros(); + for (auto j : m_flipped_boxed) { + update_a_wave(signed_span_of_boxed(j), j); + } +} +template void lp_dual_core_solver::update_d_and_xB() { + for (auto j : this->non_basis()) { + this->m_d[j] -= m_theta_D * this->m_pivot_row[j]; + } + this->m_d[m_p] = - m_theta_D; + if (m_flipped_boxed.size() > 0) { + process_flipped(); + update_xb_after_bound_flips(); + } +} + +template void lp_dual_core_solver::calculate_beta_r_precisely() { + T t = numeric_traits::zero(); + unsigned i = this->m_m(); + while (i--) { + T b = this->m_pivot_row_of_B_1[i]; + t += b * b; + } + m_betas[m_r] = t; +} +// see "Progress in the dual simplex method for large scale LP problems: practical dual phase 1 algorithms" + +template void lp_dual_core_solver::update_xb_after_bound_flips() { + this->m_factorization->solve_By(m_a_wave); + unsigned i = this->m_m(); + while (i--) { + this->m_x[this->m_basis[i]] -= m_a_wave[i]; + } +} + +template void lp_dual_core_solver::one_iteration() { + unsigned number_of_rows_to_try = get_number_of_rows_to_try_for_leaving(); + unsigned offset_in_rows = my_random() % this->m_m(); + if (this->get_status() == TENTATIVE_DUAL_UNBOUNDED) { + number_of_rows_to_try = this->m_m(); + } else { + this->set_status(FEASIBLE); + } + pricing_loop(number_of_rows_to_try, offset_in_rows); + lean_assert(problem_is_dual_feasible()); +} + +template void lp_dual_core_solver::solve() { // see the page 35 + lean_assert(d_is_correct()); + lean_assert(problem_is_dual_feasible()); + lean_assert(this->basis_heading_is_correct()); + this->set_total_iterations(0); + this->m_iters_with_no_cost_growing = 0; + do { + if (this->print_statistics_with_iterations_and_nonzeroes_and_cost_and_check_that_the_time_is_over("", *this->m_settings.get_message_ostream())){ + return; + } + one_iteration(); + } while (this->get_status() != FLOATING_POINT_ERROR && this->get_status() != DUAL_UNBOUNDED && this->get_status() != OPTIMAL && + this->m_iters_with_no_cost_growing <= this->m_settings.max_number_of_iterations_with_no_improvements + && this->total_iterations() <= this->m_settings.max_total_number_of_iterations); +} +} diff --git a/src/util/lp/lp_dual_core_solver_instances.cpp b/src/util/lp/lp_dual_core_solver_instances.cpp new file mode 100644 index 000000000..8016088f8 --- /dev/null +++ b/src/util/lp/lp_dual_core_solver_instances.cpp @@ -0,0 +1,29 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include +#include +#include +#include "util/vector.h" +#include +#include "util/lp/lp_dual_core_solver.hpp" +template void lean::lp_dual_core_solver::start_with_initial_basis_and_make_it_dual_feasible(); +template void lean::lp_dual_core_solver::solve(); +template lean::lp_dual_core_solver::lp_dual_core_solver(lean::static_matrix&, vector&, + vector&, + vector&, + vector&, + vector &, + vector &, + vector&, + vector&, + vector&, + vector&, + lean::lp_settings&, const lean::column_namer&); +template void lean::lp_dual_core_solver::start_with_initial_basis_and_make_it_dual_feasible(); +template void lean::lp_dual_core_solver::solve(); +template void lean::lp_dual_core_solver::restore_non_basis(); +template void lean::lp_dual_core_solver::restore_non_basis(); +template void lean::lp_dual_core_solver::revert_to_previous_basis(); +template void lean::lp_dual_core_solver::revert_to_previous_basis(); diff --git a/src/util/lp/lp_dual_simplex.h b/src/util/lp/lp_dual_simplex.h new file mode 100644 index 000000000..78ff08b0a --- /dev/null +++ b/src/util/lp/lp_dual_simplex.h @@ -0,0 +1,79 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#pragma once +#include "util/vector.h" +#include "util/lp/lp_utils.h" +#include "util/lp/lp_solver.h" +#include "util/lp/lp_dual_core_solver.h" +namespace lean { + +template +class lp_dual_simplex: public lp_solver { + lp_dual_core_solver * m_core_solver = nullptr; + vector m_b_copy; + vector m_low_bounds; // We don't have a convention here that all low bounds are zeros. At least it does not hold for the first stage solver + vector m_column_types_of_core_solver; + vector m_column_types_of_logicals; + vector m_can_enter_basis; +public: + ~lp_dual_simplex() { + if (m_core_solver != nullptr) { + delete m_core_solver; + } + } + + + + void decide_on_status_after_stage1(); + + void fix_logical_for_stage2(unsigned j); + + void fix_structural_for_stage2(unsigned j); + + void unmark_boxed_and_fixed_columns_and_fix_structural_costs(); + + void restore_right_sides(); + + void solve_for_stage2(); + + void fill_x_with_zeros(); + + void stage1(); + + void stage2(); + + void fill_first_stage_solver_fields(); + + column_type get_column_type(unsigned j); + + void fill_costs_bounds_types_and_can_enter_basis_for_the_first_stage_solver_structural_column(unsigned j); + + void fill_costs_bounds_types_and_can_enter_basis_for_the_first_stage_solver_logical_column(unsigned j); + + void fill_costs_and_bounds_and_column_types_for_the_first_stage_solver(); + + void set_type_for_logical(unsigned j, column_type col_type) { + this->m_column_types_of_logicals[j - this->number_of_core_structurals()] = col_type; + } + + void fill_first_stage_solver_fields_for_row_slack_and_artificial(unsigned row, + unsigned & slack_var, + unsigned & artificial); + + void augment_matrix_A_and_fill_x_and_allocate_some_fields(); + + + + void copy_m_b_aside_and_set_it_to_zeros(); + + void find_maximal_solution(); + + virtual T get_column_value(unsigned column) const { + return this->get_column_value_with_core_solver(column, m_core_solver); + } + + T get_current_cost() const; +}; +} diff --git a/src/util/lp/lp_dual_simplex.hpp b/src/util/lp/lp_dual_simplex.hpp new file mode 100644 index 000000000..5047e117f --- /dev/null +++ b/src/util/lp/lp_dual_simplex.hpp @@ -0,0 +1,362 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include "util/lp/lp_dual_simplex.h" +namespace lean{ + +template void lp_dual_simplex::decide_on_status_after_stage1() { + switch (m_core_solver->get_status()) { + case OPTIMAL: + if (this->m_settings.abs_val_is_smaller_than_artificial_tolerance(m_core_solver->get_cost())) { + this->m_status = FEASIBLE; + } else { + this->m_status = UNBOUNDED; + } + break; + case DUAL_UNBOUNDED: + lean_unreachable(); + case ITERATIONS_EXHAUSTED: + this->m_status = ITERATIONS_EXHAUSTED; + break; + case TIME_EXHAUSTED: + this->m_status = TIME_EXHAUSTED; + break; + case FLOATING_POINT_ERROR: + this->m_status = FLOATING_POINT_ERROR; + break; + default: + lean_unreachable(); + } +} + +template void lp_dual_simplex::fix_logical_for_stage2(unsigned j) { + lean_assert(j >= this->number_of_core_structurals()); + switch (m_column_types_of_logicals[j - this->number_of_core_structurals()]) { + case column_type::low_bound: + m_low_bounds[j] = numeric_traits::zero(); + m_column_types_of_core_solver[j] = column_type::low_bound; + m_can_enter_basis[j] = true; + break; + case column_type::fixed: + this->m_upper_bounds[j] = m_low_bounds[j] = numeric_traits::zero(); + m_column_types_of_core_solver[j] = column_type::fixed; + m_can_enter_basis[j] = false; + break; + default: + lean_unreachable(); + } +} + +template void lp_dual_simplex::fix_structural_for_stage2(unsigned j) { + column_info * ci = this->m_map_from_var_index_to_column_info[this->m_core_solver_columns_to_external_columns[j]]; + switch (ci->get_column_type()) { + case column_type::low_bound: + m_low_bounds[j] = numeric_traits::zero(); + m_column_types_of_core_solver[j] = column_type::low_bound; + m_can_enter_basis[j] = true; + break; + case column_type::fixed: + case column_type::upper_bound: + lean_unreachable(); + case column_type::boxed: + this->m_upper_bounds[j] = ci->get_adjusted_upper_bound() / this->m_column_scale[j]; + m_low_bounds[j] = numeric_traits::zero(); + m_column_types_of_core_solver[j] = column_type::boxed; + m_can_enter_basis[j] = true; + break; + case column_type::free_column: + m_can_enter_basis[j] = true; + m_column_types_of_core_solver[j] = column_type::free_column; + break; + default: + lean_unreachable(); + } + // T cost_was = this->m_costs[j]; + this->set_scaled_cost(j); +} + +template void lp_dual_simplex::unmark_boxed_and_fixed_columns_and_fix_structural_costs() { + unsigned j = this->m_A->column_count(); + while (j-- > this->number_of_core_structurals()) { + fix_logical_for_stage2(j); + } + j = this->number_of_core_structurals(); + while (j--) { + fix_structural_for_stage2(j); + } +} + +template void lp_dual_simplex::restore_right_sides() { + unsigned i = this->m_A->row_count(); + while (i--) { + this->m_b[i] = m_b_copy[i]; + } +} + +template void lp_dual_simplex::solve_for_stage2() { + m_core_solver->restore_non_basis(); + m_core_solver->solve_yB(m_core_solver->m_y); + m_core_solver->fill_reduced_costs_from_m_y_by_rows(); + m_core_solver->start_with_initial_basis_and_make_it_dual_feasible(); + m_core_solver->set_status(FEASIBLE); + m_core_solver->solve(); + switch (m_core_solver->get_status()) { + case OPTIMAL: + this->m_status = OPTIMAL; + break; + case DUAL_UNBOUNDED: + this->m_status = INFEASIBLE; + break; + case TIME_EXHAUSTED: + this->m_status = TIME_EXHAUSTED; + break; + case FLOATING_POINT_ERROR: + this->m_status = FLOATING_POINT_ERROR; + break; + default: + lean_unreachable(); + } + this->m_second_stage_iterations = m_core_solver->total_iterations(); + this->m_total_iterations = (this->m_first_stage_iterations + this->m_second_stage_iterations); +} + +template void lp_dual_simplex::fill_x_with_zeros() { + unsigned j = this->m_A->column_count(); + while (j--) { + this->m_x[j] = numeric_traits::zero(); + } +} + +template void lp_dual_simplex::stage1() { + lean_assert(m_core_solver == nullptr); + this->m_x.resize(this->m_A->column_count(), numeric_traits::zero()); + if (this->m_settings.get_message_ostream() != nullptr) + this->print_statistics_on_A(*this->m_settings.get_message_ostream()); + m_core_solver = new lp_dual_core_solver( + *this->m_A, + m_can_enter_basis, + this->m_b, // the right side vector + this->m_x, + this->m_basis, + this->m_nbasis, + this->m_heading, + this->m_costs, + this->m_column_types_of_core_solver, + this->m_low_bounds, + this->m_upper_bounds, + this->m_settings, + *this); + m_core_solver->fill_reduced_costs_from_m_y_by_rows(); + m_core_solver->start_with_initial_basis_and_make_it_dual_feasible(); + if (this->m_settings.abs_val_is_smaller_than_artificial_tolerance(m_core_solver->get_cost())) { + // skipping stage 1 + m_core_solver->set_status(OPTIMAL); + m_core_solver->set_total_iterations(0); + } else { + m_core_solver->solve(); + } + decide_on_status_after_stage1(); + this->m_first_stage_iterations = m_core_solver->total_iterations(); +} + +template void lp_dual_simplex::stage2() { + unmark_boxed_and_fixed_columns_and_fix_structural_costs(); + restore_right_sides(); + solve_for_stage2(); +} + +template void lp_dual_simplex::fill_first_stage_solver_fields() { + unsigned slack_var = this->number_of_core_structurals(); + unsigned artificial = this->number_of_core_structurals() + this->m_slacks; + + for (unsigned row = 0; row < this->row_count(); row++) { + fill_first_stage_solver_fields_for_row_slack_and_artificial(row, slack_var, artificial); + } + fill_costs_and_bounds_and_column_types_for_the_first_stage_solver(); +} + +template column_type lp_dual_simplex::get_column_type(unsigned j) { + lean_assert(j < this->m_A->column_count()); + if (j >= this->number_of_core_structurals()) { + return m_column_types_of_logicals[j - this->number_of_core_structurals()]; + } + return this->m_map_from_var_index_to_column_info[this->m_core_solver_columns_to_external_columns[j]]->get_column_type(); +} + +template void lp_dual_simplex::fill_costs_bounds_types_and_can_enter_basis_for_the_first_stage_solver_structural_column(unsigned j) { + // see 4.7 in the dissertation of Achim Koberstein + lean_assert(this->m_core_solver_columns_to_external_columns.find(j) != + this->m_core_solver_columns_to_external_columns.end()); + + T free_bound = T(1e4); // see 4.8 + unsigned jj = this->m_core_solver_columns_to_external_columns[j]; + lean_assert(this->m_map_from_var_index_to_column_info.find(jj) != this->m_map_from_var_index_to_column_info.end()); + column_info * ci = this->m_map_from_var_index_to_column_info[jj]; + switch (ci->get_column_type()) { + case column_type::upper_bound: { + std::stringstream s; + s << "unexpected bound type " << j << " " + << column_type_to_string(get_column_type(j)); + throw_exception(s.str()); + break; + } + case column_type::low_bound: { + m_can_enter_basis[j] = true; + this->set_scaled_cost(j); + this->m_low_bounds[j] = numeric_traits::zero(); + this->m_upper_bounds[j] =numeric_traits::one(); + break; + } + case column_type::free_column: { + m_can_enter_basis[j] = true; + this->set_scaled_cost(j); + this->m_upper_bounds[j] = free_bound; + this->m_low_bounds[j] = -free_bound; + break; + } + case column_type::boxed: + m_can_enter_basis[j] = false; + this->m_costs[j] = numeric_traits::zero(); + this->m_upper_bounds[j] = this->m_low_bounds[j] = numeric_traits::zero(); // is it needed? + break; + default: + lean_unreachable(); + } + m_column_types_of_core_solver[j] = column_type::boxed; +} + +template void lp_dual_simplex::fill_costs_bounds_types_and_can_enter_basis_for_the_first_stage_solver_logical_column(unsigned j) { + this->m_costs[j] = 0; + lean_assert(get_column_type(j) != column_type::upper_bound); + if ((m_can_enter_basis[j] = (get_column_type(j) == column_type::low_bound))) { + m_column_types_of_core_solver[j] = column_type::boxed; + this->m_low_bounds[j] = numeric_traits::zero(); + this->m_upper_bounds[j] = numeric_traits::one(); + } else { + m_column_types_of_core_solver[j] = column_type::fixed; + this->m_low_bounds[j] = numeric_traits::zero(); + this->m_upper_bounds[j] = numeric_traits::zero(); + } +} + +template void lp_dual_simplex::fill_costs_and_bounds_and_column_types_for_the_first_stage_solver() { + unsigned j = this->m_A->column_count(); + while (j-- > this->number_of_core_structurals()) { // go over logicals here + fill_costs_bounds_types_and_can_enter_basis_for_the_first_stage_solver_logical_column(j); + } + j = this->number_of_core_structurals(); + while (j--) { + fill_costs_bounds_types_and_can_enter_basis_for_the_first_stage_solver_structural_column(j); + } +} + +template void lp_dual_simplex::fill_first_stage_solver_fields_for_row_slack_and_artificial(unsigned row, + unsigned & slack_var, + unsigned & artificial) { + lean_assert(row < this->row_count()); + auto & constraint = this->m_constraints[this->m_core_solver_rows_to_external_rows[row]]; + // we need to bring the program to the form Ax = b + T rs = this->m_b[row]; + switch (constraint.m_relation) { + case Equal: // no slack variable here + set_type_for_logical(artificial, column_type::fixed); + this->m_basis[row] = artificial; + this->m_costs[artificial] = numeric_traits::zero(); + (*this->m_A)(row, artificial) = numeric_traits::one(); + artificial++; + break; + + case Greater_or_equal: + set_type_for_logical(slack_var, column_type::low_bound); + (*this->m_A)(row, slack_var) = - numeric_traits::one(); + if (rs > 0) { + // adding one artificial + set_type_for_logical(artificial, column_type::fixed); + (*this->m_A)(row, artificial) = numeric_traits::one(); + this->m_basis[row] = artificial; + this->m_costs[artificial] = numeric_traits::zero(); + artificial++; + } else { + // we can put a slack_var into the basis, and avoid adding an artificial variable + this->m_basis[row] = slack_var; + this->m_costs[slack_var] = numeric_traits::zero(); + } + slack_var++; + break; + case Less_or_equal: + // introduce a non-negative slack variable + set_type_for_logical(slack_var, column_type::low_bound); + (*this->m_A)(row, slack_var) = numeric_traits::one(); + if (rs < 0) { + // adding one artificial + set_type_for_logical(artificial, column_type::fixed); + (*this->m_A)(row, artificial) = - numeric_traits::one(); + this->m_basis[row] = artificial; + this->m_costs[artificial] = numeric_traits::zero(); + artificial++; + } else { + // we can put slack_var into the basis, and avoid adding an artificial variable + this->m_basis[row] = slack_var; + this->m_costs[slack_var] = numeric_traits::zero(); + } + slack_var++; + break; + } +} + +template void lp_dual_simplex::augment_matrix_A_and_fill_x_and_allocate_some_fields() { + this->count_slacks_and_artificials(); + this->m_A->add_columns_at_the_end(this->m_slacks + this->m_artificials); + unsigned n = this->m_A->column_count(); + this->m_column_types_of_core_solver.resize(n); + m_column_types_of_logicals.resize(this->m_slacks + this->m_artificials); + this->m_costs.resize(n); + this->m_upper_bounds.resize(n); + this->m_low_bounds.resize(n); + m_can_enter_basis.resize(n); + this->m_basis.resize(this->m_A->row_count()); +} + + + +template void lp_dual_simplex::copy_m_b_aside_and_set_it_to_zeros() { + for (unsigned i = 0; i < this->m_b.size(); i++) { + m_b_copy.push_back(this->m_b[i]); + this->m_b[i] = numeric_traits::zero(); // preparing for the first stage + } +} + +template void lp_dual_simplex::find_maximal_solution(){ + if (this->problem_is_empty()) { + this->m_status = lp_status::EMPTY; + return; + } + + this->flip_costs(); // do it for now, todo ( remove the flipping) + + this->cleanup(); + if (this->m_status == INFEASIBLE) { + return; + } + this->fill_matrix_A_and_init_right_side(); + this->fill_m_b(); + this->scale(); + augment_matrix_A_and_fill_x_and_allocate_some_fields(); + fill_first_stage_solver_fields(); + copy_m_b_aside_and_set_it_to_zeros(); + stage1(); + if (this->m_status == FEASIBLE) { + stage2(); + } +} + + +template T lp_dual_simplex::get_current_cost() const { + T ret = numeric_traits::zero(); + for (auto it : this->m_map_from_var_index_to_column_info) { + ret += this->get_column_cost_value(it.first, it.second); + } + return -ret; // we flip costs for now +} +} diff --git a/src/util/lp/lp_dual_simplex_instances.cpp b/src/util/lp/lp_dual_simplex_instances.cpp new file mode 100644 index 000000000..6610814d8 --- /dev/null +++ b/src/util/lp/lp_dual_simplex_instances.cpp @@ -0,0 +1,9 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include "util/lp/lp_dual_simplex.hpp" +template lean::mpq lean::lp_dual_simplex::get_current_cost() const; +template void lean::lp_dual_simplex::find_maximal_solution(); +template double lean::lp_dual_simplex::get_current_cost() const; +template void lean::lp_dual_simplex::find_maximal_solution(); diff --git a/src/util/lp/lp_primal_core_solver.h b/src/util/lp/lp_primal_core_solver.h new file mode 100644 index 000000000..f05f88db6 --- /dev/null +++ b/src/util/lp/lp_primal_core_solver.h @@ -0,0 +1,986 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ + +#pragma once +#include +#include +#include +#include +#include +#include "util/vector.h" +#include +#include +#include +#include +#include "util/lp/lu.h" +#include "util/lp/lp_solver.h" +#include "util/lp/static_matrix.h" +#include "util/lp/core_solver_pretty_printer.h" +#include "util/lp/lp_core_solver_base.h" +#include "util/lp/breakpoint.h" +#include "util/lp/binary_heap_priority_queue.h" +#include "util/lp/int_set.h" +#include "util/lp/iterator_on_row.h" +namespace lean { + +// This core solver solves (Ax=b, low_bound_values \leq x \leq upper_bound_values, maximize costs*x ) +// The right side b is given implicitly by x and the basis +template +class lp_primal_core_solver:public lp_core_solver_base { +public: + // m_sign_of_entering is set to 1 if the entering variable needs + // to grow and is set to -1 otherwise + unsigned m_column_norm_update_counter; + T m_enter_price_eps; + int m_sign_of_entering_delta; + vector> m_breakpoints; + binary_heap_priority_queue m_breakpoint_indices_queue; + indexed_vector m_beta; // see Swietanowski working vector beta for column norms + T m_epsilon_of_reduced_cost = T(1)/T(10000000); + vector m_costs_backup; + T m_converted_harris_eps; + unsigned m_inf_row_index_for_tableau; + bool m_bland_mode_tableau; + int_set m_left_basis_tableau; + unsigned m_bland_mode_threshold = 1000; + unsigned m_left_basis_repeated; + vector m_leaving_candidates; + // T m_converted_harris_eps = convert_struct::convert(this->m_settings.harris_feasibility_tolerance); + std::list m_non_basis_list; + void sort_non_basis(); + void sort_non_basis_rational(); + int choose_entering_column(unsigned number_of_benefitial_columns_to_go_over); + int choose_entering_column_tableau(); + int choose_entering_column_presize(unsigned number_of_benefitial_columns_to_go_over); + int find_leaving_and_t_with_breakpoints(unsigned entering, X & t); + // int find_inf_row() { + // // mimicing CLP : todo : use a heap + // int j = -1; + // for (unsigned k : this->m_inf_set.m_index) { + // if (k < static_cast(j)) + // j = static_cast(k); + // } + // if (j == -1) + // return -1; + // return this->m_basis_heading[j]; + // #if 0 + // vector choices; + // unsigned len = 100000000; + // for (unsigned j : this->m_inf_set.m_index) { + // int i = this->m_basis_heading[j]; + // lean_assert(i >= 0); + // unsigned row_len = this->m_A.m_rows[i].size(); + // if (row_len < len) { + // choices.clear(); + // choices.push_back(i); + // len = row_len; + // if (my_random() % 10) break; + // } else if (row_len == len) { + // choices.push_back(i); + // if (my_random() % 10) break; + // } + // } + + // if (choices.size() == 0) + // return -1; + + // if (choices.size() == 1) + // return choices[0]; + + // unsigned k = my_random() % choices.size(); + // return choices[k]; + // #endif + // } + + + bool column_is_benefitial_for_entering_basis_on_sign_row_strategy(unsigned j, int sign) const { + // sign = 1 means the x of the basis column of the row has to grow to become feasible, when the coeff before j is neg, or x - has to diminish when the coeff is pos + // we have xbj = -aj * xj + lean_assert(this->m_basis_heading[j] < 0); + lean_assert(this->column_is_feasible(j)); + switch (this->m_column_types[j]) { + case column_type::free_column: return true; + case column_type::fixed: return false; + case column_type::low_bound: + if (sign < 0) + return true; + return !this->x_is_at_low_bound(j); + case column_type::upper_bound: + if (sign > 0) + return true; + return !this->x_is_at_upper_bound(j); + case column_type::boxed: + if (sign < 0) + return !this->x_is_at_low_bound(j); + return !this->x_is_at_upper_bound(j); + } + + lean_assert(false); // cannot be here + return false; + } + + + bool needs_to_grow(unsigned bj) const { + lean_assert(!this->column_is_feasible(bj)); + switch(this->m_column_types[bj]) { + case column_type::free_column: + return false; + case column_type::fixed: + case column_type::low_bound: + case column_type::boxed: + return this-> x_below_low_bound(bj); + default: + return false; + } + lean_assert(false); // unreachable + return false; + } + + int inf_sign_of_column(unsigned bj) const { + lean_assert(!this->column_is_feasible(bj)); + switch(this->m_column_types[bj]) { + case column_type::free_column: + return 0; + case column_type::low_bound: + return 1; + case column_type::fixed: + case column_type::boxed: + return this->x_above_upper_bound(bj)? -1: 1; + default: + return -1; + } + lean_assert(false); // unreachable + return 0; + + } + + + bool monoid_can_decrease(const row_cell & rc) const { + unsigned j = rc.m_j; + lean_assert(this->column_is_feasible(j)); + switch (this->m_column_types[j]) { + case column_type::free_column: + return true; + case column_type::fixed: + return false; + case column_type::low_bound: + if (is_pos(rc.get_val())) { + return this->x_above_low_bound(j); + } + + return true; + case column_type::upper_bound: + if (is_pos(rc.get_val())) { + return true; + } + + return this->x_below_upper_bound(j); + case column_type::boxed: + if (is_pos(rc.get_val())) { + return this->x_above_low_bound(j); + } + + return this->x_below_upper_bound(j); + default: + return false; + } + lean_assert(false); // unreachable + return false; + } + + bool monoid_can_increase(const row_cell & rc) const { + unsigned j = rc.m_j; + lean_assert(this->column_is_feasible(j)); + switch (this->m_column_types[j]) { + case column_type::free_column: + return true; + case column_type::fixed: + return false; + case column_type::low_bound: + if (is_neg(rc.get_val())) { + return this->x_above_low_bound(j); + } + + return true; + case column_type::upper_bound: + if (is_neg(rc.get_val())) { + return true; + } + + return this->x_below_upper_bound(j); + case column_type::boxed: + if (is_neg(rc.get_val())) { + return this->x_above_low_bound(j); + } + + return this->x_below_upper_bound(j); + default: + return false; + } + lean_assert(false); // unreachable + return false; + } + + unsigned get_number_of_basic_vars_that_might_become_inf(unsigned j) const { // consider looking at the signs here: todo + unsigned r = 0; + for (auto & cc : this->m_A.m_columns[j]) { + unsigned k = this->m_basis[cc.m_i]; + if (this->m_column_types[k] != column_type::free_column) + r++; + } + return r; + } + + + int find_beneficial_column_in_row_tableau_rows_bland_mode(int i, T & a_ent) { + int j = -1; + unsigned bj = this->m_basis[i]; + bool bj_needs_to_grow = needs_to_grow(bj); + for (const row_cell& rc : this->m_A.m_rows[i]) { + if (rc.m_j == bj) + continue; + if (bj_needs_to_grow) { + if (!monoid_can_decrease(rc)) + continue; + } else { + if (!monoid_can_increase(rc)) + continue; + } + if (rc.m_j < static_cast(j) ) { + j = rc.m_j; + a_ent = rc.m_value; + } + } + if (j == -1) { + m_inf_row_index_for_tableau = i; + } + + return j; + } + + int find_beneficial_column_in_row_tableau_rows(int i, T & a_ent) { + if (m_bland_mode_tableau) + return find_beneficial_column_in_row_tableau_rows_bland_mode(i, a_ent); + // a short row produces short infeasibility explanation and benefits at least one pivot operation + vector*> choices; + unsigned num_of_non_free_basics = 1000000; + unsigned len = 100000000; + unsigned bj = this->m_basis[i]; + bool bj_needs_to_grow = needs_to_grow(bj); + for (const row_cell& rc : this->m_A.m_rows[i]) { + unsigned j = rc.m_j; + if (j == bj) + continue; + if (bj_needs_to_grow) { + if (!monoid_can_decrease(rc)) + continue; + } else { + if (!monoid_can_increase(rc)) + continue; + } + unsigned damage = get_number_of_basic_vars_that_might_become_inf(j); + if (damage < num_of_non_free_basics) { + num_of_non_free_basics = damage; + len = this->m_A.m_columns[j].size(); + choices.clear(); + choices.push_back(&rc); + } else if (damage == num_of_non_free_basics && + this->m_A.m_columns[j].size() <= len && (my_random() % 2)) { + choices.push_back(&rc); + len = this->m_A.m_columns[j].size(); + } + } + + + if (choices.size() == 0) { + m_inf_row_index_for_tableau = i; + return -1; + } + const row_cell* rc = choices.size() == 1? choices[0] : + choices[my_random() % choices.size()]; + + a_ent = rc->m_value; + return rc->m_j; + } + static X positive_infinity() { + return convert_struct::convert(std::numeric_limits::max()); + } + + bool get_harris_theta(X & theta); + + void restore_harris_eps() { m_converted_harris_eps = convert_struct::convert(this->m_settings.harris_feasibility_tolerance); } + void zero_harris_eps() { m_converted_harris_eps = zero_of_type(); } + int find_leaving_on_harris_theta(X const & harris_theta, X & t); + bool try_jump_to_another_bound_on_entering(unsigned entering, const X & theta, X & t, bool & unlimited); + bool try_jump_to_another_bound_on_entering_unlimited(unsigned entering, X & t); + int find_leaving_and_t(unsigned entering, X & t); + int find_leaving_and_t_precise(unsigned entering, X & t); + int find_leaving_and_t_tableau(unsigned entering, X & t); + + void limit_theta(const X & lim, X & theta, bool & unlimited) { + if (unlimited) { + theta = lim; + unlimited = false; + } else { + theta = std::min(lim, theta); + } + } + + void limit_theta_on_basis_column_for_inf_case_m_neg_upper_bound(unsigned j, const T & m, X & theta, bool & unlimited) { + lean_assert(m < 0 && this->m_column_types[j] == column_type::upper_bound); + limit_inf_on_upper_bound_m_neg(m, this->m_x[j], this->m_upper_bounds[j], theta, unlimited); + } + + + void limit_theta_on_basis_column_for_inf_case_m_neg_low_bound(unsigned j, const T & m, X & theta, bool & unlimited) { + lean_assert(m < 0 && this->m_column_types[j] == column_type::low_bound); + limit_inf_on_bound_m_neg(m, this->m_x[j], this->m_low_bounds[j], theta, unlimited); + } + + + void limit_theta_on_basis_column_for_inf_case_m_pos_low_bound(unsigned j, const T & m, X & theta, bool & unlimited) { + lean_assert(m > 0 && this->m_column_types[j] == column_type::low_bound); + limit_inf_on_low_bound_m_pos(m, this->m_x[j], this->m_low_bounds[j], theta, unlimited); + } + + void limit_theta_on_basis_column_for_inf_case_m_pos_upper_bound(unsigned j, const T & m, X & theta, bool & unlimited) { + lean_assert(m > 0 && this->m_column_types[j] == column_type::upper_bound); + limit_inf_on_bound_m_pos(m, this->m_x[j], this->m_upper_bounds[j], theta, unlimited); + }; + + X harris_eps_for_bound(const X & bound) const { return ( convert_struct::convert(1) + abs(bound)/10) * m_converted_harris_eps/3; + } + + void get_bound_on_variable_and_update_leaving_precisely(unsigned j, vector & leavings, T m, X & t, T & abs_of_d_of_leaving); + + vector m_low_bounds_dummy; // needed for the base class only + + X get_max_bound(vector & b); + +#ifdef LEAN_DEBUG + void check_Ax_equal_b(); + void check_the_bounds(); + void check_bound(unsigned i); + void check_correctness(); +#endif + + // from page 183 of Istvan Maros's book + // the basis structures have not changed yet + void update_reduced_costs_from_pivot_row(unsigned entering, unsigned leaving); + + // return 0 if the reduced cost at entering is close enough to the refreshed + // 1 if it is way off, and 2 if it is unprofitable + int refresh_reduced_cost_at_entering_and_check_that_it_is_off(unsigned entering); + + void backup_and_normalize_costs(); + + void init_run(); + + void calc_working_vector_beta_for_column_norms(); + + void advance_on_entering_and_leaving(int entering, int leaving, X & t); + void advance_on_entering_and_leaving_tableau(int entering, int leaving, X & t); + void advance_on_entering_equal_leaving(int entering, X & t); + void advance_on_entering_equal_leaving_tableau(int entering, X & t); + + bool need_to_switch_costs() const { + if (this->m_settings.simplex_strategy() == simplex_strategy_enum::tableau_rows) + return false; + // lean_assert(calc_current_x_is_feasible() == current_x_is_feasible()); + return this->current_x_is_feasible() == this->m_using_infeas_costs; + } + + + void advance_on_entering(int entering); + void advance_on_entering_tableau(int entering); + void advance_on_entering_precise(int entering); + void push_forward_offset_in_non_basis(unsigned & offset_in_nb); + + unsigned get_number_of_non_basic_column_to_try_for_enter(); + + void print_column_norms(std::ostream & out); + + // returns the number of iterations + unsigned solve(); + + lu * factorization() {return this->m_factorization;} + + void delete_factorization(); + + // according to Swietanowski, " A new steepest edge approximation for the simplex method for linear programming" + void init_column_norms(); + + T calculate_column_norm_exactly(unsigned j); + + void update_or_init_column_norms(unsigned entering, unsigned leaving); + + // following Swietanowski - A new steepest ... + void update_column_norms(unsigned entering, unsigned leaving); + + T calculate_norm_of_entering_exactly(); + + void find_feasible_solution(); + + bool is_tiny() const {return this->m_m < 10 && this->m_n < 20;} + + void one_iteration(); + void one_iteration_tableau(); + + void advance_on_entering_and_leaving_tableau_rows(int entering, int leaving, const X &theta ) { + this->update_basis_and_x_tableau(entering, leaving, theta); + this->update_column_in_inf_set(entering); + } + + + int find_leaving_tableau_rows(X & new_val_for_leaving) { + int j = -1; + for (unsigned k : this->m_inf_set.m_index) { + if (k < static_cast(j)) + j = static_cast(k); + } + if (j == -1) + return -1; + + lean_assert(!this->column_is_feasible(j)); + switch (this->m_column_types[j]) { + case column_type::fixed: + case column_type::upper_bound: + new_val_for_leaving = this->m_upper_bounds[j]; + break; + case column_type::low_bound: + new_val_for_leaving = this->m_low_bounds[j]; + break; + case column_type::boxed: + if (this->x_above_upper_bound(j)) + new_val_for_leaving = this->m_upper_bounds[j]; + else + new_val_for_leaving = this->m_low_bounds[j]; + break; + default: + lean_assert(false); + } + return j; + } + + void one_iteration_tableau_rows() { + X new_val_for_leaving; + int leaving = find_leaving_tableau_rows(new_val_for_leaving); + if (leaving == -1) { + this->set_status(OPTIMAL); + return; + } + + if (!m_bland_mode_tableau) { + if (m_left_basis_tableau.contains(leaving)) { + if (++m_left_basis_repeated > m_bland_mode_threshold) { + m_bland_mode_tableau = true; + } + } else { + m_left_basis_tableau.insert(leaving); + } + } + T a_ent; + int entering = find_beneficial_column_in_row_tableau_rows(this->m_basis_heading[leaving], a_ent); + if (entering == -1) { + this->set_status(INFEASIBLE); + return; + } + X theta = (this->m_x[leaving] - new_val_for_leaving) / a_ent; + advance_on_entering_and_leaving_tableau_rows(entering, leaving, theta ); + lean_assert(this->m_x[leaving] == new_val_for_leaving); + if (this->current_x_is_feasible()) + this->set_status(OPTIMAL); + } + + void fill_breakpoints_array(unsigned entering); + + void try_add_breakpoint_in_row(unsigned i); + + void clear_breakpoints(); + + void change_slope_on_breakpoint(unsigned entering, breakpoint * b, T & slope_at_entering); + void advance_on_sorted_breakpoints(unsigned entering); + + void update_basis_and_x_with_comparison(unsigned entering, unsigned leaving, X delta); + + void decide_on_status_when_cannot_find_entering() { + lean_assert(!need_to_switch_costs()); + this->set_status(this->current_x_is_feasible()? OPTIMAL: INFEASIBLE); + } + + // void limit_theta_on_basis_column_for_feas_case_m_neg(unsigned j, const T & m, X & theta) { + // lean_assert(m < 0); + // lean_assert(this->m_column_type[j] == low_bound || this->m_column_type[j] == boxed); + // const X & eps = harris_eps_for_bound(this->m_low_bounds[j]); + // if (this->above_bound(this->m_x[j], this->m_low_bounds[j])) { + // theta = std::min((this->m_low_bounds[j] -this->m_x[j] - eps) / m, theta); + // if (theta < zero_of_type()) theta = zero_of_type(); + // } + // } + + void limit_theta_on_basis_column_for_feas_case_m_neg_no_check(unsigned j, const T & m, X & theta, bool & unlimited) { + lean_assert(m < 0); + const X& eps = harris_eps_for_bound(this->m_low_bounds[j]); + limit_theta((this->m_low_bounds[j] - this->m_x[j] - eps) / m, theta, unlimited); + if (theta < zero_of_type()) theta = zero_of_type(); + } + + bool limit_inf_on_bound_m_neg(const T & m, const X & x, const X & bound, X & theta, bool & unlimited) { + // x gets smaller + lean_assert(m < 0); + if (numeric_traits::precise()) { + if (this->below_bound(x, bound)) return false; + if (this->above_bound(x, bound)) { + limit_theta((bound - x) / m, theta, unlimited); + } else { + theta = zero_of_type(); + unlimited = false; + } + } else { + const X& eps = harris_eps_for_bound(bound); + if (this->below_bound(x, bound)) return false; + if (this->above_bound(x, bound)) { + limit_theta((bound - x - eps) / m, theta, unlimited); + } else { + theta = zero_of_type(); + unlimited = false; + } + } + return true; + } + + bool limit_inf_on_bound_m_pos(const T & m, const X & x, const X & bound, X & theta, bool & unlimited) { + // x gets larger + lean_assert(m > 0); + if (numeric_traits::precise()) { + if (this->above_bound(x, bound)) return false; + if (this->below_bound(x, bound)) { + limit_theta((bound - x) / m, theta, unlimited); + } else { + theta = zero_of_type(); + unlimited = false; + } + } else { + const X& eps = harris_eps_for_bound(bound); + if (this->above_bound(x, bound)) return false; + if (this->below_bound(x, bound)) { + limit_theta((bound - x + eps) / m, theta, unlimited); + } else { + theta = zero_of_type(); + unlimited = false; + } + } + return true; + } + + void limit_inf_on_low_bound_m_pos(const T & m, const X & x, const X & bound, X & theta, bool & unlimited) { + if (numeric_traits::precise()) { + // x gets larger + lean_assert(m > 0); + if (this->below_bound(x, bound)) { + limit_theta((bound - x) / m, theta, unlimited); + } + } + else { + // x gets larger + lean_assert(m > 0); + const X& eps = harris_eps_for_bound(bound); + if (this->below_bound(x, bound)) { + limit_theta((bound - x + eps) / m, theta, unlimited); + } + } + } + + void limit_inf_on_upper_bound_m_neg(const T & m, const X & x, const X & bound, X & theta, bool & unlimited) { + // x gets smaller + lean_assert(m < 0); + const X& eps = harris_eps_for_bound(bound); + if (this->above_bound(x, bound)) { + limit_theta((bound - x - eps) / m, theta, unlimited); + } + } + + void limit_theta_on_basis_column_for_inf_case_m_pos_boxed(unsigned j, const T & m, X & theta, bool & unlimited) { + // lean_assert(m > 0 && this->m_column_type[j] == column_type::boxed); + const X & x = this->m_x[j]; + const X & lbound = this->m_low_bounds[j]; + + if (this->below_bound(x, lbound)) { + const X& eps = harris_eps_for_bound(this->m_upper_bounds[j]); + limit_theta((lbound - x + eps) / m, theta, unlimited); + } else { + const X & ubound = this->m_upper_bounds[j]; + if (this->below_bound(x, ubound)){ + const X& eps = harris_eps_for_bound(ubound); + limit_theta((ubound - x + eps) / m, theta, unlimited); + } else if (!this->above_bound(x, ubound)) { + theta = zero_of_type(); + unlimited = false; + } + } + } + + void limit_theta_on_basis_column_for_inf_case_m_neg_boxed(unsigned j, const T & m, X & theta, bool & unlimited) { + // lean_assert(m < 0 && this->m_column_type[j] == column_type::boxed); + const X & x = this->m_x[j]; + const X & ubound = this->m_upper_bounds[j]; + if (this->above_bound(x, ubound)) { + const X& eps = harris_eps_for_bound(ubound); + limit_theta((ubound - x - eps) / m, theta, unlimited); + } else { + const X & lbound = this->m_low_bounds[j]; + if (this->above_bound(x, lbound)){ + const X& eps = harris_eps_for_bound(lbound); + limit_theta((lbound - x - eps) / m, theta, unlimited); + } else if (!this->below_bound(x, lbound)) { + theta = zero_of_type(); + unlimited = false; + } + } + } + void limit_theta_on_basis_column_for_feas_case_m_pos(unsigned j, const T & m, X & theta, bool & unlimited) { + lean_assert(m > 0); + const T& eps = harris_eps_for_bound(this->m_upper_bounds[j]); + if (this->below_bound(this->m_x[j], this->m_upper_bounds[j])) { + limit_theta((this->m_upper_bounds[j] - this->m_x[j] + eps) / m, theta, unlimited); + if (theta < zero_of_type()) { + theta = zero_of_type(); + unlimited = false; + } + } + } + + void limit_theta_on_basis_column_for_feas_case_m_pos_no_check(unsigned j, const T & m, X & theta, bool & unlimited ) { + lean_assert(m > 0); + const X& eps = harris_eps_for_bound(this->m_upper_bounds[j]); + limit_theta( (this->m_upper_bounds[j] - this->m_x[j] + eps) / m, theta, unlimited); + if (theta < zero_of_type()) { + theta = zero_of_type(); + } + } + + // j is a basic column or the entering, in any case x[j] has to stay feasible. + // m is the multiplier. updating t in a way that holds the following + // x[j] + t * m >= this->m_low_bounds[j]- harris_feasibility_tolerance ( if m < 0 ) + // or + // x[j] + t * m <= this->m_upper_bounds[j] + harris_feasibility_tolerance ( if m > 0) + void limit_theta_on_basis_column(unsigned j, T m, X & theta, bool & unlimited) { + switch (this->m_column_types[j]) { + case column_type::free_column: break; + case column_type::upper_bound: + if (this->current_x_is_feasible()) { + if (m > 0) + limit_theta_on_basis_column_for_feas_case_m_pos_no_check(j, m, theta, unlimited); + } else { // inside of feasibility_loop + if (m > 0) + limit_theta_on_basis_column_for_inf_case_m_pos_upper_bound(j, m, theta, unlimited); + else + limit_theta_on_basis_column_for_inf_case_m_neg_upper_bound(j, m, theta, unlimited); + } + break; + case column_type::low_bound: + if (this->current_x_is_feasible()) { + if (m < 0) + limit_theta_on_basis_column_for_feas_case_m_neg_no_check(j, m, theta, unlimited); + } else { + if (m < 0) + limit_theta_on_basis_column_for_inf_case_m_neg_low_bound(j, m, theta, unlimited); + else + limit_theta_on_basis_column_for_inf_case_m_pos_low_bound(j, m, theta, unlimited); + } + break; + // case fixed: + // if (get_this->current_x_is_feasible()) { + // theta = zero_of_type(); + // break; + // } + // if (m < 0) + // limit_theta_on_basis_column_for_inf_case_m_neg_fixed(j, m, theta); + // else + // limit_theta_on_basis_column_for_inf_case_m_pos_fixed(j, m, theta); + // break; + case column_type::fixed: + case column_type::boxed: + if (this->current_x_is_feasible()) { + if (m > 0) { + limit_theta_on_basis_column_for_feas_case_m_pos_no_check(j, m, theta, unlimited); + } else { + limit_theta_on_basis_column_for_feas_case_m_neg_no_check(j, m, theta, unlimited); + } + } else { + if (m > 0) { + limit_theta_on_basis_column_for_inf_case_m_pos_boxed(j, m, theta, unlimited); + } else { + limit_theta_on_basis_column_for_inf_case_m_neg_boxed(j, m, theta, unlimited); + } + } + + break; + default: + lean_unreachable(); + } + if (!unlimited && theta < zero_of_type()) { + theta = zero_of_type(); + } + } + + + bool column_is_benefitial_for_entering_basis(unsigned j) const; + bool column_is_benefitial_for_entering_basis_precise(unsigned j) const; + + bool column_is_benefitial_for_entering_on_breakpoints(unsigned j) const; + + + bool can_enter_basis(unsigned j); + bool done(); + void init_infeasibility_costs(); + + void init_infeasibility_cost_for_column(unsigned j); + T get_infeasibility_cost_for_column(unsigned j) const; + void init_infeasibility_costs_for_changed_basis_only(); + + void print_column(unsigned j, std::ostream & out); + void add_breakpoint(unsigned j, X delta, breakpoint_type type); + + // j is the basic column, x is the value at x[j] + // d is the coefficient before m_entering in the row with j as the basis column + void try_add_breakpoint(unsigned j, const X & x, const T & d, breakpoint_type break_type, const X & break_value); + template + bool same_sign_with_entering_delta(const L & a) { + return (a > zero_of_type() && m_sign_of_entering_delta > 0) || (a < zero_of_type() && m_sign_of_entering_delta < 0); + } + + void init_reduced_costs(); + + bool low_bounds_are_set() const { return true; } + + int advance_on_sorted_breakpoints(unsigned entering, X & t); + + std::string break_type_to_string(breakpoint_type type); + + void print_breakpoint(const breakpoint * b, std::ostream & out); + + void print_bound_info_and_x(unsigned j, std::ostream & out); + + void init_infeasibility_after_update_x_if_inf(unsigned leaving) { + if (this->m_using_infeas_costs) { + init_infeasibility_costs_for_changed_basis_only(); + this->m_costs[leaving] = zero_of_type(); + this->m_inf_set.erase(leaving); + } + } + + void init_inf_set() { + this->m_inf_set.clear(); + for (unsigned j = 0; j < this->m_n(); j++) { + if (this->m_basis_heading[j] < 0) + continue; + if (!this->column_is_feasible(j)) + this->m_inf_set.insert(j); + } + } + + int get_column_out_of_bounds_delta_sign(unsigned j) { + switch (this->m_column_types[j]) { + case column_type::fixed: + case column_type::boxed: + if (this->x_below_low_bound(j)) + return -1; + if (this->x_above_upper_bound(j)) + return 1; + break; + case column_type::low_bound: + if (this->x_below_low_bound(j)) + return -1; + break; + case column_type::upper_bound: + if (this->x_above_upper_bound(j)) + return 1; + break; + case column_type::free_column: + return 0; + default: + lean_assert(false); + } + return 0; + } + + void init_column_row_non_zeroes() { + this->m_columns_nz.resize(this->m_A.column_count()); + this->m_rows_nz.resize(this->m_A.row_count()); + for (unsigned i = 0; i < this->m_A.column_count(); i++) { + if (this->m_columns_nz[i] == 0) + this->m_columns_nz[i] = this->m_A.m_columns[i].size(); + } + for (unsigned i = 0; i < this->m_A.row_count(); i++) { + if (this->m_rows_nz[i] == 0) + this->m_rows_nz[i] = this->m_A.m_rows[i].size(); + } + } + + + int x_at_bound_sign(unsigned j) { + switch (this->m_column_types[j]) { + case column_type::fixed: + return 0; + case column_type::boxed: + if (this->x_is_at_low_bound(j)) + return 1; + return -1; + break; + case column_type::low_bound: + return 1; + break; + case column_type::upper_bound: + return -1; + break; + default: + lean_assert(false); + } + return 0; + + } + + unsigned solve_with_tableau(); + + bool basis_column_is_set_correctly(unsigned j) const { + return this->m_A.m_columns[j].size() == 1; + + } + + bool basis_columns_are_set_correctly() const { + for (unsigned j : this->m_basis) + if(!basis_column_is_set_correctly(j)) + return false; + return true; + } + + void init_run_tableau(); + void update_x_tableau(unsigned entering, const X & delta); + void update_inf_cost_for_column_tableau(unsigned j); + +// the delta is between the old and the new cost (old - new) + void update_reduced_cost_for_basic_column_cost_change(const T & delta, unsigned j) { + lean_assert(this->m_basis_heading[j] >= 0); + unsigned i = static_cast(this->m_basis_heading[j]); + for (const row_cell & rc : this->m_A.m_rows[i]) { + unsigned k = rc.m_j; + if (k == j) + continue; + this->m_d[k] += delta * rc.get_val(); + } + } + + bool update_basis_and_x_tableau(int entering, int leaving, X const & tt); + void init_reduced_costs_tableau(); + void init_tableau_rows() { + m_bland_mode_tableau = false; + m_left_basis_tableau.clear(); + m_left_basis_tableau.resize(this->m_A.column_count()); + m_left_basis_repeated = 0; + } +// stage1 constructor + lp_primal_core_solver(static_matrix & A, + vector & b, // the right side vector + vector & x, // the number of elements in x needs to be at least as large as the number of columns in A + vector & basis, + vector & nbasis, + vector & heading, + vector & costs, + const vector & column_type_array, + const vector & low_bound_values, + const vector & upper_bound_values, + lp_settings & settings, + const column_namer& column_names): + lp_core_solver_base(A, b, + basis, + nbasis, + heading, + x, + costs, + settings, + column_names, + column_type_array, + low_bound_values, + upper_bound_values), + m_beta(A.row_count()) { + + if (!(numeric_traits::precise())) { + m_converted_harris_eps = convert_struct::convert(this->m_settings.harris_feasibility_tolerance); + } else { + m_converted_harris_eps = zero_of_type(); + } + this->set_status(UNKNOWN); + } + + // constructor + lp_primal_core_solver(static_matrix & A, + vector & b, // the right side vector + vector & x, // the number of elements in x needs to be at least as large as the number of columns in A + vector & basis, + vector & nbasis, + vector & heading, + vector & costs, + const vector & column_type_array, + const vector & upper_bound_values, + lp_settings & settings, + const column_namer& column_names): + lp_core_solver_base(A, b, + basis, + nbasis, + heading, + x, + costs, + settings, + column_names, + column_type_array, + m_low_bounds_dummy, + upper_bound_values), + m_beta(A.row_count()), + m_converted_harris_eps(convert_struct::convert(this->m_settings.harris_feasibility_tolerance)) { + lean_assert(initial_x_is_correct()); + m_low_bounds_dummy.resize(A.column_count(), zero_of_type()); + m_enter_price_eps = numeric_traits::precise() ? numeric_traits::zero() : T(1e-5); +#ifdef LEAN_DEBUG + // check_correctness(); +#endif + } + + bool initial_x_is_correct() { + std::set basis_set; + for (unsigned i = 0; i < this->m_A.row_count(); i++) { + basis_set.insert(this->m_basis[i]); + } + for (unsigned j = 0; j < this->m_n(); j++) { + if (this->column_has_low_bound(j) && this->m_x[j] < numeric_traits::zero()) { + LP_OUT(this->m_settings, "low bound for variable " << j << " does not hold: this->m_x[" << j << "] = " << this->m_x[j] << " is negative " << std::endl); + return false; + } + + if (this->column_has_upper_bound(j) && this->m_x[j] > this->m_upper_bounds[j]) { + LP_OUT(this->m_settings, "upper bound for " << j << " does not hold: " << this->m_upper_bounds[j] << ">" << this->m_x[j] << std::endl); + return false; + } + + if (basis_set.find(j) != basis_set.end()) continue; + if (this->m_column_types[j] == column_type::low_bound) { + if (numeric_traits::zero() != this->m_x[j]) { + LP_OUT(this->m_settings, "only low bound is set for " << j << " but low bound value " << numeric_traits::zero() << " is not equal to " << this->m_x[j] << std::endl); + return false; + } + } + if (this->m_column_types[j] == column_type::boxed) { + if (this->m_upper_bounds[j] != this->m_x[j] && !numeric_traits::is_zero(this->m_x[j])) { + return false; + } + } + } + return true; + } + + + friend core_solver_pretty_printer; +}; +} diff --git a/src/util/lp/lp_primal_core_solver.hpp b/src/util/lp/lp_primal_core_solver.hpp new file mode 100644 index 000000000..5bcbe317b --- /dev/null +++ b/src/util/lp/lp_primal_core_solver.hpp @@ -0,0 +1,1374 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include +#include "util/vector.h" +#include +#include +#include +#include +#include "util/lp/lp_primal_core_solver.h" +namespace lean { +// This core solver solves (Ax=b, low_bound_values \leq x \leq upper_bound_values, maximize costs*x ) +// The right side b is given implicitly by x and the basis + +template +void lp_primal_core_solver::sort_non_basis_rational() { + lean_assert(numeric_traits::precise()); + if (this->m_settings.use_tableau()) { + std::sort(this->m_nbasis.begin(), this->m_nbasis.end(), [this](unsigned a, unsigned b) { + unsigned ca = this->m_A.number_of_non_zeroes_in_column(a); + unsigned cb = this->m_A.number_of_non_zeroes_in_column(b); + if (ca == 0 && cb != 0) return false; + return ca < cb; + }); + } else { + std::sort(this->m_nbasis.begin(), this->m_nbasis.end(), [this](unsigned a, unsigned b) { + unsigned ca = this->m_columns_nz[a]; + unsigned cb = this->m_columns_nz[b]; + if (ca == 0 && cb != 0) return false; + return ca < cb; + });} + + m_non_basis_list.clear(); + // reinit m_basis_heading + for (unsigned j = 0; j < this->m_nbasis.size(); j++) { + unsigned col = this->m_nbasis[j]; + this->m_basis_heading[col] = - static_cast(j) - 1; + m_non_basis_list.push_back(col); + } +} + + +template +void lp_primal_core_solver::sort_non_basis() { + if (numeric_traits::precise()) { + sort_non_basis_rational(); + return; + } + for (unsigned j : this->m_nbasis) { + T const & da = this->m_d[j]; + this->m_steepest_edge_coefficients[j] = da * da / this->m_column_norms[j]; + } + std::sort(this->m_nbasis.begin(), this->m_nbasis.end(), [this](unsigned a, unsigned b) { + return this->m_steepest_edge_coefficients[a] > this->m_steepest_edge_coefficients[b]; + }); + + m_non_basis_list.clear(); + // reinit m_basis_heading + for (unsigned j = 0; j < this->m_nbasis.size(); j++) { + unsigned col = this->m_nbasis[j]; + this->m_basis_heading[col] = - static_cast(j) - 1; + m_non_basis_list.push_back(col); + } +} + +template +bool lp_primal_core_solver::column_is_benefitial_for_entering_on_breakpoints(unsigned j) const { + bool ret; + const T & d = this->m_d[j]; + switch (this->m_column_types[j]) { + case column_type::low_bound: + lean_assert(this->x_is_at_low_bound(j)); + ret = d < -m_epsilon_of_reduced_cost; + break; + case column_type::upper_bound: + lean_assert(this->x_is_at_upper_bound(j)); + ret = d > m_epsilon_of_reduced_cost; + break; + case column_type::fixed: + ret = false; + break; + case column_type::boxed: + { + bool low_bound = this->x_is_at_low_bound(j); + lean_assert(low_bound || this->x_is_at_upper_bound(j)); + ret = (low_bound && d < -m_epsilon_of_reduced_cost) || ((!low_bound) && d > m_epsilon_of_reduced_cost); + } + break; + case column_type::free_column: + ret = d > m_epsilon_of_reduced_cost || d < - m_epsilon_of_reduced_cost; + break; + default: + lean_unreachable(); + ret = false; + break; + } + return ret; +} +template +bool lp_primal_core_solver::column_is_benefitial_for_entering_basis(unsigned j) const { + if (numeric_traits::precise()) + return column_is_benefitial_for_entering_basis_precise(j); + if (this->m_using_infeas_costs && this->m_settings.use_breakpoints_in_feasibility_search) + return column_is_benefitial_for_entering_on_breakpoints(j); + const T& dj = this->m_d[j]; + switch (this->m_column_types[j]) { + case column_type::fixed: break; + case column_type::free_column: + if (dj > m_epsilon_of_reduced_cost || dj < -m_epsilon_of_reduced_cost) + return true; + break; + case column_type::low_bound: + if (dj > m_epsilon_of_reduced_cost) return true;; + break; + case column_type::upper_bound: + if (dj < -m_epsilon_of_reduced_cost) return true; + break; + case column_type::boxed: + if (dj > m_epsilon_of_reduced_cost) { + if (this->m_x[j] < this->m_upper_bounds[j] - this->bound_span(j)/2) + return true; + break; + } else if (dj < - m_epsilon_of_reduced_cost) { + if (this->m_x[j] > this->m_low_bounds[j] + this->bound_span(j)/2) + return true; + } + break; + default: + lean_unreachable(); + break; + } + return false; +} +template +bool lp_primal_core_solver::column_is_benefitial_for_entering_basis_precise(unsigned j) const { + lean_assert (numeric_traits::precise()); + if (this->m_using_infeas_costs && this->m_settings.use_breakpoints_in_feasibility_search) + return column_is_benefitial_for_entering_on_breakpoints(j); + const T& dj = this->m_d[j]; + switch (this->m_column_types[j]) { + case column_type::fixed: break; + case column_type::free_column: + if (!is_zero(dj)) + return true; + break; + case column_type::low_bound: + if (dj > zero_of_type()) return true; + if (dj < 0 && this->m_x[j] > this->m_low_bounds[j]){ + return true; + } + break; + case column_type::upper_bound: + if (dj < zero_of_type()) return true; + if (dj > 0 && this->m_x[j] < this->m_upper_bounds[j]) { + return true; + } + break; + case column_type::boxed: + if (dj > zero_of_type()) { + if (this->m_x[j] < this->m_upper_bounds[j]) + return true; + break; + } else if (dj < zero_of_type()) { + if (this->m_x[j] > this->m_low_bounds[j]) + return true; + } + break; + default: + lean_unreachable(); + break; + } + return false; +} + +template +int lp_primal_core_solver::choose_entering_column_presize(unsigned number_of_benefitial_columns_to_go_over) { // at this moment m_y = cB * B(-1) + lean_assert(numeric_traits::precise()); + if (number_of_benefitial_columns_to_go_over == 0) + return -1; + if (this->m_basis_sort_counter == 0) { + sort_non_basis(); + this->m_basis_sort_counter = 20; + } + else { + this->m_basis_sort_counter--; + } + unsigned j_nz = this->m_m() + 1; // this number is greater than the max column size + std::list::iterator entering_iter = m_non_basis_list.end(); + for (auto non_basis_iter = m_non_basis_list.begin(); number_of_benefitial_columns_to_go_over && non_basis_iter != m_non_basis_list.end(); ++non_basis_iter) { + unsigned j = *non_basis_iter; + if (!column_is_benefitial_for_entering_basis(j)) + continue; + + // if we are here then j is a candidate to enter the basis + unsigned t = this->m_columns_nz[j]; + if (t < j_nz) { + j_nz = t; + entering_iter = non_basis_iter; + if (number_of_benefitial_columns_to_go_over) + number_of_benefitial_columns_to_go_over--; + } else if (t == j_nz && my_random() % 2 == 0) { + entering_iter = non_basis_iter; + } + }// while (number_of_benefitial_columns_to_go_over && initial_offset_in_non_basis != offset_in_nb); + if (entering_iter == m_non_basis_list.end()) + return -1; + unsigned entering = *entering_iter; + m_sign_of_entering_delta = this->m_d[entering] > 0 ? 1 : -1; + if (this->m_using_infeas_costs && this->m_settings.use_breakpoints_in_feasibility_search) + m_sign_of_entering_delta = -m_sign_of_entering_delta; + m_non_basis_list.erase(entering_iter); + m_non_basis_list.push_back(entering); + return entering; +} + + +template +int lp_primal_core_solver::choose_entering_column(unsigned number_of_benefitial_columns_to_go_over) { // at this moment m_y = cB * B(-1) + if (numeric_traits::precise()) + return choose_entering_column_presize(number_of_benefitial_columns_to_go_over); + if (number_of_benefitial_columns_to_go_over == 0) + return -1; + if (this->m_basis_sort_counter == 0) { + sort_non_basis(); + this->m_basis_sort_counter = 20; + } else { + this->m_basis_sort_counter--; + } + T steepest_edge = zero_of_type(); + std::list::iterator entering_iter = m_non_basis_list.end(); + for (auto non_basis_iter= m_non_basis_list.begin(); number_of_benefitial_columns_to_go_over && non_basis_iter != m_non_basis_list.end(); ++non_basis_iter) { + unsigned j = *non_basis_iter; + if (!column_is_benefitial_for_entering_basis(j)) + continue; + + // if we are here then j is a candidate to enter the basis + T dj = this->m_d[j]; + T t = dj * dj / this->m_column_norms[j]; + if (t > steepest_edge) { + steepest_edge = t; + entering_iter = non_basis_iter; + if (number_of_benefitial_columns_to_go_over) + number_of_benefitial_columns_to_go_over--; + } + }// while (number_of_benefitial_columns_to_go_over && initial_offset_in_non_basis != offset_in_nb); + if (entering_iter != m_non_basis_list.end()) { + unsigned entering = *entering_iter; + m_sign_of_entering_delta = this->m_d[entering] > 0? 1 : -1; + if (this->m_using_infeas_costs && this->m_settings.use_breakpoints_in_feasibility_search) + m_sign_of_entering_delta = - m_sign_of_entering_delta; + m_non_basis_list.erase(entering_iter); + m_non_basis_list.push_back(entering); + return entering; + } + return -1; +} + +template int lp_primal_core_solver::advance_on_sorted_breakpoints(unsigned entering, X &t) { + T slope_at_entering = this->m_d[entering]; + breakpoint * last_bp = nullptr; + lean_assert(m_breakpoint_indices_queue.is_empty()==false); + while (m_breakpoint_indices_queue.is_empty() == false) { + unsigned bi = m_breakpoint_indices_queue.dequeue(); + breakpoint *b = &m_breakpoints[bi]; + change_slope_on_breakpoint(entering, b, slope_at_entering); + last_bp = b; + if (slope_at_entering * m_sign_of_entering_delta > - m_epsilon_of_reduced_cost) { // the slope started to increase infeasibility + break; + } else { + if ((numeric_traits::precise() == false) || ( numeric_traits::is_zero(slope_at_entering) && my_random() % 2 == 0)) { + // it is not cost benefitial to advance the delta more, so just break to increas the randomness + break; + } + } + } + lean_assert (last_bp != nullptr); + t = last_bp->m_delta; + return last_bp->m_j; +} + + +template int +lp_primal_core_solver::find_leaving_and_t_with_breakpoints(unsigned entering, X & t){ + lean_assert(this->precise() == false); + fill_breakpoints_array(entering); + return advance_on_sorted_breakpoints(entering, t); +} + +template bool lp_primal_core_solver::get_harris_theta(X & theta) { + lean_assert(this->m_ed.is_OK()); + bool unlimited = true; + for (unsigned i : this->m_ed.m_index) { + if (this->m_settings.abs_val_is_smaller_than_pivot_tolerance(this->m_ed[i])) continue; + limit_theta_on_basis_column(this->m_basis[i], - this->m_ed[i] * m_sign_of_entering_delta, theta, unlimited); + if (!unlimited && is_zero(theta)) break; + } + return unlimited; +} + + +template int lp_primal_core_solver:: +find_leaving_on_harris_theta(X const & harris_theta, X & t) { + int leaving = -1; + T pivot_abs_max = zero_of_type(); + // we know already that there is no bound flip on entering + // we also know that harris_theta is limited, so we will find a leaving + zero_harris_eps(); + unsigned steps = this->m_ed.m_index.size(); + unsigned k = my_random() % steps; + unsigned initial_k = k; + do { + unsigned i = this->m_ed.m_index[k]; + const T & ed = this->m_ed[i]; + if (this->m_settings.abs_val_is_smaller_than_pivot_tolerance(ed)) { + if (++k == steps) + k = 0; + continue; + } + X ratio; + unsigned j = this->m_basis[i]; + bool unlimited = true; + limit_theta_on_basis_column(j, - ed * m_sign_of_entering_delta, ratio, unlimited); + if ((!unlimited) && ratio <= harris_theta) { + if (leaving == -1 || abs(ed) > pivot_abs_max) { + t = ratio; + leaving = j; + pivot_abs_max = abs(ed); + } + } + if (++k == steps) k = 0; + } while (k != initial_k); + if (!this->precise()) + restore_harris_eps(); + return leaving; +} + + +template bool lp_primal_core_solver::try_jump_to_another_bound_on_entering(unsigned entering, + const X & theta, + X & t, + bool & unlimited) { + switch(this->m_column_types[entering]){ + case column_type::boxed: + if (m_sign_of_entering_delta > 0) { + t = this->m_upper_bounds[entering] - this->m_x[entering]; + if (unlimited || t <= theta){ + lean_assert(t >= zero_of_type()); + return true; + } + } else { // m_sign_of_entering_delta == -1 + t = this->m_x[entering] - this->m_low_bounds[entering]; + if (unlimited || t <= theta) { + lean_assert(t >= zero_of_type()); + return true; + } + } + return false; + case column_type::upper_bound: + if (m_sign_of_entering_delta > 0) { + t = this->m_upper_bounds[entering] - this->m_x[entering]; + if (unlimited || t <= theta){ + lean_assert(t >= zero_of_type()); + return true; + } + } + return false; + case column_type::low_bound: + if (m_sign_of_entering_delta < 0) { + t = this->m_x[entering] - this->m_low_bounds[entering]; + if (unlimited || t <= theta) { + lean_assert(t >= zero_of_type()); + return true; + } + } + return false; + default:return false; + } + return false; +} + +template bool lp_primal_core_solver:: +try_jump_to_another_bound_on_entering_unlimited(unsigned entering, X & t ) { + if (this->m_column_types[entering] != column_type::boxed) + return false; + + if (m_sign_of_entering_delta > 0) { + t = this->m_upper_bounds[entering] - this->m_x[entering]; + return true; + } + // m_sign_of_entering_delta == -1 + t = this->m_x[entering] - this->m_low_bounds[entering]; + return true; +} + +template int lp_primal_core_solver::find_leaving_and_t_precise(unsigned entering, X & t) { + if (this->m_settings.use_breakpoints_in_feasibility_search && !this->current_x_is_feasible()) + return find_leaving_and_t_with_breakpoints(entering, t); + bool unlimited = true; + unsigned steps = this->m_ed.m_index.size(); + unsigned k = my_random() % steps; + unsigned initial_k = k; + unsigned row_min_nz = this->m_n() + 1; + m_leaving_candidates.clear(); + do { + unsigned i = this->m_ed.m_index[k]; + const T & ed = this->m_ed[i]; + lean_assert(!numeric_traits::is_zero(ed)); + unsigned j = this->m_basis[i]; + limit_theta_on_basis_column(j, - ed * m_sign_of_entering_delta, t, unlimited); + if (!unlimited) { + m_leaving_candidates.push_back(j); + row_min_nz = this->m_rows_nz[i]; + } + if (++k == steps) k = 0; + } while (unlimited && k != initial_k); + if (unlimited) { + if (try_jump_to_another_bound_on_entering_unlimited(entering, t)) + return entering; + return -1; + } + + X ratio; + while (k != initial_k) { + unsigned i = this->m_ed.m_index[k]; + const T & ed = this->m_ed[i]; + lean_assert(!numeric_traits::is_zero(ed)); + unsigned j = this->m_basis[i]; + unlimited = true; + limit_theta_on_basis_column(j, -ed * m_sign_of_entering_delta, ratio, unlimited); + if (unlimited) { + if (++k == steps) k = 0; + continue; + } + unsigned i_nz = this->m_rows_nz[i]; + if (ratio < t) { + t = ratio; + m_leaving_candidates.clear(); + m_leaving_candidates.push_back(j); + row_min_nz = this->m_rows_nz[i]; + } else if (ratio == t && i_nz < row_min_nz) { + m_leaving_candidates.clear(); + m_leaving_candidates.push_back(j); + row_min_nz = this->m_rows_nz[i]; + } else if (ratio == t && i_nz == row_min_nz) { + m_leaving_candidates.push_back(j); + } + if (++k == steps) k = 0; + } + + ratio = t; + unlimited = false; + if (try_jump_to_another_bound_on_entering(entering, t, ratio, unlimited)) { + t = ratio; + return entering; + } + k = my_random() % m_leaving_candidates.size(); + return m_leaving_candidates[k]; +} + + +template int lp_primal_core_solver::find_leaving_and_t(unsigned entering, X & t) { + if (this->m_settings.use_breakpoints_in_feasibility_search && !this->current_x_is_feasible()) + return find_leaving_and_t_with_breakpoints(entering, t); + X theta; + bool unlimited = get_harris_theta(theta); + lean_assert(unlimited || theta >= zero_of_type()); + if (try_jump_to_another_bound_on_entering(entering, theta, t, unlimited)) return entering; + if (unlimited) + return -1; + return find_leaving_on_harris_theta(theta, t); +} + + + +// m is the multiplier. updating t in a way that holds the following +// x[j] + t * m >= m_low_bounds[j] ( if m < 0 ) +// or +// x[j] + t * m <= this->m_upper_bounds[j] ( if m > 0) +template void +lp_primal_core_solver::get_bound_on_variable_and_update_leaving_precisely(unsigned j, vector & leavings, T m, X & t, T & abs_of_d_of_leaving) { + if (m > 0) { + switch(this->m_column_types[j]) { // check that j has a low bound + case column_type::free_column: + case column_type::upper_bound: + return; + default:break; + } + X tt = - (this->m_low_bounds[j] - this->m_x[j]) / m; + if (numeric_traits::is_neg(tt)) + tt = zero_of_type(); + if (leavings.size() == 0 || tt < t || (tt == t && m > abs_of_d_of_leaving)) { + t = tt; + abs_of_d_of_leaving = m; + leavings.clear(); + leavings.push_back(j); + } + else if (tt == t || m == abs_of_d_of_leaving) { + leavings.push_back(j); + } + } else if (m < 0){ + switch (this->m_column_types[j]) { // check that j has an upper bound + case column_type::free_column: + case column_type::low_bound: + return; + default:break; + } + + X tt = (this->m_upper_bounds[j] - this->m_x[j]) / m; + if (numeric_traits::is_neg(tt)) + tt = zero_of_type(); + if (leavings.size() == 0 || tt < t || (tt == t && - m > abs_of_d_of_leaving)) { + t = tt; + abs_of_d_of_leaving = - m; + leavings.clear(); + leavings.push_back(j); + } else if (tt == t || m == abs_of_d_of_leaving) { + leavings.push_back(j); + } + } +} + +template X lp_primal_core_solver::get_max_bound(vector & b) { + X ret = zero_of_type(); + for (auto & v : b) { + X a = abs(v); + if (a > ret) ret = a; + } + return ret; +} + +#ifdef LEAN_DEBUG +template void lp_primal_core_solver::check_Ax_equal_b() { + dense_matrix d(this->m_A); + T * ls = d.apply_from_left_with_different_dims(this->m_x); + lean_assert(vectors_are_equal(ls, this->m_b, this->m_m())); + delete [] ls; +} +template void lp_primal_core_solver::check_the_bounds() { + for (unsigned i = 0; i < this->m_n(); i++) { + check_bound(i); + } +} + +template void lp_primal_core_solver::check_bound(unsigned i) { + lean_assert (!(this->column_has_low_bound(i) && (numeric_traits::zero() > this->m_x[i]))); + lean_assert (!(this->column_has_upper_bound(i) && (this->m_upper_bounds[i] < this->m_x[i]))); +} + +template void lp_primal_core_solver::check_correctness() { + check_the_bounds(); + check_Ax_equal_b(); +} +#endif + +// from page 183 of Istvan Maros's book +// the basis structures have not changed yet +template +void lp_primal_core_solver::update_reduced_costs_from_pivot_row(unsigned entering, unsigned leaving) { + // the basis heading has changed already +#ifdef LEAN_DEBUG + auto & basis_heading = this->m_basis_heading; + lean_assert(basis_heading[entering] >= 0 && static_cast(basis_heading[entering]) < this->m_m()); + lean_assert(basis_heading[leaving] < 0); +#endif + T pivot = this->m_pivot_row[entering]; + T dq = this->m_d[entering]/pivot; + for (auto j : this->m_pivot_row.m_index) { + // for (auto j : this->m_nbasis) + if (this->m_basis_heading[j] >= 0) continue; + if (j != leaving) + this->m_d[j] -= dq * this->m_pivot_row[j]; + } + this->m_d[leaving] = -dq; + if (this->current_x_is_infeasible() && !this->m_settings.use_breakpoints_in_feasibility_search) { + this->m_d[leaving] -= this->m_costs[leaving]; + this->m_costs[leaving] = zero_of_type(); + } + this->m_d[entering] = numeric_traits::zero(); +} + +// return 0 if the reduced cost at entering is close enough to the refreshed +// 1 if it is way off, and 2 if it is unprofitable +template int lp_primal_core_solver::refresh_reduced_cost_at_entering_and_check_that_it_is_off(unsigned entering) { + if (numeric_traits::precise()) return 0; + T reduced_at_entering_was = this->m_d[entering]; // can benefit from going over non-zeros of m_ed + lean_assert(abs(reduced_at_entering_was) > m_epsilon_of_reduced_cost); + T refreshed_cost = this->m_costs[entering]; + unsigned i = this->m_m(); + while (i--) refreshed_cost -= this->m_costs[this->m_basis[i]] * this->m_ed[i]; + this->m_d[entering] = refreshed_cost; + T delta = abs(reduced_at_entering_was - refreshed_cost); + if (delta * 2 > abs(reduced_at_entering_was)) { + // this->m_status = UNSTABLE; + if (reduced_at_entering_was > m_epsilon_of_reduced_cost) { + if (refreshed_cost <= zero_of_type()) + return 2; // abort entering + } else { + if (refreshed_cost > -m_epsilon_of_reduced_cost) + return 2; // abort entiring + } + return 1; // go on with this entering + } else { + if (reduced_at_entering_was > m_epsilon_of_reduced_cost) { + if (refreshed_cost <= zero_of_type()) + return 2; // abort entering + } else { + if (refreshed_cost > -m_epsilon_of_reduced_cost) + return 2; // abort entiring + } + } + return 0; +} + +template void lp_primal_core_solver::backup_and_normalize_costs() { + if (this->m_look_for_feasible_solution_only) + return; // no need to backup cost, since we are going to use only feasibility costs + if (numeric_traits::precise() ) { + m_costs_backup = this->m_costs; + } else { + T cost_max = std::max(max_abs_in_vector(this->m_costs), T(1)); + lean_assert(m_costs_backup.size() == 0); + for (unsigned j = 0; j < this->m_costs.size(); j++) + m_costs_backup.push_back(this->m_costs[j] /= cost_max); + } +} + +template void lp_primal_core_solver::init_run() { + this->m_basis_sort_counter = 0; // to initiate the sort of the basis + this->set_total_iterations(0); + this->m_iters_with_no_cost_growing = 0; + init_inf_set(); + if (this->current_x_is_feasible() && this->m_look_for_feasible_solution_only) + return; + this->m_using_infeas_costs = false; + if (this->m_settings.backup_costs) + backup_and_normalize_costs(); + m_epsilon_of_reduced_cost = numeric_traits::precise()? zero_of_type(): T(1)/T(10000000); + m_breakpoint_indices_queue.resize(this->m_n()); + init_reduced_costs(); + if (!numeric_traits::precise()) { + this->m_column_norm_update_counter = 0; + init_column_norms(); + } else { + if (this->m_columns_nz.size() != this->m_n()) + init_column_row_non_zeroes(); + } +} + + +template void lp_primal_core_solver::calc_working_vector_beta_for_column_norms(){ + lean_assert(numeric_traits::precise() == false); + lean_assert(this->m_ed.is_OK()); + lean_assert(m_beta.is_OK()); + m_beta = this->m_ed; + this->m_factorization->solve_yB_with_error_check_indexed(m_beta, this->m_basis_heading, this->m_basis, this->m_settings); +} + +template +void lp_primal_core_solver::advance_on_entering_equal_leaving(int entering, X & t) { + lean_assert(!this->A_mult_x_is_off() ); + this->update_x(entering, t * m_sign_of_entering_delta); + if (this->A_mult_x_is_off_on_index(this->m_ed.m_index) && !this->find_x_by_solving()) { + this->init_lu(); + if (!this->find_x_by_solving()) { + this->restore_x(entering, t * m_sign_of_entering_delta); + this->m_iters_with_no_cost_growing++; + LP_OUT(this->m_settings, "failing in advance_on_entering_equal_leaving for entering = " << entering << std::endl); + return; + } + } + if (this->m_using_infeas_costs) { + lean_assert(is_zero(this->m_costs[entering])); + init_infeasibility_costs_for_changed_basis_only(); + } + if (this->m_look_for_feasible_solution_only && this->current_x_is_feasible()) + return; + + if (need_to_switch_costs() ||!this->current_x_is_feasible()) { + init_reduced_costs(); + } + this->m_iters_with_no_cost_growing = 0; +} + +template void lp_primal_core_solver::advance_on_entering_and_leaving(int entering, int leaving, X & t) { + lean_assert(entering >= 0 && m_non_basis_list.back() == static_cast(entering)); + lean_assert(this->m_using_infeas_costs || t >= zero_of_type()); + lean_assert(leaving >= 0 && entering >= 0); + lean_assert(entering != leaving || !is_zero(t)); // otherwise nothing changes + if (entering == leaving) { + advance_on_entering_equal_leaving(entering, t); + return; + } + unsigned pivot_row = this->m_basis_heading[leaving]; + this->calculate_pivot_row_of_B_1(pivot_row); + this->calculate_pivot_row_when_pivot_row_of_B1_is_ready(pivot_row); + + int pivot_compare_result = this->pivots_in_column_and_row_are_different(entering, leaving); + if (!pivot_compare_result){;} + else if (pivot_compare_result == 2) { // the sign is changed, cannot continue + this->set_status(UNSTABLE); + this->m_iters_with_no_cost_growing++; + return; + } else { + lean_assert(pivot_compare_result == 1); + this->init_lu(); + if (this->m_factorization == nullptr || this->m_factorization->get_status() != LU_status::OK) { + this->set_status(UNSTABLE); + this->m_iters_with_no_cost_growing++; + return; + } + } + if (!numeric_traits::precise()) + calc_working_vector_beta_for_column_norms(); + if (this->current_x_is_feasible() || !this->m_settings.use_breakpoints_in_feasibility_search) { + if (m_sign_of_entering_delta == -1) + t = -t; + } + if (!this->update_basis_and_x(entering, leaving, t)) { + if (this->get_status() == FLOATING_POINT_ERROR) + return; + if (this->m_look_for_feasible_solution_only) { + this->set_status(FLOATING_POINT_ERROR); + return; + } + init_reduced_costs(); + return; + } + + if (!is_zero(t)) { + this->m_iters_with_no_cost_growing = 0; + init_infeasibility_after_update_x_if_inf(leaving); + } + + if (this->current_x_is_feasible()) { + this->set_status(FEASIBLE); + if (this->m_look_for_feasible_solution_only) + return; + } + if (numeric_traits::precise() == false) + update_or_init_column_norms(entering, leaving); + + + if (need_to_switch_costs()) { + init_reduced_costs(); + } else { + update_reduced_costs_from_pivot_row(entering, leaving); + } + lean_assert(!need_to_switch_costs()); + std::list::iterator it = m_non_basis_list.end(); + it--; + * it = static_cast(leaving); +} + + +template void lp_primal_core_solver::advance_on_entering_precise(int entering) { + lean_assert(numeric_traits::precise()); + lean_assert(entering > -1); + this->solve_Bd(entering); + X t; + int leaving = find_leaving_and_t_precise(entering, t); + if (leaving == -1) { + this->set_status(UNBOUNDED); + return; + } + advance_on_entering_and_leaving(entering, leaving, t); +} + +template void lp_primal_core_solver::advance_on_entering(int entering) { + if (numeric_traits::precise()) { + advance_on_entering_precise(entering); + return; + } + lean_assert(entering > -1); + this->solve_Bd(entering); + int refresh_result = refresh_reduced_cost_at_entering_and_check_that_it_is_off(entering); + if (refresh_result) { + if (this->m_look_for_feasible_solution_only) { + this->set_status(FLOATING_POINT_ERROR); + return; + } + + this->init_lu(); + init_reduced_costs(); + if (refresh_result == 2) { + this->m_iters_with_no_cost_growing++; + return; + } + } + X t; + int leaving = find_leaving_and_t(entering, t); + if (leaving == -1){ + if (!this->current_x_is_feasible()) { + lean_assert(!numeric_traits::precise()); // we cannot have unbounded with inf costs + + // if (m_look_for_feasible_solution_only) { + // this->m_status = INFEASIBLE; + // return; + // } + + + if (this->get_status() == UNSTABLE) { + this->set_status(FLOATING_POINT_ERROR); + return; + } + init_infeasibility_costs(); + this->set_status(UNSTABLE); + + return; + } + if (this->get_status() == TENTATIVE_UNBOUNDED) { + this->set_status(UNBOUNDED); + } else { + this->set_status(TENTATIVE_UNBOUNDED); + } + return; + } + advance_on_entering_and_leaving(entering, leaving, t); +} + +template void lp_primal_core_solver::push_forward_offset_in_non_basis(unsigned & offset_in_nb) { + if (++offset_in_nb == this->m_nbasis.size()) + offset_in_nb = 0; +} + +template unsigned lp_primal_core_solver::get_number_of_non_basic_column_to_try_for_enter() { + unsigned ret = static_cast(this->m_nbasis.size()); + if (this->get_status() == TENTATIVE_UNBOUNDED) + return ret; // we really need to find entering with a large reduced cost + if (ret > 300) { + ret = (unsigned)(ret * this->m_settings.percent_of_entering_to_check / 100); + } + if (ret == 0) { + return 0; + } + return std::max(static_cast(my_random() % ret), 1u); +} + +template void lp_primal_core_solver::print_column_norms(std::ostream & out) { + out << " column norms " << std::endl; + for (unsigned j = 0; j < this->m_n(); j++) { + out << this->m_column_norms[j] << " "; + } + out << std::endl; + out << std::endl; +} + +// returns the number of iterations +template unsigned lp_primal_core_solver::solve() { + if (numeric_traits::precise() && this->m_settings.use_tableau()) + return solve_with_tableau(); + + init_run(); + if (this->current_x_is_feasible() && this->m_look_for_feasible_solution_only) { + this->set_status(FEASIBLE); + return 0; + } + + if ((!numeric_traits::precise()) && this->A_mult_x_is_off()) { + this->set_status(FLOATING_POINT_ERROR); + return 0; + } + do { + if (this->print_statistics_with_iterations_and_nonzeroes_and_cost_and_check_that_the_time_is_over((this->m_using_infeas_costs? "inf" : "feas"), * this->m_settings.get_message_ostream())) { + return this->total_iterations(); + } + one_iteration(); + lean_assert(!this->m_using_infeas_costs || this->costs_on_nbasis_are_zeros()); + switch (this->get_status()) { + case OPTIMAL: // double check that we are at optimum + case INFEASIBLE: + if (this->m_look_for_feasible_solution_only && this->current_x_is_feasible()) + break; + if (!numeric_traits::precise()) { + if(this->m_look_for_feasible_solution_only) + break; + this->init_lu(); + + if (this->m_factorization->get_status() != LU_status::OK) { + this->set_status (FLOATING_POINT_ERROR); + break; + } + init_reduced_costs(); + if (choose_entering_column(1) == -1) { + decide_on_status_when_cannot_find_entering(); + break; + } + this->set_status(UNKNOWN); + } else { // precise case + if (this->m_look_for_feasible_solution_only) { // todo: keep the reduced costs correct all the time! + init_reduced_costs(); + if (choose_entering_column(1) == -1) { + decide_on_status_when_cannot_find_entering(); + break; + } + this->set_status(UNKNOWN); + } + } + break; + case TENTATIVE_UNBOUNDED: + this->init_lu(); + if (this->m_factorization->get_status() != LU_status::OK) { + this->set_status(FLOATING_POINT_ERROR); + break; + } + + init_reduced_costs(); + break; + case UNBOUNDED: + if (this->current_x_is_infeasible()) { + init_reduced_costs(); + this->set_status(UNKNOWN); + } + break; + + case UNSTABLE: + lean_assert(! (numeric_traits::precise())); + this->init_lu(); + if (this->m_factorization->get_status() != LU_status::OK) { + this->set_status(FLOATING_POINT_ERROR); + break; + } + init_reduced_costs(); + break; + + default: + break; // do nothing + } + } while (this->get_status() != FLOATING_POINT_ERROR + && + this->get_status() != UNBOUNDED + && + this->get_status() != OPTIMAL + && + this->get_status() != INFEASIBLE + && + this->m_iters_with_no_cost_growing <= this->m_settings.max_number_of_iterations_with_no_improvements + && + this->total_iterations() <= this->m_settings.max_total_number_of_iterations + && + !(this->current_x_is_feasible() && this->m_look_for_feasible_solution_only)); + + lean_assert(this->get_status() == FLOATING_POINT_ERROR + || + this->current_x_is_feasible() == false + || + this->calc_current_x_is_feasible_include_non_basis()); + return this->total_iterations(); +} + +template void lp_primal_core_solver::delete_factorization() { + if (this->m_factorization != nullptr) { + delete this->m_factorization; + this->m_factorization = nullptr; + } +} + +// according to Swietanowski, " A new steepest edge approximation for the simplex method for linear programming" +template void lp_primal_core_solver::init_column_norms() { + lean_assert(numeric_traits::precise() == false); + for (unsigned j = 0; j < this->m_n(); j++) { + this->m_column_norms[j] = T(static_cast(this->m_A.m_columns[j].size() + 1)) + + + T(static_cast(my_random() % 10000)) / T(100000); + } +} + +// debug only +template T lp_primal_core_solver::calculate_column_norm_exactly(unsigned j) { + lean_assert(numeric_traits::precise() == false); + indexed_vector w(this->m_m()); + this->m_A.copy_column_to_vector(j, w); + vector d(this->m_m()); + this->m_factorization->solve_Bd_when_w_is_ready(d, w); + T ret = zero_of_type(); + for (auto v : d) + ret += v*v; + return ret+1; +} + +template void lp_primal_core_solver::update_or_init_column_norms(unsigned entering, unsigned leaving) { + lean_assert(numeric_traits::precise() == false); + lean_assert(m_column_norm_update_counter <= this->m_settings.column_norms_update_frequency); + if (m_column_norm_update_counter == this->m_settings.column_norms_update_frequency) { + m_column_norm_update_counter = 0; + init_column_norms(); + } else { + m_column_norm_update_counter++; + update_column_norms(entering, leaving); + } +} + +// following Swietanowski - A new steepest ... +template void lp_primal_core_solver::update_column_norms(unsigned entering, unsigned leaving) { + lean_assert(numeric_traits::precise() == false); + T pivot = this->m_pivot_row[entering]; + T g_ent = calculate_norm_of_entering_exactly() / pivot / pivot; + if (!numeric_traits::precise()) { + if (g_ent < T(0.000001)) + g_ent = T(0.000001); + } + this->m_column_norms[leaving] = g_ent; + + for (unsigned j : this->m_pivot_row.m_index) { + if (j == leaving) + continue; + const T & t = this->m_pivot_row[j]; + T s = this->m_A.dot_product_with_column(m_beta.m_data, j); + T k = -2 / pivot; + T tp = t/pivot; + if (this->m_column_types[j] != column_type::fixed) { // a fixed columns do not enter the basis, we don't use the norm of a fixed column + this->m_column_norms[j] = std::max(this->m_column_norms[j] + t * (t * g_ent + k * s), // see Istvan Maros, page 196 + 1 + tp * tp); + } + } +} + +template T lp_primal_core_solver::calculate_norm_of_entering_exactly() { + T r = numeric_traits::one(); + for (auto i : this->m_ed.m_index) { + T t = this->m_ed[i]; + r += t * t; + } + return r; +} + +// calling it stage1 is too cryptic +template void lp_primal_core_solver::find_feasible_solution() { + this->m_look_for_feasible_solution_only = true; + lean_assert(this->non_basic_columns_are_set_correctly()); + this->set_status(UNKNOWN); + solve(); +} + +template void lp_primal_core_solver::one_iteration() { + unsigned number_of_benefitial_columns_to_go_over = get_number_of_non_basic_column_to_try_for_enter(); + int entering = choose_entering_column(number_of_benefitial_columns_to_go_over); + if (entering == -1) { + decide_on_status_when_cannot_find_entering(); + } + else { + advance_on_entering(entering); + } +} + +template void lp_primal_core_solver::update_basis_and_x_with_comparison(unsigned entering, unsigned leaving, X delta) { + if (entering != leaving) + this->update_basis_and_x(entering, leaving, delta); + else + this->update_x(entering, delta); +} + + +template void lp_primal_core_solver::clear_breakpoints() { + m_breakpoints.clear(); + m_breakpoint_indices_queue.clear(); +} + +template void lp_primal_core_solver::fill_breakpoints_array(unsigned entering) { + clear_breakpoints(); + for (unsigned i : this->m_ed.m_index) + try_add_breakpoint_in_row(i); + + if (this->m_column_types[entering] == column_type::boxed) { + if (m_sign_of_entering_delta < 0) + add_breakpoint(entering, - this->bound_span(entering), low_break); + else + add_breakpoint(entering, this->bound_span(entering), upper_break); + } +} + + + +template bool lp_primal_core_solver::done() { + if (this->get_status() == OPTIMAL || this->get_status() == FLOATING_POINT_ERROR) return true; + if (this->get_status() == INFEASIBLE) { + return true; + } + if (this->m_iters_with_no_cost_growing >= this->m_settings.max_number_of_iterations_with_no_improvements) { + this->get_status() = ITERATIONS_EXHAUSTED; return true; + } + if (this->total_iterations() >= this->m_settings.max_total_number_of_iterations) { + this->get_status() = ITERATIONS_EXHAUSTED; return true; + } + return false; +} + +template +void lp_primal_core_solver::init_infeasibility_costs_for_changed_basis_only() { + for (unsigned i : this->m_ed.m_index) + init_infeasibility_cost_for_column(this->m_basis[i]); + this->m_using_infeas_costs = true; +} + + +template +void lp_primal_core_solver::init_infeasibility_costs() { + lean_assert(this->m_x.size() >= this->m_n()); + lean_assert(this->m_column_types.size() >= this->m_n()); + for (unsigned j = this->m_n(); j--;) + init_infeasibility_cost_for_column(j); + this->m_using_infeas_costs = true; +} + +template T +lp_primal_core_solver::get_infeasibility_cost_for_column(unsigned j) const { + if (this->m_basis_heading[j] < 0) { + return zero_of_type(); + } + T ret; + // j is a basis column + switch (this->m_column_types[j]) { + case column_type::fixed: + case column_type::boxed: + if (this->x_above_upper_bound(j)) { + ret = 1; + } else if (this->x_below_low_bound(j)) { + ret = -1; + } else { + ret = numeric_traits::zero(); + } + break; + case column_type::low_bound: + if (this->x_below_low_bound(j)) { + ret = -1; + } else { + ret = numeric_traits::zero(); + } + break; + case column_type::upper_bound: + if (this->x_above_upper_bound(j)) { + ret = 1; + } else { + ret = numeric_traits::zero(); + } + break; + case column_type::free_column: + ret = numeric_traits::zero(); + break; + default: + lean_assert(false); + break; + } + + if (!this->m_settings.use_breakpoints_in_feasibility_search) { + ret = - ret; + } + return ret; +} + + +// changed m_inf_set too! +template void +lp_primal_core_solver::init_infeasibility_cost_for_column(unsigned j) { + + // If j is a breakpoint column, then we set the cost zero. + // When anylyzing an entering column candidate we update the cost of the breakpoints columns to get the left or the right derivative if the infeasibility function + // set zero cost for each non-basis column + if (this->m_basis_heading[j] < 0) { + this->m_costs[j] = numeric_traits::zero(); + this->m_inf_set.erase(j); + return; + } + // j is a basis column + switch (this->m_column_types[j]) { + case column_type::fixed: + case column_type::boxed: + if (this->x_above_upper_bound(j)) { + this->m_costs[j] = 1; + } else if (this->x_below_low_bound(j)) { + this->m_costs[j] = -1; + } else { + this->m_costs[j] = numeric_traits::zero(); + } + break; + case column_type::low_bound: + if (this->x_below_low_bound(j)) { + this->m_costs[j] = -1; + } else { + this->m_costs[j] = numeric_traits::zero(); + } + break; + case column_type::upper_bound: + if (this->x_above_upper_bound(j)) { + this->m_costs[j] = 1; + } else { + this->m_costs[j] = numeric_traits::zero(); + } + break; + case column_type::free_column: + this->m_costs[j] = numeric_traits::zero(); + break; + default: + lean_assert(false); + break; + } + + if (numeric_traits::is_zero(this->m_costs[j])) { + this->m_inf_set.erase(j); + } else { + this->m_inf_set.insert(j); + } + if (!this->m_settings.use_breakpoints_in_feasibility_search) { + this->m_costs[j] = - this->m_costs[j]; + } +} + + +template void lp_primal_core_solver::print_column(unsigned j, std::ostream & out) { + out << this->column_name(j) << " " << j << " " << column_type_to_string(this->m_column_type[j]) << " x = " << this->m_x[j] << " " << "c = " << this->m_costs[j];; + switch (this->m_column_type[j]) { + case column_type::fixed: + case column_type::boxed: + out << "( " << this->m_low_bounds[j] << " " << this->m_x[j] << " " << this->m_upper_bounds[j] << ")" << std::endl; + break; + case column_type::upper_bound: + out << "( _" << this->m_x[j] << " " << this->m_upper_bounds[j] << ")" << std::endl; + break; + case column_type::low_bound: + out << "( " << this->m_low_bounds[j] << " " << this->m_x[j] << " " << "_ )" << std::endl; + break; + case column_type::free_column: + out << "( _" << this->m_x[j] << "_)" << std::endl; + default: + lean_unreachable(); + } +} + +template void lp_primal_core_solver::add_breakpoint(unsigned j, X delta, breakpoint_type type) { + m_breakpoints.push_back(breakpoint(j, delta, type)); + m_breakpoint_indices_queue.enqueue(m_breakpoint_indices_queue.size(), abs(delta)); +} + +// j is the basic column, x is the value at x[j] +// d is the coefficient before m_entering in the row with j as the basis column +template void lp_primal_core_solver::try_add_breakpoint(unsigned j, const X & x, const T & d, breakpoint_type break_type, const X & break_value) { + X diff = x - break_value; + if (is_zero(diff)) { + switch (break_type) { + case low_break: + if (!same_sign_with_entering_delta(d)) + return; // no breakpoint + break; + case upper_break: + if (same_sign_with_entering_delta(d)) + return; // no breakpoint + break; + default: break; + } + add_breakpoint(j, zero_of_type(), break_type); + return; + } + auto delta_j = diff / d; + if (same_sign_with_entering_delta(delta_j)) + add_breakpoint(j, delta_j, break_type); +} + +template std::string lp_primal_core_solver::break_type_to_string(breakpoint_type type) { + switch (type){ + case low_break: return "low_break"; + case upper_break: return "upper_break"; + case fixed_break: return "fixed_break"; + default: + lean_assert(false); + break; + } + return "type is not found"; +} + +template void lp_primal_core_solver::print_breakpoint(const breakpoint * b, std::ostream & out) { + out << "(" << this->column_name(b->m_j) << "," << break_type_to_string(b->m_type) << "," << T_to_string(b->m_delta) << ")" << std::endl; + print_bound_info_and_x(b->m_j, out); +} + +template +void lp_primal_core_solver::init_reduced_costs() { + lean_assert(!this->use_tableau()); + if (this->current_x_is_infeasible() && !this->m_using_infeas_costs) { + init_infeasibility_costs(); + } else if (this->current_x_is_feasible() && this->m_using_infeas_costs) { + if (this->m_look_for_feasible_solution_only) + return; + this->m_costs = m_costs_backup; + this->m_using_infeas_costs = false; + } + + this->init_reduced_costs_for_one_iteration(); +} + +template void lp_primal_core_solver::change_slope_on_breakpoint(unsigned entering, breakpoint * b, T & slope_at_entering) { + if (b->m_j == entering) { + lean_assert(b->m_type != fixed_break && (!is_zero(b->m_delta))); + slope_at_entering += m_sign_of_entering_delta; + return; + } + + lean_assert(this->m_basis_heading[b->m_j] >= 0); + unsigned i_row = this->m_basis_heading[b->m_j]; + const T & d = - this->m_ed[i_row]; + if (numeric_traits::is_zero(d)) return; + + T delta = m_sign_of_entering_delta * abs(d); + switch (b->m_type) { + case fixed_break: + if (is_zero(b->m_delta)) { + slope_at_entering += delta; + } else { + slope_at_entering += 2 * delta; + } + break; + case low_break: + case upper_break: + slope_at_entering += delta; + break; + default: + lean_assert(false); + } +} + + +template void lp_primal_core_solver::try_add_breakpoint_in_row(unsigned i) { + lean_assert(i < this->m_m()); + const T & d = this->m_ed[i]; // the coefficient before m_entering in the i-th row + if (d == 0) return; // the change of x[m_entering] will not change the corresponding basis x + unsigned j = this->m_basis[i]; + const X & x = this->m_x[j]; + switch (this->m_column_types[j]) { + case column_type::fixed: + try_add_breakpoint(j, x, d, fixed_break, this->m_low_bounds[j]); + break; + case column_type::boxed: + try_add_breakpoint(j, x, d, low_break, this->m_low_bounds[j]); + try_add_breakpoint(j, x, d, upper_break, this->m_upper_bounds[j]); + break; + case column_type::low_bound: + try_add_breakpoint(j, x, d, low_break, this->m_low_bounds[j]); + break; + case column_type::upper_bound: + try_add_breakpoint(j, x, d, upper_break, this->m_upper_bounds[j]); + break; + case column_type::free_column: + break; + default: + lean_assert(false); + break; + } +} + + +template void lp_primal_core_solver::print_bound_info_and_x(unsigned j, std::ostream & out) { + out << "type of " << this->column_name(j) << " is " << column_type_to_string(this->m_column_types[j]) << std::endl; + out << "x[" << this->column_name(j) << "] = " << this->m_x[j] << std::endl; + switch (this->m_column_types[j]) { + case column_type::fixed: + case column_type::boxed: + out << "[" << this->m_low_bounds[j] << "," << this->m_upper_bounds[j] << "]" << std::endl; + break; + case column_type::low_bound: + out << "[" << this->m_low_bounds[j] << ", inf" << std::endl; + break; + case column_type::upper_bound: + out << "inf ," << this->m_upper_bounds[j] << "]" << std::endl; + break; + case column_type::free_column: + out << "inf, inf" << std::endl; + break; + default: + lean_assert(false); + break; + } +} + + +} diff --git a/src/util/lp/lp_primal_core_solver_instances.cpp b/src/util/lp/lp_primal_core_solver_instances.cpp new file mode 100644 index 000000000..3b5a3fedb --- /dev/null +++ b/src/util/lp/lp_primal_core_solver_instances.cpp @@ -0,0 +1,24 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include +#include +#include +#include "util/vector.h" +#include +#include "util/lp/lar_solver.h" +#include "util/lp/lp_primal_core_solver.hpp" +#include "util/lp/lp_primal_core_solver_tableau.hpp" +namespace lean { +template void lp_primal_core_solver::find_feasible_solution(); +template void lean::lp_primal_core_solver >::find_feasible_solution(); + +template unsigned lp_primal_core_solver::solve(); +template unsigned lp_primal_core_solver::solve_with_tableau(); +template unsigned lp_primal_core_solver::solve(); +template void lean::lp_primal_core_solver::clear_breakpoints(); +template bool lean::lp_primal_core_solver::update_basis_and_x_tableau(int, int, lean::mpq const&); +template bool lean::lp_primal_core_solver::update_basis_and_x_tableau(int, int, double const&); +template bool lean::lp_primal_core_solver >::update_basis_and_x_tableau(int, int, lean::numeric_pair const&); +} diff --git a/src/util/lp/lp_primal_core_solver_tableau.hpp b/src/util/lp/lp_primal_core_solver_tableau.hpp new file mode 100644 index 000000000..e48e0ddbd --- /dev/null +++ b/src/util/lp/lp_primal_core_solver_tableau.hpp @@ -0,0 +1,393 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +// this is a part of lp_primal_core_solver that deals with the tableau +#include "util/lp/lp_primal_core_solver.h" +namespace lean { +template void lp_primal_core_solver::one_iteration_tableau() { + int entering = choose_entering_column_tableau(); + if (entering == -1) { + decide_on_status_when_cannot_find_entering(); + } + else { + advance_on_entering_tableau(entering); + } + lean_assert(this->inf_set_is_correct()); +} + +template void lp_primal_core_solver::advance_on_entering_tableau(int entering) { + X t; + int leaving = find_leaving_and_t_tableau(entering, t); + if (leaving == -1) { + this->set_status(UNBOUNDED); + return; + } + advance_on_entering_and_leaving_tableau(entering, leaving, t); +} +/* +template int lp_primal_core_solver::choose_entering_column_tableau_rows() { + int i = find_inf_row(); + if (i == -1) + return -1; + return find_shortest_beneficial_column_in_row(i); + } +*/ + template int lp_primal_core_solver::choose_entering_column_tableau() { + //this moment m_y = cB * B(-1) + unsigned number_of_benefitial_columns_to_go_over = get_number_of_non_basic_column_to_try_for_enter(); + + lean_assert(numeric_traits::precise()); + if (number_of_benefitial_columns_to_go_over == 0) + return -1; + if (this->m_basis_sort_counter == 0) { + sort_non_basis(); + this->m_basis_sort_counter = 20; + } + else { + this->m_basis_sort_counter--; + } + unsigned j_nz = this->m_m() + 1; // this number is greater than the max column size + std::list::iterator entering_iter = m_non_basis_list.end(); + for (auto non_basis_iter = m_non_basis_list.begin(); number_of_benefitial_columns_to_go_over && non_basis_iter != m_non_basis_list.end(); ++non_basis_iter) { + unsigned j = *non_basis_iter; + if (!column_is_benefitial_for_entering_basis(j)) + continue; + + // if we are here then j is a candidate to enter the basis + unsigned t = this->m_A.number_of_non_zeroes_in_column(j); + if (t < j_nz) { + j_nz = t; + entering_iter = non_basis_iter; + if (number_of_benefitial_columns_to_go_over) + number_of_benefitial_columns_to_go_over--; + } + else if (t == j_nz && my_random() % 2 == 0) { + entering_iter = non_basis_iter; + } + }// while (number_of_benefitial_columns_to_go_over && initial_offset_in_non_basis != offset_in_nb); + if (entering_iter == m_non_basis_list.end()) + return -1; + unsigned entering = *entering_iter; + m_sign_of_entering_delta = this->m_d[entering] > 0 ? 1 : -1; + if (this->m_using_infeas_costs && this->m_settings.use_breakpoints_in_feasibility_search) + m_sign_of_entering_delta = -m_sign_of_entering_delta; + m_non_basis_list.erase(entering_iter); + m_non_basis_list.push_back(entering); + return entering; + +} + + + + +template +unsigned lp_primal_core_solver::solve_with_tableau() { + init_run_tableau(); + if (this->current_x_is_feasible() && this->m_look_for_feasible_solution_only) { + this->set_status(FEASIBLE); + return 0; + } + + if ((!numeric_traits::precise()) && this->A_mult_x_is_off()) { + this->set_status(FLOATING_POINT_ERROR); + return 0; + } + do { + if (this->print_statistics_with_iterations_and_nonzeroes_and_cost_and_check_that_the_time_is_over((this->m_using_infeas_costs? "inf t" : "feas t"), * this->m_settings.get_message_ostream())) { + return this->total_iterations(); + } + if (this->m_settings.use_tableau_rows()) + one_iteration_tableau_rows(); + else + one_iteration_tableau(); + switch (this->get_status()) { + case OPTIMAL: // double check that we are at optimum + case INFEASIBLE: + if (this->m_look_for_feasible_solution_only && this->current_x_is_feasible()) + break; + if (!numeric_traits::precise()) { + if(this->m_look_for_feasible_solution_only) + break; + this->init_lu(); + + if (this->m_factorization->get_status() != LU_status::OK) { + this->set_status(FLOATING_POINT_ERROR); + break; + } + init_reduced_costs(); + if (choose_entering_column(1) == -1) { + decide_on_status_when_cannot_find_entering(); + break; + } + this->set_status(UNKNOWN); + } else { // precise case + if ((!this->infeasibility_costs_are_correct())) { + init_reduced_costs_tableau(); // forcing recalc + if (choose_entering_column_tableau() == -1) { + decide_on_status_when_cannot_find_entering(); + break; + } + this->set_status(UNKNOWN); + } + } + break; + case TENTATIVE_UNBOUNDED: + this->init_lu(); + if (this->m_factorization->get_status() != LU_status::OK) { + this->set_status(FLOATING_POINT_ERROR); + break; + } + + init_reduced_costs(); + break; + case UNBOUNDED: + if (this->current_x_is_infeasible()) { + init_reduced_costs(); + this->set_status(UNKNOWN); + } + break; + + case UNSTABLE: + lean_assert(! (numeric_traits::precise())); + this->init_lu(); + if (this->m_factorization->get_status() != LU_status::OK) { + this->set_status(FLOATING_POINT_ERROR); + break; + } + init_reduced_costs(); + break; + + default: + break; // do nothing + } + } while (this->get_status() != FLOATING_POINT_ERROR + && + this->get_status() != UNBOUNDED + && + this->get_status() != OPTIMAL + && + this->get_status() != INFEASIBLE + && + this->m_iters_with_no_cost_growing <= this->m_settings.max_number_of_iterations_with_no_improvements + && + this->total_iterations() <= this->m_settings.max_total_number_of_iterations + && + !(this->current_x_is_feasible() && this->m_look_for_feasible_solution_only)); + + lean_assert(this->get_status() == FLOATING_POINT_ERROR + || + this->current_x_is_feasible() == false + || + this->calc_current_x_is_feasible_include_non_basis()); + return this->total_iterations(); + +} +template void lp_primal_core_solver::advance_on_entering_and_leaving_tableau(int entering, int leaving, X & t) { + lean_assert(this->A_mult_x_is_off() == false); + lean_assert(leaving >= 0 && entering >= 0); + lean_assert((this->m_settings.simplex_strategy() == + simplex_strategy_enum::tableau_rows) || + m_non_basis_list.back() == static_cast(entering)); + lean_assert(this->m_using_infeas_costs || !is_neg(t)); + lean_assert(entering != leaving || !is_zero(t)); // otherwise nothing changes + if (entering == leaving) { + advance_on_entering_equal_leaving_tableau(entering, t); + return; + } + if (!is_zero(t)) { + if (this->current_x_is_feasible() || !this->m_settings.use_breakpoints_in_feasibility_search ) { + if (m_sign_of_entering_delta == -1) + t = -t; + } + this->update_basis_and_x_tableau(entering, leaving, t); + lean_assert(this->A_mult_x_is_off() == false); + this->m_iters_with_no_cost_growing = 0; + } else { + this->pivot_column_tableau(entering, this->m_basis_heading[leaving]); + this->change_basis(entering, leaving); + } + + if (this->m_look_for_feasible_solution_only && this->current_x_is_feasible()) + return; + + if (this->m_settings.simplex_strategy() != simplex_strategy_enum::tableau_rows) { + if (need_to_switch_costs()) { + this->init_reduced_costs_tableau(); + } + + lean_assert(!need_to_switch_costs()); + std::list::iterator it = m_non_basis_list.end(); + it--; + * it = static_cast(leaving); + } +} + +template +void lp_primal_core_solver::advance_on_entering_equal_leaving_tableau(int entering, X & t) { + lean_assert(!this->A_mult_x_is_off() ); + this->update_x_tableau(entering, t * m_sign_of_entering_delta); + if (this->m_look_for_feasible_solution_only && this->current_x_is_feasible()) + return; + + if (need_to_switch_costs()) { + init_reduced_costs_tableau(); + } + this->m_iters_with_no_cost_growing = 0; +} +template int lp_primal_core_solver::find_leaving_and_t_tableau(unsigned entering, X & t) { + unsigned k = 0; + bool unlimited = true; + unsigned row_min_nz = this->m_n() + 1; + m_leaving_candidates.clear(); + auto & col = this->m_A.m_columns[entering]; + unsigned col_size = col.size(); + for (;k < col_size && unlimited; k++) { + const column_cell & c = col[k]; + unsigned i = c.m_i; + const T & ed = this->m_A.get_val(c); + lean_assert(!numeric_traits::is_zero(ed)); + unsigned j = this->m_basis[i]; + limit_theta_on_basis_column(j, - ed * m_sign_of_entering_delta, t, unlimited); + if (!unlimited) { + m_leaving_candidates.push_back(j); + row_min_nz = this->m_A.m_rows[i].size(); + } + } + if (unlimited) { + if (try_jump_to_another_bound_on_entering_unlimited(entering, t)) + return entering; + return -1; + } + + X ratio; + for (;k < col_size; k++) { + const column_cell & c = col[k]; + unsigned i = c.m_i; + const T & ed = this->m_A.get_val(c); + lean_assert(!numeric_traits::is_zero(ed)); + unsigned j = this->m_basis[i]; + unlimited = true; + limit_theta_on_basis_column(j, -ed * m_sign_of_entering_delta, ratio, unlimited); + if (unlimited) continue; + unsigned i_nz = this->m_A.m_rows[i].size(); + if (ratio < t) { + t = ratio; + m_leaving_candidates.clear(); + m_leaving_candidates.push_back(j); + row_min_nz = i_nz; + } else if (ratio == t && i_nz < row_min_nz) { + m_leaving_candidates.clear(); + m_leaving_candidates.push_back(j); + row_min_nz = this->m_A.m_rows[i].size(); + } else if (ratio == t && i_nz == row_min_nz) { + m_leaving_candidates.push_back(j); + } + } + + ratio = t; + unlimited = false; + if (try_jump_to_another_bound_on_entering(entering, t, ratio, unlimited)) { + t = ratio; + return entering; + } + if (m_leaving_candidates.size() == 1) + return m_leaving_candidates[0]; + k = my_random() % m_leaving_candidates.size(); + return m_leaving_candidates[k]; +} +template void lp_primal_core_solver::init_run_tableau() { + // print_matrix(&(this->m_A), std::cout); + lean_assert(this->A_mult_x_is_off() == false); + lean_assert(basis_columns_are_set_correctly()); + this->m_basis_sort_counter = 0; // to initiate the sort of the basis + this->set_total_iterations(0); + this->m_iters_with_no_cost_growing = 0; + lean_assert(this->inf_set_is_correct()); + if (this->current_x_is_feasible() && this->m_look_for_feasible_solution_only) + return; + if (this->m_settings.backup_costs) + backup_and_normalize_costs(); + m_epsilon_of_reduced_cost = numeric_traits::precise() ? zero_of_type() : T(1) / T(10000000); + if (this->m_settings.use_breakpoints_in_feasibility_search) + m_breakpoint_indices_queue.resize(this->m_n()); + if (!numeric_traits::precise()) { + this->m_column_norm_update_counter = 0; + init_column_norms(); + } + if (this->m_settings.m_simplex_strategy == simplex_strategy_enum::tableau_rows) + init_tableau_rows(); + lean_assert(this->reduced_costs_are_correct_tableau()); + lean_assert(!this->need_to_pivot_to_basis_tableau()); +} + +template bool lp_primal_core_solver:: +update_basis_and_x_tableau(int entering, int leaving, X const & tt) { + lean_assert(this->use_tableau()); + update_x_tableau(entering, tt); + this->pivot_column_tableau(entering, this->m_basis_heading[leaving]); + this->change_basis(entering, leaving); + return true; +} +template void lp_primal_core_solver:: +update_x_tableau(unsigned entering, const X& delta) { + if (!this->m_using_infeas_costs) { + this->m_x[entering] += delta; + for (const auto & c : this->m_A.m_columns[entering]) { + unsigned i = c.m_i; + this->m_x[this->m_basis[i]] -= delta * this->m_A.get_val(c); + this->update_column_in_inf_set(this->m_basis[i]); + } + } else { // m_using_infeas_costs == true + this->m_x[entering] += delta; + lean_assert(this->column_is_feasible(entering)); + lean_assert(this->m_costs[entering] == zero_of_type()); + // m_d[entering] can change because of the cost change for basic columns. + for (const auto & c : this->m_A.m_columns[entering]) { + unsigned i = c.m_i; + unsigned j = this->m_basis[i]; + this->m_x[j] -= delta * this->m_A.get_val(c); + update_inf_cost_for_column_tableau(j); + if (is_zero(this->m_costs[j])) + this->m_inf_set.erase(j); + else + this->m_inf_set.insert(j); + } + } + lean_assert(this->A_mult_x_is_off() == false); +} + +template void lp_primal_core_solver:: +update_inf_cost_for_column_tableau(unsigned j) { + lean_assert(this->m_settings.simplex_strategy() != simplex_strategy_enum::tableau_rows); + lean_assert(this->m_using_infeas_costs); + T new_cost = get_infeasibility_cost_for_column(j); + T delta = this->m_costs[j] - new_cost; + if (is_zero(delta)) + return; + this->m_costs[j] = new_cost; + update_reduced_cost_for_basic_column_cost_change(delta, j); +} + +template void lp_primal_core_solver::init_reduced_costs_tableau() { + if (this->current_x_is_infeasible() && !this->m_using_infeas_costs) { + init_infeasibility_costs(); + } else if (this->current_x_is_feasible() && this->m_using_infeas_costs) { + if (this->m_look_for_feasible_solution_only) + return; + this->m_costs = m_costs_backup; + this->m_using_infeas_costs = false; + } + unsigned size = this->m_basis_heading.size(); + for (unsigned j = 0; j < size; j++) { + if (this->m_basis_heading[j] >= 0) + this->m_d[j] = zero_of_type(); + else { + T& d = this->m_d[j] = this->m_costs[j]; + for (auto & cc : this->m_A.m_columns[j]) { + d -= this->m_costs[this->m_basis[cc.m_i]] * this->m_A.get_val(cc); + } + } + } +} +} diff --git a/src/util/lp/lp_primal_simplex.h b/src/util/lp/lp_primal_simplex.h new file mode 100644 index 000000000..3b1288fc7 --- /dev/null +++ b/src/util/lp/lp_primal_simplex.h @@ -0,0 +1,96 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#pragma once +#include "util/vector.h" +#include +#include +#include +#include "util/lp/lp_utils.h" +#include "util/lp/column_info.h" +#include "util/lp/lp_primal_core_solver.h" +#include "util/lp/lp_solver.h" +#include "util/lp/iterator_on_row.h" +namespace lean { +template +class lp_primal_simplex: public lp_solver { + lp_primal_core_solver * m_core_solver = nullptr; + vector m_low_bounds; +private: + unsigned original_rows() { return this->m_external_rows_to_core_solver_rows.size(); } + + void fill_costs_and_x_for_first_stage_solver(unsigned original_number_of_columns); + + void init_buffer(unsigned k, vector & r); + + void refactor(); + + void set_scaled_costs(); +public: + lp_primal_simplex() {} + + column_info * get_or_create_column_info(unsigned column); + + void set_status(lp_status status) { + this->m_status = status; + } + + lp_status get_status() { + return this->m_status; + } + + void fill_acceptable_values_for_x(); + + + void set_zero_bound(bool * bound_is_set, T * bounds, unsigned i); + + void fill_costs_and_x_for_first_stage_solver_for_row( + int row, + unsigned & slack_var, + unsigned & artificial); + + + + + void set_core_solver_bounds(); + + void update_time_limit_from_starting_time(int start_time) { + this->m_settings.time_limit -= (get_millisecond_span(start_time) / 1000.); + } + + void find_maximal_solution(); + + void fill_A_x_and_basis_for_stage_one_total_inf(); + + void fill_A_x_and_basis_for_stage_one_total_inf_for_row(unsigned row); + + void solve_with_total_inf(); + + + ~lp_primal_simplex(); + + bool bounds_hold(std::unordered_map const & solution); + + T get_row_value(unsigned i, std::unordered_map const & solution, std::ostream * out); + + bool row_constraint_holds(unsigned i, std::unordered_map const & solution, std::ostream * out); + + bool row_constraints_hold(std::unordered_map const & solution); + + + T * get_array_from_map(std::unordered_map const & solution); + + bool solution_is_feasible(std::unordered_map const & solution) { + return bounds_hold(solution) && row_constraints_hold(solution); + } + + virtual T get_column_value(unsigned column) const { + return this->get_column_value_with_core_solver(column, m_core_solver); + } + + T get_current_cost() const; + + +}; +} diff --git a/src/util/lp/lp_primal_simplex.hpp b/src/util/lp/lp_primal_simplex.hpp new file mode 100644 index 000000000..9e1af2bbe --- /dev/null +++ b/src/util/lp/lp_primal_simplex.hpp @@ -0,0 +1,355 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include +#include "util/vector.h" +#include "util/lp/lp_primal_simplex.h" + +namespace lean { +template void lp_primal_simplex::fill_costs_and_x_for_first_stage_solver(unsigned original_number_of_columns) { + unsigned slack_var = original_number_of_columns; + unsigned artificial = original_number_of_columns + this->m_slacks; + + for (unsigned row = 0; row < this->row_count(); row++) { + fill_costs_and_x_for_first_stage_solver_for_row(row, slack_var, artificial); + } +} + +template void lp_primal_simplex::init_buffer(unsigned k, vector & r) { + for (unsigned i = 0; i < k; i++) { + r[i] = 0; + } + r[k] = 1; + for (unsigned i = this->row_count() -1; i > k; i--) { + r[i] = 0; + } +} + +template void lp_primal_simplex::refactor() { + m_core_solver->init_lu(); + if (m_core_solver->factorization()->get_status() != LU_status::OK) { + throw_exception("cannot refactor"); + } +} + +template void lp_primal_simplex::set_scaled_costs() { + unsigned j = this->number_of_core_structurals(); + while (j-- > 0) { + this->set_scaled_cost(j); + } +} + +template column_info * lp_primal_simplex::get_or_create_column_info(unsigned column) { + auto it = this->m_columns.find(column); + return (it == this->m_columns.end())? ( this->m_columns[column] = new column_info) : it->second; +} + +template void lp_primal_simplex::fill_acceptable_values_for_x() { + for (auto t : this->m_core_solver_columns_to_external_columns) { + this->m_x[t.first] = numeric_traits::zero(); + } +} + + +template void lp_primal_simplex::set_zero_bound(bool * bound_is_set, T * bounds, unsigned i) { + bound_is_set[i] = true; + bounds[i] = numeric_traits::zero(); +} + +template void lp_primal_simplex::fill_costs_and_x_for_first_stage_solver_for_row( + int row, + unsigned & slack_var, + unsigned & artificial) { + lean_assert(row >= 0 && row < this->row_count()); + auto & constraint = this->m_constraints[this->m_core_solver_rows_to_external_rows[row]]; + // we need to bring the program to the form Ax = b + T rs = this->m_b[row]; + T artificial_cost = - numeric_traits::one(); + switch (constraint.m_relation) { + case Equal: // no slack variable here + this->m_column_types[artificial] = column_type::low_bound; + this->m_costs[artificial] = artificial_cost; // we are maximizing, so the artificial, which is non-negatiive, will be pushed to zero + this->m_basis[row] = artificial; + if (rs >= 0) { + (*this->m_A)(row, artificial) = numeric_traits::one(); + this->m_x[artificial] = rs; + } else { + (*this->m_A)(row, artificial) = - numeric_traits::one(); + this->m_x[artificial] = - rs; + } + artificial++; + break; + + case Greater_or_equal: + this->m_column_types[slack_var] = column_type::low_bound; + (*this->m_A)(row, slack_var) = - numeric_traits::one(); + + if (rs > 0) { + lean_assert(numeric_traits::is_zero(this->m_x[slack_var])); + // adding one artificial + this->m_column_types[artificial] = column_type::low_bound; + (*this->m_A)(row, artificial) = numeric_traits::one(); + this->m_costs[artificial] = artificial_cost; + this->m_basis[row] = artificial; + this->m_x[artificial] = rs; + artificial++; + } else { + // we can put a slack_var into the basis, and atemplate void lp_primal_simplex::adding an artificial variable + this->m_basis[row] = slack_var; + this->m_x[slack_var] = - rs; + } + slack_var++; + break; + case Less_or_equal: + // introduce a non-negative slack variable + this->m_column_types[slack_var] = column_type::low_bound; + (*this->m_A)(row, slack_var) = numeric_traits::one(); + + if (rs < 0) { + // adding one artificial + lean_assert(numeric_traits::is_zero(this->m_x[slack_var])); + this->m_column_types[artificial] = column_type::low_bound; + (*this->m_A)(row, artificial) = - numeric_traits::one(); + this->m_costs[artificial] = artificial_cost; + this->m_x[artificial] = - rs; + this->m_basis[row] = artificial++; + } else { + // we can put slack_var into the basis, and atemplate void lp_primal_simplex::adding an artificial variable + this->m_basis[row] = slack_var; + this->m_x[slack_var] = rs; + } + slack_var++; + break; + } +} + + + + + +template void lp_primal_simplex::set_core_solver_bounds() { + unsigned total_vars = this->m_A->column_count() + this->m_slacks + this->m_artificials; + this->m_column_types.resize(total_vars); + this->m_upper_bounds.resize(total_vars); + for (auto cit : this->m_map_from_var_index_to_column_info) { + column_info * ci = cit.second; + unsigned j = ci->get_column_index(); + if (!is_valid(j)) + continue; // the variable is not mapped to a column + switch (this->m_column_types[j] = ci->get_column_type()){ + case column_type::fixed: + this->m_upper_bounds[j] = numeric_traits::zero(); + break; + case column_type::boxed: + this->m_upper_bounds[j] = ci->get_adjusted_upper_bound() / this->m_column_scale[j]; + break; + + default: break; // do nothing + } + } +} + + +template void lp_primal_simplex::find_maximal_solution() { + int preprocessing_start_time = get_millisecond_count(); + if (this->problem_is_empty()) { + this->m_status = lp_status::EMPTY; + return; + } + + this->cleanup(); + this->fill_matrix_A_and_init_right_side(); + if (this->m_status == lp_status::INFEASIBLE) { + return; + } + this->m_x.resize(this->m_A->column_count()); + this->fill_m_b(); + this->scale(); + fill_acceptable_values_for_x(); + this->count_slacks_and_artificials(); + set_core_solver_bounds(); + update_time_limit_from_starting_time(preprocessing_start_time); + solve_with_total_inf(); +} + +template void lp_primal_simplex::fill_A_x_and_basis_for_stage_one_total_inf() { + for (unsigned row = 0; row < this->row_count(); row++) + fill_A_x_and_basis_for_stage_one_total_inf_for_row(row); +} + +template void lp_primal_simplex::fill_A_x_and_basis_for_stage_one_total_inf_for_row(unsigned row) { + lean_assert(row < this->row_count()); + auto ext_row_it = this->m_core_solver_rows_to_external_rows.find(row); + lean_assert(ext_row_it != this->m_core_solver_rows_to_external_rows.end()); + unsigned ext_row = ext_row_it->second; + auto constr_it = this->m_constraints.find(ext_row); + lean_assert(constr_it != this->m_constraints.end()); + auto & constraint = constr_it->second; + unsigned j = this->m_A->column_count(); // j is a slack variable + this->m_A->add_column(); + // we need to bring the program to the form Ax = b + this->m_basis[row] = j; + switch (constraint.m_relation) { + case Equal: + this->m_x[j] = this->m_b[row]; + (*this->m_A)(row, j) = numeric_traits::one(); + this->m_column_types[j] = column_type::fixed; + this->m_upper_bounds[j] = m_low_bounds[j] = zero_of_type(); + break; + + case Greater_or_equal: + this->m_x[j] = - this->m_b[row]; + (*this->m_A)(row, j) = - numeric_traits::one(); + this->m_column_types[j] = column_type::low_bound; + this->m_upper_bounds[j] = zero_of_type(); + break; + case Less_or_equal: + this->m_x[j] = this->m_b[row]; + (*this->m_A)(row, j) = numeric_traits::one(); + this->m_column_types[j] = column_type::low_bound; + this->m_upper_bounds[j] = m_low_bounds[j] = zero_of_type(); + break; + default: + lean_unreachable(); + } +} + +template void lp_primal_simplex::solve_with_total_inf() { + int total_vars = this->m_A->column_count() + this->row_count(); + if (total_vars == 0) { + this->m_status = OPTIMAL; + return; + } + m_low_bounds.clear(); + m_low_bounds.resize(total_vars, zero_of_type()); // low bounds are shifted ot zero + this->m_x.resize(total_vars, numeric_traits::zero()); + this->m_basis.resize(this->row_count()); + this->m_costs.clear(); + this->m_costs.resize(total_vars, zero_of_type()); + fill_A_x_and_basis_for_stage_one_total_inf(); + if (this->m_settings.get_message_ostream() != nullptr) + this->print_statistics_on_A(*this->m_settings.get_message_ostream()); + set_scaled_costs(); + + m_core_solver = new lp_primal_core_solver(*this->m_A, + this->m_b, + this->m_x, + this->m_basis, + this->m_nbasis, + this->m_heading, + this->m_costs, + this->m_column_types, + m_low_bounds, + this->m_upper_bounds, + this->m_settings, *this); + m_core_solver->solve(); + this->set_status(m_core_solver->get_status()); + this->m_total_iterations = m_core_solver->total_iterations(); +} + + +template lp_primal_simplex::~lp_primal_simplex() { + if (m_core_solver != nullptr) { + delete m_core_solver; + } +} + +template bool lp_primal_simplex::bounds_hold(std::unordered_map const & solution) { + for (auto it : this->m_map_from_var_index_to_column_info) { + auto sol_it = solution.find(it.second->get_name()); + if (sol_it == solution.end()) { + std::stringstream s; + s << "cannot find column " << it.first << " in solution"; + throw_exception(s.str() ); + } + + if (!it.second->bounds_hold(sol_it->second)) { + // std::cout << "bounds do not hold for " << it.second->get_name() << std::endl; + it.second->bounds_hold(sol_it->second); + return false; + } + } + return true; +} + +template T lp_primal_simplex::get_row_value(unsigned i, std::unordered_map const & solution, std::ostream * out) { + auto it = this->m_A_values.find(i); + if (it == this->m_A_values.end()) { + std::stringstream s; + s << "cannot find row " << i; + throw_exception(s.str() ); + } + T ret = numeric_traits::zero(); + for (auto & pair : it->second) { + auto cit = this->m_map_from_var_index_to_column_info.find(pair.first); + lean_assert(cit != this->m_map_from_var_index_to_column_info.end()); + column_info * ci = cit->second; + auto sol_it = solution.find(ci->get_name()); + lean_assert(sol_it != solution.end()); + T column_val = sol_it->second; + if (out != nullptr) { + (*out) << pair.second << "(" << ci->get_name() << "=" << column_val << ") "; + } + ret += pair.second * column_val; + } + if (out != nullptr) { + (*out) << " = " << ret << std::endl; + } + return ret; +} + +template bool lp_primal_simplex::row_constraint_holds(unsigned i, std::unordered_map const & solution, std::ostream *out) { + T row_val = get_row_value(i, solution, out); + auto & constraint = this->m_constraints[i]; + T rs = constraint.m_rs; + bool print = out != nullptr; + switch (constraint.m_relation) { + case Equal: + if (fabs(numeric_traits::get_double(row_val - rs)) > 0.00001) { + if (print) { + (*out) << "should be = " << rs << std::endl; + } + return false; + } + return true; + case Greater_or_equal: + if (numeric_traits::get_double(row_val - rs) < -0.00001) { + if (print) { + (*out) << "should be >= " << rs << std::endl; + } + return false; + } + return true;; + + case Less_or_equal: + if (numeric_traits::get_double(row_val - rs) > 0.00001) { + if (print) { + (*out) << "should be <= " << rs << std::endl; + } + return false; + } + return true;; + } + lean_unreachable(); + return false; // it is unreachable +} + +template bool lp_primal_simplex::row_constraints_hold(std::unordered_map const & solution) { + for (auto it : this->m_A_values) { + if (!row_constraint_holds(it.first, solution, nullptr)) { + row_constraint_holds(it.first, solution, nullptr); + return false; + } + } + return true; +} + +template T lp_primal_simplex::get_current_cost() const { + T ret = numeric_traits::zero(); + for (auto it : this->m_map_from_var_index_to_column_info) { + ret += this->get_column_cost_value(it.first, it.second); + } + return ret; +} +} diff --git a/src/util/lp/lp_primal_simplex_instances.cpp b/src/util/lp/lp_primal_simplex_instances.cpp new file mode 100644 index 000000000..37b639489 --- /dev/null +++ b/src/util/lp/lp_primal_simplex_instances.cpp @@ -0,0 +1,20 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include +#include +#include +#include "util/vector.h" +#include +#include "util/lp/lp_primal_simplex.hpp" +template bool lean::lp_primal_simplex::bounds_hold(std::unordered_map, std::equal_to, std::allocator > > const&); +template bool lean::lp_primal_simplex::row_constraints_hold(std::unordered_map, std::equal_to, std::allocator > > const&); +template double lean::lp_primal_simplex::get_current_cost() const; +template double lean::lp_primal_simplex::get_column_value(unsigned int) const; +template lean::lp_primal_simplex::~lp_primal_simplex(); +template lean::lp_primal_simplex::~lp_primal_simplex(); +template lean::mpq lean::lp_primal_simplex::get_current_cost() const; +template lean::mpq lean::lp_primal_simplex::get_column_value(unsigned int) const; +template void lean::lp_primal_simplex::find_maximal_solution(); +template void lean::lp_primal_simplex::find_maximal_solution(); diff --git a/src/util/lp/lp_settings.h b/src/util/lp/lp_settings.h new file mode 100644 index 000000000..7ec132f5e --- /dev/null +++ b/src/util/lp/lp_settings.h @@ -0,0 +1,339 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ + +#pragma once +#include "util/vector.h" +#include +#include +#include +#include +#include +#include "util/lp/lp_utils.h" + +namespace lean { +typedef unsigned var_index; +typedef unsigned constraint_index; +typedef unsigned row_index; +enum class column_type { + free_column = 0, + low_bound = 1, + upper_bound = 2, + boxed = 3, + fixed = 4 + }; + +enum class simplex_strategy_enum { + tableau_rows = 0, + tableau_costs = 1, + no_tableau = 2 +}; + +std::string column_type_to_string(column_type t); + +enum lp_status { + UNKNOWN, + INFEASIBLE, + TENTATIVE_UNBOUNDED, + UNBOUNDED, + TENTATIVE_DUAL_UNBOUNDED, + DUAL_UNBOUNDED, + OPTIMAL, + FEASIBLE, + FLOATING_POINT_ERROR, + TIME_EXHAUSTED, + ITERATIONS_EXHAUSTED, + EMPTY, + UNSTABLE +}; + +// when the ratio of the vector lenth to domain size to is greater than the return value we switch to solve_By_for_T_indexed_only +template +unsigned ratio_of_index_size_to_all_size() { + if (numeric_traits::precise()) + return 10; + return 120; +} + +const char* lp_status_to_string(lp_status status); + +inline std::ostream& operator<<(std::ostream& out, lp_status status) { + return out << lp_status_to_string(status); +} + +lp_status lp_status_from_string(std::string status); + +enum non_basic_column_value_position { at_low_bound, at_upper_bound, at_fixed, free_of_bounds, not_at_bound }; + +template bool is_epsilon_small(const X & v, const double& eps); // forward definition + +int get_millisecond_count(); +int get_millisecond_span(int start_time); +unsigned my_random(); +void my_random_init(long unsigned seed); + + +class lp_resource_limit { +public: + virtual bool get_cancel_flag() = 0; +}; + +struct stats { + unsigned m_total_iterations; + unsigned m_iters_with_no_cost_growing; + unsigned m_num_factorizations; + unsigned m_num_of_implied_bounds; + unsigned m_need_to_solve_inf; + stats() { reset(); } + void reset() { memset(this, 0, sizeof(*this)); } +}; + +struct lp_settings { +private: + class default_lp_resource_limit : public lp_resource_limit { + lp_settings& m_settings; + int m_start_time; + public: + default_lp_resource_limit(lp_settings& s): m_settings(s), m_start_time(get_millisecond_count()) {} + virtual bool get_cancel_flag() { + int span_in_mills = get_millisecond_span(m_start_time); + return (span_in_mills / 1000.0 > m_settings.time_limit); + } + }; + + default_lp_resource_limit m_default_resource_limit; + lp_resource_limit* m_resource_limit; + // used for debug output + std::ostream* m_debug_out = &std::cout; + // used for messages, for example, the computation progress messages + std::ostream* m_message_out = &std::cout; + + stats m_stats; + +public: + unsigned reps_in_scaler = 20; + // when the absolute value of an element is less than pivot_epsilon + // in pivoting, we treat it as a zero + double pivot_epsilon = 0.00000001; + // see Chatal, page 115 + double positive_price_epsilon = 1e-7; + // a quatation "if some choice of the entering vairable leads to an eta matrix + // whose diagonal element in the eta column is less than e2 (entering_diag_epsilon) in magnitude, the this choice is rejected ... + double entering_diag_epsilon = 1e-8; + int c_partial_pivoting = 10; // this is the constant c from page 410 + unsigned depth_of_rook_search = 4; + bool using_partial_pivoting = true; + // dissertation of Achim Koberstein + // if Bx - b is different at any component more that refactor_epsilon then we refactor + double refactor_tolerance = 1e-4; + double pivot_tolerance = 1e-6; + double zero_tolerance = 1e-12; + double drop_tolerance = 1e-14; + double tolerance_for_artificials = 1e-4; + double can_be_taken_to_basis_tolerance = 0.00001; + + unsigned percent_of_entering_to_check = 5; // we try to find a profitable column in a percentage of the columns + bool use_scaling = true; + double scaling_maximum = 1; + double scaling_minimum = 0.5; + double harris_feasibility_tolerance = 1e-7; // page 179 of Istvan Maros + double ignore_epsilon_of_harris = 10e-5; + unsigned max_number_of_iterations_with_no_improvements = 2000000; + unsigned max_total_number_of_iterations = 20000000; + double time_limit = std::numeric_limits::max(); // the maximum time limit of the total run time in seconds + // dual section + double dual_feasibility_tolerance = 1e-7; // // page 71 of the PhD thesis of Achim Koberstein + double primal_feasibility_tolerance = 1e-7; // page 71 of the PhD thesis of Achim Koberstein + double relative_primal_feasibility_tolerance = 1e-9; // page 71 of the PhD thesis of Achim Koberstein + + bool m_bound_propagation = true; + + bool bound_progation() const { + return m_bound_propagation; + } + + bool& bound_propagation() { + return m_bound_propagation; + } + + lp_settings() : m_default_resource_limit(*this), m_resource_limit(&m_default_resource_limit) {} + + void set_resource_limit(lp_resource_limit& lim) { m_resource_limit = &lim; } + bool get_cancel_flag() const { return m_resource_limit->get_cancel_flag(); } + + void set_debug_ostream(std::ostream* out) { m_debug_out = out; } + void set_message_ostream(std::ostream* out) { m_message_out = out; } + + std::ostream* get_debug_ostream() { return m_debug_out; } + std::ostream* get_message_ostream() { return m_message_out; } + stats& st() { return m_stats; } + stats const& st() const { return m_stats; } + + template static bool is_eps_small_general(const T & t, const double & eps) { + return (!numeric_traits::precise())? is_epsilon_small(t, eps) : numeric_traits::is_zero(t); + } + + template + bool abs_val_is_smaller_than_dual_feasibility_tolerance(T const & t) { + return is_eps_small_general(t, dual_feasibility_tolerance); + } + + template + bool abs_val_is_smaller_than_primal_feasibility_tolerance(T const & t) { + return is_eps_small_general(t, primal_feasibility_tolerance); + } + + template + bool abs_val_is_smaller_than_can_be_taken_to_basis_tolerance(T const & t) { + return is_eps_small_general(t, can_be_taken_to_basis_tolerance); + } + + template + bool abs_val_is_smaller_than_drop_tolerance(T const & t) const { + return is_eps_small_general(t, drop_tolerance); + } + + + template + bool abs_val_is_smaller_than_zero_tolerance(T const & t) { + return is_eps_small_general(t, zero_tolerance); + } + + template + bool abs_val_is_smaller_than_refactor_tolerance(T const & t) { + return is_eps_small_general(t, refactor_tolerance); + } + + + template + bool abs_val_is_smaller_than_pivot_tolerance(T const & t) { + return is_eps_small_general(t, pivot_tolerance); + } + + template + bool abs_val_is_smaller_than_harris_tolerance(T const & t) { + return is_eps_small_general(t, harris_feasibility_tolerance); + } + + template + bool abs_val_is_smaller_than_ignore_epslilon_for_harris(T const & t) { + return is_eps_small_general(t, ignore_epsilon_of_harris); + } + + template + bool abs_val_is_smaller_than_artificial_tolerance(T const & t) { + return is_eps_small_general(t, tolerance_for_artificials); + } + // the method of lar solver to use + bool presolve_with_double_solver_for_lar = true; + simplex_strategy_enum m_simplex_strategy = simplex_strategy_enum::tableau_rows; + simplex_strategy_enum simplex_strategy() const { + return m_simplex_strategy; + } + + simplex_strategy_enum & simplex_strategy() { + return m_simplex_strategy; + } + + bool use_tableau() const { + return m_simplex_strategy != simplex_strategy_enum::no_tableau; + } + + bool use_tableau_rows() const { + return m_simplex_strategy == simplex_strategy_enum::tableau_rows; + } + + int report_frequency = 1000; + bool print_statistics = false; + unsigned column_norms_update_frequency = 12000; + bool scale_with_ratio = true; + double density_threshold = 0.7; // need to tune it up, todo +#ifdef LEAN_DEBUG + static unsigned ddd; // used for debugging +#endif + bool use_breakpoints_in_feasibility_search = false; + unsigned random_seed = 1; + static unsigned long random_next; + unsigned max_row_length_for_bound_propagation = 300; + bool backup_costs = true; +}; // end of lp_settings class + + +#define LP_OUT(_settings_, _msg_) { if (_settings_.get_debug_ostream()) { *_settings_.get_debug_ostream() << _msg_; } } + +template +std::string T_to_string(const T & t) { + std::ostringstream strs; + strs << t; + return strs.str(); +} + +inline std::string T_to_string(const numeric_pair & t) { + std::ostringstream strs; + double r = (t.x + t.y / mpq(1000)).get_double(); + strs << r; + return strs.str(); +} + + +inline std::string T_to_string(const mpq & t) { + std::ostringstream strs; + strs << t.get_double(); + return strs.str(); +} + +template +bool val_is_smaller_than_eps(T const & t, double const & eps) { + if (!numeric_traits::precise()) { + return numeric_traits::get_double(t) < eps; + } + return t <= numeric_traits::zero(); +} + +template +bool vectors_are_equal(T * a, vector &b, unsigned n); + +template +bool vectors_are_equal(const vector & a, const buffer &b); + +template +bool vectors_are_equal(const vector & a, const vector &b); + +template +T abs (T const & v) { return v >= zero_of_type() ? v : -v; } + +template +X max_abs_in_vector(vector& t){ + X r(zero_of_type()); + for (auto & v : t) + r = std::max(abs(v) , r); + return r; +} +inline void print_blanks(int n, std::ostream & out) { + while (n--) {out << ' '; } +} + + +// after a push of the last element we ensure that the vector increases +// we also suppose that before the last push the vector was increasing +inline void ensure_increasing(vector & v) { + lean_assert(v.size() > 0); + unsigned j = v.size() - 1; + for (; j > 0; j-- ) + if (v[j] <= v[j - 1]) { + // swap + unsigned t = v[j]; + v[j] = v[j-1]; + v[j-1] = t; + } else { + break; + } +} + + + +#if LEAN_DEBUG +bool D(); +#endif +} diff --git a/src/util/lp/lp_settings.hpp b/src/util/lp/lp_settings.hpp new file mode 100644 index 000000000..d110d51af --- /dev/null +++ b/src/util/lp/lp_settings.hpp @@ -0,0 +1,133 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include +#include +#include "util/vector.h" +#include "util/lp/lp_settings.h" +namespace lean { +std::string column_type_to_string(column_type t) { + switch (t) { + case column_type::fixed: return "fixed"; + case column_type::boxed: return "boxed"; + case column_type::low_bound: return "low_bound"; + case column_type::upper_bound: return "upper_bound"; + case column_type::free_column: return "free_column"; + default: lean_unreachable(); + } + return "unknown"; // it is unreachable +} + +const char* lp_status_to_string(lp_status status) { + switch (status) { + case UNKNOWN: return "UNKNOWN"; + case INFEASIBLE: return "INFEASIBLE"; + case UNBOUNDED: return "UNBOUNDED"; + case TENTATIVE_DUAL_UNBOUNDED: return "TENTATIVE_DUAL_UNBOUNDED"; + case DUAL_UNBOUNDED: return "DUAL_UNBOUNDED"; + case OPTIMAL: return "OPTIMAL"; + case FEASIBLE: return "FEASIBLE"; + case FLOATING_POINT_ERROR: return "FLOATING_POINT_ERROR"; + case TIME_EXHAUSTED: return "TIME_EXHAUSTED"; + case ITERATIONS_EXHAUSTED: return "ITERATIONS_EXHAUSTED"; + case EMPTY: return "EMPTY"; + case UNSTABLE: return "UNSTABLE"; + default: + lean_unreachable(); + } + return "UNKNOWN"; // it is unreachable +} + +lp_status lp_status_from_string(std::string status) { + if (status == "UNKNOWN") return lp_status::UNKNOWN; + if (status == "INFEASIBLE") return lp_status::INFEASIBLE; + if (status == "UNBOUNDED") return lp_status::UNBOUNDED; + if (status == "OPTIMAL") return lp_status::OPTIMAL; + if (status == "FEASIBLE") return lp_status::FEASIBLE; + if (status == "FLOATING_POINT_ERROR") return lp_status::FLOATING_POINT_ERROR; + if (status == "TIME_EXHAUSTED") return lp_status::TIME_EXHAUSTED; + if (status == "ITERATIONS_EXHAUSTED") return lp_status::ITERATIONS_EXHAUSTED; + if (status == "EMPTY") return lp_status::EMPTY; + lean_unreachable(); + return lp_status::UNKNOWN; // it is unreachable +} +int get_millisecond_count() { + timeb tb; + ftime(&tb); + return tb.millitm + (tb.time & 0xfffff) * 1000; +} + +int get_millisecond_span(int start_time) { + int span = get_millisecond_count() - start_time; + if (span < 0) + span += 0x100000 * 1000; + return span; +} + + + +void my_random_init(long unsigned seed) { + lp_settings::random_next = seed; +} + +unsigned my_random() { + lp_settings::random_next = lp_settings::random_next * 1103515245 + 12345; + return((unsigned)(lp_settings::random_next/65536) % 32768); +} + +template +bool vectors_are_equal(T * a, vector &b, unsigned n) { + if (numeric_traits::precise()) { + for (unsigned i = 0; i < n; i ++){ + if (!numeric_traits::is_zero(a[i] - b[i])) { + // std::cout << "a[" << i <<"]" << a[i] << ", " << "b[" << i <<"]" << b[i] << std::endl; + return false; + } + } + } else { + for (unsigned i = 0; i < n; i ++){ + if (std::abs(numeric_traits::get_double(a[i] - b[i])) > 0.000001) { + // std::cout << "a[" << i <<"]" << a[i] << ", " << "b[" << i <<"]" << b[i] << std::endl; + return false; + } + } + } + return true; +} + + +template +bool vectors_are_equal(const vector & a, const vector &b) { + unsigned n = static_cast(a.size()); + if (n != b.size()) return false; + if (numeric_traits::precise()) { + for (unsigned i = 0; i < n; i ++){ + if (!numeric_traits::is_zero(a[i] - b[i])) { + // std::cout << "a[" << i <<"]" << a[i] << ", " << "b[" << i <<"]" << b[i] << std::endl; + return false; + } + } + } else { + for (unsigned i = 0; i < n; i ++){ + double da = numeric_traits::get_double(a[i]); + double db = numeric_traits::get_double(b[i]); + double amax = std::max(fabs(da), fabs(db)); + if (amax > 1) { + da /= amax; + db /= amax; + } + + if (fabs(da - db) > 0.000001) { + // std::cout << "a[" << i <<"] = " << a[i] << ", but " << "b[" << i <<"] = " << b[i] << std::endl; + return false; + } + } + } + return true; +} +unsigned long lp_settings::random_next = 1; +#ifdef LEAN_DEBUG +unsigned lp_settings::ddd = 0; +#endif +} diff --git a/src/util/lp/lp_settings_instances.cpp b/src/util/lp/lp_settings_instances.cpp new file mode 100644 index 000000000..e9a3888ba --- /dev/null +++ b/src/util/lp/lp_settings_instances.cpp @@ -0,0 +1,10 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include "util/vector.h" +#include +#include "util/lp/lp_settings.hpp" +template bool lean::vectors_are_equal(vector const&, vector const&); +template bool lean::vectors_are_equal(vector const&, vector const&); + diff --git a/src/util/lp/lp_solver.h b/src/util/lp/lp_solver.h new file mode 100644 index 000000000..eeb3ff6d3 --- /dev/null +++ b/src/util/lp/lp_solver.h @@ -0,0 +1,253 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ + +#pragma once +#include +#include +#include +#include "util/vector.h" +#include "util/lp/lp_settings.h" +#include "util/lp/column_info.h" +#include "util/lp/static_matrix.h" +#include "util/lp/lp_core_solver_base.h" +#include "util/lp/scaler.h" +#include "util/lp/linear_combination_iterator.h" +#include "util/lp/bound_analyzer_on_row.h" +namespace lean { +enum lp_relation { + Less_or_equal, + Equal, + Greater_or_equal +}; + +template +struct lp_constraint { + X m_rs; // right side of the constraint + lp_relation m_relation; + lp_constraint() {} // empty constructor + lp_constraint(T rs, lp_relation relation): m_rs(rs), m_relation(relation) {} +}; + + +template +class lp_solver : public column_namer { + column_info * get_or_create_column_info(unsigned column); + +protected: + T get_column_cost_value(unsigned j, column_info * ci) const; +public: + unsigned m_total_iterations; + static_matrix* m_A = nullptr; // this is the matrix of constraints + vector m_b; // the right side vector + unsigned m_first_stage_iterations = 0; + unsigned m_second_stage_iterations = 0; + std::unordered_map> m_constraints; + std::unordered_map*> m_map_from_var_index_to_column_info; + std::unordered_map > m_A_values; + std::unordered_map m_names_to_columns; // don't have to use it + std::unordered_map m_external_rows_to_core_solver_rows; + std::unordered_map m_core_solver_rows_to_external_rows; + std::unordered_map m_core_solver_columns_to_external_columns; + vector m_column_scale; + std::unordered_map m_name_map; + unsigned m_artificials = 0; + unsigned m_slacks = 0; + vector m_column_types; + vector m_costs; + vector m_x; + vector m_upper_bounds; + vector m_basis; + vector m_nbasis; + vector m_heading; + + + lp_status m_status = lp_status::UNKNOWN; + + lp_settings m_settings; + lp_solver() {} + + unsigned row_count() const { return this->m_A->row_count(); } + + void add_constraint(lp_relation relation, T right_side, unsigned row_index); + + void set_cost_for_column(unsigned column, T column_cost) { + get_or_create_column_info(column)->set_cost(column_cost); + } + std::string get_column_name(unsigned j) const override; + + void set_row_column_coefficient(unsigned row, unsigned column, T const & val) { + m_A_values[row][column] = val; + } + // returns the current cost + virtual T get_current_cost() const = 0; + // do not have to call it + void give_symbolic_name_to_column(std::string name, unsigned column); + + virtual T get_column_value(unsigned column) const = 0; + + T get_column_value_by_name(std::string name) const; + + // returns -1 if not found + virtual int get_column_index_by_name(std::string name) const; + + void set_low_bound(unsigned i, T bound) { + column_info *ci = get_or_create_column_info(i); + ci->set_low_bound(bound); + } + + void set_upper_bound(unsigned i, T bound) { + column_info *ci = get_or_create_column_info(i); + ci->set_upper_bound(bound); + } + + void unset_low_bound(unsigned i) { + get_or_create_column_info(i)->unset_low_bound(); + } + + void unset_upper_bound(unsigned i) { + get_or_create_column_info(i)->unset_upper_bound(); + } + + void set_fixed_value(unsigned i, T val) { + column_info *ci = get_or_create_column_info(i); + ci->set_fixed_value(val); + } + + void unset_fixed_value(unsigned i) { + get_or_create_column_info(i)->unset_fixed(); + } + + lp_status get_status() const { + return m_status; + } + + void set_status(lp_status st) { + m_status = st; + } + + + virtual ~lp_solver(); + + void flip_costs(); + + virtual void find_maximal_solution() = 0; + void set_time_limit(unsigned time_limit_in_seconds) { + m_settings.time_limit = time_limit_in_seconds; + } + + void set_max_iterations_per_stage(unsigned max_iterations) { + m_settings.max_total_number_of_iterations = max_iterations; + } + + unsigned get_max_iterations_per_stage() const { + return m_settings.max_total_number_of_iterations; + } +protected: + bool problem_is_empty(); + + void scale(); + + + void print_rows_scale_stats(std::ostream & out); + + void print_columns_scale_stats(std::ostream & out); + + void print_row_scale_stats(unsigned i, std::ostream & out); + + void print_column_scale_stats(unsigned j, std::ostream & out); + + void print_scale_stats(std::ostream & out); + + void get_max_abs_in_row(std::unordered_map & row_map); + + void pin_vars_down_on_row(std::unordered_map & row) { + pin_vars_on_row_with_sign(row, - numeric_traits::one()); + } + + void pin_vars_up_on_row(std::unordered_map & row) { + pin_vars_on_row_with_sign(row, numeric_traits::one()); + } + + void pin_vars_on_row_with_sign(std::unordered_map & row, T sign ); + + bool get_minimal_row_value(std::unordered_map & row, T & low_bound); + + bool get_maximal_row_value(std::unordered_map & row, T & low_bound); + + bool row_is_zero(std::unordered_map & row); + + bool row_e_is_obsolete(std::unordered_map & row, unsigned row_index); + + bool row_ge_is_obsolete(std::unordered_map & row, unsigned row_index); + + bool row_le_is_obsolete(std::unordered_map & row, unsigned row_index); + + // analyse possible max and min values that are derived from var boundaries + // Let us say that the we have a "ge" constraint, and the min value is equal to the rs. + // Then we know what values of the variables are. For each positive coeff of the row it has to be + // the low boundary of the var and for a negative - the upper. + + // this routing also pins the variables to the boundaries + bool row_is_obsolete(std::unordered_map & row, unsigned row_index ); + + void remove_fixed_or_zero_columns(); + + void remove_fixed_or_zero_columns_from_row(unsigned i, std::unordered_map & row); + + unsigned try_to_remove_some_rows(); + + void cleanup(); + + void map_external_rows_to_core_solver_rows(); + + void map_external_columns_to_core_solver_columns(); + + unsigned number_of_core_structurals() { + return static_cast(m_core_solver_columns_to_external_columns.size()); + } + + void restore_column_scales_to_one() { + for (unsigned i = 0; i < m_column_scale.size(); i++) m_column_scale[i] = numeric_traits::one(); + } + + void unscale(); + + void fill_A_from_A_values(); + + void fill_matrix_A_and_init_right_side(); + + void count_slacks_and_artificials(); + + void count_slacks_and_artificials_for_row(unsigned i); + + T low_bound_shift_for_row(unsigned i); + + void fill_m_b(); + + T get_column_value_with_core_solver(unsigned column, lp_core_solver_base * core_solver) const; + void set_scaled_cost(unsigned j); + void print_statistics_on_A(std::ostream & out) { + out << "extended A[" << this->m_A->row_count() << "," << this->m_A->column_count() << "]" << std::endl; + } + + struct row_tighten_stats { + unsigned n_of_new_bounds = 0; + unsigned n_of_fixed = 0; + bool is_obsolete = false; + }; + + + +public: + lp_settings & settings() { return m_settings;} + void print_model(std::ostream & s) const { + s << "objective = " << get_current_cost() << std::endl; + s << "column values\n"; + for (auto & it : m_names_to_columns) { + s << it.first << " = " << get_column_value(it.second) << std::endl; + } + } +}; +} diff --git a/src/util/lp/lp_solver.hpp b/src/util/lp/lp_solver.hpp new file mode 100644 index 000000000..135616a69 --- /dev/null +++ b/src/util/lp/lp_solver.hpp @@ -0,0 +1,554 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include +#include +#include "util/vector.h" +#include "util/lp/lp_solver.h" +namespace lean { +template column_info * lp_solver::get_or_create_column_info(unsigned column) { + auto it = m_map_from_var_index_to_column_info.find(column); + return (it == m_map_from_var_index_to_column_info.end())? (m_map_from_var_index_to_column_info[column] = new column_info(static_cast(-1))) : it->second; +} + +template +std::string lp_solver::get_column_name(unsigned j) const { // j here is the core solver index + auto it = this->m_core_solver_columns_to_external_columns.find(j); + if (it == this->m_core_solver_columns_to_external_columns.end()) + return std::string("x")+T_to_string(j); + unsigned external_j = it->second; + auto t = this->m_map_from_var_index_to_column_info.find(external_j); + if (t == this->m_map_from_var_index_to_column_info.end()) { + return std::string("x") +T_to_string(external_j); + } + return t->second->get_name(); +} + +template T lp_solver::get_column_cost_value(unsigned j, column_info * ci) const { + if (ci->is_fixed()) { + return ci->get_cost() * ci->get_fixed_value(); + } + return ci->get_cost() * get_column_value(j); +} +template void lp_solver::add_constraint(lp_relation relation, T right_side, unsigned row_index) { + lean_assert(m_constraints.find(row_index) == m_constraints.end()); + lp_constraint cs(right_side, relation); + m_constraints[row_index] = cs; +} + +template void lp_solver::give_symbolic_name_to_column(std::string name, unsigned column) { + auto it = m_map_from_var_index_to_column_info.find(column); + column_info *ci; + if (it == m_map_from_var_index_to_column_info.end()){ + m_map_from_var_index_to_column_info[column] = ci = new column_info; + } else { + ci = it->second; + } + ci->set_name(name); + m_names_to_columns[name] = column; +} + + +template T lp_solver::get_column_value_by_name(std::string name) const { + auto it = m_names_to_columns.find(name); + if (it == m_names_to_columns.end()) { + std::stringstream s; + s << "get_column_value_by_name " << name; + throw_exception(s.str()); + } + return get_column_value(it -> second); +} + +// returns -1 if not found +template int lp_solver::get_column_index_by_name(std::string name) const { + auto t = m_names_to_columns.find(name); + if (t == m_names_to_columns.end()) { + return -1; + } + return t->second; +} + + +template lp_solver::~lp_solver(){ + if (m_A != nullptr) { + delete m_A; + } + for (auto t : m_map_from_var_index_to_column_info) { + delete t.second; + } +} + +template void lp_solver::flip_costs() { + for (auto t : m_map_from_var_index_to_column_info) { + column_info *ci = t.second; + ci->set_cost(-ci->get_cost()); + } +} + +template bool lp_solver::problem_is_empty() { + for (auto & c : m_A_values) + if (c.second.size()) + return false; + return true; +} + +template void lp_solver::scale() { + if (numeric_traits::precise() || m_settings.use_scaling == false) { + m_column_scale.clear(); + m_column_scale.resize(m_A->column_count(), one_of_type()); + return; + } + + T smin = T(m_settings.scaling_minimum); + T smax = T(m_settings.scaling_maximum); + + scaler scaler(m_b, *m_A, smin, smax, m_column_scale, this->m_settings); + if (!scaler.scale()) { + unscale(); + } +} + + +template void lp_solver::print_rows_scale_stats(std::ostream & out) { + out << "rows max" << std::endl; + for (unsigned i = 0; i < m_A->row_count(); i++) { + print_row_scale_stats(i, out); + } + out << std::endl; +} + +template void lp_solver::print_columns_scale_stats(std::ostream & out) { + out << "columns max" << std::endl; + for (unsigned i = 0; i < m_A->column_count(); i++) { + print_column_scale_stats(i, out); + } + out << std::endl; +} + +template void lp_solver::print_row_scale_stats(unsigned i, std::ostream & out) { + out << "(" << std::min(m_A->get_min_abs_in_row(i), abs(m_b[i])) << " "; + out << std::max(m_A->get_max_abs_in_row(i), abs(m_b[i])) << ")"; +} + +template void lp_solver::print_column_scale_stats(unsigned j, std::ostream & out) { + out << "(" << m_A->get_min_abs_in_row(j) << " "; + out << m_A->get_max_abs_in_column(j) << ")"; +} + +template void lp_solver::print_scale_stats(std::ostream & out) { + print_rows_scale_stats(out); + print_columns_scale_stats(out); +} + +template void lp_solver::get_max_abs_in_row(std::unordered_map & row_map) { + T ret = numeric_traits::zero(); + for (auto jp : row_map) { + T ac = numeric_traits::abs(jp->second); + if (ac > ret) { + ret = ac; + } + } + return ret; +} + +template void lp_solver::pin_vars_on_row_with_sign(std::unordered_map & row, T sign ) { + for (auto t : row) { + unsigned j = t.first; + column_info * ci = m_map_from_var_index_to_column_info[j]; + T a = t.second; + if (a * sign > numeric_traits::zero()) { + lean_assert(ci->upper_bound_is_set()); + ci->set_fixed_value(ci->get_upper_bound()); + } else { + lean_assert(ci->low_bound_is_set()); + ci->set_fixed_value(ci->get_low_bound()); + } + } +} + +template bool lp_solver::get_minimal_row_value(std::unordered_map & row, T & low_bound) { + low_bound = numeric_traits::zero(); + for (auto & t : row) { + T a = t.second; + column_info * ci = m_map_from_var_index_to_column_info[t.first]; + if (a > numeric_traits::zero()) { + if (ci->low_bound_is_set()) { + low_bound += ci->get_low_bound() * a; + } else { + return false; + } + } else { + if (ci->upper_bound_is_set()) { + low_bound += ci->get_upper_bound() * a; + } else { + return false; + } + } + } + return true; +} + +template bool lp_solver::get_maximal_row_value(std::unordered_map & row, T & low_bound) { + low_bound = numeric_traits::zero(); + for (auto & t : row) { + T a = t.second; + column_info * ci = m_map_from_var_index_to_column_info[t.first]; + if (a < numeric_traits::zero()) { + if (ci->low_bound_is_set()) { + low_bound += ci->get_low_bound() * a; + } else { + return false; + } + } else { + if (ci->upper_bound_is_set()) { + low_bound += ci->get_upper_bound() * a; + } else { + return false; + } + } + } + return true; +} + +template bool lp_solver::row_is_zero(std::unordered_map & row) { + for (auto & t : row) { + if (!is_zero(t.second)) + return false; + } + return true; +} + +template bool lp_solver::row_e_is_obsolete(std::unordered_map & row, unsigned row_index) { + T rs = m_constraints[row_index].m_rs; + if (row_is_zero(row)) { + if (!is_zero(rs)) + m_status = INFEASIBLE; + return true; + } + + T low_bound; + bool lb = get_minimal_row_value(row, low_bound); + if (lb) { + T diff = low_bound - rs; + if (!val_is_smaller_than_eps(diff, m_settings.refactor_tolerance)){ + // low_bound > rs + m_settings.refactor_epsilon + m_status = INFEASIBLE; + return true; + } + if (val_is_smaller_than_eps(-diff, m_settings.refactor_tolerance)){ + pin_vars_down_on_row(row); + return true; + } + } + + T upper_bound; + bool ub = get_maximal_row_value(row, upper_bound); + if (ub) { + T diff = rs - upper_bound; + if (!val_is_smaller_than_eps(diff, m_settings.refactor_tolerance)) { + // upper_bound < rs - m_settings.refactor_tolerance + m_status = INFEASIBLE; + return true; + } + if (val_is_smaller_than_eps(-diff, m_settings.refactor_tolerance)){ + pin_vars_up_on_row(row); + return true; + } + } + + return false; +} + +template bool lp_solver::row_ge_is_obsolete(std::unordered_map & row, unsigned row_index) { + T rs = m_constraints[row_index].m_rs; + if (row_is_zero(row)) { + if (rs > zero_of_type()) + m_status = INFEASIBLE; + return true; + } + + T upper_bound; + if (get_maximal_row_value(row, upper_bound)) { + T diff = rs - upper_bound; + if (!val_is_smaller_than_eps(diff, m_settings.refactor_tolerance)) { + // upper_bound < rs - m_settings.refactor_tolerance + m_status = INFEASIBLE; + return true; + } + if (val_is_smaller_than_eps(-diff, m_settings.refactor_tolerance)){ + pin_vars_up_on_row(row); + return true; + } + } + + return false; +} + +template bool lp_solver::row_le_is_obsolete(std::unordered_map & row, unsigned row_index) { + T low_bound; + T rs = m_constraints[row_index].m_rs; + if (row_is_zero(row)) { + if (rs < zero_of_type()) + m_status = INFEASIBLE; + return true; + } + + if (get_minimal_row_value(row, low_bound)) { + T diff = low_bound - rs; + if (!val_is_smaller_than_eps(diff, m_settings.refactor_tolerance)){ + // low_bound > rs + m_settings.refactor_tolerance + m_status = lp_status::INFEASIBLE; + return true; + } + if (val_is_smaller_than_eps(-diff, m_settings.refactor_tolerance)){ + pin_vars_down_on_row(row); + return true; + } + } + + return false; +} + +// analyse possible max and min values that are derived from var boundaries +// Let us say that the we have a "ge" constraint, and the min value is equal to the rs. +// Then we know what values of the variables are. For each positive coeff of the row it has to be +// the low boundary of the var and for a negative - the upper. + +// this routing also pins the variables to the boundaries +template bool lp_solver::row_is_obsolete(std::unordered_map & row, unsigned row_index ) { + auto & constraint = m_constraints[row_index]; + switch (constraint.m_relation) { + case lp_relation::Equal: + return row_e_is_obsolete(row, row_index); + + case lp_relation::Greater_or_equal: + return row_ge_is_obsolete(row, row_index); + + case lp_relation::Less_or_equal: + return row_le_is_obsolete(row, row_index); + } + lean_unreachable(); + return false; // it is unreachable +} + +template void lp_solver::remove_fixed_or_zero_columns() { + for (auto & i_row : m_A_values) { + remove_fixed_or_zero_columns_from_row(i_row.first, i_row.second); + } +} + +template void lp_solver::remove_fixed_or_zero_columns_from_row(unsigned i, std::unordered_map & row) { + auto & constraint = m_constraints[i]; + vector removed; + for (auto & col : row) { + unsigned j = col.first; + lean_assert(m_map_from_var_index_to_column_info.find(j) != m_map_from_var_index_to_column_info.end()); + column_info * ci = m_map_from_var_index_to_column_info[j]; + if (ci->is_fixed()) { + removed.push_back(j); + T aj = col.second; + constraint.m_rs -= aj * ci->get_fixed_value(); + } else { + if (numeric_traits::is_zero(col.second)){ + removed.push_back(j); + } + } + } + + for (auto j : removed) { + row.erase(j); + } +} + +template unsigned lp_solver::try_to_remove_some_rows() { + vector rows_to_delete; + for (auto & t : m_A_values) { + if (row_is_obsolete(t.second, t.first)) { + rows_to_delete.push_back(t.first); + } + + if (m_status == lp_status::INFEASIBLE) { + return 0; + } + } + if (rows_to_delete.size() > 0) { + for (unsigned k : rows_to_delete) { + m_A_values.erase(k); + } + } + remove_fixed_or_zero_columns(); + return static_cast(rows_to_delete.size()); +} + +template void lp_solver::cleanup() { + int n = 0; // number of deleted rows + int d; + while ((d = try_to_remove_some_rows() > 0)) + n += d; + + if (n == 1) { + LP_OUT(m_settings, "deleted one row" << std::endl); + } else if (n) { + LP_OUT(m_settings, "deleted " << n << " rows" << std::endl); + } +} + +template void lp_solver::map_external_rows_to_core_solver_rows() { + unsigned size = 0; + for (auto & row : m_A_values) { + m_external_rows_to_core_solver_rows[row.first] = size; + m_core_solver_rows_to_external_rows[size] = row.first; + size++; + } +} + +template void lp_solver::map_external_columns_to_core_solver_columns() { + unsigned size = 0; + for (auto & row : m_A_values) { + for (auto & col : row.second) { + if (col.second == numeric_traits::zero() || m_map_from_var_index_to_column_info[col.first]->is_fixed()) { + throw_exception("found fixed column"); + } + unsigned j = col.first; + auto column_info_it = m_map_from_var_index_to_column_info.find(j); + lean_assert(column_info_it != m_map_from_var_index_to_column_info.end()); + + auto j_column = column_info_it->second->get_column_index(); + if (!is_valid(j_column)) { // j is a newcomer + m_map_from_var_index_to_column_info[j]->set_column_index(size); + m_core_solver_columns_to_external_columns[size++] = j; + } + } + } +} + +template void lp_solver::unscale() { + delete m_A; + m_A = nullptr; + fill_A_from_A_values(); + restore_column_scales_to_one(); + fill_m_b(); +} + +template void lp_solver::fill_A_from_A_values() { + m_A = new static_matrix(static_cast(m_A_values.size()), number_of_core_structurals()); + for (auto & t : m_A_values) { + auto row_it = m_external_rows_to_core_solver_rows.find(t.first); + lean_assert(row_it != m_external_rows_to_core_solver_rows.end()); + unsigned row = row_it->second; + for (auto k : t.second) { + auto column_info_it = m_map_from_var_index_to_column_info.find(k.first); + lean_assert(column_info_it != m_map_from_var_index_to_column_info.end()); + column_info *ci = column_info_it->second; + unsigned col = ci->get_column_index(); + lean_assert(is_valid(col)); + bool col_is_flipped = m_map_from_var_index_to_column_info[k.first]->is_flipped(); + if (!col_is_flipped) { + (*m_A)(row, col) = k.second; + } else { + (*m_A)(row, col) = - k.second; + } + } + } +} + +template void lp_solver::fill_matrix_A_and_init_right_side() { + map_external_rows_to_core_solver_rows(); + map_external_columns_to_core_solver_columns(); + lean_assert(m_A == nullptr); + fill_A_from_A_values(); + m_b.resize(m_A->row_count()); +} + +template void lp_solver::count_slacks_and_artificials() { + for (int i = row_count() - 1; i >= 0; i--) { + count_slacks_and_artificials_for_row(i); + } +} + +template void lp_solver::count_slacks_and_artificials_for_row(unsigned i) { + lean_assert(this->m_constraints.find(this->m_core_solver_rows_to_external_rows[i]) != this->m_constraints.end()); + auto & constraint = this->m_constraints[this->m_core_solver_rows_to_external_rows[i]]; + switch (constraint.m_relation) { + case Equal: + m_artificials++; + break; + case Greater_or_equal: + m_slacks++; + if (this->m_b[i] > 0) { + m_artificials++; + } + break; + case Less_or_equal: + m_slacks++; + if (this->m_b[i] < 0) { + m_artificials++; + } + break; + } +} + +template T lp_solver::low_bound_shift_for_row(unsigned i) { + T ret = numeric_traits::zero(); + + auto row = this->m_A_values.find(i); + if (row == this->m_A_values.end()) { + throw_exception("cannot find row"); + } + for (auto col : row->second) { + ret += col.second * this->m_map_from_var_index_to_column_info[col.first]->get_shift(); + } + return ret; +} + +template void lp_solver::fill_m_b() { + for (int i = this->row_count() - 1; i >= 0; i--) { + lean_assert(this->m_constraints.find(this->m_core_solver_rows_to_external_rows[i]) != this->m_constraints.end()); + unsigned external_i = this->m_core_solver_rows_to_external_rows[i]; + auto & constraint = this->m_constraints[external_i]; + this->m_b[i] = constraint.m_rs - low_bound_shift_for_row(external_i); + } +} + +template T lp_solver::get_column_value_with_core_solver(unsigned column, lp_core_solver_base * core_solver) const { + auto cit = this->m_map_from_var_index_to_column_info.find(column); + if (cit == this->m_map_from_var_index_to_column_info.end()) { + return numeric_traits::zero(); + } + + column_info * ci = cit->second; + + if (ci->is_fixed()) { + return ci->get_fixed_value(); + } + + unsigned cj = ci->get_column_index(); + if (cj != static_cast(-1)) { + T v = core_solver->get_var_value(cj) * this->m_column_scale[cj]; + if (ci->is_free()) { + return v; + } + if (!ci->is_flipped()) { + return v + ci->get_low_bound(); + } + + // the flipped case when there is only upper bound + return -v + ci->get_upper_bound(); // + } + + return numeric_traits::zero(); // returns zero for out of boundary columns +} + +template void lp_solver::set_scaled_cost(unsigned j) { + // grab original costs but modify it with the column scales + lean_assert(j < this->m_column_scale.size()); + column_info * ci = this->m_map_from_var_index_to_column_info[this->m_core_solver_columns_to_external_columns[j]]; + T cost = ci->get_cost(); + if (ci->is_flipped()){ + cost *= -1; + } + lean_assert(ci->is_fixed() == false); + this->m_costs[j] = cost * this->m_column_scale[j]; +} +} diff --git a/src/util/lp/lp_solver_instances.cpp b/src/util/lp/lp_solver_instances.cpp new file mode 100644 index 000000000..5df490cae --- /dev/null +++ b/src/util/lp/lp_solver_instances.cpp @@ -0,0 +1,40 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include +#include "util/lp/lp_solver.hpp" +template void lean::lp_solver::add_constraint(lean::lp_relation, double, unsigned int); +template void lean::lp_solver::cleanup(); +template void lean::lp_solver::count_slacks_and_artificials(); +template void lean::lp_solver::fill_m_b(); +template void lean::lp_solver::fill_matrix_A_and_init_right_side(); +template void lean::lp_solver::flip_costs(); +template double lean::lp_solver::get_column_cost_value(unsigned int, lean::column_info*) const; +template int lean::lp_solver::get_column_index_by_name(std::string) const; +template double lean::lp_solver::get_column_value_with_core_solver(unsigned int, lean::lp_core_solver_base*) const; +template lean::column_info* lean::lp_solver::get_or_create_column_info(unsigned int); +template void lean::lp_solver::give_symbolic_name_to_column(std::string, unsigned int); +template void lean::lp_solver::print_statistics_on_A(std::ostream & out); +template bool lean::lp_solver::problem_is_empty(); +template void lean::lp_solver::scale(); +template void lean::lp_solver::set_scaled_cost(unsigned int); +template lean::lp_solver::~lp_solver(); +template void lean::lp_solver::add_constraint(lean::lp_relation, lean::mpq, unsigned int); +template void lean::lp_solver::cleanup(); +template void lean::lp_solver::count_slacks_and_artificials(); +template void lean::lp_solver::fill_m_b(); +template void lean::lp_solver::fill_matrix_A_and_init_right_side(); +template void lean::lp_solver::flip_costs(); +template lean::mpq lean::lp_solver::get_column_cost_value(unsigned int, lean::column_info*) const; +template int lean::lp_solver::get_column_index_by_name(std::string) const; +template lean::mpq lean::lp_solver::get_column_value_by_name(std::string) const; +template lean::mpq lean::lp_solver::get_column_value_with_core_solver(unsigned int, lean::lp_core_solver_base*) const; +template lean::column_info* lean::lp_solver::get_or_create_column_info(unsigned int); +template void lean::lp_solver::give_symbolic_name_to_column(std::string, unsigned int); +template void lean::lp_solver::print_statistics_on_A(std::ostream & out); +template bool lean::lp_solver::problem_is_empty(); +template void lean::lp_solver::scale(); +template void lean::lp_solver::set_scaled_cost(unsigned int); +template lean::lp_solver::~lp_solver(); +template double lean::lp_solver::get_column_value_by_name(std::string) const; diff --git a/src/util/lp/lp_utils.cpp b/src/util/lp/lp_utils.cpp new file mode 100644 index 000000000..8cb98974e --- /dev/null +++ b/src/util/lp/lp_utils.cpp @@ -0,0 +1,11 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include "util/lp/lp_utils.h" +#ifdef lp_for_z3 +namespace lean { +double numeric_traits::g_zero = 0.0; +double numeric_traits::g_one = 1.0; +} +#endif diff --git a/src/util/lp/lp_utils.h b/src/util/lp/lp_utils.h new file mode 100644 index 000000000..2be15d79a --- /dev/null +++ b/src/util/lp/lp_utils.h @@ -0,0 +1,141 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson + This file should be present in z3 and in Lean. +*/ +#pragma once +#include +#include "util/lp/numeric_pair.h" +#include "util/debug.h" +#include +template +bool try_get_val(const std::unordered_map & map, const A& key, B & val) { + const auto it = map.find(key); + if (it == map.end()) return false; + val = it->second; + return true; +} + +template +bool contains(const std::unordered_map & map, const A& key) { + return map.find(key) != map.end(); +} + +#ifdef lp_for_z3 + +#ifdef Z3DEBUG +#define LEAN_DEBUG 1 +#endif + +namespace lean { + inline void throw_exception(const std::string & str) { + throw default_exception(str); + } + typedef z3_exception exception; + +#define lean_assert(_x_) { SASSERT(_x_); } + inline void lean_unreachable() { lean_assert(false); } + template inline X zero_of_type() { return numeric_traits::zero(); } + template inline X one_of_type() { return numeric_traits::one(); } + template inline bool is_zero(const X & v) { return numeric_traits::is_zero(v); } + template inline bool is_pos(const X & v) { return numeric_traits::is_pos(v); } + template inline bool is_neg(const X & v) { return numeric_traits::is_neg(v); } + + template inline bool precise() { return numeric_traits::precise(); } +} +namespace std { +template<> +struct hash { + inline size_t operator()(const rational & v) const { + return v.hash(); + } +}; +} + +template +inline void hash_combine(std::size_t & seed, const T & v) { + seed ^= std::hash()(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); +} + +namespace std { +template struct hash> { + inline size_t operator()(const pair & v) const { + size_t seed = 0; + hash_combine(seed, v.first); + hash_combine(seed, v.second); + return seed; + } +}; + +template<> +struct hash> { + inline size_t operator()(const lean::numeric_pair & v) const { + size_t seed = 0; + hash_combine(seed, v.x); + hash_combine(seed, v.y); + return seed; + } +}; + +} +#else // else of #if lp_for_z3 +#include +#include +//include "util/numerics/mpq.h" +//include "util/numerics/numeric_traits.h" +//include "util/numerics/double.h" + +#ifdef __CLANG__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wmismatched-tags" +#endif +namespace std { +template<> +struct hash { + inline size_t operator()(const lean::mpq & v) const { + return v.hash(); + } +}; +} +namespace lean { +template inline bool precise() { return numeric_traits::precise();} +template inline X one_of_type() { return numeric_traits::one(); } +template inline bool is_zero(const X & v) { return numeric_traits::is_zero(v); } +template inline double get_double(const X & v) { return numeric_traits::get_double(v); } +template inline T zero_of_type() {return numeric_traits::zero();} +inline void throw_exception(std::string str) { throw exception(str); } +template inline T from_string(std::string const & ) { lean_unreachable();} +template <> double inline from_string(std::string const & str) { return atof(str.c_str());} +template <> mpq inline from_string(std::string const & str) { + return mpq(atof(str.c_str())); +} + +} // closing lean +template +inline void hash_combine(std::size_t & seed, const T & v) { + seed ^= std::hash()(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); +} + +namespace std { +template struct hash> { + inline size_t operator()(const pair & v) const { + size_t seed = 0; + hash_combine(seed, v.first); + hash_combine(seed, v.second); + return seed; + } +}; +template<> +struct hash> { + inline size_t operator()(const lean::numeric_pair & v) const { + size_t seed = 0; + hash_combine(seed, v.x); + hash_combine(seed, v.y); + return seed; + } +}; +} // std +#ifdef __CLANG__ +#pragma clang diagnostic pop +#endif +#endif diff --git a/src/util/lp/lu.h b/src/util/lp/lu.h new file mode 100644 index 000000000..415b7f978 --- /dev/null +++ b/src/util/lp/lu.h @@ -0,0 +1,359 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ + +#pragma once + +#include "util/vector.h" +#include "util/debug.h" +#include +#include +#include "util/lp/sparse_matrix.h" +#include "util/lp/static_matrix.h" +#include +#include "util/lp/numeric_pair.h" +#include +#include +#include "util/lp/row_eta_matrix.h" +#include "util/lp/square_dense_submatrix.h" +#include "util/lp/dense_matrix.h" +namespace lean { +#ifdef LEAN_DEBUG +template // print the nr x nc submatrix at the top left corner +void print_submatrix(sparse_matrix & m, unsigned mr, unsigned nc); + +template +void print_matrix(static_matrix &m, std::ostream & out); + +template +void print_matrix(sparse_matrix& m, std::ostream & out); +#endif + +template +X dot_product(const vector & a, const vector & b) { + lean_assert(a.size() == b.size()); + auto r = zero_of_type(); + for (unsigned i = 0; i < a.size(); i++) { + r += a[i] * b[i]; + } + return r; +} + + +template +class one_elem_on_diag: public tail_matrix { + unsigned m_i; + T m_val; +public: + one_elem_on_diag(unsigned i, T val) : m_i(i), m_val(val) { +#ifdef LEAN_DEBUG + m_one_over_val = numeric_traits::one() / m_val; +#endif + } + + bool is_dense() const { return false; } + + one_elem_on_diag(const one_elem_on_diag & o); + +#ifdef LEAN_DEBUG + unsigned m_m; + unsigned m_n; + virtual void set_number_of_rows(unsigned m) { m_m = m; m_n = m; } + virtual void set_number_of_columns(unsigned n) { m_m = n; m_n = n; } + T m_one_over_val; + + T get_elem (unsigned i, unsigned j) const; + + unsigned row_count() const { return m_m; } // not defined } + unsigned column_count() const { return m_m; } // not defined } +#endif + void apply_from_left(vector & w, lp_settings &) { + w[m_i] /= m_val; + } + + void apply_from_right(vector & w) { + w[m_i] /= m_val; + } + + void apply_from_right(indexed_vector & w) { + if (is_zero(w.m_data[m_i])) + return; + auto & v = w.m_data[m_i] /= m_val; + if (lp_settings::is_eps_small_general(v, 1e-14)) { + w.erase_from_index(m_i); + v = zero_of_type(); + } + } + + + void apply_from_left_to_T(indexed_vector & w, lp_settings & settings); + + void conjugate_by_permutation(permutation_matrix & p) { + // this = p * this * p(-1) +#ifdef LEAN_DEBUG + // auto rev = p.get_reverse(); + // auto deb = ((*this) * rev); + // deb = p * deb; +#endif + m_i = p.apply_reverse(m_i); + +#ifdef LEAN_DEBUG + // lean_assert(*this == deb); +#endif + } +}; // end of one_elem_on_diag + +enum class LU_status { OK, Degenerated}; + +// This class supports updates of the columns of B, and solves systems Bx=b,and yB=c +// Using Suhl-Suhl method described in the dissertation of Achim Koberstein, Chapter 5 +template +class lu { + LU_status m_status = LU_status::OK; +public: + // the fields + unsigned m_dim; + static_matrix const &m_A; + permutation_matrix m_Q; + permutation_matrix m_R; + permutation_matrix m_r_wave; + sparse_matrix m_U; + square_dense_submatrix* m_dense_LU; + + vector *> m_tail; + lp_settings & m_settings; + bool m_failure = false; + indexed_vector m_row_eta_work_vector; + indexed_vector m_w_for_extension; + indexed_vector m_y_copy; + indexed_vector m_ii; //to optimize the work with the m_index fields + unsigned m_refactor_counter = 0; + // constructor + // if A is an m by n matrix then basis has length m and values in [0,n); the values are all different + // they represent the set of m columns + lu(static_matrix const & A, + vector& basis, + lp_settings & settings); + void debug_test_of_basis(static_matrix const & A, vector & basis); + void solve_Bd_when_w_is_ready(vector & d, indexed_vector& w ); + void solve_By(indexed_vector & y); + + void solve_By(vector & y); + + void solve_By_for_T_indexed_only(indexed_vector& y, const lp_settings &); + + template + void solve_By_when_y_is_ready(indexed_vector & y); + void solve_By_when_y_is_ready_for_X(vector & y); + void solve_By_when_y_is_ready_for_T(vector & y, vector & index); + void print_indexed_vector(indexed_vector & w, std::ofstream & f); + + void print_matrix_compact(std::ostream & f); + + void print(indexed_vector & w, const vector& basis); + void solve_Bd(unsigned a_column, vector & d, indexed_vector & w); + void solve_Bd(unsigned a_column, indexed_vector & d, indexed_vector & w); + void solve_Bd_faster(unsigned a_column, indexed_vector & d); // d is the right side on the input and the solution at the exit + + void solve_yB(vector& y); + + void solve_yB_indexed(indexed_vector& y); + + void add_delta_to_solution_indexed(indexed_vector& y); + + void add_delta_to_solution(const vector& yc, vector& y); + + + void find_error_of_yB(vector& yc, const vector& y, + const vector& basis); + + void find_error_of_yB_indexed(const indexed_vector& y, + const vector& heading, const lp_settings& settings); + + + void solve_yB_with_error_check(vector & y, const vector& basis); + + void solve_yB_with_error_check_indexed(indexed_vector & y, const vector& heading, const vector & basis, const lp_settings &); + + void apply_Q_R_to_U(permutation_matrix & r_wave); + + + LU_status get_status() { return m_status; } + + void set_status(LU_status status) { + m_status = status; + } + + ~lu(); + + void init_vector_y(vector & y); + + void perform_transformations_on_w(indexed_vector& w); + + void init_vector_w(unsigned entering, indexed_vector & w); + void apply_lp_list_to_w(indexed_vector & w); + void apply_lp_list_to_y(vector& y); + + void swap_rows(int j, int k); + + void swap_columns(int j, int pivot_column); + + void push_matrix_to_tail(tail_matrix* tm) { + m_tail.push_back(tm); + } + + bool pivot_the_row(int row); + + eta_matrix * get_eta_matrix_for_pivot(unsigned j); + // we're processing the column j now + eta_matrix * get_eta_matrix_for_pivot(unsigned j, sparse_matrix& copy_of_U); + + // see page 407 of Chvatal + unsigned transform_U_to_V_by_replacing_column(indexed_vector & w, unsigned leaving_column_of_U); + +#ifdef LEAN_DEBUG + void check_vector_w(unsigned entering); + + void check_apply_matrix_to_vector(matrix *lp, T *w); + + void check_apply_lp_lists_to_w(T * w); + + // provide some access operators for testing + permutation_matrix & Q() { return m_Q; } + permutation_matrix & R() { return m_R; } + matrix & U() { return m_U; } + unsigned tail_size() { return m_tail.size(); } + + tail_matrix * get_lp_matrix(unsigned i) { + return m_tail[i]; + } + + T B_(unsigned i, unsigned j, const vector& basis) { + return m_A.get_elem(i, basis[j]); + } + + unsigned dimension() { return m_dim; } + +#endif + + + unsigned get_number_of_nonzeroes() { + return m_U.get_number_of_nonzeroes(); + } + + + void process_column(int j); + + bool is_correct(const vector& basis); + + +#ifdef LEAN_DEBUG + dense_matrix tail_product(); + dense_matrix get_left_side(const vector& basis); + + dense_matrix get_right_side(); +#endif + + // needed for debugging purposes + void copy_w(T *buffer, indexed_vector & w); + + // needed for debugging purposes + void restore_w(T *buffer, indexed_vector & w); + bool all_columns_and_rows_are_active(); + + bool too_dense(unsigned j) const; + + void pivot_in_dense_mode(unsigned i); + + void create_initial_factorization(); + + void calculate_r_wave_and_update_U(unsigned bump_start, unsigned bump_end, permutation_matrix & r_wave); + + void scan_last_row_to_work_vector(unsigned lowest_row_of_the_bump); + + bool diagonal_element_is_off(T /* diag_element */) { return false; } + + void pivot_and_solve_the_system(unsigned replaced_column, unsigned lowest_row_of_the_bump); + // see Achim Koberstein's thesis page 58, but here we solve the system and pivot to the last + // row at the same time + row_eta_matrix *get_row_eta_matrix_and_set_row_vector(unsigned replaced_column, unsigned lowest_row_of_the_bump, const T & pivot_elem_for_checking); + + void replace_column(T pivot_elem, indexed_vector & w, unsigned leaving_column_of_U); + + void calculate_Lwave_Pwave_for_bump(unsigned replaced_column, unsigned lowest_row_of_the_bump); + + void calculate_Lwave_Pwave_for_last_row(unsigned lowest_row_of_the_bump, T diagonal_element); + + void prepare_entering(unsigned entering, indexed_vector & w) { + init_vector_w(entering, w); + } + bool need_to_refactor() { return m_refactor_counter >= 200; } + + void adjust_dimension_with_matrix_A() { + lean_assert(m_A.row_count() >= m_dim); + m_dim = m_A.row_count(); + m_U.resize(m_dim); + m_Q.resize(m_dim); + m_R.resize(m_dim); + m_row_eta_work_vector.resize(m_dim); + } + + + std::unordered_set get_set_of_columns_to_replace_for_add_last_rows(const vector & heading) const { + std::unordered_set columns_to_replace; + unsigned m = m_A.row_count(); + unsigned m_prev = m_U.dimension(); + + lean_assert(m_A.column_count() == heading.size()); + + for (unsigned i = m_prev; i < m; i++) { + for (const row_cell & c : m_A.m_rows[i]) { + int h = heading[c.m_j]; + if (h < 0) { + continue; + } + columns_to_replace.insert(c.m_j); + } + } + return columns_to_replace; + } + + void add_last_rows_to_B(const vector & heading, const std::unordered_set & columns_to_replace) { + unsigned m = m_A.row_count(); + lean_assert(m_A.column_count() == heading.size()); + adjust_dimension_with_matrix_A(); + m_w_for_extension.resize(m); + // At this moment the LU is correct + // for B extended by only by ones at the diagonal in the lower right corner + + for (unsigned j :columns_to_replace) { + lean_assert(heading[j] >= 0); + replace_column_with_only_change_at_last_rows(j, heading[j]); + if (get_status() == LU_status::Degenerated) + break; + } + } + // column j is a basis column, and there is a change in the last rows + void replace_column_with_only_change_at_last_rows(unsigned j, unsigned column_to_change_in_U) { + init_vector_w(j, m_w_for_extension); + replace_column(zero_of_type(), m_w_for_extension, column_to_change_in_U); + } + + bool has_dense_submatrix() const { + for (auto m : m_tail) + if (m->is_dense()) + return true; + return false; + } + +}; // end of lu + +template +void init_factorization(lu* & factorization, static_matrix & m_A, vector & m_basis, lp_settings &m_settings); + +#ifdef LEAN_DEBUG +template +dense_matrix get_B(lu& f, const vector& basis); +#endif +} diff --git a/src/util/lp/lu.hpp b/src/util/lp/lu.hpp new file mode 100644 index 000000000..ba5e092d3 --- /dev/null +++ b/src/util/lp/lu.hpp @@ -0,0 +1,940 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include +#include +#include +#include "util/vector.h" +#include +#include "util/debug.h" +#include "util/lp/lu.h" +namespace lean { +#ifdef LEAN_DEBUG +template // print the nr x nc submatrix at the top left corner +void print_submatrix(sparse_matrix & m, unsigned mr, unsigned nc, std::ostream & out) { + vector> A; + vector widths; + for (unsigned i = 0; i < m.row_count() && i < mr ; i++) { + A.push_back(vector()); + for (unsigned j = 0; j < m.column_count() && j < nc; j++) { + A[i].push_back(T_to_string(static_cast(m(i, j)))); + } + } + + for (unsigned j = 0; j < m.column_count() && j < nc; j++) { + widths.push_back(get_width_of_column(j, A)); + } + + print_matrix_with_widths(A, widths, out); +} + +template +void print_matrix(static_matrix &m, std::ostream & out) { + vector> A; + vector widths; + std::set> domain = m.get_domain(); + for (unsigned i = 0; i < m.row_count(); i++) { + A.push_back(vector()); + for (unsigned j = 0; j < m.column_count(); j++) { + A[i].push_back(T_to_string(static_cast(m(i, j)))); + } + } + + for (unsigned j = 0; j < m.column_count(); j++) { + widths.push_back(get_width_of_column(j, A)); + } + + print_matrix_with_widths(A, widths, out); +} + +template +void print_matrix(sparse_matrix& m, std::ostream & out) { + vector> A; + vector widths; + for (unsigned i = 0; i < m.row_count(); i++) { + A.push_back(vector()); + for (unsigned j = 0; j < m.column_count(); j++) { + A[i].push_back(T_to_string(static_cast(m(i, j)))); + } + } + + for (unsigned j = 0; j < m.column_count(); j++) { + widths.push_back(get_width_of_column(j, A)); + } + + print_matrix_with_widths(A, widths, out); +} +#endif + + +template +one_elem_on_diag::one_elem_on_diag(const one_elem_on_diag & o) { + m_i = o.m_i; + m_val = o.m_val; +#ifdef LEAN_DEBUG + m_m = m_n = o.m_m; + m_one_over_val = numeric_traits::one() / o.m_val; +#endif +} + +#ifdef LEAN_DEBUG +template +T one_elem_on_diag::get_elem(unsigned i, unsigned j) const { + if (i == j){ + if (j == m_i) { + return m_one_over_val; + } + return numeric_traits::one(); + } + + return numeric_traits::zero(); +} +#endif +template +void one_elem_on_diag::apply_from_left_to_T(indexed_vector & w, lp_settings & settings) { + T & t = w[m_i]; + if (numeric_traits::is_zero(t)) { + return; + } + t /= m_val; + if (numeric_traits::precise()) return; + if (settings.abs_val_is_smaller_than_drop_tolerance(t)) { + w.erase_from_index(m_i); + t = numeric_traits::zero(); + } +} + +// This class supports updates of the columns of B, and solves systems Bx=b,and yB=c +// Using Suhl-Suhl method described in the dissertation of Achim Koberstein, Chapter 5 +template +lu::lu(static_matrix const & A, + vector& basis, + lp_settings & settings): + m_dim(A.row_count()), + m_A(A), + m_Q(m_dim), + m_R(m_dim), + m_r_wave(m_dim), + m_U(A, basis), // create the square matrix that eventually will be factorized + m_settings(settings), + m_row_eta_work_vector(A.row_count()){ + lean_assert(!(numeric_traits::precise() && settings.use_tableau())); +#ifdef LEAN_DEBUG + debug_test_of_basis(A, basis); +#endif + ++m_settings.st().m_num_factorizations; + create_initial_factorization(); +#ifdef LEAN_DEBUG + // lean_assert(check_correctness()); +#endif +} +template +void lu::debug_test_of_basis(static_matrix const & A, vector & basis) { + std::set set; + for (unsigned i = 0; i < A.row_count(); i++) { + lean_assert(basis[i]< A.column_count()); + set.insert(basis[i]); + } + lean_assert(set.size() == A.row_count()); +} + + template + void lu::solve_By(indexed_vector & y) { + lean_assert(false); // not implemented + // init_vector_y(y); + // solve_By_when_y_is_ready(y); + } + + +template +void lu::solve_By(vector & y) { + init_vector_y(y); + solve_By_when_y_is_ready_for_X(y); +} + +template +void lu::solve_By_when_y_is_ready_for_X(vector & y) { + if (numeric_traits::precise()) { + m_U.solve_U_y(y); + m_R.apply_reverse_from_left_to_X(y); // see 24.3 from Chvatal + return; + } + m_U.double_solve_U_y(y); + m_R.apply_reverse_from_left_to_X(y); // see 24.3 from Chvatal + unsigned i = m_dim; + while (i--) { + if (is_zero(y[i])) continue; + if (m_settings.abs_val_is_smaller_than_drop_tolerance(y[i])){ + y[i] = zero_of_type(); + } + } +} + +template +void lu::solve_By_when_y_is_ready_for_T(vector & y, vector & index) { + if (numeric_traits::precise()) { + m_U.solve_U_y(y); + m_R.apply_reverse_from_left_to_T(y); // see 24.3 from Chvatal + unsigned j = m_dim; + while (j--) { + if (!is_zero(y[j])) + index.push_back(j); + } + return; + } + m_U.double_solve_U_y(y); + m_R.apply_reverse_from_left_to_T(y); // see 24.3 from Chvatal + unsigned i = m_dim; + while (i--) { + if (is_zero(y[i])) continue; + if (m_settings.abs_val_is_smaller_than_drop_tolerance(y[i])){ + y[i] = zero_of_type(); + } else { + index.push_back(i); + } + } +} + +template +void lu::solve_By_for_T_indexed_only(indexed_vector & y, const lp_settings & settings) { + if (numeric_traits::precise()) { + vector active_rows; + m_U.solve_U_y_indexed_only(y, settings, active_rows); + m_R.apply_reverse_from_left(y); // see 24.3 from Chvatal + return; + } + m_U.double_solve_U_y(y, m_settings); + m_R.apply_reverse_from_left(y); // see 24.3 from Chvatal +} + +template +void lu::print_matrix_compact(std::ostream & f) { + f << "matrix_start" << std::endl; + f << "nrows " << m_A.row_count() << std::endl; + f << "ncolumns " << m_A.column_count() << std::endl; + for (unsigned i = 0; i < m_A.row_count(); i++) { + auto & row = m_A.m_rows[i]; + f << "row " << i << std::endl; + for (auto & t : row) { + f << "column " << t.m_j << " value " << t.m_value << std::endl; + } + f << "row_end" << std::endl; + } + f << "matrix_end" << std::endl; +} +template +void lu::print(indexed_vector & w, const vector& basis) { + std::string dump_file_name("/tmp/lu"); + remove(dump_file_name.c_str()); + std::ofstream f(dump_file_name); + if (!f.is_open()) { + LP_OUT(m_settings, "cannot open file " << dump_file_name << std::endl); + return; + } + LP_OUT(m_settings, "writing lu dump to " << dump_file_name << std::endl); + print_matrix_compact(f); + print_vector(basis, f); + print_indexed_vector(w, f); + f.close(); +} +template +void lu::solve_Bd(unsigned a_column, indexed_vector & d, indexed_vector & w) { + init_vector_w(a_column, w); + + if (w.m_index.size() * ratio_of_index_size_to_all_size() < d.m_data.size()) { // this const might need some tuning + d = w; + solve_By_for_T_indexed_only(d, m_settings); + } else { + d.m_data = w.m_data; + d.m_index.clear(); + solve_By_when_y_is_ready_for_T(d.m_data, d.m_index); + } +} + +template +void lu::solve_Bd_faster(unsigned a_column, indexed_vector & d) { // puts the a_column into d + init_vector_w(a_column, d); + solve_By_for_T_indexed_only(d, m_settings); +} + +template +void lu::solve_yB(vector& y) { + // first solve yU = cb*R(-1) + m_R.apply_reverse_from_right_to_T(y); // got y = cb*R(-1) + m_U.solve_y_U(y); // got y*U=cb*R(-1) + m_Q.apply_reverse_from_right_to_T(y); // + for (auto e = m_tail.rbegin(); e != m_tail.rend(); ++e) { +#ifdef LEAN_DEBUG + (*e)->set_number_of_columns(m_dim); +#endif + (*e)->apply_from_right(y); + } +} + +template +void lu::solve_yB_indexed(indexed_vector& y) { + lean_assert(y.is_OK()); + // first solve yU = cb*R(-1) + m_R.apply_reverse_from_right_to_T(y); // got y = cb*R(-1) + lean_assert(y.is_OK()); + m_U.solve_y_U_indexed(y, m_settings); // got y*U=cb*R(-1) + lean_assert(y.is_OK()); + m_Q.apply_reverse_from_right_to_T(y); + lean_assert(y.is_OK()); + for (auto e = m_tail.rbegin(); e != m_tail.rend(); ++e) { +#ifdef LEAN_DEBUG + (*e)->set_number_of_columns(m_dim); +#endif + (*e)->apply_from_right(y); + lean_assert(y.is_OK()); + } +} + +template +void lu::add_delta_to_solution(const vector& yc, vector& y){ + unsigned i = static_cast(y.size()); + while (i--) + y[i]+=yc[i]; +} + +template +void lu::add_delta_to_solution_indexed(indexed_vector& y) { + // the delta sits in m_y_copy, put result into y + lean_assert(y.is_OK()); + lean_assert(m_y_copy.is_OK()); + m_ii.clear(); + m_ii.resize(y.data_size()); + for (unsigned i : y.m_index) + m_ii.set_value(1, i); + for (unsigned i : m_y_copy.m_index) { + y.m_data[i] += m_y_copy[i]; + if (m_ii[i] == 0) + m_ii.set_value(1, i); + } + lean_assert(m_ii.is_OK()); + y.m_index.clear(); + + for (unsigned i : m_ii.m_index) { + T & v = y.m_data[i]; + if (!lp_settings::is_eps_small_general(v, 1e-14)) + y.m_index.push_back(i); + else if (!numeric_traits::is_zero(v)) + v = zero_of_type(); + } + + lean_assert(y.is_OK()); +} + +template +void lu::find_error_of_yB(vector& yc, const vector& y, const vector& m_basis) { + unsigned i = m_dim; + while (i--) { + yc[i] -= m_A.dot_product_with_column(y, m_basis[i]); + } +} + +template +void lu::find_error_of_yB_indexed(const indexed_vector& y, const vector& heading, const lp_settings& settings) { +#if 0 == 1 + // it is a non efficient version + indexed_vector yc = m_y_copy; + yc.m_index.clear(); + lean_assert(!numeric_traits::precise()); + { + + vector d_basis(y.m_data.size()); + for (unsigned j = 0; j < heading.size(); j++) { + if (heading[j] >= 0) { + d_basis[heading[j]] = j; + } + } + + + unsigned i = m_dim; + while (i--) { + T & v = yc.m_data[i] -= m_A.dot_product_with_column(y.m_data, d_basis[i]); + if (settings.abs_val_is_smaller_than_drop_tolerance(v)) + v = zero_of_type(); + else + yc.m_index.push_back(i); + } + } +#endif + lean_assert(m_ii.is_OK()); + m_ii.clear(); + m_ii.resize(y.data_size()); + lean_assert(m_y_copy.is_OK()); + // put the error into m_y_copy + for (auto k : y.m_index) { + auto & row = m_A.m_rows[k]; + const T & y_k = y.m_data[k]; + for (auto & c : row) { + unsigned j = c.m_j; + int hj = heading[j]; + if (hj < 0) continue; + if (m_ii.m_data[hj] == 0) + m_ii.set_value(1, hj); + m_y_copy.m_data[hj] -= c.get_val() * y_k; + } + } + // add the index of m_y_copy to m_ii + for (unsigned i : m_y_copy.m_index) { + if (m_ii.m_data[i] == 0) + m_ii.set_value(1, i); + } + + // there is no guarantee that m_y_copy is OK here, but its index + // is contained in m_ii index + m_y_copy.m_index.clear(); + // setup the index of m_y_copy + for (auto k : m_ii.m_index) { + T& v = m_y_copy.m_data[k]; + if (settings.abs_val_is_smaller_than_drop_tolerance(v)) + v = zero_of_type(); + else { + m_y_copy.set_value(v, k); + } + } + lean_assert(m_y_copy.is_OK()); + +} + + + + +// solves y*B = y +// y is the input +template +void lu::solve_yB_with_error_check_indexed(indexed_vector & y, const vector& heading, const vector & basis, const lp_settings & settings) { + if (numeric_traits::precise()) { + if (y.m_index.size() * ratio_of_index_size_to_all_size() * 3 < m_A.column_count()) { + solve_yB_indexed(y); + } else { + solve_yB(y.m_data); + y.restore_index_and_clean_from_data(); + } + return; + } + lean_assert(m_y_copy.is_OK()); + lean_assert(y.is_OK()); + if (y.m_index.size() * ratio_of_index_size_to_all_size() < m_A.column_count()) { + m_y_copy = y; + solve_yB_indexed(y); + lean_assert(y.is_OK()); + if (y.m_index.size() * ratio_of_index_size_to_all_size() >= m_A.column_count()) { + find_error_of_yB(m_y_copy.m_data, y.m_data, basis); + solve_yB(m_y_copy.m_data); + add_delta_to_solution(m_y_copy.m_data, y.m_data); + y.restore_index_and_clean_from_data(); + m_y_copy.clear_all(); + } else { + find_error_of_yB_indexed(y, heading, settings); // this works with m_y_copy + solve_yB_indexed(m_y_copy); + add_delta_to_solution_indexed(y); + } + lean_assert(m_y_copy.is_OK()); + } else { + solve_yB_with_error_check(y.m_data, basis); + y.restore_index_and_clean_from_data(); + } +} + + +// solves y*B = y +// y is the input +template +void lu::solve_yB_with_error_check(vector & y, const vector& basis) { + if (numeric_traits::precise()) { + solve_yB(y); + return; + } + auto & yc = m_y_copy.m_data; + yc =y; // copy y aside + solve_yB(y); + find_error_of_yB(yc, y, basis); + solve_yB(yc); + add_delta_to_solution(yc, y); + m_y_copy.clear_all(); +} +template +void lu::apply_Q_R_to_U(permutation_matrix & r_wave) { + m_U.multiply_from_right(r_wave); + m_U.multiply_from_left_with_reverse(r_wave); +} + + +// Solving yB = cb to find the entering variable, +// where cb is the cost vector projected to B. +// The result is stored in cb. + +// solving Bd = a ( to find the column d of B^{-1} A_N corresponding to the entering +// variable +template +lu::~lu(){ + for (auto t : m_tail) { + delete t; + } +} +template +void lu::init_vector_y(vector & y) { + apply_lp_list_to_y(y); + m_Q.apply_reverse_from_left_to_X(y); +} + +template +void lu::perform_transformations_on_w(indexed_vector& w) { + apply_lp_list_to_w(w); + m_Q.apply_reverse_from_left(w); + // TBD does not compile: lean_assert(numeric_traits::precise() || check_vector_for_small_values(w, m_settings)); +} + +// see Chvatal 24.3 +template +void lu::init_vector_w(unsigned entering, indexed_vector & w) { + w.clear(); + m_A.copy_column_to_indexed_vector(entering, w); // w = a, the column + perform_transformations_on_w(w); +} +template +void lu::apply_lp_list_to_w(indexed_vector & w) { + for (unsigned i = 0; i < m_tail.size(); i++) { + m_tail[i]->apply_from_left_to_T(w, m_settings); + // TBD does not compile: lean_assert(check_vector_for_small_values(w, m_settings)); + } +} +template +void lu::apply_lp_list_to_y(vector& y) { + for (unsigned i = 0; i < m_tail.size(); i++) { + m_tail[i]->apply_from_left(y, m_settings); + } +} +template +void lu::swap_rows(int j, int k) { + if (j != k) { + m_Q.transpose_from_left(j, k); + m_U.swap_rows(j, k); + } +} + +template +void lu::swap_columns(int j, int pivot_column) { + if (j == pivot_column) + return; + m_R.transpose_from_right(j, pivot_column); + m_U.swap_columns(j, pivot_column); +} +template +bool lu::pivot_the_row(int row) { + eta_matrix * eta_matrix = get_eta_matrix_for_pivot(row); + if (get_status() != LU_status::OK) { + return false; + } + + if (eta_matrix == nullptr) { + m_U.shorten_active_matrix(row, nullptr); + return true; + } + if (!m_U.pivot_with_eta(row, eta_matrix, m_settings)) + return false; + eta_matrix->conjugate_by_permutation(m_Q); + push_matrix_to_tail(eta_matrix); + return true; +} +// we're processing the column j now +template +eta_matrix * lu::get_eta_matrix_for_pivot(unsigned j) { + eta_matrix *ret; + if(!m_U.fill_eta_matrix(j, &ret)) { + set_status(LU_status::Degenerated); + } + return ret; +} +// we're processing the column j now +template +eta_matrix * lu::get_eta_matrix_for_pivot(unsigned j, sparse_matrix& copy_of_U) { + eta_matrix *ret; + copy_of_U.fill_eta_matrix(j, &ret); + return ret; +} + +// see page 407 of Chvatal +template +unsigned lu::transform_U_to_V_by_replacing_column(indexed_vector & w, + unsigned leaving_column) { + unsigned column_to_replace = m_R.apply_reverse(leaving_column); + m_U.replace_column(column_to_replace, w, m_settings); + return column_to_replace; +} + +#ifdef LEAN_DEBUG +template +void lu::check_vector_w(unsigned entering) { + T * w = new T[m_dim]; + m_A.copy_column_to_vector(entering, w); + check_apply_lp_lists_to_w(w); + delete [] w; +} +template +void lu::check_apply_matrix_to_vector(matrix *lp, T *w) { + if (lp != nullptr) { + lp -> set_number_of_rows(m_dim); + lp -> set_number_of_columns(m_dim); + apply_to_vector(*lp, w); + } +} + +template +void lu::check_apply_lp_lists_to_w(T * w) { + for (unsigned i = 0; i < m_tail.size(); i++) { + check_apply_matrix_to_vector(m_tail[i], w); + } + permutation_matrix qr = m_Q.get_reverse(); + apply_to_vector(qr, w); + for (int i = m_dim - 1; i >= 0; i--) { + lean_assert(abs(w[i] - w[i]) < 0.0000001); + } +} + +#endif +template +void lu::process_column(int j) { + unsigned pi, pj; + bool success = m_U.get_pivot_for_column(pi, pj, m_settings.c_partial_pivoting, j); + if (!success) { + LP_OUT(m_settings, "get_pivot returned false: cannot find the pivot for column " << j << std::endl); + m_failure = true; + return; + } + + if (static_cast(pi) == -1) { + LP_OUT(m_settings, "cannot find the pivot for column " << j << std::endl); + m_failure = true; + return; + } + swap_columns(j, pj); + swap_rows(j, pi); + if (!pivot_the_row(j)) { + // LP_OUT(m_settings, "pivot_the_row(" << j << ") failed" << std::endl); + m_failure = true; + } +} +template +bool lu::is_correct(const vector& basis) { +#ifdef LEAN_DEBUG + if (get_status() != LU_status::OK) { + return false; + } + dense_matrix left_side = get_left_side(basis); + dense_matrix right_side = get_right_side(); + return left_side == right_side; +#else + return true; +#endif +} + + +#ifdef LEAN_DEBUG +template +dense_matrix lu::tail_product() { + lean_assert(tail_size() > 0); + dense_matrix left_side = permutation_matrix(m_dim); + for (unsigned i = 0; i < tail_size(); i++) { + matrix* lp = get_lp_matrix(i); + lp->set_number_of_rows(m_dim); + lp->set_number_of_columns(m_dim); + left_side = ((*lp) * left_side); + } + return left_side; +} +template +dense_matrix lu::get_left_side(const vector& basis) { + dense_matrix left_side = get_B(*this, basis); + for (unsigned i = 0; i < tail_size(); i++) { + matrix* lp = get_lp_matrix(i); + lp->set_number_of_rows(m_dim); + lp->set_number_of_columns(m_dim); + left_side = ((*lp) * left_side); + } + return left_side; +} +template +dense_matrix lu::get_right_side() { + auto ret = U() * R(); + ret = Q() * ret; + return ret; +} +#endif + +// needed for debugging purposes +template +void lu::copy_w(T *buffer, indexed_vector & w) { + unsigned i = m_dim; + while (i--) { + buffer[i] = w[i]; + } +} + +// needed for debugging purposes +template +void lu::restore_w(T *buffer, indexed_vector & w) { + unsigned i = m_dim; + while (i--) { + w[i] = buffer[i]; + } +} +template +bool lu::all_columns_and_rows_are_active() { + unsigned i = m_dim; + while (i--) { + lean_assert(m_U.col_is_active(i)); + lean_assert(m_U.row_is_active(i)); + } + return true; +} +template +bool lu::too_dense(unsigned j) const { + unsigned r = m_dim - j; + if (r < 5) + return false; + // if (j * 5 < m_dim * 4) // start looking for dense only at the bottom of the rows + // return false; + // return r * r * m_settings.density_threshold <= m_U.get_number_of_nonzeroes_below_row(j); + return r * r * m_settings.density_threshold <= m_U.get_n_of_active_elems(); +} +template +void lu::pivot_in_dense_mode(unsigned i) { + int j = m_dense_LU->find_pivot_column_in_row(i); + if (j == -1) { + m_failure = true; + return; + } + if (i != static_cast(j)) { + swap_columns(i, j); + m_dense_LU->swap_columns(i, j); + } + m_dense_LU->pivot(i, m_settings); +} +template +void lu::create_initial_factorization(){ + m_U.prepare_for_factorization(); + unsigned j; + for (j = 0; j < m_dim; j++) { + process_column(j); + if (m_failure) { + set_status(LU_status::Degenerated); + return; + } + if (too_dense(j)) { + break; + } + } + if (j == m_dim) { + // TBD does not compile: lean_assert(m_U.is_upper_triangular_and_maximums_are_set_correctly_in_rows(m_settings)); + // lean_assert(is_correct()); + // lean_assert(m_U.is_upper_triangular_and_maximums_are_set_correctly_in_rows(m_settings)); + return; + } + j++; + m_dense_LU = new square_dense_submatrix(&m_U, j); + for (; j < m_dim; j++) { + pivot_in_dense_mode(j); + if (m_failure) { + set_status(LU_status::Degenerated); + return; + } + } + m_dense_LU->update_parent_matrix(m_settings); + lean_assert(m_dense_LU->is_L_matrix()); + m_dense_LU->conjugate_by_permutation(m_Q); + push_matrix_to_tail(m_dense_LU); + m_refactor_counter = 0; + // lean_assert(is_correct()); + // lean_assert(m_U.is_upper_triangular_and_maximums_are_set_correctly_in_rows(m_settings)); +} + +template +void lu::calculate_r_wave_and_update_U(unsigned bump_start, unsigned bump_end, permutation_matrix & r_wave) { + if (bump_start > bump_end) { + set_status(LU_status::Degenerated); + return; + } + if (bump_start == bump_end) { + return; + } + + r_wave[bump_start] = bump_end; // sending the offensive column to the end of the bump + + for ( unsigned i = bump_start + 1 ; i <= bump_end; i++ ) { + r_wave[i] = i - 1; + } + + m_U.multiply_from_right(r_wave); + m_U.multiply_from_left_with_reverse(r_wave); +} +template +void lu::scan_last_row_to_work_vector(unsigned lowest_row_of_the_bump) { + vector> & last_row_vec = m_U.get_row_values(m_U.adjust_row(lowest_row_of_the_bump)); + for (auto & iv : last_row_vec) { + if (is_zero(iv.m_value)) continue; + lean_assert(!m_settings.abs_val_is_smaller_than_drop_tolerance(iv.m_value)); + unsigned adjusted_col = m_U.adjust_column_inverse(iv.m_index); + if (adjusted_col < lowest_row_of_the_bump) { + m_row_eta_work_vector.set_value(-iv.m_value, adjusted_col); + } else { + m_row_eta_work_vector.set_value(iv.m_value, adjusted_col); // preparing to calculate the real value in the matrix + } + } +} + +template +void lu::pivot_and_solve_the_system(unsigned replaced_column, unsigned lowest_row_of_the_bump) { + // we have the system right side at m_row_eta_work_vector now + // solve the system column wise + for (unsigned j = replaced_column; j < lowest_row_of_the_bump; j++) { + T v = m_row_eta_work_vector[j]; + if (numeric_traits::is_zero(v)) continue; // this column does not contribute to the solution + unsigned aj = m_U.adjust_row(j); + vector> & row = m_U.get_row_values(aj); + for (auto & iv : row) { + unsigned col = m_U.adjust_column_inverse(iv.m_index); + lean_assert(col >= j || numeric_traits::is_zero(iv.m_value)); + if (col == j) continue; + if (numeric_traits::is_zero(iv.m_value)) { + continue; + } + // the -v is for solving the system ( to zero the last row), and +v is for pivoting + T delta = col < lowest_row_of_the_bump? -v * iv.m_value: v * iv.m_value; + lean_assert(numeric_traits::is_zero(delta) == false); + + + + // m_row_eta_work_vector.add_value_at_index_with_drop_tolerance(col, delta); + if (numeric_traits::is_zero(m_row_eta_work_vector[col])) { + if (!m_settings.abs_val_is_smaller_than_drop_tolerance(delta)){ + m_row_eta_work_vector.set_value(delta, col); + } + } else { + T t = (m_row_eta_work_vector[col] += delta); + if (m_settings.abs_val_is_smaller_than_drop_tolerance(t)){ + m_row_eta_work_vector[col] = numeric_traits::zero(); + auto it = std::find(m_row_eta_work_vector.m_index.begin(), m_row_eta_work_vector.m_index.end(), col); + if (it != m_row_eta_work_vector.m_index.end()) + m_row_eta_work_vector.m_index.erase(it); + } + } + } + } +} +// see Achim Koberstein's thesis page 58, but here we solve the system and pivot to the last +// row at the same time +template +row_eta_matrix *lu::get_row_eta_matrix_and_set_row_vector(unsigned replaced_column, unsigned lowest_row_of_the_bump, const T & pivot_elem_for_checking) { + if (replaced_column == lowest_row_of_the_bump) return nullptr; + scan_last_row_to_work_vector(lowest_row_of_the_bump); + pivot_and_solve_the_system(replaced_column, lowest_row_of_the_bump); + if (numeric_traits::precise() == false && !is_zero(pivot_elem_for_checking)) { + T denom = std::max(T(1), abs(pivot_elem_for_checking)); + if ( + !m_settings.abs_val_is_smaller_than_pivot_tolerance((m_row_eta_work_vector[lowest_row_of_the_bump] - pivot_elem_for_checking) / denom)) { + set_status(LU_status::Degenerated); + // LP_OUT(m_settings, "diagonal element is off" << std::endl); + return nullptr; + } + } +#ifdef LEAN_DEBUG + auto ret = new row_eta_matrix(replaced_column, lowest_row_of_the_bump, m_dim); +#else + auto ret = new row_eta_matrix(replaced_column, lowest_row_of_the_bump); +#endif + + for (auto j : m_row_eta_work_vector.m_index) { + if (j < lowest_row_of_the_bump) { + auto & v = m_row_eta_work_vector[j]; + if (!is_zero(v)) { + if (!m_settings.abs_val_is_smaller_than_drop_tolerance(v)){ + ret->push_back(j, v); + } + v = numeric_traits::zero(); + } + } + } // now the lowest_row_of_the_bump contains the rest of the row to the right of the bump with correct values + return ret; +} + +template +void lu::replace_column(T pivot_elem_for_checking, indexed_vector & w, unsigned leaving_column_of_U){ + m_refactor_counter++; + unsigned replaced_column = transform_U_to_V_by_replacing_column( w, leaving_column_of_U); + unsigned lowest_row_of_the_bump = m_U.lowest_row_in_column(replaced_column); + m_r_wave.init(m_dim); + calculate_r_wave_and_update_U(replaced_column, lowest_row_of_the_bump, m_r_wave); + auto row_eta = get_row_eta_matrix_and_set_row_vector(replaced_column, lowest_row_of_the_bump, pivot_elem_for_checking); + + if (get_status() == LU_status::Degenerated) { + m_row_eta_work_vector.clear_all(); + return; + } + m_Q.multiply_by_permutation_from_right(m_r_wave); + m_R.multiply_by_permutation_reverse_from_left(m_r_wave); + if (row_eta != nullptr) { + row_eta->conjugate_by_permutation(m_Q); + push_matrix_to_tail(row_eta); + } + calculate_Lwave_Pwave_for_bump(replaced_column, lowest_row_of_the_bump); + // lean_assert(m_U.is_upper_triangular_and_maximums_are_set_correctly_in_rows(m_settings)); + // lean_assert(w.is_OK() && m_row_eta_work_vector.is_OK()); +} +template +void lu::calculate_Lwave_Pwave_for_bump(unsigned replaced_column, unsigned lowest_row_of_the_bump){ + T diagonal_elem; + if (replaced_column < lowest_row_of_the_bump) { + diagonal_elem = m_row_eta_work_vector[lowest_row_of_the_bump]; + // lean_assert(m_row_eta_work_vector.is_OK()); + m_U.set_row_from_work_vector_and_clean_work_vector_not_adjusted(m_U.adjust_row(lowest_row_of_the_bump), m_row_eta_work_vector, m_settings); + } else { + diagonal_elem = m_U(lowest_row_of_the_bump, lowest_row_of_the_bump); // todo - get it more efficiently + } + if (m_settings.abs_val_is_smaller_than_pivot_tolerance(diagonal_elem)) { + set_status(LU_status::Degenerated); + return; + } + + calculate_Lwave_Pwave_for_last_row(lowest_row_of_the_bump, diagonal_elem); + // lean_assert(m_U.is_upper_triangular_and_maximums_are_set_correctly_in_rows(m_settings)); +} + +template +void lu::calculate_Lwave_Pwave_for_last_row(unsigned lowest_row_of_the_bump, T diagonal_element) { + auto l = new one_elem_on_diag(lowest_row_of_the_bump, diagonal_element); +#ifdef LEAN_DEBUG + l->set_number_of_columns(m_dim); +#endif + push_matrix_to_tail(l); + m_U.divide_row_by_constant(lowest_row_of_the_bump, diagonal_element, m_settings); + l->conjugate_by_permutation(m_Q); +} + +template +void init_factorization(lu* & factorization, static_matrix & m_A, vector & m_basis, lp_settings &m_settings) { + if (factorization != nullptr) + delete factorization; + factorization = new lu(m_A, m_basis, m_settings); + // if (factorization->get_status() != LU_status::OK) + // LP_OUT(m_settings, "failing in init_factorization" << std::endl); +} + +#ifdef LEAN_DEBUG +template +dense_matrix get_B(lu& f, const vector& basis) { + lean_assert(basis.size() == f.dimension()); + lean_assert(basis.size() == f.m_U.dimension()); + dense_matrix B(f.dimension(), f.dimension()); + for (unsigned i = 0; i < f.dimension(); i++) + for (unsigned j = 0; j < f.dimension(); j++) + B.set_elem(i, j, f.B_(i, j, basis)); + + return B; +} +#endif +} diff --git a/src/util/lp/lu_instances.cpp b/src/util/lp/lu_instances.cpp new file mode 100644 index 000000000..c8ff7b2f4 --- /dev/null +++ b/src/util/lp/lu_instances.cpp @@ -0,0 +1,63 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include +#include +#include +#include "util/vector.h" +#include "util/debug.h" +#include "util/lp/lu.hpp" +template double lean::dot_product(vector const&, vector const&); +template lean::lu::lu(lean::static_matrix const&, vector&, lean::lp_settings&); +template void lean::lu::push_matrix_to_tail(lean::tail_matrix*); +template void lean::lu::replace_column(double, lean::indexed_vector&, unsigned); +template void lean::lu::solve_Bd(unsigned int, lean::indexed_vector&, lean::indexed_vector&); +template lean::lu::~lu(); +template void lean::lu::push_matrix_to_tail(lean::tail_matrix*); +template void lean::lu::solve_Bd(unsigned int, lean::indexed_vector&, lean::indexed_vector&); +template lean::lu::~lu(); +template void lean::lu >::push_matrix_to_tail(lean::tail_matrix >*); +template void lean::lu >::solve_Bd(unsigned int, lean::indexed_vector&, lean::indexed_vector&); +template lean::lu >::~lu(); +template lean::mpq lean::dot_product(vector const&, vector const&); +template void lean::init_factorization(lean::lu*&, lean::static_matrix&, vector&, lean::lp_settings&); +template void lean::init_factorization(lean::lu*&, lean::static_matrix&, vector&, lean::lp_settings&); +template void lean::init_factorization >(lean::lu >*&, lean::static_matrix >&, vector&, lean::lp_settings&); +#ifdef LEAN_DEBUG +template void lean::print_matrix(lean::sparse_matrix&, std::ostream & out); +template void lean::print_matrix(lean::static_matrix&, std::ostream&); +template void lean::print_matrix >(lean::static_matrix >&, std::ostream&); +template void lean::print_matrix(lean::static_matrix&, std::ostream & out); +template bool lean::lu::is_correct(const vector& basis); +template bool lean::lu >::is_correct( vector const &); +template lean::dense_matrix lean::get_B(lean::lu&, const vector& basis); +template lean::dense_matrix lean::get_B(lean::lu&, vector const&); + +#endif + +template bool lean::lu::pivot_the_row(int); // NOLINT +template void lean::lu::init_vector_w(unsigned int, lean::indexed_vector&); +template void lean::lu::solve_By(vector&); +template void lean::lu::solve_By_when_y_is_ready_for_X(vector&); +template void lean::lu::solve_yB_with_error_check(vector&, const vector& basis); +template void lean::lu::solve_yB_with_error_check_indexed(lean::indexed_vector&, vector const&, const vector & basis, const lp_settings&); +template void lean::lu::replace_column(lean::mpq, lean::indexed_vector&, unsigned); +template void lean::lu::solve_By(vector&); +template void lean::lu::solve_By_when_y_is_ready_for_X(vector&); +template void lean::lu::solve_yB_with_error_check(vector&, const vector& basis); +template void lean::lu::solve_yB_with_error_check_indexed(lean::indexed_vector&, vector< int > const&, const vector & basis, const lp_settings&); +template void lean::lu >::solve_yB_with_error_check_indexed(lean::indexed_vector&, vector< int > const&, const vector & basis, const lp_settings&); +template void lean::lu >::init_vector_w(unsigned int, lean::indexed_vector&); +template void lean::lu >::replace_column(lean::mpq, lean::indexed_vector&, unsigned); +template void lean::lu >::solve_Bd_faster(unsigned int, lean::indexed_vector&); +template void lean::lu >::solve_By(vector >&); +template void lean::lu >::solve_By_when_y_is_ready_for_X(vector >&); +template void lean::lu >::solve_yB_with_error_check(vector&, const vector& basis); +template void lean::lu::solve_By(lean::indexed_vector&); +template void lean::lu::solve_By(lean::indexed_vector&); +template void lean::lu::solve_yB_indexed(lean::indexed_vector&); +template void lean::lu::solve_yB_indexed(lean::indexed_vector&); +template void lean::lu >::solve_yB_indexed(lean::indexed_vector&); +template void lean::lu::solve_By_for_T_indexed_only(lean::indexed_vector&, lean::lp_settings const&); +template void lean::lu::solve_By_for_T_indexed_only(lean::indexed_vector&, lean::lp_settings const&); diff --git a/src/util/lp/matrix.h b/src/util/lp/matrix.h new file mode 100644 index 000000000..63fd5c01e --- /dev/null +++ b/src/util/lp/matrix.h @@ -0,0 +1,44 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#ifdef Z3DEBUG +#pragma once +#include "util/lp/numeric_pair.h" +#include "util/vector.h" +#include +#include "util/lp/lp_settings.h" +namespace lean { +// used for debugging purposes only +template +class matrix { +public: + virtual T get_elem (unsigned i, unsigned j) const = 0; + virtual unsigned row_count() const = 0; + virtual unsigned column_count() const = 0; + virtual void set_number_of_rows(unsigned m) = 0; + virtual void set_number_of_columns(unsigned n) = 0; + + virtual ~matrix() {} + + bool is_equal(const matrix& other); + bool operator == (matrix const & other) { + return is_equal(other); + } + T operator()(unsigned i, unsigned j) const { return get_elem(i, j); } +}; + +template +void apply_to_vector(matrix & m, T * w); + + + +unsigned get_width_of_column(unsigned j, vector> & A); +void print_matrix_with_widths(vector> & A, vector & ws, std::ostream & out); +void print_string_matrix(vector> & A); + +template +void print_matrix(matrix const * m, std::ostream & out); + +} +#endif diff --git a/src/util/lp/matrix.hpp b/src/util/lp/matrix.hpp new file mode 100644 index 000000000..27cdabd3e --- /dev/null +++ b/src/util/lp/matrix.hpp @@ -0,0 +1,105 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ + +#ifdef Z3DEBUG +#include +#include +#include "util/lp/matrix.h" +namespace lean { +template +bool matrix::is_equal(const matrix& other) { + if (other.row_count() != row_count() || other.column_count() != column_count()) + return false; + for (unsigned i = 0; i < row_count(); i++) { + for (unsigned j = 0; j < column_count(); j++) { + auto a = get_elem(i, j); + auto b = other.get_elem(i, j); + if (numeric_traits::precise()) { + if (a != b) return false; + } else if (fabs(numeric_traits::get_double(a - b)) > 0.000001) { + // cout << "returning false from operator== of matrix comparison" << endl; + // cout << "this matrix is " << endl; + // print_matrix(*this); + // cout << "other matrix is " << endl; + // print_matrix(other); + return false; + } + } + } + return true; +} + +template +void apply_to_vector(matrix & m, T * w) { + // here m is a square matrix + unsigned dim = m.row_count(); + + T * wc = new T[dim]; + + for (unsigned i = 0; i < dim; i++) { + wc[i] = w[i]; + } + + for (unsigned i = 0; i < dim; i++) { + T t = numeric_traits::zero(); + for (unsigned j = 0; j < dim; j++) { + t += m(i, j) * wc[j]; + } + w[i] = t; + } + delete [] wc; +} + + + +unsigned get_width_of_column(unsigned j, vector> & A) { + unsigned r = 0; + for (unsigned i = 0; i < A.size(); i++) { + vector & t = A[i]; + std::string str= t[j]; + unsigned s = str.size(); + if (r < s) { + r = s; + } + } + return r; +} + +void print_matrix_with_widths(vector> & A, vector & ws, std::ostream & out) { + for (unsigned i = 0; i < A.size(); i++) { + for (unsigned j = 0; j < A[i].size(); j++) { + print_blanks(ws[j] - A[i][j].size(), out); + out << A[i][j] << " "; + } + out << std::endl; + } +} + +void print_string_matrix(vector> & A, std::ostream & out) { + vector widths; + + if (A.size() > 0) + for (unsigned j = 0; j < A[0].size(); j++) { + widths.push_back(get_width_of_column(j, A)); + } + + print_matrix_with_widths(A, widths, out); + out << std::endl; +} + +template +void print_matrix(matrix const * m, std::ostream & out) { + vector> A(m->row_count()); + for (unsigned i = 0; i < m->row_count(); i++) { + for (unsigned j = 0; j < m->column_count(); j++) { + A[i].push_back(T_to_string(m->get_elem(i, j))); + } + } + + print_string_matrix(A, out); +} + +} +#endif diff --git a/src/util/lp/matrix_instances.cpp b/src/util/lp/matrix_instances.cpp new file mode 100644 index 000000000..aeee62786 --- /dev/null +++ b/src/util/lp/matrix_instances.cpp @@ -0,0 +1,16 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include "util/lp/lp_settings.h" +#ifdef LEAN_DEBUG +#include "util/lp/matrix.hpp" +#include "util/lp/static_matrix.h" +#include +template void lean::print_matrix(lean::matrix const*, std::ostream & out); +template bool lean::matrix::is_equal(lean::matrix const&); +template void lean::print_matrix >(lean::matrix > const *, std::basic_ostream > &); +template void lean::print_matrix(lean::matrix const*, std::ostream&); +template bool lean::matrix >::is_equal(lean::matrix > const&); +template bool lean::matrix::is_equal(lean::matrix const&); +#endif diff --git a/src/util/lp/mps_reader.h b/src/util/lp/mps_reader.h new file mode 100644 index 000000000..c79a7d426 --- /dev/null +++ b/src/util/lp/mps_reader.h @@ -0,0 +1,867 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ + +#pragma once + +// reads an MPS file reperesenting a Mixed Integer Program +#include +#include +#include +#include "util/vector.h" +#include +#include +#include +#include +#include "util/lp/lp_primal_simplex.h" +#include "util/lp/lp_dual_simplex.h" +#include "util/lp/lar_solver.h" +#include "util/lp/lp_utils.h" +#include "util/lp/lp_solver.h" +namespace lean { +inline bool my_white_space(const char & a) { + return a == ' ' || a == '\t'; +} +inline size_t number_of_whites(const std::string & s) { + size_t i = 0; + for(;i < s.size(); i++) + if (!my_white_space(s[i])) return i; + return i; +} +inline size_t number_of_whites_from_end(const std::string & s) { + size_t ret = 0; + for(int i = static_cast(s.size()) - 1;i >= 0; i--) + if (my_white_space(s[i])) ret++;else break; + + return ret; +} + + + // trim from start +inline std::string <rim(std::string &s) { + s.erase(0, number_of_whites(s)); + return s; +} + + + + + // trim from end +inline std::string &rtrim(std::string &s) { + // s.erase(std::find_if(s.rbegin(), s.rend(), std::not1(std::ptr_fun(std::isspace))).base(), s.end()); + s.erase(s.end() - number_of_whites_from_end(s), s.end()); + return s; +} + // trim from both ends +inline std::string &trim(std::string &s) { + return ltrim(rtrim(s)); +} + +inline std::string trim(std::string const &r) { + std::string s = r; + return ltrim(rtrim(s)); +} + + +inline vector string_split(const std::string &source, const char *delimiter, bool keep_empty) { + vector results; + size_t prev = 0; + size_t next = 0; + while ((next = source.find_first_of(delimiter, prev)) != std::string::npos) { + if (keep_empty || (next - prev != 0)) { + results.push_back(source.substr(prev, next - prev)); + } + prev = next + 1; + } + if (prev < source.size()) { + results.push_back(source.substr(prev)); + } + return results; +} + +inline vector split_and_trim(std::string line) { + auto split = string_split(line, " \t", false); + vector ret; + for (auto s : split) { + ret.push_back(trim(s)); + } + return ret; +} + +template +class mps_reader { + enum row_type { Cost, Less_or_equal, Greater_or_equal, Equal }; + struct bound { + bool m_low_is_set = true; + T m_low; + bool m_upper_is_set = false; + T m_upper; + bool m_value_is_fixed = false; + T m_fixed_value; + bool m_free = false; + // constructor + bound() : m_low(numeric_traits::zero()) {} // it seems all mps files I have seen have the default low value 0 on a variable + }; + + struct column { + std::string m_name; + bound * m_bound = nullptr; + unsigned m_index; + column(std::string name, unsigned index): m_name(name), m_index(index) { + } + }; + + struct row { + row_type m_type; + std::string m_name; + std::unordered_map m_row_columns; + T m_right_side = numeric_traits::zero(); + unsigned m_index; + T m_range = numeric_traits::zero(); + row(row_type type, std::string name, unsigned index) : m_type(type), m_name(name), m_index(index) { + } + }; + + std::string m_file_name; + bool m_is_OK = true; + std::unordered_map m_rows; + std::unordered_map m_columns; + std::unordered_map m_names_to_var_index; + std::string m_line; + std::string m_name; + std::string m_cost_row_name; + std::ifstream m_file_stream; + // needed to adjust the index row + unsigned m_cost_line_count = 0; + unsigned m_line_number = 0; + std::ostream * m_message_stream = & std::cout; + + void set_m_ok_to_false() { + *m_message_stream << "setting m_is_OK to false" << std::endl; + m_is_OK = false; + } + + std::string get_string_from_position(unsigned offset) { + unsigned i = offset; + for (; i < m_line.size(); i++){ + if (m_line[i] == ' ') + break; + } + lean_assert(m_line.size() >= offset); + lean_assert(m_line.size() >> i); + lean_assert(i >= offset); + return m_line.substr(offset, i - offset); + } + + void set_boundary_for_column(unsigned col, bound * b, lp_solver * solver){ + if (b == nullptr) { + solver->set_low_bound(col, numeric_traits::zero()); + return; + } + + if (b->m_free) { + return; + } + if (b->m_low_is_set) { + solver->set_low_bound(col, b->m_low); + } + if (b->m_upper_is_set) { + solver->set_upper_bound(col, b->m_upper); + } + + if (b->m_value_is_fixed) { + solver->set_fixed_value(col, b->m_fixed_value); + } + } + + bool all_white_space() { + for (unsigned i = 0; i < m_line.size(); i++) { + char c = m_line[i]; + if (c != ' ' && c != '\t') { + return false; + } + } + return true; + } + + void read_line() { + while (m_is_OK) { + if (!getline(m_file_stream, m_line)) { + m_line_number++; + set_m_ok_to_false(); + *m_message_stream << "cannot read from file" << std::endl; + } + m_line_number++; + if (m_line.size() != 0 && m_line[0] != '*' && !all_white_space()) + break; + } + } + + void read_name() { + do { + read_line(); + if (m_line.find("NAME") != 0) { + continue; + } + m_line = m_line.substr(4); + m_name = trim(m_line); + break; + } while (m_is_OK); + } + + void read_rows() { + // look for start of the rows + read_line(); + do { + if (static_cast(m_line.find("ROWS")) >= 0) { + break; + } + } while (m_is_OK); + do { + read_line(); + if (m_line.find("COLUMNS") == 0) { + break; + } + add_row(); + } while (m_is_OK); + } + + void read_column_by_columns(std::string column_name, std::string column_data) { + // uph, let us try to work with columns + if (column_data.size() >= 22) { + std::string ss = column_data.substr(0, 8); + std::string row_name = trim(ss); + auto t = m_rows.find(row_name); + + if (t == m_rows.end()) { + *m_message_stream << "cannot find " << row_name << std::endl; + goto fail; + } else { + row * row = t->second; + row->m_row_columns[column_name] = numeric_traits::from_string(column_data.substr(8)); + if (column_data.size() > 24) { + column_data = column_data.substr(25); + if (column_data.size() >= 22) { + read_column_by_columns(column_name, column_data); + } + } + } + } else { + fail: + set_m_ok_to_false(); + *m_message_stream << "cannot understand this line" << std::endl; + *m_message_stream << "line = " << m_line << ", line number is " << m_line_number << std::endl; + return; + } + } + + void read_column(std::string column_name, std::string column_data){ + auto tokens = split_and_trim(column_data); + for (unsigned i = 0; i < tokens.size() - 1; i+= 2) { + auto row_name = tokens[i]; + if (row_name == "'MARKER'") return; // it is the integrality marker, no real data here + auto t = m_rows.find(row_name); + if (t == m_rows.end()) { + read_column_by_columns(column_name, column_data); + return; + } + row *r = t->second; + r->m_row_columns[column_name] = numeric_traits::from_string(tokens[i + 1]); + } + } + + void read_columns(){ + std::string column_name; + do { + read_line(); + if (m_line.find("RHS") == 0) { + // cout << "found RHS" << std::endl; + break; + } + if (m_line.size() < 22) { + (*m_message_stream) << "line is too short for a column" << std::endl; + (*m_message_stream) << m_line << std::endl; + (*m_message_stream) << "line number is " << m_line_number << std::endl; + set_m_ok_to_false(); + return; + } + std::string column_name_tmp = trim(m_line.substr(4, 8)); + if (!column_name_tmp.empty()) { + column_name = column_name_tmp; + } + auto col_it = m_columns.find(column_name); + mps_reader::column * col; + if (col_it == m_columns.end()) { + col = new mps_reader::column(column_name, static_cast(m_columns.size())); + m_columns[column_name] = col; + // (*m_message_stream) << column_name << '[' << col->m_index << ']'<< std::endl; + } else { + col = col_it->second; + } + read_column(column_name, m_line.substr(14)); + } while (m_is_OK); + } + + void read_rhs() { + do { + read_line(); + if (m_line.find("BOUNDS") == 0 || m_line.find("ENDATA") == 0 || m_line.find("RANGES") == 0) { + break; + } + fill_rhs(); + } while (m_is_OK); + } + + + void fill_rhs_by_columns(std::string rhsides) { + // uph, let us try to work with columns + if (rhsides.size() >= 22) { + std::string ss = rhsides.substr(0, 8); + std::string row_name = trim(ss); + auto t = m_rows.find(row_name); + + if (t == m_rows.end()) { + (*m_message_stream) << "cannot find " << row_name << std::endl; + goto fail; + } else { + row * row = t->second; + row->m_right_side = numeric_traits::from_string(rhsides.substr(8)); + if (rhsides.size() > 24) { + rhsides = rhsides.substr(25); + if (rhsides.size() >= 22) { + fill_rhs_by_columns(rhsides); + } + } + } + } else { + fail: + set_m_ok_to_false(); + (*m_message_stream) << "cannot understand this line" << std::endl; + (*m_message_stream) << "line = " << m_line << ", line number is " << m_line_number << std::endl; + return; + } + } + + void fill_rhs() { + if (m_line.size() < 14) { + (*m_message_stream) << "line is too short" << std::endl; + (*m_message_stream) << m_line << std::endl; + (*m_message_stream) << "line number is " << m_line_number << std::endl; + set_m_ok_to_false(); + return; + } + std::string rhsides = m_line.substr(14); + vector splitted_line = split_and_trim(rhsides); + + for (unsigned i = 0; i < splitted_line.size() - 1; i += 2) { + auto t = m_rows.find(splitted_line[i]); + if (t == m_rows.end()) { + fill_rhs_by_columns(rhsides); + return; + } + row * row = t->second; + row->m_right_side = numeric_traits::from_string(splitted_line[i + 1]); + } + } + + void read_bounds() { + if (m_line.find("BOUNDS") != 0) { + return; + } + + do { + read_line(); + if (m_line[0] != ' ') { + break; + } + create_or_update_bound(); + } while (m_is_OK); + } + + void read_ranges() { + if (m_line.find("RANGES") != 0) { + return; + } + do { + read_line(); + auto sl = split_and_trim(m_line); + if (sl.size() < 2) { + break; + } + read_range(sl); + } while (m_is_OK); + } + + + void read_bound_by_columns(std::string colstr) { + if (colstr.size() < 14) { + (*m_message_stream) << "line is too short" << std::endl; + (*m_message_stream) << m_line << std::endl; + (*m_message_stream) << "line number is " << m_line_number << std::endl; + set_m_ok_to_false(); + return; + } + // uph, let us try to work with columns + if (colstr.size() >= 22) { + std::string ss = colstr.substr(0, 8); + std::string column_name = trim(ss); + auto t = m_columns.find(column_name); + + if (t == m_columns.end()) { + (*m_message_stream) << "cannot find " << column_name << std::endl; + goto fail; + } else { + vector bound_string; + bound_string.push_back(column_name); + if (colstr.size() > 14) { + bound_string.push_back(colstr.substr(14)); + } + mps_reader::column * col = t->second; + bound * b = col->m_bound; + if (b == nullptr) { + col->m_bound = b = new bound(); + } + update_bound(b, bound_string); + } + } else { + fail: + set_m_ok_to_false(); + (*m_message_stream) << "cannot understand this line" << std::endl; + (*m_message_stream) << "line = " << m_line << ", line number is " << m_line_number << std::endl; + return; + } + } + + void update_bound(bound * b, vector bound_string) { + /* + UP means an upper bound is applied to the variable. A bound of type LO means a lower bound is applied. A bound type of FX ("fixed") means that the variable has upper and lower bounds equal to a single value. A bound type of FR ("free") means the variable has neither lower nor upper bounds and so can take on negative values. A variation on that is MI for free negative, giving an upper bound of 0 but no lower bound. Bound type PL is for a free positive for zero to plus infinity, but as this is the normal default, it is seldom used. There are also bound types for use in MIP models - BV for binary, being 0 or 1. UI for upper integer and LI for lower integer. SC stands for semi-continuous and indicates that the variable may be zero, but if not must be equal to at least the value given. + */ + + std::string bound_type = get_string_from_position(1); + if (bound_type == "BV") { + b->m_upper_is_set = true; + b->m_upper = 1; + return; + } + + if (bound_type == "UP" || bound_type == "UI" || bound_type == "LIMITMAX") { + if (bound_string.size() <= 1){ + set_m_ok_to_false(); + return; + } + b->m_upper_is_set = true; + b->m_upper= numeric_traits::from_string(bound_string[1]); + } else if (bound_type == "LO" || bound_type == "LI") { + if (bound_string.size() <= 1){ + set_m_ok_to_false(); + return; + } + + b->m_low_is_set = true; + b->m_low = numeric_traits::from_string(bound_string[1]); + } else if (bound_type == "FR") { + b->m_free = true; + } else if (bound_type == "FX") { + if (bound_string.size() <= 1){ + set_m_ok_to_false(); + return; + } + + b->m_value_is_fixed = true; + b->m_fixed_value = numeric_traits::from_string(bound_string[1]); + } else if (bound_type == "PL") { + b->m_low_is_set = true; + b->m_low = 0; + } else if (bound_type == "MI") { + b->m_upper_is_set = true; + b->m_upper = 0; + } else { + (*m_message_stream) << "unexpected bound type " << bound_type << " at line " << m_line_number << std::endl; + set_m_ok_to_false(); + throw; + } + } + + void create_or_update_bound() { + const unsigned name_offset = 14; + lean_assert(m_line.size() >= 14); + vector bound_string = split_and_trim(m_line.substr(name_offset, m_line.size())); + + if (bound_string.size() == 0) { + set_m_ok_to_false(); + (*m_message_stream) << "error at line " << m_line_number << std::endl; + throw m_line; + } + + std::string name = bound_string[0]; + auto it = m_columns.find(name); + if (it == m_columns.end()){ + read_bound_by_columns(m_line.substr(14)); + return; + } + mps_reader::column * col = it->second; + bound * b = col->m_bound; + if (b == nullptr) { + col->m_bound = b = new bound(); + } + update_bound(b, bound_string); + } + + + + void read_range_by_columns(std::string rhsides) { + if (m_line.size() < 14) { + (*m_message_stream) << "line is too short" << std::endl; + (*m_message_stream) << m_line << std::endl; + (*m_message_stream) << "line number is " << m_line_number << std::endl; + set_m_ok_to_false(); + return; + } + // uph, let us try to work with columns + if (rhsides.size() >= 22) { + std::string ss = rhsides.substr(0, 8); + std::string row_name = trim(ss); + auto t = m_rows.find(row_name); + + if (t == m_rows.end()) { + (*m_message_stream) << "cannot find " << row_name << std::endl; + goto fail; + } else { + row * row = t->second; + row->m_range = numeric_traits::from_string(rhsides.substr(8)); + maybe_modify_current_row_and_add_row_for_range(row); + if (rhsides.size() > 24) { + rhsides = rhsides.substr(25); + if (rhsides.size() >= 22) { + read_range_by_columns(rhsides); + } + } + } + } else { + fail: + set_m_ok_to_false(); + (*m_message_stream) << "cannot understand this line" << std::endl; + (*m_message_stream) << "line = " << m_line << ", line number is " << m_line_number << std::endl; + return; + } + } + + + void read_range(vector & splitted_line){ + for (unsigned i = 1; i < splitted_line.size() - 1; i += 2) { + auto it = m_rows.find(splitted_line[i]); + if (it == m_rows.end()) { + read_range_by_columns(m_line.substr(14)); + return; + } + row * row = it->second; + row->m_range = numeric_traits::from_string(splitted_line[i + 1]); + maybe_modify_current_row_and_add_row_for_range(row); + } + } + + void maybe_modify_current_row_and_add_row_for_range(row * row_with_range) { + unsigned index= static_cast(m_rows.size() - m_cost_line_count); + std::string row_name = row_with_range->m_name + "_range"; + row * other_bound_range_row; + switch (row_with_range->m_type) { + case row_type::Greater_or_equal: + m_rows[row_name] = other_bound_range_row = new row(row_type::Less_or_equal, row_name, index); + other_bound_range_row->m_right_side = row_with_range->m_right_side + abs(row_with_range->m_range); + break; + case row_type::Less_or_equal: + m_rows[row_name] = other_bound_range_row = new row(row_type::Greater_or_equal, row_name, index); + other_bound_range_row->m_right_side = row_with_range->m_right_side - abs(row_with_range->m_range); + break; + case row_type::Equal: + if (row_with_range->m_range > 0) { + row_with_range->m_type = row_type::Greater_or_equal; // the existing row type change + m_rows[row_name] = other_bound_range_row = new row(row_type::Less_or_equal, row_name, index); + } else { // row->m_range < 0; + row_with_range->m_type = row_type::Less_or_equal; // the existing row type change + m_rows[row_name] = other_bound_range_row = new row(row_type::Greater_or_equal, row_name, index); + } + other_bound_range_row->m_right_side = row_with_range->m_right_side + row_with_range->m_range; + break; + default: + (*m_message_stream) << "unexpected bound type " << row_with_range->m_type << " at line " << m_line_number << std::endl; + set_m_ok_to_false(); + throw; + } + + for (auto s : row_with_range->m_row_columns) { + lean_assert(m_columns.find(s.first) != m_columns.end()); + other_bound_range_row->m_row_columns[s.first] = s.second; + } + } + + void add_row() { + if (m_line.length() < 2) { + return; + } + + m_line = trim(m_line); + char c = m_line[0]; + m_line = m_line.substr(1); + m_line = trim(m_line); + add_row(c); + } + + void add_row(char c) { + unsigned index= static_cast(m_rows.size() - m_cost_line_count); + switch (c) { + case 'E': + m_rows[m_line] = new row(row_type::Equal, m_line, index); + break; + case 'L': + m_rows[m_line] = new row(row_type::Less_or_equal, m_line, index); + break; + case 'G': + m_rows[m_line] = new row(row_type::Greater_or_equal, m_line, index); + break; + case 'N': + m_rows[m_line] = new row(row_type::Cost, m_line, index); + m_cost_row_name = m_line; + m_cost_line_count++; + break; + } + } + unsigned range_count() { + unsigned ret = 0; + for (auto s : m_rows) { + if (s.second->m_range != 0) { + ret++; + } + } + return ret; + } + + /* + If rhs is a constraint's right-hand-side value and range is the constraint's range value, then the range interval is defined according to the following table: + + sense interval + G [rhs, rhs + |range|] + L [rhs - |range|, rhs] + E [rhs, rhs + |range|] if range ¡Ý 0 [rhs - |range|, rhs] if range < 0 + where |range| is range's absolute value. + */ + + lp_relation get_relation_from_row(row_type rt) { + switch (rt) { + case mps_reader::Less_or_equal: return lp_relation::Less_or_equal; + case mps_reader::Greater_or_equal: return lp_relation::Greater_or_equal; + case mps_reader::Equal: return lp_relation::Equal; + default: + (*m_message_stream) << "Unexpected rt " << rt << std::endl; + set_m_ok_to_false(); + throw; + } + } + + unsigned solver_row_count() { + return m_rows.size() - m_cost_line_count + range_count(); + } + + void fill_solver_on_row(row * row, lp_solver *solver) { + if (row->m_name != m_cost_row_name) { + solver->add_constraint(get_relation_from_row(row->m_type), row->m_right_side, row->m_index); + for (auto s : row->m_row_columns) { + lean_assert(m_columns.find(s.first) != m_columns.end()); + solver->set_row_column_coefficient(row->m_index, m_columns[s.first]->m_index, s.second); + } + } else { + set_solver_cost(row, solver); + } + } + + T abs(T & t) { return t < numeric_traits::zero() ? -t: t; } + + void fill_solver_on_rows(lp_solver * solver) { + for (auto row_it : m_rows) { + fill_solver_on_row(row_it.second, solver); + } + } + + + void fill_solver_on_columns(lp_solver * solver){ + for (auto s : m_columns) { + mps_reader::column * col = s.second; + unsigned index = col->m_index; + set_boundary_for_column(index, col->m_bound, solver); + // optional call + solver->give_symbolic_name_to_column(col->m_name, col->m_index); + } + } + + void fill_solver(lp_solver *solver) { + fill_solver_on_rows(solver); + fill_solver_on_columns(solver); + } + + void set_solver_cost(row * row, lp_solver *solver) { + for (auto s : row->m_row_columns) { + std::string name = s.first; + lean_assert(m_columns.find(name) != m_columns.end()); + mps_reader::column * col = m_columns[name]; + solver->set_cost_for_column(col->m_index, s.second); + } + } + +public: + + void set_message_stream(std::ostream * o) { + lean_assert(o != nullptr); + m_message_stream = o; + } + vector column_names() { + vector v; + for (auto s : m_columns) { + v.push_back(s.first); + } + return v; + } + + ~mps_reader() { + for (auto s : m_rows) { + delete s.second; + } + for (auto s : m_columns) { + auto col = s.second; + auto b = col->m_bound; + if (b != nullptr) { + delete b; + } + delete col; + } + } + + mps_reader(std::string file_name): + m_file_name(file_name), m_file_stream(file_name) { + } + void read() { + if (!m_file_stream.is_open()){ + set_m_ok_to_false(); + return; + } + + read_name(); + read_rows(); + read_columns(); + read_rhs(); + if (m_line.find("BOUNDS") == 0) { + read_bounds(); + read_ranges(); + } else if (m_line.find("RANGES") == 0) { + read_ranges(); + read_bounds(); + } + } + + bool is_ok() { + return m_is_OK; + } + + lp_solver * create_solver(bool dual) { + lp_solver * solver = dual? (lp_solver*)new lp_dual_simplex() : new lp_primal_simplex(); + fill_solver(solver); + return solver; + } + + lconstraint_kind get_lar_relation_from_row(row_type rt) { + switch (rt) { + case Less_or_equal: return LE; + case Greater_or_equal: return GE; + case Equal: return EQ; + default: + (*m_message_stream) << "Unexpected rt " << rt << std::endl; + set_m_ok_to_false(); + throw; + } + } + + unsigned get_var_index(std::string s) { + auto it = m_names_to_var_index.find(s); + if (it != m_names_to_var_index.end()) + return it->second; + unsigned ret = m_names_to_var_index.size(); + m_names_to_var_index[s] = ret; + return ret; + } + + void fill_lar_solver_on_row(row * row, lar_solver *solver) { + if (row->m_name != m_cost_row_name) { + auto kind = get_lar_relation_from_row(row->m_type); + vector> ls; + for (auto s : row->m_row_columns) { + var_index i = solver->add_var(get_var_index(s.first)); + ls.push_back(std::make_pair(s.second, i)); + } + solver->add_constraint(ls, kind, row->m_right_side); + } else { + // ignore the cost row + } + } + + + void fill_lar_solver_on_rows(lar_solver * solver) { + for (auto row_it : m_rows) { + fill_lar_solver_on_row(row_it.second, solver); + } + } + + void create_low_constraint_for_var(column* col, bound * b, lar_solver *solver) { + vector> ls; + var_index i = solver->add_var(col->m_index); + ls.push_back(std::make_pair(numeric_traits::one(), i)); + solver->add_constraint(ls, GE, b->m_low); + } + + void create_upper_constraint_for_var(column* col, bound * b, lar_solver *solver) { + var_index i = solver->add_var(col->m_index); + vector> ls; + ls.push_back(std::make_pair(numeric_traits::one(), i)); + solver->add_constraint(ls, LE, b->m_upper); + } + + void create_equality_contraint_for_var(column* col, bound * b, lar_solver *solver) { + var_index i = solver->add_var(col->m_index); + vector> ls; + ls.push_back(std::make_pair(numeric_traits::one(), i)); + solver->add_constraint(ls, EQ, b->m_fixed_value); + } + + void fill_lar_solver_on_columns(lar_solver * solver) { + for (auto s : m_columns) { + mps_reader::column * col = s.second; + solver->add_var(col->m_index); + auto b = col->m_bound; + if (b == nullptr) return; + + if (b->m_free) continue; + + if (b->m_low_is_set) { + create_low_constraint_for_var(col, b, solver); + } + if (b->m_upper_is_set) { + create_upper_constraint_for_var(col, b, solver); + } + if (b->m_value_is_fixed) { + create_equality_contraint_for_var(col, b, solver); + } + } + } + + + void fill_lar_solver(lar_solver * solver) { + fill_lar_solver_on_columns(solver); + fill_lar_solver_on_rows(solver); + } + + lar_solver * create_lar_solver() { + lar_solver * solver = new lar_solver(); + fill_lar_solver(solver); + return solver; + } +}; +} diff --git a/src/util/lp/numeric_pair.h b/src/util/lp/numeric_pair.h new file mode 100644 index 000000000..2cea4158d --- /dev/null +++ b/src/util/lp/numeric_pair.h @@ -0,0 +1,329 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson + The idea is that it is only one different file in Lean and z3 source inside of LP +*/ +#pragma once +#define lp_for_z3 +#include +#include +#include +#ifdef lp_for_z3 +#include "../rational.h" +#include "../sstream.h" +#include "../z3_exception.h" + +#else + // include "util/numerics/mpq.h" + // include "util/numerics/numeric_traits.h" +#endif +namespace lean { +#ifdef lp_for_z3 // rename rationals + typedef rational mpq; +#else + typedef lean::mpq mpq; +#endif + + +template +std::string T_to_string(const T & t); // forward definition +#ifdef lp_for_z3 +template class numeric_traits {}; + +template <> class numeric_traits { +public: + static bool precise() { return true; } + static unsigned const zero() { return 0; } + static unsigned const one() { return 1; } + static bool is_zero(unsigned v) { return v == 0; } + static double const get_double(unsigned const & d) { return d; } +}; + +template <> class numeric_traits { + public: + static bool precise() { return false; } + static double g_zero; + static double const &zero() { return g_zero; } + static double g_one; + static double const &one() { return g_one; } + static bool is_zero(double v) { return v == 0.0; } + static double const & get_double(double const & d) { return d;} + static double log(double const & d) { NOT_IMPLEMENTED_YET(); return d;} + static double from_string(std::string const & str) { return atof(str.c_str()); } + static bool is_pos(const double & d) {return d > 0.0;} + static bool is_neg(const double & d) {return d < 0.0;} + }; + + template<> + class numeric_traits { + public: + static bool precise() { return true; } + static rational const & zero() { return rational::zero(); } + static rational const & one() { return rational::one(); } + static bool is_zero(const rational & v) { return v.is_zero(); } + static double const get_double(const rational & d) { return d.get_double();} + static rational log(rational const& r) { UNREACHABLE(); return r; } + static rational from_string(std::string const & str) { return rational(str.c_str()); } + static bool is_pos(const rational & d) {return d.is_pos();} + static bool is_neg(const rational & d) {return d.is_neg();} + }; +#endif + +template +struct convert_struct { + static X convert(const Y & y){ return X(y);} + static bool is_epsilon_small(const X & x, const double & y) { return std::abs(numeric_traits::get_double(x)) < y; } + static bool below_bound_numeric(const X &, const X &, const Y &) { /*lean_unreachable();*/ return false;} + static bool above_bound_numeric(const X &, const X &, const Y &) { /*lean_unreachable();*/ return false; } +}; + + +template <> +struct convert_struct { + static double convert(const mpq & q) {return q.get_double();} +}; + + +template <> +struct convert_struct { + static mpq convert(unsigned q) {return mpq(q);} +}; + + + +template +struct numeric_pair { + T x; + T y; + // empty constructor + numeric_pair() {} + // another constructor + + numeric_pair(T xp, T yp) : x(xp), y(yp) {} + + + template + numeric_pair(const X & n) : x(n), y(0) { + } + + template + numeric_pair(const numeric_pair & n) : x(n.x), y(n.y) {} + + template + numeric_pair(X xp, Y yp) : numeric_pair(convert_struct::convert(xp), convert_struct::convert(yp)) {} + + bool operator<(const numeric_pair& a) const { + return x < a.x || (x == a.x && y < a.y); + } + + bool operator>(const numeric_pair& a) const { + return x > a.x || (x == a.x && y > a.y); + } + + bool operator==(const numeric_pair& a) const { + return a.x == x && a.y == y; + } + + bool operator!=(const numeric_pair& a) const { + return !(*this == a); + } + + bool operator<=(const numeric_pair& a) const { + return *this < a || *this == a; + } + + bool operator>=(const numeric_pair& a) const { + return *this > a || a == *this; + } + + numeric_pair operator*(const T & a) const { + return numeric_pair(a * x, a * y); + } + + numeric_pair operator/(const T & a) const { + T a_as_T(a); + return numeric_pair(x / a_as_T, y / a_as_T); + } + + numeric_pair operator/(const numeric_pair &) const { + // lean_unreachable(); + } + + + numeric_pair operator+(const numeric_pair & a) const { + return numeric_pair(a.x + x, a.y + y); + } + + numeric_pair operator*(const numeric_pair & /*a*/) const { + // lean_unreachable(); + } + + numeric_pair& operator+=(const numeric_pair & a) { + x += a.x; + y += a.y; + return *this; + } + + numeric_pair& operator-=(const numeric_pair & a) { + x -= a.x; + y -= a.y; + return *this; + } + + numeric_pair& operator/=(const T & a) { + x /= a; + y /= a; + return *this; + } + + numeric_pair& operator*=(const T & a) { + x *= a; + y *= a; + return *this; + } + + numeric_pair operator-(const numeric_pair & a) const { + return numeric_pair(x - a.x, y - a.y); + } + + numeric_pair operator-() const { + return numeric_pair(-x, -y); + } + + static bool precize() { return lean::numeric_traits::precize();} + + bool is_zero() const { return x.is_zero() && y.is_zero(); } + + bool is_pos() const { return x.is_pos() || (x.is_zero() && y.is_pos());} + + bool is_neg() const { return x.is_neg() || (x.is_zero() && y.is_neg());} + + std::string to_string() const { + return std::string("(") + T_to_string(x) + ", " + T_to_string(y) + ")"; + } +}; + + +template +std::ostream& operator<<(std::ostream& os, numeric_pair const & obj) { + os << obj.to_string(); + return os; +} + +template +numeric_pair operator*(const X & a, const numeric_pair & r) { + return numeric_pair(a * r.x, a * r.y); +} + +template +numeric_pair operator*(const numeric_pair & r, const X & a) { + return numeric_pair(a * r.x, a * r.y); +} + + +template +numeric_pair operator/(const numeric_pair & r, const X & a) { + return numeric_pair(r.x / a, r.y / a); +} + +// template bool precise() { return numeric_traits::precise();} +template double get_double(const lean::numeric_pair & ) { /* lean_unreachable(); */ return 0;} +template +class numeric_traits> { + public: + static bool precise() { return numeric_traits::precise();} + static lean::numeric_pair zero() { return lean::numeric_pair(numeric_traits::zero(), numeric_traits::zero()); } + static bool is_zero(const lean::numeric_pair & v) { return numeric_traits::is_zero(v.x) && numeric_traits::is_zero(v.y); } + static double get_double(const lean::numeric_pair & v){ return numeric_traits::get_double(v.x); } // just return the double of the first coordinate + static double one() { /*lean_unreachable();*/ return 0;} + static bool is_pos(const numeric_pair &p) { + return numeric_traits::is_pos(p.x) || + (numeric_traits::is_zero(p.x) && numeric_traits::is_pos(p.y)); + } + static bool is_neg(const numeric_pair &p) { + return numeric_traits::is_neg(p.x) || + (numeric_traits::is_zero(p.x) && numeric_traits::is_neg(p.y)); + } + +}; + +template <> +struct convert_struct> { + static double convert(const numeric_pair & q) {return q.x;} +}; + +typedef numeric_pair impq; + +template bool is_epsilon_small(const X & v, const double& eps); // forward definition { return convert_struct::is_epsilon_small(v, eps);} + +template +struct convert_struct, double> { + static numeric_pair convert(const double & q) { + return numeric_pair(convert_struct::convert(q), numeric_traits::zero()); + } + static bool is_epsilon_small(const numeric_pair & p, const double & eps) { + return convert_struct::is_epsilon_small(p.x, eps) && convert_struct::is_epsilon_small(p.y, eps); + } + static bool below_bound_numeric(const numeric_pair &, const numeric_pair &, const double &) { + // lean_unreachable(); + return false; + } + static bool above_bound_numeric(const numeric_pair &, const numeric_pair &, const double &) { + // lean_unreachable(); + return false; + } +}; +template <> +struct convert_struct, double> { + static numeric_pair convert(const double & q) { + return numeric_pair(q, 0.0); + } + static bool is_epsilon_small(const numeric_pair & p, const double & eps) { + return std::abs(p.x) < eps && std::abs(p.y) < eps; + } + + static int compare_on_coord(const double & x, const double & bound, const double eps) { + if (bound == 0) return (x < - eps)? -1: (x > eps? 1 : 0); // it is an important special case + double relative = (bound > 0)? - eps: eps; + return (x < bound * (1.0 + relative) - eps)? -1 : ((x > bound * (1.0 - relative) + eps)? 1 : 0); + } + + static bool below_bound_numeric(const numeric_pair & x, const numeric_pair & bound, const double & eps) { + int r = compare_on_coord(x.x, bound.x, eps); + if (r == 1) return false; + if (r == -1) return true; + // the first coordinates are almost the same + return compare_on_coord(x.y, bound.y, eps) == -1; + } + + static bool above_bound_numeric(const numeric_pair & x, const numeric_pair & bound, const double & eps) { + int r = compare_on_coord(x.x, bound.x, eps); + if (r == -1) return false; + if (r == 1) return true; + // the first coordinates are almost the same + return compare_on_coord(x.y, bound.y, eps) == 1; + } +}; + +template <> +struct convert_struct { + static bool is_epsilon_small(const double& x, const double & eps) { + return x < eps && x > -eps; + } + static double convert(const double & y){ return y;} + static bool below_bound_numeric(const double & x, const double & bound, const double & eps) { + if (bound == 0) return x < - eps; + double relative = (bound > 0)? - eps: eps; + return x < bound * (1.0 + relative) - eps; + } + static bool above_bound_numeric(const double & x, const double & bound, const double & eps) { + if (bound == 0) return x > eps; + double relative = (bound > 0)? eps: - eps; + return x > bound * (1.0 + relative) + eps; + } +}; + +template bool is_epsilon_small(const X & v, const double &eps) { return convert_struct::is_epsilon_small(v, eps);} +template bool below_bound_numeric(const X & x, const X & bound, const double& eps) { return convert_struct::below_bound_numeric(x, bound, eps);} +template bool above_bound_numeric(const X & x, const X & bound, const double& eps) { return convert_struct::above_bound_numeric(x, bound, eps);} +} diff --git a/src/util/lp/permutation_matrix.h b/src/util/lp/permutation_matrix.h new file mode 100644 index 000000000..6c0367482 --- /dev/null +++ b/src/util/lp/permutation_matrix.h @@ -0,0 +1,173 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#pragma once +#include "util/vector.h" +#include +#include "util/debug.h" +#include +#include "util/lp/sparse_vector.h" +#include "util/lp/indexed_vector.h" +#include "util/lp/lp_settings.h" +#include "util/lp/matrix.h" +#include "util/lp/tail_matrix.h" +namespace lean { +#ifdef LEAN_DEBUG + inline bool is_even(int k) { return (k/2)*2 == k; } +#endif + + template +class permutation_matrix : public tail_matrix { + vector m_permutation; + vector m_rev; + vector m_work_array; + vector m_T_buffer; + vector m_X_buffer; + + + class ref { + permutation_matrix & m_p; + unsigned m_i; + public: + ref(permutation_matrix & m, unsigned i):m_p(m), m_i(i) {} + + ref & operator=(unsigned v) { m_p.set_val(m_i, v); return *this; } + + ref & operator=(ref const & v) { + m_p.set_val(m_i, v.m_p.m_permutation[v.m_i]); + return *this; + } + operator unsigned & () const { return m_p.m_permutation[m_i]; } + }; + + public: + permutation_matrix() {} + permutation_matrix(unsigned length); + + permutation_matrix(unsigned length, vector const & values); + // create a unit permutation of the given length + void init(unsigned length); + unsigned get_rev(unsigned i) { return m_rev[i]; } + bool is_dense() const { return false; } +#ifdef LEAN_DEBUG + permutation_matrix get_inverse() const { + return permutation_matrix(size(), m_rev); + } + void print(std::ostream & out) const; +#endif + + ref operator[](unsigned i) { return ref(*this, i); } + + unsigned operator[](unsigned i) const { return m_permutation[i]; } + + void apply_from_left(vector & w, lp_settings &); + + void apply_from_left_to_T(indexed_vector & w, lp_settings & settings); + + void apply_from_right(vector & w); + + void apply_from_right(indexed_vector & w); + + template + void copy_aside(vector & t, vector & tmp_index, indexed_vector & w); + + template + void clear_data(indexed_vector & w); + + template + void apply_reverse_from_left(indexed_vector & w); + + void apply_reverse_from_left_to_T(vector & w); + void apply_reverse_from_left_to_X(vector & w); + + void apply_reverse_from_right_to_T(vector & w); + void apply_reverse_from_right_to_T(indexed_vector & w); + void apply_reverse_from_right_to_X(vector & w); + + void set_val(unsigned i, unsigned pi) { + lean_assert(i < size() && pi < size()); m_permutation[i] = pi; m_rev[pi] = i; } + + void transpose_from_left(unsigned i, unsigned j); + + unsigned apply_reverse(unsigned i) const { return m_rev[i]; } + + void transpose_from_right(unsigned i, unsigned j); +#ifdef LEAN_DEBUG + T get_elem(unsigned i, unsigned j) const{ + return m_permutation[i] == j? numeric_traits::one() : numeric_traits::zero(); + } + unsigned row_count() const{ return size(); } + unsigned column_count() const { return size(); } + virtual void set_number_of_rows(unsigned /*m*/) { } + virtual void set_number_of_columns(unsigned /*n*/) { } +#endif + void multiply_by_permutation_from_left(permutation_matrix & p); + + // this is multiplication in the matrix sense + void multiply_by_permutation_from_right(permutation_matrix & p); + + void multiply_by_reverse_from_right(permutation_matrix & q); + + void multiply_by_permutation_reverse_from_left(permutation_matrix & r); + + void shrink_by_one_identity(); + + bool is_identity() const; + + unsigned size() const { return static_cast(m_rev.size()); } + + unsigned * values() const { return m_permutation; } + + void resize(unsigned size) { + unsigned old_size = m_permutation.size(); + m_permutation.resize(size); + m_rev.resize(size); + m_T_buffer.resize(size); + m_X_buffer.resize(size); + for (unsigned i = old_size; i < size; i++) { + m_permutation[i] = m_rev[i] = i; + } + } + + }; // end of the permutation class + +#ifdef LEAN_DEBUG +template +class permutation_generator { + unsigned m_n; + permutation_generator* m_lower; + bool m_done = false; + permutation_matrix m_current; + unsigned m_last; +public: + permutation_generator(unsigned n); + permutation_generator(const permutation_generator & o); + bool move_next(); + + ~permutation_generator() { + if (m_lower != nullptr) { + delete m_lower; + } + } + + permutation_matrix *current() { + return &m_current; + } +}; + +template +inline unsigned number_of_inversions(permutation_matrix & p); + +template +int sign(permutation_matrix & p) { + return is_even(number_of_inversions(p))? 1: -1; +} + +template +T det_val_on_perm(permutation_matrix* u, const matrix& m); + +template +T determinant(const matrix& m); +#endif +} diff --git a/src/util/lp/permutation_matrix.hpp b/src/util/lp/permutation_matrix.hpp new file mode 100644 index 000000000..09435674d --- /dev/null +++ b/src/util/lp/permutation_matrix.hpp @@ -0,0 +1,419 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include "util/vector.h" +#include "util/lp/permutation_matrix.h" +namespace lean { +template permutation_matrix::permutation_matrix(unsigned length): m_permutation(length), m_rev(length), m_T_buffer(length), m_X_buffer(length) { + for (unsigned i = 0; i < length; i++) { // do not change the direction of the loop because of the vectorization bug in clang3.3 + m_permutation[i] = m_rev[i] = i; + } +} + +template permutation_matrix::permutation_matrix(unsigned length, vector const & values): m_permutation(length), m_rev(length) , m_T_buffer(length), m_X_buffer(length) { + for (unsigned i = 0; i < length; i++) { + set_val(i, values[i]); + } +} +// create a unit permutation of the given length +template void permutation_matrix::init(unsigned length) { + m_permutation.resize(length); + m_rev.resize(length); + m_T_buffer.resize(length); + m_X_buffer.resize(length); + for (unsigned i = 0; i < length; i++) { + m_permutation[i] = m_rev[i] = i; + } +} + +#ifdef LEAN_DEBUG +template void permutation_matrix::print(std::ostream & out) const { + out << "["; + for (unsigned i = 0; i < size(); i++) { + out << m_permutation[i]; + if (i < size() - 1) { + out << ","; + } else { + out << "]"; + } + } + out << std::endl; +} +#endif + +template +void permutation_matrix::apply_from_left(vector & w, lp_settings & ) { +#ifdef LEAN_DEBUG + // dense_matrix deb(*this); + // L * deb_w = clone_vector(w, row_count()); + // deb.apply_from_left(deb_w); +#endif + // std::cout << " apply_from_left " << std::endl; + lean_assert(m_X_buffer.size() == w.size()); + unsigned i = size(); + while (i-- > 0) { + m_X_buffer[i] = w[m_permutation[i]]; + } + i = size(); + while (i-- > 0) { + w[i] = m_X_buffer[i]; + } +#ifdef LEAN_DEBUG + // lean_assert(vectors_are_equal(deb_w, w, row_count())); + // delete [] deb_w; +#endif +} + +template +void permutation_matrix::apply_from_left_to_T(indexed_vector & w, lp_settings & ) { + vector t(w.m_index.size()); + vector tmp_index(w.m_index.size()); + copy_aside(t, tmp_index, w); // todo: is it too much copying + clear_data(w); + // set the new values + for (unsigned i = static_cast(t.size()); i > 0;) { + i--; + unsigned j = m_rev[tmp_index[i]]; + w[j] = t[i]; + w.m_index[i] = j; + } +} + +template void permutation_matrix::apply_from_right(vector & w) { +#ifdef LEAN_DEBUG + // dense_matrix deb(*this); + // T * deb_w = clone_vector(w, row_count()); + // deb.apply_from_right(deb_w); +#endif + lean_assert(m_T_buffer.size() == w.size()); + for (unsigned i = 0; i < size(); i++) { + m_T_buffer[i] = w[m_rev[i]]; + } + + for (unsigned i = 0; i < size(); i++) { + w[i] = m_T_buffer[i]; + } +#ifdef LEAN_DEBUG + // lean_assert(vectors_are_equal(deb_w, w, row_count())); + // delete [] deb_w; +#endif +} + +template void permutation_matrix::apply_from_right(indexed_vector & w) { +#ifdef LEAN_DEBUG + vector wcopy(w.m_data); + apply_from_right(wcopy); +#endif + vector buffer(w.m_index.size()); + vector index_copy(w.m_index); + for (unsigned i = 0; i < w.m_index.size(); i++) { + buffer[i] = w.m_data[w.m_index[i]]; + } + w.clear(); + + for (unsigned i = 0; i < index_copy.size(); i++) { + unsigned j = index_copy[i]; + unsigned pj = m_permutation[j]; + w.set_value(buffer[i], pj); + } + lean_assert(w.is_OK()); +#ifdef LEAN_DEBUG + lean_assert(vectors_are_equal(wcopy, w.m_data)); +#endif +} + + +template template +void permutation_matrix::copy_aside(vector & t, vector & tmp_index, indexed_vector & w) { + for (unsigned i = static_cast(t.size()); i > 0;) { + i--; + unsigned j = w.m_index[i]; + t[i] = w[j]; // copy aside all non-zeroes + tmp_index[i] = j; // and the indices too + } +} + +template template +void permutation_matrix::clear_data(indexed_vector & w) { + // clear old non-zeroes + for (unsigned i = static_cast(w.m_index.size()); i > 0;) { + i--; + unsigned j = w.m_index[i]; + w[j] = zero_of_type(); + } +} + +template template +void permutation_matrix::apply_reverse_from_left(indexed_vector & w) { + // the result will be w = p(-1) * w +#ifdef LEAN_DEBUG + // dense_matrix deb(get_reverse()); + // L * deb_w = clone_vector(w.m_data, row_count()); + // deb.apply_from_left(deb_w); +#endif + vector t(w.m_index.size()); + vector tmp_index(w.m_index.size()); + + copy_aside(t, tmp_index, w); + clear_data(w); + + // set the new values + for (unsigned i = static_cast(t.size()); i > 0;) { + i--; + unsigned j = m_permutation[tmp_index[i]]; + w[j] = t[i]; + w.m_index[i] = j; + } +#ifdef LEAN_DEBUG + // lean_assert(vectors_are_equal(deb_w, w.m_data, row_count())); + // delete [] deb_w; +#endif +} + +template +void permutation_matrix::apply_reverse_from_left_to_T(vector & w) { + // the result will be w = p(-1) * w + lean_assert(m_T_buffer.size() == w.size()); + unsigned i = size(); + while (i-- > 0) { + m_T_buffer[m_permutation[i]] = w[i]; + } + i = size(); + while (i-- > 0) { + w[i] = m_T_buffer[i]; + } +} +template +void permutation_matrix::apply_reverse_from_left_to_X(vector & w) { + // the result will be w = p(-1) * w + lean_assert(m_X_buffer.size() == w.size()); + unsigned i = size(); + while (i-- > 0) { + m_X_buffer[m_permutation[i]] = w[i]; + } + i = size(); + while (i-- > 0) { + w[i] = m_X_buffer[i]; + } +} + +template +void permutation_matrix::apply_reverse_from_right_to_T(vector & w) { + // the result will be w = w * p(-1) + lean_assert(m_T_buffer.size() == w.size()); + unsigned i = size(); + while (i-- > 0) { + m_T_buffer[i] = w[m_permutation[i]]; + } + i = size(); + while (i-- > 0) { + w[i] = m_T_buffer[i]; + } +} + +template +void permutation_matrix::apply_reverse_from_right_to_T(indexed_vector & w) { + // the result will be w = w * p(-1) +#ifdef LEAN_DEBUG + // vector wcopy(w.m_data); + // apply_reverse_from_right_to_T(wcopy); +#endif + lean_assert(w.is_OK()); + vector tmp; + vector tmp_index(w.m_index); + for (auto i : w.m_index) { + tmp.push_back(w[i]); + } + w.clear(); + + for (unsigned k = 0; k < tmp_index.size(); k++) { + unsigned j = tmp_index[k]; + w.set_value(tmp[k], m_rev[j]); + } + + // lean_assert(w.is_OK()); + // lean_assert(vectors_are_equal(w.m_data, wcopy)); +} + + +template +void permutation_matrix::apply_reverse_from_right_to_X(vector & w) { + // the result will be w = w * p(-1) + lean_assert(m_X_buffer.size() == w.size()); + unsigned i = size(); + while (i-- > 0) { + m_X_buffer[i] = w[m_permutation[i]]; + } + i = size(); + while (i-- > 0) { + w[i] = m_X_buffer[i]; + } +} + +template void permutation_matrix::transpose_from_left(unsigned i, unsigned j) { + // the result will be this = (i,j)*this + lean_assert(i < size() && j < size() && i != j); + auto pi = m_rev[i]; + auto pj = m_rev[j]; + set_val(pi, j); + set_val(pj, i); +} + +template void permutation_matrix::transpose_from_right(unsigned i, unsigned j) { + // the result will be this = this * (i,j) + lean_assert(i < size() && j < size() && i != j); + auto pi = m_permutation[i]; + auto pj = m_permutation[j]; + set_val(i, pj); + set_val(j, pi); +} + +template void permutation_matrix::multiply_by_permutation_from_left(permutation_matrix & p) { + m_work_array = m_permutation; + lean_assert(p.size() == size()); + unsigned i = size(); + while (i-- > 0) { + set_val(i, m_work_array[p[i]]); // we have m(P)*m(Q) = m(QP), where m is the matrix of the permutation + } +} + +// this is multiplication in the matrix sense +template void permutation_matrix::multiply_by_permutation_from_right(permutation_matrix & p) { + m_work_array = m_permutation; + lean_assert(p.size() == size()); + unsigned i = size(); + while (i-- > 0) + set_val(i, p[m_work_array[i]]); // we have m(P)*m(Q) = m(QP), where m is the matrix of the permutation + +} + +template void permutation_matrix::multiply_by_reverse_from_right(permutation_matrix & q){ // todo : condensed permutations ? + lean_assert(q.size() == size()); + m_work_array = m_permutation; + // the result is this = this*q(-1) + unsigned i = size(); + while (i-- > 0) { + set_val(i, q.m_rev[m_work_array[i]]); // we have m(P)*m(Q) = m(QP), where m is the matrix of the permutation + } +} + +template void permutation_matrix::multiply_by_permutation_reverse_from_left(permutation_matrix & r){ // todo : condensed permutations? + // the result is this = r(-1)*this + m_work_array = m_permutation; + // the result is this = this*q(-1) + unsigned i = size(); + while (i-- > 0) { + set_val(i, m_work_array[r.m_rev[i]]); + } +} + + +template bool permutation_matrix::is_identity() const { + unsigned i = size(); + while (i-- > 0) { + if (m_permutation[i] != i) { + return false; + } + } + return true; +} + + +#ifdef LEAN_DEBUG +template +permutation_generator::permutation_generator(unsigned n): m_n(n), m_current(n) { + lean_assert(n > 0); + if (n > 1) { + m_lower = new permutation_generator(n - 1); + } else { + m_lower = nullptr; + } + + m_last = 0; +} + +template +permutation_generator::permutation_generator(const permutation_generator & o): m_n(o.m_n), m_done(o.m_done), m_current(o.m_current), m_last(o.m_last) { + if (m_lower != nullptr) { + m_lower = new permutation_generator(o.m_lower); + } else { + m_lower = nullptr; + } +} + +template bool +permutation_generator::move_next() { + if (m_done) { + return false; + } + + if (m_lower == nullptr) { + if (m_last == 0) { + m_last++; + return true; + } else { + m_done = true; + return false; + } + } else { + if (m_last < m_n && m_last > 0) { + m_current[m_last - 1] = m_current[m_last]; + m_current[m_last] = m_n - 1; + m_last++; + return true; + } else { + if (m_lower -> move_next()) { + auto lower_curr = m_lower -> current(); + for ( unsigned i = 1; i < m_n; i++ ){ + m_current[i] = (*lower_curr)[i - 1]; + } + m_current[0] = m_n - 1; + m_last = 1; + return true; + } else { + m_done = true; + return false; + } + } + } +} + +template +inline unsigned number_of_inversions(permutation_matrix & p) { + unsigned ret = 0; + unsigned n = p.size(); + for (unsigned i = 0; i < n; i++) { + for (unsigned j = i + 1; j < n; j++) { + if (p[i] > p[j]) { + ret++; + } + } + } + return ret; +} + +template +T det_val_on_perm(permutation_matrix* u, const matrix& m) { + unsigned n = m.row_count(); + T ret = numeric_traits::one(); + for (unsigned i = 0; i < n; i++) { + unsigned j = (*u)[i]; + ret *= m(i, j); + } + return ret * sign(*u); +} + +template +T determinant(const matrix& m) { + lean_assert(m.column_count() == m.row_count()); + unsigned n = m.row_count(); + permutation_generator allp(n); + T ret = numeric_traits::zero(); + while (allp.move_next()){ + ret += det_val_on_perm(allp.current(), m); + } + return ret; +} +#endif +} diff --git a/src/util/lp/permutation_matrix_instances.cpp b/src/util/lp/permutation_matrix_instances.cpp new file mode 100644 index 000000000..e0cc1a144 --- /dev/null +++ b/src/util/lp/permutation_matrix_instances.cpp @@ -0,0 +1,60 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include +#include "util/vector.h" +#include "util/lp/permutation_matrix.hpp" +#include "util/lp/numeric_pair.h" +template void lean::permutation_matrix::apply_from_right(vector&); +template void lean::permutation_matrix::init(unsigned int); +template void lean::permutation_matrix::init(unsigned int); +template void lean::permutation_matrix>::init(unsigned int); +template bool lean::permutation_matrix::is_identity() const; +template void lean::permutation_matrix::multiply_by_permutation_from_left(lean::permutation_matrix&); +template void lean::permutation_matrix::multiply_by_permutation_reverse_from_left(lean::permutation_matrix&); +template void lean::permutation_matrix::multiply_by_reverse_from_right(lean::permutation_matrix&); +template lean::permutation_matrix::permutation_matrix(unsigned int, vector const&); +template void lean::permutation_matrix::transpose_from_left(unsigned int, unsigned int); + +template void lean::permutation_matrix::apply_from_right(vector&); +template bool lean::permutation_matrix::is_identity() const; +template void lean::permutation_matrix::multiply_by_permutation_from_left(lean::permutation_matrix&); +template void lean::permutation_matrix::multiply_by_permutation_from_right(lean::permutation_matrix&); +template void lean::permutation_matrix::multiply_by_permutation_reverse_from_left(lean::permutation_matrix&); +template void lean::permutation_matrix::multiply_by_reverse_from_right(lean::permutation_matrix&); +template lean::permutation_matrix::permutation_matrix(unsigned int); +template void lean::permutation_matrix::transpose_from_left(unsigned int, unsigned int); +template void lean::permutation_matrix::transpose_from_right(unsigned int, unsigned int); +template void lean::permutation_matrix >::apply_from_right(vector&); +template bool lean::permutation_matrix >::is_identity() const; +template void lean::permutation_matrix >::multiply_by_permutation_from_left(lean::permutation_matrix >&); +template void lean::permutation_matrix >::multiply_by_permutation_from_right(lean::permutation_matrix >&); +template void lean::permutation_matrix >::multiply_by_permutation_reverse_from_left(lean::permutation_matrix >&); +template void lean::permutation_matrix >::multiply_by_reverse_from_right(lean::permutation_matrix >&); +template lean::permutation_matrix >::permutation_matrix(unsigned int); +template void lean::permutation_matrix >::transpose_from_left(unsigned int, unsigned int); +template void lean::permutation_matrix >::transpose_from_right(unsigned int, unsigned int); +template void lean::permutation_matrix::apply_reverse_from_left(lean::indexed_vector&); +template void lean::permutation_matrix::apply_reverse_from_left_to_T(vector&); +template void lean::permutation_matrix::apply_reverse_from_right_to_T(vector&); +template void lean::permutation_matrix::transpose_from_right(unsigned int, unsigned int); +template void lean::permutation_matrix::apply_reverse_from_left(lean::indexed_vector&); +template void lean::permutation_matrix::apply_reverse_from_left_to_T(vector&); +template void lean::permutation_matrix::apply_reverse_from_right_to_T(vector&); +template void lean::permutation_matrix >::apply_reverse_from_left(lean::indexed_vector&); +template void lean::permutation_matrix >::apply_reverse_from_left_to_T(vector&); +template void lean::permutation_matrix >::apply_reverse_from_right_to_T(vector&); +template void lean::permutation_matrix::multiply_by_permutation_from_right(lean::permutation_matrix&); + +#ifdef LEAN_DEBUG +template bool lean::permutation_generator::move_next(); +template lean::permutation_generator::permutation_generator(unsigned int); +#endif +template lean::permutation_matrix::permutation_matrix(unsigned int); +template void lean::permutation_matrix::apply_reverse_from_left_to_X(vector &); +template void lean::permutation_matrix< lean::mpq, lean::mpq>::apply_reverse_from_left_to_X(vector &); +template void lean::permutation_matrix< lean::mpq, lean::numeric_pair< lean::mpq> >::apply_reverse_from_left_to_X(vector> &); +template void lean::permutation_matrix::apply_reverse_from_right_to_T(lean::indexed_vector&); +template void lean::permutation_matrix::apply_reverse_from_right_to_T(lean::indexed_vector&); +template void lean::permutation_matrix >::apply_reverse_from_right_to_T(lean::indexed_vector&); diff --git a/src/util/lp/quick_xplain.cpp b/src/util/lp/quick_xplain.cpp new file mode 100644 index 000000000..a4b6fb0e6 --- /dev/null +++ b/src/util/lp/quick_xplain.cpp @@ -0,0 +1,138 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include "util/lp/lar_solver.h" +namespace lean { +quick_xplain::quick_xplain(vector> & explanation, const lar_solver & ls, lar_solver & qsol) : + m_explanation(explanation), + m_parent_solver(ls), + m_qsol(qsol) { +} +void quick_xplain::add_constraint_to_qsol(unsigned j) { + auto & lar_c = m_constraints_in_local_vars[j]; + auto ls = lar_c.get_left_side_coefficients(); + auto ci = m_qsol.add_constraint(ls, lar_c.m_kind, lar_c.m_right_side); + m_local_ci_to_constraint_offsets[ci] = j; +} + +void quick_xplain::copy_constraint_and_add_constraint_vars(const lar_constraint& lar_c) { + vector < std::pair> ls; + for (auto & p : lar_c.get_left_side_coefficients()) { + unsigned j = p.second; + unsigned lj = m_qsol.add_var(j); + ls.push_back(std::make_pair(p.first, lj)); + } + m_constraints_in_local_vars.push_back(lar_constraint(ls, lar_c.m_kind, lar_c.m_right_side)); + +} + +bool quick_xplain::infeasible() { + m_qsol.solve(); + return m_qsol.get_status() == INFEASIBLE; +} + +// u - unexplored constraints +// c and x are assumed, in other words, all constrains of x and c are already added to m_qsol +void quick_xplain::minimize(const vector& u) { + unsigned k = 0; + unsigned initial_stack_size = m_qsol.constraint_stack_size(); + for (; k < u.size();k++) { + m_qsol.push(); + add_constraint_to_qsol(u[k]); + if (infeasible()) + break; + } + m_x.insert(u[k]); + unsigned m = k / 2; // the split + if (m < k) { + m_qsol.pop(k + 1 - m); + add_constraint_to_qsol(u[k]); + if (!infeasible()) { + vector un; + for (unsigned j = m; j < k; j++) + un.push_back(u[j]); + minimize(un); + } + } + if (m > 0) { + lean_assert(m_qsol.constraint_stack_size() >= initial_stack_size); + m_qsol.pop(m_qsol.constraint_stack_size() - initial_stack_size); + for (auto j : m_x) + add_constraint_to_qsol(j); + if (!infeasible()) { + vector un; + for (unsigned j = 0; j < m; j++) + un.push_back(u[j]); + minimize(un); + } + } +} + + +void quick_xplain::run(vector> & explanation, const lar_solver & ls){ + if (explanation.size() <= 2) return; + lar_solver qsol; + lean_assert(ls.explanation_is_correct(explanation)); + quick_xplain q(explanation, ls, qsol); + q.solve(); +} + +void quick_xplain::copy_constraints_to_local_constraints() { + for (auto & p : m_explanation) { + const auto & lar_c = m_parent_solver.get_constraint(p.second); + m_local_constraint_offset_to_external_ci.push_back(p.second); + copy_constraint_and_add_constraint_vars(lar_c); + } +} + +bool quick_xplain::is_feasible(const vector & x, unsigned k) const { + lar_solver l; + for (unsigned i : x) { + if (i == k) + continue; + vector < std::pair> ls; + const lar_constraint & c = m_constraints_in_local_vars[i]; + for (auto & p : c.get_left_side_coefficients()) { + unsigned lj = l.add_var(p.second); + ls.push_back(std::make_pair(p.first, lj)); + } + l.add_constraint(ls, c.m_kind, c.m_right_side); + } + l.solve(); + return l.get_status() != INFEASIBLE; +} + +bool quick_xplain::x_is_minimal() const { + vector x; + for (auto j : m_x) + x.push_back(j); + + for (unsigned k = 0; k < x.size(); k++) { + lean_assert(is_feasible(x, x[k])); + } + return true; +} + +void quick_xplain::solve() { + copy_constraints_to_local_constraints(); + m_qsol.push(); + lean_assert(m_qsol.constraint_count() == 0) + vector u; + for (unsigned k = 0; k < m_constraints_in_local_vars.size(); k++) + u.push_back(k); + minimize(u); + while (m_qsol.constraint_count() > 0) + m_qsol.pop(); + for (unsigned i : m_x) + add_constraint_to_qsol(i); + m_qsol.solve(); + lean_assert(m_qsol.get_status() == INFEASIBLE); + m_qsol.get_infeasibility_explanation(m_explanation); + lean_assert(m_qsol.explanation_is_correct(m_explanation)); + lean_assert(x_is_minimal()); + for (auto & p : m_explanation) { + p.second = this->m_local_constraint_offset_to_external_ci[m_local_ci_to_constraint_offsets[p.second]]; + } +} +} diff --git a/src/util/lp/quick_xplain.h b/src/util/lp/quick_xplain.h new file mode 100644 index 000000000..9faa5f41c --- /dev/null +++ b/src/util/lp/quick_xplain.h @@ -0,0 +1,33 @@ +/* +Copyright (c) 2017 Microsoft Corporation +Author: Lev Nachmanson +*/ + +#pragma once +#include "util/vector.h" +#include + +namespace lean { + class lar_solver; // forward definition + + class quick_xplain { + std::unordered_set m_x; // the minimal set of constraints, the core - it is empty at the begining + vector m_constraints_in_local_vars; + vector> & m_explanation; + const lar_solver& m_parent_solver; + lar_solver & m_qsol; + vector m_local_constraint_offset_to_external_ci; + std::unordered_map m_local_ci_to_constraint_offsets; + quick_xplain(vector> & explanation, const lar_solver & parent_lar_solver, lar_solver & qsol); + void minimize(const vector & u); + void add_constraint_to_qsol(unsigned j); + void copy_constraint_and_add_constraint_vars(const lar_constraint& lar_c); + void copy_constraints_to_local_constraints(); + bool infeasible(); + bool is_feasible(const vector & x, unsigned k) const; + bool x_is_minimal() const; + public: + static void run(vector> & explanation,const lar_solver & ls); + void solve(); + }; +} diff --git a/src/util/lp/random_updater.h b/src/util/lp/random_updater.h new file mode 100644 index 000000000..3dbab8323 --- /dev/null +++ b/src/util/lp/random_updater.h @@ -0,0 +1,77 @@ +/* +Copyright (c) 2017 Microsoft Corporation +Author: Lev Nachmanson +*/ +#pragma once +#include +#include "util/vector.h" +#include +#include +#include +#include "util/lp/lp_settings.h" +#include "util/lp/linear_combination_iterator.h" +// see http://research.microsoft.com/projects/z3/smt07.pdf +// The class searches for a feasible solution with as many different values of variables as it can find +namespace lean { +template struct numeric_pair; // forward definition +class lar_core_solver; // forward definition +class random_updater { + unsigned range = 100000; + struct interval { + bool upper_bound_is_set = false; + numeric_pair upper_bound; + bool low_bound_is_set = false; + numeric_pair low_bound; + void set_low_bound(const numeric_pair & v) { + if (low_bound_is_set) { + low_bound = std::max(v, low_bound); + } else { + low_bound = v; + low_bound_is_set = true; + } + } + void set_upper_bound(const numeric_pair & v) { + if (upper_bound_is_set) { + upper_bound = std::min(v, upper_bound); + } else { + upper_bound = v; + upper_bound_is_set = true; + } + } + bool is_empty() const {return + upper_bound_is_set && low_bound_is_set && low_bound >= upper_bound; + } + + bool low_bound_holds(const numeric_pair & a) const { + return low_bound_is_set == false || a >= low_bound; + } + bool upper_bound_holds(const numeric_pair & a) const { + return upper_bound_is_set == false || a <= upper_bound; + } + + bool contains(const numeric_pair & a) const { + return low_bound_holds(a) && upper_bound_holds(a); + } + std::string lbs() { return low_bound_is_set ? T_to_string(low_bound):std::string("inf");} + std::string rbs() { return upper_bound_is_set? T_to_string(upper_bound):std::string("inf");} + std::string to_str() { return std::string("[")+ lbs() + ", " + rbs() + "]";} + }; + std::set m_var_set; + lar_core_solver & m_core_solver; + linear_combination_iterator* m_column_j; // the actual column + interval find_shift_interval(unsigned j); + interval get_interval_of_non_basic_var(unsigned j); + void add_column_to_sets(unsigned j); + void random_shift_var(unsigned j); + std::unordered_map, unsigned> m_values; // it maps a value to the number of time it occurs + void diminish_interval_to_leave_basic_vars_feasible(numeric_pair &nb_x, interval & inter); + void shift_var(unsigned j, interval & r); + void diminish_interval_for_basic_var(numeric_pair &nb_x, unsigned j, mpq & a, interval & r); + numeric_pair get_random_from_interval(interval & r); + void add_value(numeric_pair& v); + void remove_value(numeric_pair & v); + public: + random_updater(lar_core_solver & core_solver, const vector & column_list); + void update(); +}; +} diff --git a/src/util/lp/random_updater.hpp b/src/util/lp/random_updater.hpp new file mode 100644 index 000000000..67bfcc49b --- /dev/null +++ b/src/util/lp/random_updater.hpp @@ -0,0 +1,205 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include "util/lp/random_updater.h" +#include "util/lp/static_matrix.h" +#include "util/lp/lar_solver.h" +#include "util/vector.h" +namespace lean { + + + +random_updater::random_updater( + lar_core_solver & lar_core_solver, + const vector & column_indices) : m_core_solver(lar_core_solver) { + for (unsigned j : column_indices) + add_column_to_sets(j); +} + +random_updater::interval random_updater::get_interval_of_non_basic_var(unsigned j) { + interval ret; + switch (m_core_solver.get_column_type(j)) { + case column_type::free_column: + break; + case column_type::low_bound: + ret.set_low_bound(m_core_solver.m_r_low_bounds[j]); + break; + case column_type::upper_bound: + ret.set_upper_bound(m_core_solver.m_r_upper_bounds[j]); + break; + case column_type::boxed: + case column_type::fixed: + ret.set_low_bound(m_core_solver.m_r_low_bounds[j]); + ret.set_upper_bound(m_core_solver.m_r_upper_bounds[j]); + break; + default: + lean_assert(false); + } + return ret; +} + +void random_updater::diminish_interval_for_basic_var(numeric_pair& nb_x, unsigned j, + mpq & a, + interval & r) { + lean_assert(m_core_solver.m_r_heading[j] >= 0); + numeric_pair delta; + lean_assert(a != zero_of_type()); + switch (m_core_solver.get_column_type(j)) { + case column_type::free_column: + break; + case column_type::low_bound: + delta = m_core_solver.m_r_x[j] - m_core_solver.m_r_low_bounds[j]; + lean_assert(delta >= zero_of_type>()); + if (a > 0) { + r.set_upper_bound(nb_x + delta / a); + } else { + r.set_low_bound(nb_x + delta / a); + } + break; + case column_type::upper_bound: + delta = m_core_solver.m_r_upper_bounds()[j] - m_core_solver.m_r_x[j]; + lean_assert(delta >= zero_of_type>()); + if (a > 0) { + r.set_low_bound(nb_x - delta / a); + } else { + r.set_upper_bound(nb_x - delta / a); + } + break; + case column_type::boxed: + if (a > 0) { + delta = m_core_solver.m_r_x[j] - m_core_solver.m_r_low_bounds[j]; + lean_assert(delta >= zero_of_type>()); + r.set_upper_bound(nb_x + delta / a); + delta = m_core_solver.m_r_upper_bounds()[j] - m_core_solver.m_r_x[j]; + lean_assert(delta >= zero_of_type>()); + r.set_low_bound(nb_x - delta / a); + } else { // a < 0 + delta = m_core_solver.m_r_upper_bounds()[j] - m_core_solver.m_r_x[j]; + lean_assert(delta >= zero_of_type>()); + r.set_upper_bound(nb_x - delta / a); + delta = m_core_solver.m_r_x[j] - m_core_solver.m_r_low_bounds[j]; + lean_assert(delta >= zero_of_type>()); + r.set_low_bound(nb_x + delta / a); + } + break; + case column_type::fixed: + r.set_low_bound(nb_x); + r.set_upper_bound(nb_x); + break; + default: + lean_assert(false); + } +} + + +void random_updater::diminish_interval_to_leave_basic_vars_feasible(numeric_pair &nb_x, interval & r) { + m_column_j->reset(); + unsigned i; + mpq a; + while (m_column_j->next(a, i)) { + diminish_interval_for_basic_var(nb_x, m_core_solver.m_r_basis[i], a, r); + if (r.is_empty()) + break; + } +} + +random_updater::interval random_updater::find_shift_interval(unsigned j) { + interval ret = get_interval_of_non_basic_var(j); + diminish_interval_to_leave_basic_vars_feasible(m_core_solver.m_r_x[j], ret); + return ret; +} + +void random_updater::shift_var(unsigned j, interval & r) { + lean_assert(r.contains(m_core_solver.m_r_x[j])); + lean_assert(m_core_solver.m_r_solver.column_is_feasible(j)); + auto old_x = m_core_solver.m_r_x[j]; + remove_value(old_x); + auto new_val = m_core_solver.m_r_x[j] = get_random_from_interval(r); + add_value(new_val); + + lean_assert(r.contains(m_core_solver.m_r_x[j])); + lean_assert(m_core_solver.m_r_solver.column_is_feasible(j)); + auto delta = m_core_solver.m_r_x[j] - old_x; + + unsigned i; + m_column_j->reset(); + mpq a; + while(m_column_j->next(a, i)) { + unsigned bj = m_core_solver.m_r_basis[i]; + m_core_solver.m_r_x[bj] -= a * delta; + lean_assert(m_core_solver.m_r_solver.column_is_feasible(bj)); + } + lean_assert(m_core_solver.m_r_solver.A_mult_x_is_off() == false); +} + +numeric_pair random_updater::get_random_from_interval(interval & r) { + unsigned rand = my_random(); + if ((!r.low_bound_is_set) && (!r.upper_bound_is_set)) + return numeric_pair(rand % range, 0); + if (r.low_bound_is_set && (!r.upper_bound_is_set)) + return r.low_bound + numeric_pair(rand % range, 0); + if ((!r.low_bound_is_set) && r.upper_bound_is_set) + return r.upper_bound - numeric_pair(rand % range, 0); + lean_assert(r.low_bound_is_set && r.upper_bound_is_set); + return r.low_bound + (rand % range) * (r.upper_bound - r.low_bound)/ range; +} + +void random_updater::random_shift_var(unsigned j) { + m_column_j = m_core_solver.get_column_iterator(j); + if (m_column_j->size() >= 50) { + delete m_column_j; + return; + } + interval interv = find_shift_interval(j); + if (interv.is_empty()) { + delete m_column_j; + return; + } + + shift_var(j, interv); + delete m_column_j; +} + +void random_updater::update() { + for (auto j : m_var_set) { + if (m_var_set.size() <= m_values.size()) { + break; // we are done + } + random_shift_var(j); + } +} + +void random_updater::add_value(numeric_pair& v) { + auto it = m_values.find(v); + if (it == m_values.end()) { + m_values[v] = 1; + } else { + it->second++; + } +} + +void random_updater::remove_value(numeric_pair& v) { + auto it = m_values.find(v); + lean_assert(it != m_values.end()); + it->second--; + if (it->second == 0) + m_values.erase(it); +} + +void random_updater::add_column_to_sets(unsigned j) { + if (m_core_solver.m_r_heading[j] < 0) { + m_var_set.insert(j); + add_value(m_core_solver.m_r_x[j]); + } else { + unsigned row = m_core_solver.m_r_heading[j]; + for (auto row_c : m_core_solver.m_r_A.m_rows[row]) { + unsigned cj = row_c.m_j; + if (m_core_solver.m_r_heading[cj] < 0) { + m_var_set.insert(cj); + add_value(m_core_solver.m_r_x[cj]); + } + } + } +} +} diff --git a/src/util/lp/random_updater_instances.cpp b/src/util/lp/random_updater_instances.cpp new file mode 100644 index 000000000..5b4c89bd5 --- /dev/null +++ b/src/util/lp/random_updater_instances.cpp @@ -0,0 +1,5 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include "util/lp/random_updater.hpp" diff --git a/src/util/lp/row_eta_matrix.h b/src/util/lp/row_eta_matrix.h new file mode 100644 index 000000000..90acb89f3 --- /dev/null +++ b/src/util/lp/row_eta_matrix.h @@ -0,0 +1,74 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ + +#pragma once +#include "util/vector.h" +#include "util/debug.h" +#include +#include "util/lp/sparse_vector.h" +#include "util/lp/indexed_vector.h" +#include "util/lp/permutation_matrix.h" +namespace lean { + // This is the sum of a unit matrix and a lower triangular matrix + // with non-zero elements only in one row +template +class row_eta_matrix + : public tail_matrix { +#ifdef LEAN_DEBUG + unsigned m_dimension; +#endif + unsigned m_row_start; + unsigned m_row; + sparse_vector m_row_vector; +public: +#ifdef LEAN_DEBUG + row_eta_matrix(unsigned row_start, unsigned row, unsigned dim): +#else + row_eta_matrix(unsigned row_start, unsigned row): +#endif + +#ifdef LEAN_DEBUG + m_dimension(dim), +#endif + m_row_start(row_start), m_row(row) { + } + + bool is_dense() const { return false; } + + void print(std::ostream & out) { + print_matrix(*this, out); + } + + const T & get_diagonal_element() const { + return m_row_vector.m_data[m_row]; + } + + void apply_from_left(vector & w, lp_settings &); + + void apply_from_left_local_to_T(indexed_vector & w, lp_settings & settings); + void apply_from_left_local_to_X(indexed_vector & w, lp_settings & settings); + + void apply_from_left_to_T(indexed_vector & w, lp_settings & settings) { + apply_from_left_local_to_T(w, settings); + } + + void push_back(unsigned row_index, T val ) { + lean_assert(row_index != m_row); + m_row_vector.push_back(row_index, val); + } + + void apply_from_right(vector & w); + void apply_from_right(indexed_vector & w); + + void conjugate_by_permutation(permutation_matrix & p); +#ifdef LEAN_DEBUG + T get_elem(unsigned row, unsigned col) const; + unsigned row_count() const { return m_dimension; } + unsigned column_count() const { return m_dimension; } + void set_number_of_rows(unsigned m) { m_dimension = m; } + void set_number_of_columns(unsigned n) { m_dimension = n; } +#endif +}; // end of row_eta_matrix +} diff --git a/src/util/lp/row_eta_matrix.hpp b/src/util/lp/row_eta_matrix.hpp new file mode 100644 index 000000000..5758abeb8 --- /dev/null +++ b/src/util/lp/row_eta_matrix.hpp @@ -0,0 +1,171 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include "util/vector.h" +#include "util/lp/row_eta_matrix.h" +namespace lean { +template +void row_eta_matrix::apply_from_left(vector & w, lp_settings &) { + // #ifdef LEAN_DEBUG + // dense_matrix deb(*this); + // auto clone_w = clone_vector(w, m_dimension); + // deb.apply_from_left(clone_w, settings); + // #endif + + auto & w_at_row = w[m_row]; + for (auto & it : m_row_vector.m_data) { + w_at_row += w[it.first] * it.second; + } + // w[m_row] = w_at_row; + // #ifdef LEAN_DEBUG + // lean_assert(vectors_are_equal(clone_w, w, m_dimension)); + // delete [] clone_w; + // #endif +} + +template +void row_eta_matrix::apply_from_left_local_to_T(indexed_vector & w, lp_settings & settings) { + auto w_at_row = w[m_row]; + bool was_zero_at_m_row = is_zero(w_at_row); + + for (auto & it : m_row_vector.m_data) { + w_at_row += w[it.first] * it.second; + } + + if (!settings.abs_val_is_smaller_than_drop_tolerance(w_at_row)){ + if (was_zero_at_m_row) { + w.m_index.push_back(m_row); + } + w[m_row] = w_at_row; + } else if (!was_zero_at_m_row){ + w[m_row] = zero_of_type(); + auto it = std::find(w.m_index.begin(), w.m_index.end(), m_row); + w.m_index.erase(it); + } + // TBD: lean_assert(check_vector_for_small_values(w, settings)); +} + +template +void row_eta_matrix::apply_from_left_local_to_X(indexed_vector & w, lp_settings & settings) { + auto w_at_row = w[m_row]; + bool was_zero_at_m_row = is_zero(w_at_row); + + for (auto & it : m_row_vector.m_data) { + w_at_row += w[it.first] * it.second; + } + + if (!settings.abs_val_is_smaller_than_drop_tolerance(w_at_row)){ + if (was_zero_at_m_row) { + w.m_index.push_back(m_row); + } + w[m_row] = w_at_row; + } else if (!was_zero_at_m_row){ + w[m_row] = zero_of_type(); + auto it = std::find(w.m_index.begin(), w.m_index.end(), m_row); + w.m_index.erase(it); + } + // TBD: does not compile lean_assert(check_vector_for_small_values(w, settings)); +} + +template +void row_eta_matrix::apply_from_right(vector & w) { + const T & w_row = w[m_row]; + if (numeric_traits::is_zero(w_row)) return; +#ifdef LEAN_DEBUG + // dense_matrix deb(*this); + // auto clone_w = clone_vector(w, m_dimension); + // deb.apply_from_right(clone_w); +#endif + for (auto & it : m_row_vector.m_data) { + w[it.first] += w_row * it.second; + } +#ifdef LEAN_DEBUG + // lean_assert(vectors_are_equal(clone_w, w, m_dimension)); + // delete clone_w; +#endif +} + +template +void row_eta_matrix::apply_from_right(indexed_vector & w) { + lean_assert(w.is_OK()); + const T & w_row = w[m_row]; + if (numeric_traits::is_zero(w_row)) return; +#ifdef LEAN_DEBUG + // vector wcopy(w.m_data); + // apply_from_right(wcopy); +#endif + if (numeric_traits::precise()) { + for (auto & it : m_row_vector.m_data) { + unsigned j = it.first; + bool was_zero = numeric_traits::is_zero(w[j]); + const T & v = w[j] += w_row * it.second; + + if (was_zero) { + if (!numeric_traits::is_zero(v)) + w.m_index.push_back(j); + } else { + if (numeric_traits::is_zero(v)) + w.erase_from_index(j); + } + } + } else { // the non precise version + const double drop_eps = 1e-14; + for (auto & it : m_row_vector.m_data) { + unsigned j = it.first; + bool was_zero = numeric_traits::is_zero(w[j]); + T & v = w[j] += w_row * it.second; + + if (was_zero) { + if (!lp_settings::is_eps_small_general(v, drop_eps)) + w.m_index.push_back(j); + else + v = zero_of_type(); + } else { + if (lp_settings::is_eps_small_general(v, drop_eps)) { + w.erase_from_index(j); + v = zero_of_type(); + } + } + } + } +#ifdef LEAN_DEBUG + // lean_assert(vectors_are_equal(wcopy, w.m_data)); + +#endif +} + +template +void row_eta_matrix::conjugate_by_permutation(permutation_matrix & p) { + // this = p * this * p(-1) +#ifdef LEAN_DEBUG + // auto rev = p.get_reverse(); + // auto deb = ((*this) * rev); + // deb = p * deb; +#endif + m_row = p.apply_reverse(m_row); + // copy aside the column indices + vector columns; + for (auto & it : m_row_vector.m_data) + columns.push_back(it.first); + for (unsigned i = static_cast(columns.size()); i-- > 0;) + m_row_vector.m_data[i].first = p.get_rev(columns[i]); +#ifdef LEAN_DEBUG + // lean_assert(deb == *this); +#endif +} +#ifdef LEAN_DEBUG +template +T row_eta_matrix::get_elem(unsigned row, unsigned col) const { + if (row == m_row){ + if (col == row) { + return numeric_traits::one(); + } + return m_row_vector[col]; + } + + return col == row ? numeric_traits::one() : numeric_traits::zero(); +} +#endif +} + diff --git a/src/util/lp/row_eta_matrix_instances.cpp b/src/util/lp/row_eta_matrix_instances.cpp new file mode 100644 index 000000000..c32023164 --- /dev/null +++ b/src/util/lp/row_eta_matrix_instances.cpp @@ -0,0 +1,33 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include "util/vector.h" +#include +#include "util/lp/row_eta_matrix.hpp" +#include "util/lp/lu.h" +namespace lean { +template void row_eta_matrix::conjugate_by_permutation(permutation_matrix&); +template void row_eta_matrix >::conjugate_by_permutation(permutation_matrix >&); +template void row_eta_matrix::conjugate_by_permutation(permutation_matrix&); +#ifdef LEAN_DEBUG +template mpq row_eta_matrix::get_elem(unsigned int, unsigned int) const; +template mpq row_eta_matrix >::get_elem(unsigned int, unsigned int) const; +template double row_eta_matrix::get_elem(unsigned int, unsigned int) const; +#endif +template void row_eta_matrix::apply_from_left(vector&, lp_settings&); +template void row_eta_matrix::apply_from_right(vector&); +template void row_eta_matrix::apply_from_right(indexed_vector&); +template void row_eta_matrix >::apply_from_left(vector>&, lp_settings&); +template void row_eta_matrix >::apply_from_right(vector&); +template void row_eta_matrix >::apply_from_right(indexed_vector&); +template void row_eta_matrix::apply_from_left(vector&, lp_settings&); +template void row_eta_matrix::apply_from_right(vector&); +template void row_eta_matrix::apply_from_right(indexed_vector&); +template void row_eta_matrix::apply_from_left_to_T(indexed_vector&, lp_settings&); +template void row_eta_matrix::apply_from_left_local_to_T(indexed_vector&, lp_settings&); +template void row_eta_matrix >::apply_from_left_to_T(indexed_vector&, lp_settings&); +template void row_eta_matrix >::apply_from_left_local_to_T(indexed_vector&, lp_settings&); +template void row_eta_matrix::apply_from_left_to_T(indexed_vector&, lp_settings&); +template void row_eta_matrix::apply_from_left_local_to_T(indexed_vector&, lp_settings&); +} diff --git a/src/util/lp/scaler.h b/src/util/lp/scaler.h new file mode 100644 index 000000000..33c5a6cc4 --- /dev/null +++ b/src/util/lp/scaler.h @@ -0,0 +1,79 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ + +#pragma once +#include "util/vector.h" +#include +#include +#include /* printf, fopen */ +#include /* exit, EXIT_FAILURE */ +#include "util/lp/lp_utils.h" +#include "util/lp/static_matrix.h" +namespace lean { +// for scaling an LP +template +class scaler { + vector & m_b; // right side + static_matrix &m_A; // the constraint matrix + const T & m_scaling_minimum; + const T & m_scaling_maximum; + vector& m_column_scale; + lp_settings & m_settings; +public: + // constructor + scaler(vector & b, static_matrix &A, const T & scaling_minimum, const T & scaling_maximum, vector & column_scale, + lp_settings & settings): + m_b(b), + m_A(A), + m_scaling_minimum(scaling_minimum), + m_scaling_maximum(scaling_maximum), + m_column_scale(column_scale), + m_settings(settings) { + lean_assert(m_column_scale.size() == 0); + m_column_scale.resize(m_A.column_count(), numeric_traits::one()); + } + + T right_side_balance(); + + T get_balance() { return m_A.get_balance(); } + + T A_min() const; + + T A_max() const; + + T get_A_ratio() const; + + T get_max_ratio_on_rows() const; + + T get_max_ratio_on_columns() const; + + void scale_rows_with_geometric_mean(); + + void scale_columns_with_geometric_mean(); + + void scale_once_for_ratio(); + + bool scale_with_ratio(); + + void bring_row_maximums_to_one(); + + void bring_column_maximums_to_one(); + + void bring_rows_and_columns_maximums_to_one(); + + bool scale_with_log_balance(); + // Returns true if and only if the scaling was successful. + // It is the caller responsibility to restore the matrix + bool scale(); + + void scale_rows(); + + void scale_row(unsigned i); + + void scale_column(unsigned i); + + void scale_columns(); +}; +} diff --git a/src/util/lp/scaler.hpp b/src/util/lp/scaler.hpp new file mode 100644 index 000000000..69427eea0 --- /dev/null +++ b/src/util/lp/scaler.hpp @@ -0,0 +1,254 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include +#include "util/lp/scaler.h" +#include "util/lp/numeric_pair.h" +namespace lean { +// for scaling an LP +template T scaler::right_side_balance() { + T ret = zero_of_type(); + unsigned i = m_A.row_count(); + while (i--) { + T rs = abs(convert_struct::convert(m_b[i])); + if (!is_zero(rs)) { + numeric_traits::log(rs); + ret += rs * rs; + } + } + return ret; +} + +template T scaler::A_min() const { + T min = zero_of_type(); + for (unsigned i = 0; i < m_A.row_count(); i++) { + T t = m_A.get_min_abs_in_row(i); + min = i == 0 ? t : std::min(t, min); + } + return min; +} + +template T scaler::A_max() const { + T max = zero_of_type(); + for (unsigned i = 0; i < m_A.row_count(); i++) { + T t = m_A.get_max_abs_in_row(i); + max = i == 0? t : std::max(t, max); + } + return max; +} + +template T scaler::get_A_ratio() const { + T min = A_min(); + T max = A_max(); + lean_assert(!m_settings.abs_val_is_smaller_than_zero_tolerance(min)); + T ratio = max / min; + return ratio; +} + +template T scaler::get_max_ratio_on_rows() const { + T ret = T(1); + unsigned i = m_A.row_count(); + while (i--) { + T den = m_A.get_min_abs_in_row(i); + lean_assert(!m_settings.abs_val_is_smaller_than_zero_tolerance(den)); + T t = m_A.get_max_abs_in_row(i)/ den; + if (t > ret) + ret = t; + } + return ret; +} + +template T scaler::get_max_ratio_on_columns() const { + T ret = T(1); + unsigned i = m_A.column_count(); + while (i--) { + T den = m_A.get_min_abs_in_column(i); + if (m_settings.abs_val_is_smaller_than_zero_tolerance(den)) + continue; // got a zero column + T t = m_A.get_max_abs_in_column(i)/den; + if (t > ret) + ret = t; + } + return ret; +} + +template void scaler::scale_rows_with_geometric_mean() { + unsigned i = m_A.row_count(); + while (i--) { + T max = m_A.get_max_abs_in_row(i); + T min = m_A.get_min_abs_in_row(i); + lean_assert(max > zero_of_type() && min > zero_of_type()); + if (is_zero(max) || is_zero(min)) + continue; + T gm = T(sqrt(numeric_traits::get_double(max*min))); + if (m_settings.is_eps_small_general(gm, 0.01)) { + continue; + } + m_A.multiply_row(i, one_of_type() / gm); + m_b[i] /= gm; + } +} + +template void scaler::scale_columns_with_geometric_mean() { + unsigned i = m_A.column_count(); + while (i--) { + T max = m_A.get_max_abs_in_column(i); + T min = m_A.get_min_abs_in_column(i); + T den = T(sqrt(numeric_traits::get_double(max*min))); + if (m_settings.is_eps_small_general(den, 0.01)) + continue; // got a zero column + T gm = T(1)/ den; + T cs = m_column_scale[i] * gm; + if (m_settings.is_eps_small_general(cs, 0.1)) + continue; + m_A.multiply_column(i, gm); + m_column_scale[i] = cs; + } +} + +template void scaler::scale_once_for_ratio() { + T max_ratio_on_rows = get_max_ratio_on_rows(); + T max_ratio_on_columns = get_max_ratio_on_columns(); + bool scale_rows_first = max_ratio_on_rows > max_ratio_on_columns; + // if max_ratio_on_columns is the largerst then the rows are in worser shape then columns + if (scale_rows_first) { + scale_rows_with_geometric_mean(); + scale_columns_with_geometric_mean(); + } else { + scale_columns_with_geometric_mean(); + scale_rows_with_geometric_mean(); + } +} + +template bool scaler::scale_with_ratio() { + T ratio = get_A_ratio(); + // The ratio is greater than or equal to one. We would like to diminish it and bring it as close to 1 as possible + unsigned reps = m_settings.reps_in_scaler; + do { + scale_once_for_ratio(); + T new_r = get_A_ratio(); + if (new_r >= T(0.9) * ratio) + break; + } while (reps--); + + bring_rows_and_columns_maximums_to_one(); + return true; +} + +template void scaler::bring_row_maximums_to_one() { + unsigned i = m_A.row_count(); + while (i--) { + T t = m_A.get_max_abs_in_row(i); + if (m_settings.abs_val_is_smaller_than_zero_tolerance(t)) continue; + m_A.multiply_row(i, one_of_type() / t); + m_b[i] /= t; + } +} + +template void scaler::bring_column_maximums_to_one() { + unsigned i = m_A.column_count(); + while (i--) { + T max = m_A.get_max_abs_in_column(i); + if (m_settings.abs_val_is_smaller_than_zero_tolerance(max)) continue; + T t = T(1) / max; + m_A.multiply_column(i, t); + m_column_scale[i] *= t; + } +} + +template void scaler::bring_rows_and_columns_maximums_to_one() { + if (get_max_ratio_on_rows() > get_max_ratio_on_columns()) { + bring_row_maximums_to_one(); + bring_column_maximums_to_one(); + } else { + bring_column_maximums_to_one(); + bring_row_maximums_to_one(); + } +} + +template bool scaler::scale_with_log_balance() { + T balance = get_balance(); + T balance_before_scaling = balance; + // todo : analyze the scale order : rows-columns, or columns-rows. Iterate if needed + for (int i = 0; i < 10; i++) { + scale_rows(); + scale_columns(); + T nb = get_balance(); + if (nb < T(0.9) * balance) { + balance = nb; + } else { + balance = nb; + break; + } + } + return balance <= balance_before_scaling; +} +// Returns true if and only if the scaling was successful. +// It is the caller responsibility to restore the matrix +template bool scaler::scale() { + if (numeric_traits::precise()) return true; + if (m_settings.scale_with_ratio) + return scale_with_ratio(); + return scale_with_log_balance(); +} + +template void scaler::scale_rows() { + for (unsigned i = 0; i < m_A.row_count(); i++) + scale_row(i); +} + +template void scaler::scale_row(unsigned i) { + T row_max = std::max(m_A.get_max_abs_in_row(i), abs(convert_struct::convert(m_b[i]))); + T alpha = numeric_traits::one(); + if (numeric_traits::is_zero(row_max)) { + return; + } + if (numeric_traits::get_double(row_max) < m_scaling_minimum) { + do { + alpha *= 2; + row_max *= 2; + } while (numeric_traits::get_double(row_max) < m_scaling_minimum); + m_A.multiply_row(i, alpha); + m_b[i] *= alpha; + } else if (numeric_traits::get_double(row_max) > m_scaling_maximum) { + do { + alpha /= 2; + row_max /= 2; + } while (numeric_traits::get_double(row_max) > m_scaling_maximum); + m_A.multiply_row(i, alpha); + m_b[i] *= alpha; + } +} + +template void scaler::scale_column(unsigned i) { + T column_max = m_A.get_max_abs_in_column(i); + T alpha = numeric_traits::one(); + + if (numeric_traits::is_zero(column_max)){ + return; // the column has zeros only + } + + if (numeric_traits::get_double(column_max) < m_scaling_minimum) { + do { + alpha *= 2; + column_max *= 2; + } while (numeric_traits::get_double(column_max) < m_scaling_minimum); + } else if (numeric_traits::get_double(column_max) > m_scaling_maximum) { + do { + alpha /= 2; + column_max /= 2; + } while (numeric_traits::get_double(column_max) > m_scaling_maximum); + } else { + return; + } + m_A.multiply_column(i, alpha); + m_column_scale[i] = alpha; +} + +template void scaler::scale_columns() { + for (unsigned i = 0; i < m_A.column_count(); i++) { + scale_column(i); + } +} +} diff --git a/src/util/lp/scaler_instances.cpp b/src/util/lp/scaler_instances.cpp new file mode 100644 index 000000000..f97e8098f --- /dev/null +++ b/src/util/lp/scaler_instances.cpp @@ -0,0 +1,7 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include "util/lp/scaler.hpp" +template bool lean::scaler::scale(); +template bool lean::scaler::scale(); diff --git a/src/util/lp/signature_bound_evidence.h b/src/util/lp/signature_bound_evidence.h new file mode 100644 index 000000000..a22c188b4 --- /dev/null +++ b/src/util/lp/signature_bound_evidence.h @@ -0,0 +1,23 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#pragma once +#include "util/lp/lp_settings.h" +#include "util/lp/lar_constraints.h" +namespace lean { +struct bound_signature { + unsigned m_i; + bool m_at_low; + bound_signature(unsigned i, bool at_low) :m_i(i), m_at_low(m_at_low) {} + bool at_upper_bound() const { return !m_at_low_bound;} + bool at_low_bound() const { return m_at_low;} +}; +template +struct signature_bound_evidence { + vector m_evidence; + unsigned m_j; // found new bound + bool m_low_bound; + X m_bound; +}; +} diff --git a/src/util/lp/sparse_matrix.h b/src/util/lp/sparse_matrix.h new file mode 100644 index 000000000..96f0cf2ae --- /dev/null +++ b/src/util/lp/sparse_matrix.h @@ -0,0 +1,417 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ + +#pragma once +#include "util/vector.h" +#include "util/lp/permutation_matrix.h" +#include +#include "util/lp/static_matrix.h" +#include +#include +#include +#include +#include +#include "util/lp/indexed_value.h" +#include "util/lp/indexed_vector.h" +#include +#include "util/lp/lp_settings.h" +#include "util/lp/eta_matrix.h" +#include "util/lp/binary_heap_upair_queue.h" +#include "util/lp/numeric_pair.h" +#include "util/lp/int_set.h" +namespace lean { +// it is a square matrix +template +class sparse_matrix +#ifdef LEAN_DEBUG + : public matrix +#endif +{ + struct col_header { + unsigned m_shortened_markovitz = 0; + vector> m_values; // the actual column values + + col_header() {} + + void shorten_markovich_by_one() { + m_shortened_markovitz++; + } + + void zero_shortened_markovitz() { + m_shortened_markovitz = 0; + } + }; + + unsigned m_n_of_active_elems = 0; + binary_heap_upair_queue m_pivot_queue; +public: + vector>> m_rows; + vector m_columns; + permutation_matrix m_row_permutation; + permutation_matrix m_column_permutation; + // m_work_pivot_vector[j] = offset of elementh of j-th column in the row we are pivoting to + // if the column is not present then m_work_pivot_vector[j] is -1 + vector m_work_pivot_vector; + vector m_processed; + unsigned get_n_of_active_elems() const { return m_n_of_active_elems; } + +#ifdef LEAN_DEBUG + // dense_matrix m_dense; +#endif + /* + the rule is: row i is mapped to m_row_permutation[i] and + column j is mapped to m_column_permutation.apply_reverse(j) + */ + + unsigned adjust_row(unsigned row) const{ + return m_row_permutation[row]; + } + + unsigned adjust_column(unsigned col) const{ + return m_column_permutation.apply_reverse(col); + } + + unsigned adjust_row_inverse(unsigned row) const{ + return m_row_permutation.apply_reverse(row); + } + + unsigned adjust_column_inverse(unsigned col) const{ + return m_column_permutation[col]; + } + + void copy_column_from_static_matrix(unsigned col, static_matrix const &A, unsigned col_index_in_the_new_matrix); + void copy_B(static_matrix const &A, vector & basis); + +public: + // constructor that copies columns of the basis from A + sparse_matrix(static_matrix const &A, vector & basis); + + class ref_matrix_element { + sparse_matrix & m_matrix; + unsigned m_row; + unsigned m_col; + public: + ref_matrix_element(sparse_matrix & m, unsigned row, unsigned col):m_matrix(m), m_row(row), m_col(col) {} + ref_matrix_element & operator=(T const & v) { m_matrix.set( m_row, m_col, v); return *this; } + ref_matrix_element & operator=(ref_matrix_element const & v) { m_matrix.set(m_row, m_col, v.m_matrix.get(v.m_row, v.m_col)); return *this; } + operator T () const { return m_matrix.get(m_row, m_col); } + }; + + class ref_row { + sparse_matrix & m_matrix; + unsigned m_row; + public: + ref_row(sparse_matrix & m, unsigned row) : m_matrix(m), m_row(row) {} + ref_matrix_element operator[](unsigned col) const { return ref_matrix_element(m_matrix, m_row, col); } + }; + + void set_with_no_adjusting_for_row(unsigned row, unsigned col, T val); + void set_with_no_adjusting_for_col(unsigned row, unsigned col, T val); + + void set_with_no_adjusting(unsigned row, unsigned col, T val); + + void set(unsigned row, unsigned col, T val); + + T const & get_not_adjusted(unsigned row, unsigned col) const; + T const & get(unsigned row, unsigned col) const; + + ref_row operator[](unsigned row) { return ref_row(*this, row); } + + ref_matrix_element operator()(unsigned row, unsigned col) { return ref_matrix_element(*this, row, col); } + + T operator() (unsigned row, unsigned col) const { return get(row, col); } + + vector> & get_row_values(unsigned row) { + return m_rows[row]; + } + + vector> const & get_row_values(unsigned row) const { + return m_rows[row]; + } + + vector> & get_column_values(unsigned col) { + return m_columns[col].m_values; + } + + vector> const & get_column_values(unsigned col) const { + return m_columns[col].m_values; + } + + // constructor creating a zero matrix of dim*dim + sparse_matrix(unsigned dim); + + + + unsigned dimension() const {return static_cast(m_row_permutation.size());} + +#ifdef LEAN_DEBUG + unsigned row_count() const {return dimension();} + unsigned column_count() const {return dimension();} +#endif + + void init_row_headers(); + + void init_column_headers(); + + unsigned lowest_row_in_column(unsigned j); + + indexed_value & column_iv_other(indexed_value & iv) { + return m_rows[iv.m_index][iv.m_other]; + } + + indexed_value & row_iv_other(indexed_value & iv) { + return m_columns[iv.m_index].m_values[iv.m_other]; + } + + void remove_element(vector> & row_vals, unsigned row_offset, vector> & column_vals, unsigned column_offset); + + void remove_element(vector> & row_chunk, indexed_value & row_el_iv); + + void put_max_index_to_0(vector> & row_vals, unsigned max_index); + + void set_max_in_row(unsigned row) { + set_max_in_row(m_rows[row]); + } + + + void set_max_in_row(vector> & row_vals); + + bool pivot_with_eta(unsigned i, eta_matrix *eta_matrix, lp_settings & settings); + + void scan_row_to_work_vector_and_remove_pivot_column(unsigned row, unsigned pivot_column); + + // This method pivots row i to row i0 by muliplying row i by + // alpha and adding it to row i0. + // After pivoting the row i0 has a max abs value set correctly at the beginning of m_start, + // Returns false if the resulting row is all zeroes, and true otherwise + bool pivot_row_to_row(unsigned i, const T& alpha, unsigned i0, lp_settings & settings ); + + // set the max val as well + // returns false if the resulting row is all zeroes, and true otherwise + bool set_row_from_work_vector_and_clean_work_vector_not_adjusted(unsigned i0, indexed_vector & work_vec, + lp_settings & settings); + + + // set the max val as well + // returns false if the resulting row is all zeroes, and true otherwise + bool set_row_from_work_vector_and_clean_work_vector(unsigned i0); + + void remove_zero_elements_and_set_data_on_existing_elements(unsigned row); + + // work_vec here has not adjusted column indices + void remove_zero_elements_and_set_data_on_existing_elements_not_adjusted(unsigned row, indexed_vector & work_vec, lp_settings & settings); + + void multiply_from_right(permutation_matrix& p) { + // m_dense = m_dense * p; + m_column_permutation.multiply_by_permutation_from_right(p); + // lean_assert(*this == m_dense); + } + + void multiply_from_left(permutation_matrix& p) { + // m_dense = p * m_dense; + m_row_permutation.multiply_by_permutation_from_left(p); + // lean_assert(*this == m_dense); + } + + void multiply_from_left_with_reverse(permutation_matrix& p) { + // m_dense = p * m_dense; + m_row_permutation.multiply_by_permutation_reverse_from_left(p); + // lean_assert(*this == m_dense); + } + + // adding delta columns at the end of the matrix + void add_columns_at_the_end(unsigned delta); + + void delete_column(int i); + + void swap_columns(unsigned a, unsigned b) { + // cout << "swaapoiiin" << std::endl; + // dense_matrix d(*this); + m_column_permutation.transpose_from_left(a, b); + // d.swap_columns(a, b); + // lean_assert(*this == d); + } + + void swap_rows(unsigned a, unsigned b) { + m_row_permutation.transpose_from_right(a, b); + // m_dense.swap_rows(a, b); + // lean_assert(*this == m_dense); + } + + void divide_row_by_constant(unsigned i, const T & t, lp_settings & settings); + + bool close(T a, T b) { + return // (numeric_traits::precise() && numeric_traits::is_zero(a - b)) + // || + fabs(numeric_traits::get_double(a - b)) < 0.0000001; + } + + // solving x * this = y, and putting the answer into y + // the matrix here has to be upper triangular + void solve_y_U(vector & y) const; + + // solving x * this = y, and putting the answer into y + // the matrix here has to be upper triangular + void solve_y_U_indexed(indexed_vector & y, const lp_settings &); + + // fills the indices for such that y[i] can be not a zero + // sort them so the smaller indices come first + void fill_reachable_indices(std::set & rset, T *y); + + template + void find_error_in_solution_U_y(vector& y_orig, vector & y); + + template + void find_error_in_solution_U_y_indexed(indexed_vector& y_orig, indexed_vector & y, const vector& sorted_active_rows); + + template + void add_delta_to_solution(const vector& del, vector & y); + + template + void add_delta_to_solution(const indexed_vector& del, indexed_vector & y); + + template + void double_solve_U_y(indexed_vector& y, const lp_settings & settings); + + template + void double_solve_U_y(vector& y); + // solving this * x = y, and putting the answer into y + // the matrix here has to be upper triangular + template + void solve_U_y(vector & y); + // solving this * x = y, and putting the answer into y + // the matrix here has to be upper triangular + template + void solve_U_y_indexed_only(indexed_vector & y, const lp_settings&, vector & sorted_active_rows ); + +#ifdef LEAN_DEBUG + T get_elem(unsigned i, unsigned j) const { return get(i, j); } + unsigned get_number_of_rows() const { return dimension(); } + unsigned get_number_of_columns() const { return dimension(); } + virtual void set_number_of_rows(unsigned /*m*/) { } + virtual void set_number_of_columns(unsigned /*n*/) { } +#endif + template + L dot_product_with_row (unsigned row, const vector & y) const; + + template + L dot_product_with_row (unsigned row, const indexed_vector & y) const; + + unsigned get_number_of_nonzeroes() const; + + bool get_non_zero_column_in_row(unsigned i, unsigned *j) const; + + void remove_element_that_is_not_in_w(vector> & column_vals, indexed_value & col_el_iv); + + + // w contains the new column + // the old column inside of the matrix has not been changed yet + void remove_elements_that_are_not_in_w_and_update_common_elements(unsigned column_to_replace, indexed_vector & w); + + void add_new_element(unsigned row, unsigned col, const T& val); + + // w contains the "rest" of the new column; all common elements of w and the old column has been zeroed + // the old column inside of the matrix has not been changed yet + void add_new_elements_of_w_and_clear_w(unsigned column_to_replace, indexed_vector & w, lp_settings & settings); + + void replace_column(unsigned column_to_replace, indexed_vector & w, lp_settings &settings); + + unsigned pivot_score(unsigned i, unsigned j); + + void enqueue_domain_into_pivot_queue(); + + void set_max_in_rows(); + + void zero_shortened_markovitz_numbers(); + + void prepare_for_factorization(); + + void recover_pivot_queue(vector & rejected_pivots); + + int elem_is_too_small(unsigned i, unsigned j, int c_partial_pivoting); + + bool remove_row_from_active_pivots_and_shorten_columns(unsigned row); + + void remove_pivot_column(unsigned row); + + void update_active_pivots(unsigned row); + + bool shorten_active_matrix(unsigned row, eta_matrix *eta_matrix); + + unsigned pivot_score_without_shortened_counters(unsigned i, unsigned j, unsigned k); +#ifdef LEAN_DEBUG + bool can_improve_score_for_row(unsigned row, unsigned score, T const & c_partial_pivoting, unsigned k); + bool really_best_pivot(unsigned i, unsigned j, T const & c_partial_pivoting, unsigned k); + void print_active_matrix(unsigned k, std::ostream & out); +#endif + bool pivot_queue_is_correct_for_row(unsigned i, unsigned k); + + bool pivot_queue_is_correct_after_pivoting(int k); + + bool get_pivot_for_column(unsigned &i, unsigned &j, int c_partial_pivoting, unsigned k); + + bool elem_is_too_small(vector> & row_chunk, indexed_value & iv, int c_partial_pivoting); + + unsigned number_of_non_zeroes_in_row(unsigned row) const { + return static_cast(m_rows[row].size()); + } + + unsigned number_of_non_zeroes_in_column(unsigned col) const { + return m_columns[col].m_values.size(); + } + + bool shorten_columns_by_pivot_row(unsigned i, unsigned pivot_column); + + bool col_is_active(unsigned j, unsigned pivot) { + return adjust_column_inverse(j) > pivot; + } + + bool row_is_active(unsigned i, unsigned pivot) { + return adjust_row_inverse(i) > pivot; + } + + bool fill_eta_matrix(unsigned j, eta_matrix ** eta); +#ifdef LEAN_DEBUG + bool is_upper_triangular_and_maximums_are_set_correctly_in_rows(lp_settings & settings) const; + + bool is_upper_triangular_until(unsigned k) const; + void check_column_vs_rows(unsigned col); + + void check_row_vs_columns(unsigned row); + + void check_rows_vs_columns(); + + void check_columns_vs_rows(); + + void check_matrix(); +#endif + void create_graph_G(const vector & active_rows, vector & sorted_active_rows); + void process_column_recursively(unsigned i, vector & sorted_rows); + void extend_and_sort_active_rows(const vector & active_rows, vector & sorted_active_rows); + void process_index_recursively_for_y_U(unsigned j, vector & sorted_rows); + void resize(unsigned new_dim) { + unsigned old_dim = dimension(); + lean_assert(new_dim >= old_dim); + for (unsigned j = old_dim; j < new_dim; j++) { + m_rows.push_back(vector>()); + m_columns.push_back(col_header()); + } + m_pivot_queue.resize(new_dim); + m_row_permutation.resize(new_dim); + m_column_permutation.resize(new_dim); + m_work_pivot_vector.resize(new_dim); + m_processed.resize(new_dim); + for (unsigned j = old_dim; j < new_dim; j++) { + add_new_element(j, j, numeric_traits::one()); + } + } +#ifdef LEAN_DEBUG +vector get_full_row(unsigned i) const; +#endif + unsigned pivot_queue_size() const { return m_pivot_queue.size(); } +}; +}; + + diff --git a/src/util/lp/sparse_matrix.hpp b/src/util/lp/sparse_matrix.hpp new file mode 100644 index 000000000..0d2a90ea0 --- /dev/null +++ b/src/util/lp/sparse_matrix.hpp @@ -0,0 +1,1255 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ + +#include "util/vector.h" +#include "util/lp/sparse_matrix.h" +#include +#include +namespace lean { +template +void sparse_matrix::copy_column_from_static_matrix(unsigned col, static_matrix const &A, unsigned col_index_in_the_new_matrix) { + vector const & A_col_vector = A.m_columns[col]; + unsigned size = static_cast(A_col_vector.size()); + vector> & new_column_vector = m_columns[col_index_in_the_new_matrix].m_values; + for (unsigned l = 0; l < size; l++) { + column_cell const & col_cell = A_col_vector[l]; + unsigned col_offset = static_cast(new_column_vector.size()); + vector> & row_vector = m_rows[col_cell.m_i]; + unsigned row_offset = static_cast(row_vector.size()); + const T & val = A.get_val(col_cell); + new_column_vector.push_back(indexed_value(val, col_cell.m_i, row_offset)); + row_vector.push_back(indexed_value(val, col_index_in_the_new_matrix, col_offset)); + m_n_of_active_elems++; + } +} + +template +void sparse_matrix::copy_B(static_matrix const &A, vector & basis) { + unsigned m = A.row_count(); // this should be the size of basis + for (unsigned j = m; j-- > 0;) { + copy_column_from_static_matrix(basis[j], A, j); + } +} + +// constructor that copies columns of the basis from A +template +sparse_matrix::sparse_matrix(static_matrix const &A, vector & basis) : + m_pivot_queue(A.row_count()), + m_row_permutation(A.row_count()), + m_column_permutation(A.row_count()), + m_work_pivot_vector(A.row_count(), -1), + m_processed(A.row_count()) { + init_row_headers(); + init_column_headers(); + copy_B(A, basis); +} + +template +void sparse_matrix::set_with_no_adjusting_for_row(unsigned row, unsigned col, T val) { // should not be used in efficient code + vector> & row_vec = m_rows[row]; + for (auto & iv : row_vec) { + if (iv.m_index == col) { + iv.set_value(val); + return; + } + } + // have not found the column between the indices + row_vec.push_back(indexed_value(val, col)); // what about m_other ??? +} + +template +void sparse_matrix::set_with_no_adjusting_for_col(unsigned row, unsigned col, T val) { // should not be used in efficient code + vector> & col_vec = m_columns[col].m_values; + for (auto & iv : col_vec) { + if (iv.m_index == row) { + iv.set_value(val); + return; + } + } + // have not found the column between the indices + col_vec.push_back(indexed_value(val, row)); // what about m_other ??? +} + + +template +void sparse_matrix::set_with_no_adjusting(unsigned row, unsigned col, T val) { // should not be used in efficient code + set_with_no_adjusting_for_row(row, col, val); + set_with_no_adjusting_for_col(row, col, val); +} + +template +void sparse_matrix::set(unsigned row, unsigned col, T val) { // should not be used in efficient code + lean_assert(row < dimension() && col < dimension()); + // m_dense.set_elem(row, col, val); + row = adjust_row(row); + col = adjust_column(col); + set_with_no_adjusting(row, col, val); + // lean_assert(*this == m_dense); +} + +template +T const & sparse_matrix::get_not_adjusted(unsigned row, unsigned col) const { + for (indexed_value const & iv : m_rows[row]) { + if (iv.m_index == col) { + return iv.m_value; + } + } + return numeric_traits::zero(); +} + +template +T const & sparse_matrix::get(unsigned row, unsigned col) const { // should not be used in efficient code + row = adjust_row(row); + auto & row_chunk = m_rows[row]; + col = adjust_column(col); + for (indexed_value const & iv : row_chunk) { + if (iv.m_index == col) { + return iv.m_value; + } + } + return numeric_traits::zero(); +} + +// constructor creating a zero matrix of dim*dim +template +sparse_matrix::sparse_matrix(unsigned dim) : + m_pivot_queue(dim), // dim will be the initial size of the queue + m_row_permutation(dim), + m_column_permutation(dim), + m_work_pivot_vector(dim, -1), + m_processed(dim) { + init_row_headers(); + init_column_headers(); + } + +template +void sparse_matrix::init_row_headers() { + for (unsigned l = 0; l < m_row_permutation.size(); l++) { + m_rows.push_back(vector>()); + } +} + +template +void sparse_matrix::init_column_headers() { // we alway have only square sparse_matrix + for (unsigned l = 0; l < m_row_permutation.size(); l++) { + m_columns.push_back(col_header()); + } +} + +template +unsigned sparse_matrix::lowest_row_in_column(unsigned j) { + auto & mc = get_column_values(adjust_column(j)); + unsigned ret = 0; + for (auto & iv : mc) { + unsigned row = adjust_row_inverse(iv.m_index); + if (row > ret) { + ret = row; + } + } + return ret; +} + +template +void sparse_matrix::remove_element(vector> & row_vals, unsigned row_offset, vector> & column_vals, unsigned column_offset) { + if (column_offset != column_vals.size() - 1) { + auto & column_iv = column_vals[column_offset] = column_vals.back(); // copy from the tail + column_iv_other(column_iv).m_other = column_offset; + if (row_offset != row_vals.size() - 1) { + auto & row_iv = row_vals[row_offset] = row_vals.back(); // copy from the tail + row_iv_other(row_iv).m_other = row_offset; + } + } else if (row_offset != row_vals.size() - 1) { + auto & row_iv = row_vals[row_offset] = row_vals.back(); // copy from the tail + row_iv_other(row_iv).m_other = row_offset; + } + // do nothing - just decrease the sizes + column_vals.pop_back(); + row_vals.pop_back(); + m_n_of_active_elems--; // the value is correct only when refactoring +} + +template +void sparse_matrix::remove_element(vector> & row_chunk, indexed_value & row_el_iv) { + auto & column_chunk = get_column_values(row_el_iv.m_index); + indexed_value & col_el_iv = column_chunk[row_el_iv.m_other]; + remove_element(row_chunk, col_el_iv.m_other, column_chunk, row_el_iv.m_other); +} + +template +void sparse_matrix::put_max_index_to_0(vector> & row_vals, unsigned max_index) { + if (max_index == 0) return; + indexed_value * max_iv = & row_vals[max_index]; + indexed_value * start_iv = & row_vals[0]; + // update the "other" columns elements which are bound to the start_iv and max_iv + m_columns[max_iv->m_index].m_values[max_iv->m_other].m_other = 0; + m_columns[start_iv->m_index].m_values[start_iv->m_other].m_other = max_index; + + // swap the elements + indexed_value t = * max_iv; + * max_iv = * start_iv; + * start_iv = t; +} + +template +void sparse_matrix::set_max_in_row(vector> & row_vals) { + if (row_vals.size() == 0) + return; + T max_val = abs(row_vals[0].m_value); + unsigned max_index = 0; + for (unsigned i = 1; i < row_vals.size(); i++) { + T iabs = abs(row_vals[i].m_value); + if (iabs > max_val) { + max_val = iabs; + max_index = i; + } + } + put_max_index_to_0(row_vals, max_index); +} + +template +bool sparse_matrix::pivot_with_eta(unsigned i, eta_matrix *eta_matrix, lp_settings & settings) { + const T& pivot = eta_matrix->get_diagonal_element(); + for (auto & it : eta_matrix->m_column_vector.m_data) { + if (!pivot_row_to_row(i, it.second, it.first, settings)) { + return false; + } + } + + divide_row_by_constant(i, pivot, settings); + if (!shorten_active_matrix(i, eta_matrix)) { + return false; + } + + return true; +} + +// returns the offset of the pivot column in the row +template +void sparse_matrix::scan_row_to_work_vector_and_remove_pivot_column(unsigned row, unsigned pivot_column) { + auto & rvals = m_rows[row]; + unsigned size = rvals.size(); + for (unsigned j = 0; j < size; j++) { + auto & iv = rvals[j]; + if (iv.m_index != pivot_column) { + m_work_pivot_vector[iv.m_index] = j; + } else { + remove_element(rvals, iv); + j--; + size--; + } + } +} + +#ifdef LEAN_DEBUG +template +vector sparse_matrix::get_full_row(unsigned i) const { + vector r; + for (unsigned j = 0; j < column_count(); j++) + r.push_back(get(i, j)); + return r; +} +#endif + + + +// This method pivots row i to row i0 by muliplying row i by +// alpha and adding it to row i0. +// After pivoting the row i0 has a max abs value set correctly at the beginning of m_start, +// Returns false if the resulting row is all zeroes, and true otherwise +template +bool sparse_matrix::pivot_row_to_row(unsigned i, const T& alpha, unsigned i0, lp_settings & settings ) { + lean_assert(i < dimension() && i0 < dimension()); + lean_assert(i != i0); + unsigned pivot_col = adjust_column(i); + i = adjust_row(i); + i0 = adjust_row(i0); + vector became_zeros; + // the offset of element of the pivot column in row i0 + scan_row_to_work_vector_and_remove_pivot_column(i0, pivot_col); + auto & i0_row_vals = m_rows[i0]; + // run over the pivot row and update row i0 + unsigned prev_size_i0 = i0_row_vals.size(); + for (const auto & iv : m_rows[i]) { + unsigned j = iv.m_index; + if (j == pivot_col) continue; + T alv = alpha * iv.m_value; + int j_offs = m_work_pivot_vector[j]; + if (j_offs == -1) { // it is a new element + if (!settings.abs_val_is_smaller_than_drop_tolerance(alv)) { + add_new_element(i0, j, alv); + } + } + else { + auto & row_el_iv = i0_row_vals[j_offs]; + row_el_iv.m_value += alv; + if (settings.abs_val_is_smaller_than_drop_tolerance(row_el_iv.m_value)) { + became_zeros.push_back(j_offs); + ensure_increasing(became_zeros); + } + else { + m_columns[j].m_values[row_el_iv.m_other].set_value(row_el_iv.m_value); + } + } + } + + + // clean the work vector + for (unsigned k = 0; k < prev_size_i0; k++) { + m_work_pivot_vector[i0_row_vals[k].m_index] = -1; + } + + for (unsigned k = became_zeros.size(); k-- > 0; ) { + unsigned j = became_zeros[k]; + remove_element(i0_row_vals, i0_row_vals[j]); + if (i0_row_vals.empty()) + return false; + } + + if (numeric_traits::precise() == false) + set_max_in_row(i0_row_vals); + + return !i0_row_vals.empty(); +} + + + +// set the max val as well +// returns false if the resulting row is all zeroes, and true otherwise +template +bool sparse_matrix::set_row_from_work_vector_and_clean_work_vector_not_adjusted(unsigned i0, indexed_vector & work_vec, + lp_settings & settings) { + remove_zero_elements_and_set_data_on_existing_elements_not_adjusted(i0, work_vec, settings); + // all non-zero elements in m_work_pivot_vector are new + for (unsigned j : work_vec.m_index) { + if (numeric_traits::is_zero(work_vec[j])) { + continue; + } + lean_assert(!settings.abs_val_is_smaller_than_drop_tolerance(work_vec[j])); + add_new_element(i0, adjust_column(j), work_vec[j]); + work_vec[j] = numeric_traits::zero(); + } + work_vec.m_index.clear(); + auto & row_vals = m_rows[i0]; + if (row_vals.size() == 0) { + return false; + } + set_max_in_row(row_vals); // it helps to find larger pivots + return true; +} + + + +template +void sparse_matrix::remove_zero_elements_and_set_data_on_existing_elements(unsigned row) { + auto & row_vals = m_rows[row]; + for (unsigned k = static_cast(row_vals.size()); k-- > 0;) { // we cannot simply run the iterator since we are removing + // elements from row_vals + auto & row_el_iv = row_vals[k]; + unsigned j = row_el_iv.m_index; + T & wj = m_work_pivot_vector[j]; + if (is_zero(wj)) { + remove_element(row_vals, row_el_iv); + } else { + m_columns[j].m_values[row_el_iv.m_other].set_value(wj); + row_el_iv.set_value(wj); + wj = zero_of_type(); + } + } +} + +// work_vec here has not adjusted column indices +template +void sparse_matrix::remove_zero_elements_and_set_data_on_existing_elements_not_adjusted(unsigned row, indexed_vector & work_vec, lp_settings & settings) { + auto & row_vals = m_rows[row]; + for (unsigned k = static_cast(row_vals.size()); k-- > 0;) { // we cannot simply run the iterator since we are removing + // elements from row_vals + auto & row_el_iv = row_vals[k]; + unsigned j = row_el_iv.m_index; + unsigned rj = adjust_column_inverse(j); + T val = work_vec[rj]; + if (settings.abs_val_is_smaller_than_drop_tolerance(val)) { + remove_element(row_vals, row_el_iv); + lean_assert(numeric_traits::is_zero(val)); + } else { + m_columns[j].m_values[row_el_iv.m_other].set_value(row_el_iv.m_value = val); + work_vec[rj] = numeric_traits::zero(); + } + } +} + + +// adding delta columns at the end of the matrix +template +void sparse_matrix::add_columns_at_the_end(unsigned delta) { + for (unsigned i = 0; i < delta; i++) { + col_header col_head; + m_columns.push_back(col_head); + } + m_column_permutation.enlarge(delta); +} + +template +void sparse_matrix::delete_column(int i) { + lean_assert(i < dimension()); + for (auto cell = m_columns[i].m_head; cell != nullptr;) { + auto next_cell = cell->m_down; + kill_cell(cell); + cell = next_cell; + } +} + +template +void sparse_matrix::divide_row_by_constant(unsigned i, const T & t, lp_settings & settings) { + lean_assert(!settings.abs_val_is_smaller_than_zero_tolerance(t)); + i = adjust_row(i); + for (auto & iv : m_rows[i]) { + T &v = iv.m_value; + v /= t; + if (settings.abs_val_is_smaller_than_drop_tolerance(v)){ + v = numeric_traits::zero(); + } + m_columns[iv.m_index].m_values[iv.m_other].set_value(v); + } +} + + +// solving x * this = y, and putting the answer into y +// the matrix here has to be upper triangular +template +void sparse_matrix::solve_y_U(vector & y) const { // works by rows +#ifdef LEAN_DEBUG + // T * rs = clone_vector(y, dimension()); +#endif + unsigned end = dimension(); + for (unsigned i = 0; i + 1 < end; i++) { + // all y[i] has correct values already + const T & yv = y[i]; + if (numeric_traits::is_zero(yv)) continue; + auto & mc = get_row_values(adjust_row(i)); + for (auto & c : mc) { + unsigned col = adjust_column_inverse(c.m_index); + if (col != i) { + y[col] -= c.m_value * yv; + } + } + } +#ifdef LEAN_DEBUG + // dense_matrix deb(*this); + // T * clone_y = clone_vector(y, dimension()); + // deb.apply_from_right(clone_y); + // lean_assert(vectors_are_equal(rs, clone_y, dimension())); + // delete [] clone_y; + // delete [] rs; +#endif +} + +// solving x * this = y, and putting the answer into y +// the matrix here has to be upper triangular +template +void sparse_matrix::solve_y_U_indexed(indexed_vector & y, const lp_settings & settings) { +#if 0 && LEAN_DEBUG + vector ycopy(y.m_data); + if (numeric_traits::precise() == false) + solve_y_U(ycopy); +#endif + vector sorted_active_columns; + extend_and_sort_active_rows(y.m_index, sorted_active_columns); + for (unsigned k = sorted_active_columns.size(); k-- > 0; ) { + unsigned j = sorted_active_columns[k]; + auto & yj = y[j]; + for (auto & c: m_columns[adjust_column(j)].m_values) { + unsigned i = adjust_row_inverse(c.m_index); + if (i == j) continue; + yj -= y[i] * c.m_value; + } + } + y.m_index.clear(); + for (auto j : sorted_active_columns) { + if (!settings.abs_val_is_smaller_than_drop_tolerance(y[j])) + y.m_index.push_back(j); + else if (!numeric_traits::precise()) + y.m_data[j] = zero_of_type(); + } + + lean_assert(y.is_OK()); +#if 0 && LEAN_DEBUG + if (numeric_traits::precise() == false) + lean_assert(vectors_are_equal(ycopy, y.m_data)); +#endif +} + + +// fills the indices for such that y[i] can be not a zero +// sort them so the smaller indices come first +// void fill_reachable_indices(std::set & rset, T *y) { +// std::queue q; +// int m = dimension(); +// for (int i = m - 1; i >= 0; i--) { +// if (!numeric_traits::is_zero(y[i])){ +// for (cell * c = m_columns[adjust_column(i)].m_head; c != nullptr; c = c->m_down) { +// unsigned row = adjust_row_inverse(c->m_i); +// q.push(row); +// } +// } +// } +// while (!q.empty()) { +// unsigned i = q.front(); +// q.pop(); +// for (cell * c = m_columns[adjust_column(i)].m_head; c != nullptr; c = c->m_down) { +// unsigned row = adjust_row_inverse(c->m_i); +// if (rset.find(row) == rset.end()){ +// rset.insert(row); +// q.push(row); +// } +// } +// } +// } + +template +template +void sparse_matrix::find_error_in_solution_U_y(vector& y_orig, vector & y) { + unsigned i = dimension(); + while (i--) { + y_orig[i] -= dot_product_with_row(i, y); + } +} + +template +template +void sparse_matrix::find_error_in_solution_U_y_indexed(indexed_vector& y_orig, indexed_vector & y, const vector& sorted_active_rows) { + for (unsigned i: sorted_active_rows) + y_orig.add_value_at_index(i, -dot_product_with_row(i, y)); // cannot round up here!!! + // y_orig can contain very small values +} + + +template +template +void sparse_matrix::add_delta_to_solution(const vector& del, vector & y) { + unsigned i = dimension(); + while (i--) { + y[i] += del[i]; + } +} +template +template +void sparse_matrix::add_delta_to_solution(const indexed_vector& del, indexed_vector & y) { +// lean_assert(del.is_OK()); + // lean_assert(y.is_OK()); + for (auto i : del.m_index) { + y.add_value_at_index(i, del[i]); + } +} +template +template +void sparse_matrix::double_solve_U_y(indexed_vector& y, const lp_settings & settings){ + lean_assert(y.is_OK()); + indexed_vector y_orig(y); // copy y aside + vector active_rows; + solve_U_y_indexed_only(y, settings, active_rows); + lean_assert(y.is_OK()); + find_error_in_solution_U_y_indexed(y_orig, y, active_rows); + // y_orig contains the error now + if (y_orig.m_index.size() * ratio_of_index_size_to_all_size() < 32 * dimension()) { + active_rows.clear(); + solve_U_y_indexed_only(y_orig, settings, active_rows); + add_delta_to_solution(y_orig, y); + y.clean_up(); + } else { // the dense version + solve_U_y(y_orig.m_data); + add_delta_to_solution(y_orig.m_data, y.m_data); + y.restore_index_and_clean_from_data(); + } + lean_assert(y.is_OK()); +} +template +template +void sparse_matrix::double_solve_U_y(vector& y){ + vector y_orig(y); // copy y aside + solve_U_y(y); + find_error_in_solution_U_y(y_orig, y); + // y_orig contains the error now + solve_U_y(y_orig); + add_delta_to_solution(y_orig, y); +} + +// solving this * x = y, and putting the answer into y +// the matrix here has to be upper triangular +template +template +void sparse_matrix::solve_U_y(vector & y) { // it is a column wise version +#ifdef LEAN_DEBUG + // T * rs = clone_vector(y, dimension()); +#endif + + for (unsigned j = dimension(); j--; ) { + const L & yj = y[j]; + if (is_zero(yj)) continue; + for (const auto & iv : m_columns[adjust_column(j)].m_values) { + unsigned i = adjust_row_inverse(iv.m_index); + if (i != j) { + y[i] -= iv.m_value * yj; + } + } + } +#ifdef LEAN_DEBUG + // dense_matrix deb(*this); + // T * clone_y = clone_vector(y, dimension()); + // deb.apply_from_left(clone_y); + // lean_assert(vectors_are_equal(rs, clone_y, dimension())); +#endif +} +template +void sparse_matrix::process_index_recursively_for_y_U(unsigned j, vector & sorted_active_rows) { + lean_assert(m_processed[j] == false); + m_processed[j]=true; + auto & row = m_rows[adjust_row(j)]; + for (auto & c : row) { + unsigned i = adjust_column_inverse(c.m_index); + if (i == j) continue; + if (!m_processed[i]) { + process_index_recursively_for_y_U(i, sorted_active_rows); + } + } + sorted_active_rows.push_back(j); +} + +template +void sparse_matrix::process_column_recursively(unsigned j, vector & sorted_active_rows) { + lean_assert(m_processed[j] == false); + auto & mc = m_columns[adjust_column(j)].m_values; + for (auto & iv : mc) { + unsigned i = adjust_row_inverse(iv.m_index); + if (i == j) continue; + if (!m_processed[i]) { + process_column_recursively(i, sorted_active_rows); + } + } + m_processed[j]=true; + sorted_active_rows.push_back(j); +} + + +template +void sparse_matrix::create_graph_G(const vector & index_or_right_side, vector & sorted_active_rows) { + for (auto i : index_or_right_side) { + if (m_processed[i]) continue; + process_column_recursively(i, sorted_active_rows); + } + + for (auto i : sorted_active_rows) { + m_processed[i] = false; + } +} + + +template +void sparse_matrix::extend_and_sort_active_rows(const vector & index_or_right_side, vector & sorted_active_rows) { + for (auto i : index_or_right_side) { + if (m_processed[i]) continue; + process_index_recursively_for_y_U(i, sorted_active_rows); + } + + for (auto i : sorted_active_rows) { + m_processed[i] = false; + } +} + + +template +template +void sparse_matrix::solve_U_y_indexed_only(indexed_vector & y, const lp_settings & settings, vector & sorted_active_rows) { // it is a column wise version + create_graph_G(y.m_index, sorted_active_rows); + + for (auto k = sorted_active_rows.size(); k-- > 0;) { + unsigned j = sorted_active_rows[k]; + const L & yj = y[j]; + if (is_zero(yj)) continue; + auto & mc = m_columns[adjust_column(j)].m_values; + for (auto & iv : mc) { + unsigned i = adjust_row_inverse(iv.m_index); + if (i != j) { + y[i] -= iv.m_value * yj; + } + } + } + y.m_index.clear(); + for (auto j : sorted_active_rows) { + if (!settings.abs_val_is_smaller_than_drop_tolerance(y[j])) + y.m_index.push_back(j); + else if (!numeric_traits::precise()) + y[j] = zero_of_type(); + } + + lean_assert(y.is_OK()); +#ifdef LEAN_DEBUG + // dense_matrix deb(this); + // vector clone_y(y.m_data); + // deb.apply_from_left(clone_y); + // lean_assert(vectors_are_equal(rs, clone_y)); +#endif +} + +template +template +L sparse_matrix::dot_product_with_row (unsigned row, const vector & y) const { + L ret = zero_of_type(); + auto & mc = get_row_values(adjust_row(row)); + for (auto & c : mc) { + unsigned col = m_column_permutation[c.m_index]; + ret += c.m_value * y[col]; + } + return ret; +} + +template +template +L sparse_matrix::dot_product_with_row (unsigned row, const indexed_vector & y) const { + L ret = zero_of_type(); + auto & mc = get_row_values(adjust_row(row)); + for (auto & c : mc) { + unsigned col = m_column_permutation[c.m_index]; + ret += c.m_value * y[col]; + } + return ret; +} + + +template +unsigned sparse_matrix::get_number_of_nonzeroes() const { + unsigned ret = 0; + for (unsigned i = dimension(); i--; ) { + ret += number_of_non_zeroes_in_row(i); + } + return ret; +} + +template +bool sparse_matrix::get_non_zero_column_in_row(unsigned i, unsigned *j) const { + // go over the i-th row + auto & mc = get_row_values(adjust_row(i)); + if (mc.size() > 0) { + *j = m_column_permutation[mc[0].m_index]; + return true; + } + return false; +} + +template +void sparse_matrix::remove_element_that_is_not_in_w(vector> & column_vals, indexed_value & col_el_iv) { + auto & row_chunk = m_rows[col_el_iv.m_index]; + indexed_value & row_el_iv = row_chunk[col_el_iv.m_other]; + unsigned index_in_row = col_el_iv.m_other; + remove_element(row_chunk, col_el_iv.m_other, column_vals, row_el_iv.m_other); + if (index_in_row == 0) + set_max_in_row(row_chunk); +} + + +// w contains the new column +// the old column inside of the matrix has not been changed yet +template +void sparse_matrix::remove_elements_that_are_not_in_w_and_update_common_elements(unsigned column_to_replace, indexed_vector & w) { + // -------------------------------- + // column_vals represents the old column + auto & column_vals = m_columns[column_to_replace].m_values; + for (unsigned k = static_cast(column_vals.size()); k-- > 0;) { + indexed_value & col_el_iv = column_vals[k]; + unsigned i = col_el_iv.m_index; + T &w_data_at_i = w[adjust_row_inverse(i)]; + if (numeric_traits::is_zero(w_data_at_i)) { + remove_element_that_is_not_in_w(column_vals, col_el_iv); + } else { + auto& row_chunk = m_rows[i]; + unsigned index_in_row = col_el_iv.m_other; + if (index_in_row == 0) { + bool look_for_max = abs(w_data_at_i) < abs(row_chunk[0].m_value); + row_chunk[0].set_value(col_el_iv.m_value = w_data_at_i); + if (look_for_max) + set_max_in_row(i); + } else { + row_chunk[index_in_row].set_value(col_el_iv.m_value = w_data_at_i); + if (abs(w_data_at_i) > abs(row_chunk[0].m_value)) + put_max_index_to_0(row_chunk, index_in_row); + } + w_data_at_i = numeric_traits::zero(); + } + } +} + +template +void sparse_matrix::add_new_element(unsigned row, unsigned col, const T& val) { + auto & row_vals = m_rows[row]; + auto & col_vals = m_columns[col].m_values; + unsigned row_el_offs = static_cast(row_vals.size()); + unsigned col_el_offs = static_cast(col_vals.size()); + row_vals.push_back(indexed_value(val, col, col_el_offs)); + col_vals.push_back(indexed_value(val, row, row_el_offs)); + m_n_of_active_elems++; +} + +// w contains the "rest" of the new column; all common elements of w and the old column has been zeroed +// the old column inside of the matrix has not been changed yet +template +void sparse_matrix::add_new_elements_of_w_and_clear_w(unsigned column_to_replace, indexed_vector & w, lp_settings & settings) { + for (unsigned i : w.m_index) { + T w_at_i = w[i]; + if (numeric_traits::is_zero(w_at_i)) continue; // was dealt with already + if (!settings.abs_val_is_smaller_than_drop_tolerance(w_at_i)) { + unsigned ai = adjust_row(i); + add_new_element(ai, column_to_replace, w_at_i); + auto & row_chunk = m_rows[ai]; + lean_assert(row_chunk.size() > 0); + if (abs(w_at_i) > abs(row_chunk[0].m_value)) + put_max_index_to_0(row_chunk, static_cast(row_chunk.size()) - 1); + } + w[i] = numeric_traits::zero(); + } + w.m_index.clear(); +} + +template +void sparse_matrix::replace_column(unsigned column_to_replace, indexed_vector & w, lp_settings &settings) { + column_to_replace = adjust_column(column_to_replace); + remove_elements_that_are_not_in_w_and_update_common_elements(column_to_replace, w); + add_new_elements_of_w_and_clear_w(column_to_replace, w, settings); +} + +template +unsigned sparse_matrix::pivot_score(unsigned i, unsigned j) { + // It goes like this (rnz-1)(cnz-1) is the Markovitz number, that is the max number of + // new non zeroes we can obtain after the pivoting. + // In addition we will get another cnz - 1 elements in the eta matrix created for this pivot, + // which gives rnz(cnz-1). For example, is 0 for a column singleton, but not for + // a row singleton ( which is not a column singleton). + + auto col_header = m_columns[j]; + + return static_cast(get_row_values(i).size() * (col_header.m_values.size() - col_header.m_shortened_markovitz - 1)); +} + +template +void sparse_matrix::enqueue_domain_into_pivot_queue() { + lean_assert(m_pivot_queue.size() == 0); + for (unsigned i = 0; i < dimension(); i++) { + auto & rh = m_rows[i]; + unsigned rnz = static_cast(rh.size()); + for (auto iv : rh) { + unsigned j = iv.m_index; + m_pivot_queue.enqueue(i, j, rnz * (static_cast(m_columns[j].m_values.size()) - 1)); + } + } +} + +template +void sparse_matrix::set_max_in_rows() { + unsigned i = dimension(); + while (i--) + set_max_in_row(i); +} + + +template +void sparse_matrix::zero_shortened_markovitz_numbers() { + for (auto & ch : m_columns) + ch.zero_shortened_markovitz(); +} + +template +void sparse_matrix::prepare_for_factorization() { + zero_shortened_markovitz_numbers(); + set_max_in_rows(); + enqueue_domain_into_pivot_queue(); +} + +template +void sparse_matrix::recover_pivot_queue(vector & rejected_pivots) { + for (auto p : rejected_pivots) { + m_pivot_queue.enqueue(p.first, p.second, pivot_score(p.first, p.second)); + } +} + +template +int sparse_matrix::elem_is_too_small(unsigned i, unsigned j, int c_partial_pivoting) { + auto & row_chunk = m_rows[i]; + + if (j == row_chunk[0].m_index) { + return 0; // the max element is at the head + } + T max = abs(row_chunk[0].m_value); + for (unsigned k = 1; k < row_chunk.size(); k++) { + auto &iv = row_chunk[k]; + if (iv.m_index == j) + return abs(iv.m_value) * c_partial_pivoting < max ? 1: 0; + } + return 2; // the element became zero but it still sits in the active pivots? +} + +template +bool sparse_matrix::remove_row_from_active_pivots_and_shorten_columns(unsigned row) { + unsigned arow = adjust_row(row); + for (auto & iv : m_rows[arow]) { + m_pivot_queue.remove(arow, iv.m_index); + m_n_of_active_elems--; // the value is correct only when refactoring + if (adjust_column_inverse(iv.m_index) <= row) + continue; // this column will be removed anyway + auto & col = m_columns[iv.m_index]; + + col.shorten_markovich_by_one(); + if (col.m_values.size() <= col.m_shortened_markovitz) + return false; // got a zero column + } + return true; +} + +template +void sparse_matrix::remove_pivot_column(unsigned row) { + unsigned acol = adjust_column(row); + for (const auto & iv : m_columns[acol].m_values) + if (adjust_row_inverse(iv.m_index) >= row) + m_pivot_queue.remove(iv.m_index, acol); +} + +template +void sparse_matrix::update_active_pivots(unsigned row) { + unsigned arow = adjust_row(row); + for (const auto & iv : m_rows[arow]) { + col_header & ch = m_columns[iv.m_index]; + int cols = static_cast(ch.m_values.size()) - ch.m_shortened_markovitz - 1; + lean_assert(cols >= 0); + for (const auto &ivc : ch.m_values) { + unsigned i = ivc.m_index; + if (adjust_row_inverse(i) <= row) continue; // the i is not an active row + m_pivot_queue.enqueue(i, iv.m_index, static_cast(m_rows[i].size())*cols); + } + } +} + +template +bool sparse_matrix::shorten_active_matrix(unsigned row, eta_matrix *eta_matrix) { + if (!remove_row_from_active_pivots_and_shorten_columns(row)) + return false; + remove_pivot_column(row); + // need to know the max priority of the queue here + update_active_pivots(row); + if (eta_matrix == nullptr) return true; + // it looks like double work, but the pivot scores have changed for all rows + // touched by eta_matrix + for (auto & it : eta_matrix->m_column_vector.m_data) { + unsigned row = adjust_row(it.first); + const auto & row_values = m_rows[row]; + unsigned rnz = static_cast(row_values.size()); + for (auto & iv : row_values) { + const col_header& ch = m_columns[iv.m_index]; + int cnz = static_cast(ch.m_values.size()) - ch.m_shortened_markovitz - 1; + lean_assert(cnz >= 0); + m_pivot_queue.enqueue(row, iv.m_index, rnz * cnz); + } + } + + return true; +} + +template +unsigned sparse_matrix::pivot_score_without_shortened_counters(unsigned i, unsigned j, unsigned k) { + auto &cols = m_columns[j].m_values; + unsigned cnz = cols.size(); + for (auto & iv : cols) { + if (adjust_row_inverse(iv.m_index) < k) + cnz--; + } + lean_assert(cnz > 0); + return m_rows[i].m_values.size() * (cnz - 1); +} +#ifdef LEAN_DEBUG +template +bool sparse_matrix::can_improve_score_for_row(unsigned row, unsigned score, T const & c_partial_pivoting, unsigned k) { + unsigned arow = adjust_row(row); + auto & row_vals = m_rows[arow].m_values; + auto & begin_iv = row_vals[0]; + T row_max = abs(begin_iv.m_value); + lean_assert(adjust_column_inverse(begin_iv.m_index) >= k); + if (pivot_score_without_shortened_counters(arow, begin_iv.m_index, k) < score) { + print_active_matrix(k); + return true; + } + for (unsigned jj = 1; jj < row_vals.size(); jj++) { + auto & iv = row_vals[jj]; + lean_assert(adjust_column_inverse(iv.m_index) >= k); + lean_assert(abs(iv.m_value) <= row_max); + if (c_partial_pivoting * abs(iv.m_value) < row_max) continue; + if (pivot_score_without_shortened_counters(arow, iv.m_index, k) < score) { + print_active_matrix(k); + return true; + } + } + return false; +} + +template +bool sparse_matrix::really_best_pivot(unsigned i, unsigned j, T const & c_partial_pivoting, unsigned k) { + unsigned queue_pivot_score = pivot_score_without_shortened_counters(i, j, k); + for (unsigned ii = k; ii < dimension(); ii++) { + lean_assert(!can_improve_score_for_row(ii, queue_pivot_score, c_partial_pivoting, k)); + } + return true; +} +template +void sparse_matrix::print_active_matrix(unsigned k, std::ostream & out) { + out << "active matrix for k = " << k << std::endl; + if (k >= dimension()) { + out << "empty" << std::endl; + return; + } + unsigned dim = dimension() - k; + dense_matrix b(dim, dim); + for (unsigned i = 0; i < dim; i++) + for (unsigned j = 0; j < dim; j++ ) + b.set_elem(i, j, zero_of_type()); + for (int i = k; i < dimension(); i++) { + unsigned col = adjust_column(i); + for (auto &iv : get_column_values(col)) { + unsigned row = iv.m_index; + unsigned row_ex = this->adjust_row_inverse(row); + if (row_ex < k) continue; + auto v = this->get_not_adjusted(row, col); + b.set_elem(row_ex - k, i -k, v); + } + } + print_matrix(b, out); +} + +template +bool sparse_matrix::pivot_queue_is_correct_for_row(unsigned i, unsigned k) { + unsigned arow = adjust_row(i); + for (auto & iv : m_rows[arow].m_values) { + lean_assert(pivot_score_without_shortened_counters(arow, iv.m_index, k + 1) == + m_pivot_queue.get_priority(arow, iv.m_index)); + } + return true; +} + +template +bool sparse_matrix::pivot_queue_is_correct_after_pivoting(int k) { + for (unsigned i = k + 1; i < dimension(); i++ ) + lean_assert(pivot_queue_is_correct_for_row(i, k)); + lean_assert(m_pivot_queue.is_correct()); + return true; +} +#endif + +template +bool sparse_matrix::get_pivot_for_column(unsigned &i, unsigned &j, int c_partial_pivoting, unsigned k) { + vector pivots_candidates_that_are_too_small; + while (!m_pivot_queue.is_empty()) { + m_pivot_queue.dequeue(i, j); + unsigned i_inv = adjust_row_inverse(i); + if (i_inv < k) continue; + unsigned j_inv = adjust_column_inverse(j); + if (j_inv < k) continue; + int small = elem_is_too_small(i, j, c_partial_pivoting); + if (!small) { +#ifdef LEAN_DEBUG + // if (!really_best_pivot(i, j, c_partial_pivoting, k)) { + // print_active_matrix(k); + // lean_assert(false); + // } +#endif + recover_pivot_queue(pivots_candidates_that_are_too_small); + i = i_inv; + j = j_inv; + return true; + } + if (small != 2) { // 2 means that the pair is not in the matrix + pivots_candidates_that_are_too_small.push_back(std::make_pair(i, j)); + } + } + recover_pivot_queue(pivots_candidates_that_are_too_small); + return false; +} + +template +bool sparse_matrix::elem_is_too_small(vector> & row_chunk, indexed_value & iv, int c_partial_pivoting) { + if (&iv == &row_chunk[0]) { + return false; // the max element is at the head + } + T val = abs(iv.m_value); + T max = abs(row_chunk[0].m_value); + return val * c_partial_pivoting < max; +} + +template +bool sparse_matrix::shorten_columns_by_pivot_row(unsigned i, unsigned pivot_column) { + vector> & row_chunk = get_row_values(i); + + for (indexed_value & iv : row_chunk) { + unsigned j = iv.m_index; + if (j == pivot_column) { + lean_assert(!col_is_active(j)); + continue; + } + m_columns[j].shorten_markovich_by_one(); + + if (m_columns[j].m_shortened_markovitz >= get_column_values(j).size()) { // got the zero column under the row! + return false; + } + } + return true; +} + +template +bool sparse_matrix::fill_eta_matrix(unsigned j, eta_matrix ** eta) { + const vector> & col_chunk = get_column_values(adjust_column(j)); + bool is_unit = true; + for (const auto & iv : col_chunk) { + unsigned i = adjust_row_inverse(iv.m_index); + if (i > j) { + is_unit = false; + break; + } + if (i == j && iv.m_value != 1) { + is_unit = false; + break; + } + } + + if (is_unit) { + *eta = nullptr; + return true; + } + +#ifdef LEAN_DEBUG + *eta = new eta_matrix(j, dimension()); +#else + *eta = new eta_matrix(j); +#endif + for (const auto & iv : col_chunk) { + unsigned i = adjust_row_inverse(iv.m_index); + if (i < j) { + continue; + } + if (i > j) { + (*eta)->push_back(i, - iv.m_value); + } else { // i == j + if ( !(*eta)->set_diagonal_element(iv.m_value)) { + delete *eta; + *eta = nullptr; + return false; + } + + } + } + + (*eta)->divide_by_diagonal_element(); + return true; +} +#ifdef LEAN_DEBUG +template +bool sparse_matrix::is_upper_triangular_and_maximums_are_set_correctly_in_rows(lp_settings & settings) const { + for (unsigned i = 0; i < dimension(); i++) { + vector> const & row_chunk = get_row_values(i); + lean_assert(row_chunk.size()); + T const & max = abs(row_chunk[0].m_value); + unsigned ai = adjust_row_inverse(i); + for (auto & iv : row_chunk) { + lean_assert(abs(iv.m_value) <= max); + unsigned aj = adjust_column_inverse(iv.m_index); + if (!(ai <= aj || numeric_traits::is_zero(iv.m_value))) + return false; + if (aj == ai) { + if (iv.m_value != 1) { + // std::cout << "value at diagonal = " << iv.m_value << std::endl; + return false; + } + } + if (settings.abs_val_is_smaller_than_drop_tolerance(iv.m_value) && (!is_zero(iv.m_value))) + return false; + } + } + return true; +} + +template +bool sparse_matrix::is_upper_triangular_until(unsigned k) const { + for (unsigned j = 0; j < dimension() && j < k; j++) { + unsigned aj = adjust_column(j); + auto & col = get_column_values(aj); + for (auto & iv : col) { + unsigned row = adjust_row_inverse(iv.m_index); + if (row > j) + return false; + } + } + return true; +} + +template +void sparse_matrix::check_column_vs_rows(unsigned col) { + auto & mc = get_column_values(col); + for (indexed_value & column_iv : mc) { + indexed_value & row_iv = column_iv_other(column_iv); + if (row_iv.m_index != col) { + // std::cout << "m_other in row does not belong to column " << col << ", but to column " << row_iv.m_index << std::endl; + lean_assert(false); + } + + if (& row_iv_other(row_iv) != &column_iv) { + // std::cout << "row and col do not point to each other" << std::endl; + lean_assert(false); + } + + if (row_iv.m_value != column_iv.m_value) { + // std::cout << "the data from col " << col << " for row " << column_iv.m_index << " is different in the column " << std::endl; + // std::cout << "in the col it is " << column_iv.m_value << ", but in the row it is " << row_iv.m_value << std::endl; + lean_assert(false); + } + } +} + +template +void sparse_matrix::check_row_vs_columns(unsigned row) { + auto & mc = get_row_values(row); + for (indexed_value & row_iv : mc) { + indexed_value & column_iv = row_iv_other(row_iv); + + if (column_iv.m_index != row) { + // std::cout << "col_iv does not point to correct row " << row << " but to " << column_iv.m_index << std::endl; + lean_assert(false); + } + + if (& row_iv != & column_iv_other(column_iv)) { + // std::cout << "row and col do not point to each other" << std::endl; + lean_assert(false); + } + + if (row_iv.m_value != column_iv.m_value) { + // std::cout << "the data from col " << column_iv.m_index << " for row " << row << " is different in the column " << std::endl; + // std::cout << "in the col it is " << column_iv.m_value << ", but in the row it is " << row_iv.m_value << std::endl; + lean_assert(false); + } + } +} + +template +void sparse_matrix::check_rows_vs_columns() { + for (unsigned i = 0; i < dimension(); i++) { + check_row_vs_columns(i); + } +} + +template +void sparse_matrix::check_columns_vs_rows() { + for (unsigned i = 0; i < dimension(); i++) { + check_column_vs_rows(i); + } +} +template +void sparse_matrix::check_matrix() { + check_rows_vs_columns(); + check_columns_vs_rows(); +} +#endif +} + diff --git a/src/util/lp/sparse_matrix_instances.cpp b/src/util/lp/sparse_matrix_instances.cpp new file mode 100644 index 000000000..f80b60365 --- /dev/null +++ b/src/util/lp/sparse_matrix_instances.cpp @@ -0,0 +1,101 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include "util/vector.h" +#include +#include "util/lp/lp_settings.h" +#include "util/lp/lu.h" +#include "util/lp/sparse_matrix.hpp" +#include "util/lp/dense_matrix.h" +namespace lean { +template double sparse_matrix::dot_product_with_row(unsigned int, vector const&) const; +template void sparse_matrix::add_new_element(unsigned int, unsigned int, const double&); +template void sparse_matrix::divide_row_by_constant(unsigned int, const double&, lp_settings&); +template bool sparse_matrix::fill_eta_matrix(unsigned int, eta_matrix**); +template const double & sparse_matrix::get(unsigned int, unsigned int) const; +template unsigned sparse_matrix::get_number_of_nonzeroes() const; +template bool sparse_matrix::get_pivot_for_column(unsigned int&, unsigned int&, int, unsigned int); +template unsigned sparse_matrix::lowest_row_in_column(unsigned int); +template bool sparse_matrix::pivot_row_to_row(unsigned int, const double&, unsigned int, lp_settings&); +template bool sparse_matrix::pivot_with_eta(unsigned int, eta_matrix*, lp_settings&); +template void sparse_matrix::prepare_for_factorization(); +template void sparse_matrix::remove_element(vector >&, indexed_value&); +template void sparse_matrix::replace_column(unsigned int, indexed_vector&, lp_settings&); +template void sparse_matrix::set(unsigned int, unsigned int, double); +template void sparse_matrix::set_max_in_row(vector >&); +template bool sparse_matrix::set_row_from_work_vector_and_clean_work_vector_not_adjusted(unsigned int, indexed_vector&, lp_settings&); +template bool sparse_matrix::shorten_active_matrix(unsigned int, eta_matrix*); +template void sparse_matrix::solve_y_U(vector&) const; +template sparse_matrix::sparse_matrix(static_matrix const&, vector&); +template sparse_matrix::sparse_matrix(unsigned int); +template void sparse_matrix::add_new_element(unsigned int, unsigned int, const mpq&); +template void sparse_matrix::divide_row_by_constant(unsigned int, const mpq&, lp_settings&); +template bool sparse_matrix::fill_eta_matrix(unsigned int, eta_matrix**); +template mpq const & sparse_matrix::get(unsigned int, unsigned int) const; +template unsigned sparse_matrix::get_number_of_nonzeroes() const; +template bool sparse_matrix::get_pivot_for_column(unsigned int&, unsigned int&, int, unsigned int); +template unsigned sparse_matrix::lowest_row_in_column(unsigned int); +template bool sparse_matrix::pivot_with_eta(unsigned int, eta_matrix*, lp_settings&); +template void sparse_matrix::prepare_for_factorization(); +template void sparse_matrix::remove_element(vector> &, indexed_value&); +template void sparse_matrix::replace_column(unsigned int, indexed_vector&, lp_settings&); +template void sparse_matrix::set_max_in_row(vector>&); +template bool sparse_matrix::set_row_from_work_vector_and_clean_work_vector_not_adjusted(unsigned int, indexed_vector&, lp_settings&); +template bool sparse_matrix::shorten_active_matrix(unsigned int, eta_matrix*); +template void sparse_matrix::solve_y_U(vector&) const; +template sparse_matrix::sparse_matrix(static_matrix const&, vector&); +template void sparse_matrix>::add_new_element(unsigned int, unsigned int, const mpq&); +template void sparse_matrix>::divide_row_by_constant(unsigned int, const mpq&, lp_settings&); +template bool sparse_matrix>::fill_eta_matrix(unsigned int, eta_matrix >**); +template const mpq & sparse_matrix>::get(unsigned int, unsigned int) const; +template unsigned sparse_matrix>::get_number_of_nonzeroes() const; +template bool sparse_matrix>::get_pivot_for_column(unsigned int&, unsigned int&, int, unsigned int); +template unsigned sparse_matrix>::lowest_row_in_column(unsigned int); +template bool sparse_matrix>::pivot_with_eta(unsigned int, eta_matrix >*, lp_settings&); +template void sparse_matrix>::prepare_for_factorization(); +template void sparse_matrix>::remove_element(vector>&, indexed_value&); +template void sparse_matrix>::replace_column(unsigned int, indexed_vector&, lp_settings&); +template void sparse_matrix>::set_max_in_row(vector>&); +template bool sparse_matrix>::set_row_from_work_vector_and_clean_work_vector_not_adjusted(unsigned int, indexed_vector&, lp_settings&); +template bool sparse_matrix>::shorten_active_matrix(unsigned int, eta_matrix >*); +template void sparse_matrix>::solve_y_U(vector&) const; +template sparse_matrix>::sparse_matrix(static_matrix > const&, vector&); +template void sparse_matrix::double_solve_U_y(indexed_vector&, const lp_settings &); +template void sparse_matrix::double_solve_U_y(indexed_vector&, const lp_settings&); +template void sparse_matrix>::double_solve_U_y(indexed_vector&, const lp_settings&); +template void sparse_matrix >::double_solve_U_y >(indexed_vector>&, const lp_settings&); +template void lean::sparse_matrix::solve_U_y_indexed_only(lean::indexed_vector&, const lp_settings&, vector &); +template void lean::sparse_matrix::solve_U_y_indexed_only(lean::indexed_vector&, const lp_settings &, vector &); +#ifdef LEAN_DEBUG +template bool sparse_matrix::is_upper_triangular_and_maximums_are_set_correctly_in_rows(lp_settings&) const; +template bool sparse_matrix::is_upper_triangular_and_maximums_are_set_correctly_in_rows(lp_settings&) const; +template bool sparse_matrix >::is_upper_triangular_and_maximums_are_set_correctly_in_rows(lp_settings&) const; +#endif +} +template void lean::sparse_matrix >::solve_U_y_indexed_only(lean::indexed_vector&, const lp_settings &, vector &); +template void lean::sparse_matrix::solve_U_y(vector&); +template void lean::sparse_matrix::double_solve_U_y(vector&); +template void lean::sparse_matrix::solve_U_y(vector&); +template void lean::sparse_matrix::double_solve_U_y(vector&); +template void lean::sparse_matrix >::solve_U_y >(vector >&); +template void lean::sparse_matrix >::double_solve_U_y >(vector >&); +template void lean::sparse_matrix::find_error_in_solution_U_y_indexed(lean::indexed_vector&, lean::indexed_vector&, const vector &); +template double lean::sparse_matrix::dot_product_with_row(unsigned int, lean::indexed_vector const&) const; +template void lean::sparse_matrix::find_error_in_solution_U_y_indexed(lean::indexed_vector&, lean::indexed_vector&, const vector &); +template lean::mpq lean::sparse_matrix::dot_product_with_row(unsigned int, lean::indexed_vector const&) const; +template void lean::sparse_matrix >::find_error_in_solution_U_y_indexed(lean::indexed_vector&, lean::indexed_vector&, const vector &); +template lean::mpq lean::sparse_matrix >::dot_product_with_row(unsigned int, lean::indexed_vector const&) const; +template void lean::sparse_matrix >::find_error_in_solution_U_y_indexed >(lean::indexed_vector >&, lean::indexed_vector >&, const vector &); +template lean::numeric_pair lean::sparse_matrix >::dot_product_with_row >(unsigned int, lean::indexed_vector > const&) const; +template void lean::sparse_matrix::extend_and_sort_active_rows(vector const&, vector&); + +template void lean::sparse_matrix >::extend_and_sort_active_rows(vector const&, vector&); + +template void lean::sparse_matrix >::solve_U_y(vector&); +template void lean::sparse_matrix >::double_solve_U_y(vector&); +template void lean::sparse_matrix< lean::mpq,lean::numeric_pair< lean::mpq> >::set(unsigned int,unsigned int, lean::mpq); +template void lean::sparse_matrix::solve_y_U_indexed(lean::indexed_vector&, const lp_settings & ); +template void lean::sparse_matrix::solve_y_U_indexed(lean::indexed_vector&, const lp_settings &); +template void lean::sparse_matrix >::solve_y_U_indexed(lean::indexed_vector&, const lp_settings &); + diff --git a/src/util/lp/sparse_vector.h b/src/util/lp/sparse_vector.h new file mode 100644 index 000000000..78d7ff5be --- /dev/null +++ b/src/util/lp/sparse_vector.h @@ -0,0 +1,38 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ + +#pragma once +#include "util/vector.h" +#include +#include "util/debug.h" +#include "util/lp/lp_utils.h" +#include "util/lp/lp_settings.h" +namespace lean { + +template +class sparse_vector { +public: + vector> m_data; + void push_back(unsigned index, T val) { + m_data.push_back(std::make_pair(index, val)); + } +#ifdef LEAN_DEBUG + T operator[] (unsigned i) const { + for (auto t : m_data) { + if (t.first == i) return t.second; + } + return numeric_traits::zero(); + } +#endif + void divide(T const & a) { + lean_assert(!lp_settings::is_eps_small_general(a, 1e-12)); + for (auto & t : m_data) { t.second /= a; } + } + + unsigned size() const { + return m_data.size(); + } +}; +} diff --git a/src/util/lp/square_dense_submatrix.h b/src/util/lp/square_dense_submatrix.h new file mode 100644 index 000000000..10ae973d6 --- /dev/null +++ b/src/util/lp/square_dense_submatrix.h @@ -0,0 +1,210 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ + +#pragma once +#include "util/vector.h" +#include "util/lp/permutation_matrix.h" +#include +#include "util/lp/static_matrix.h" +#include +#include +#include +#include +#include +#include "util/lp/indexed_value.h" +#include "util/lp/indexed_vector.h" +#include +#include "util/lp/lp_settings.h" +#include "util/lp/eta_matrix.h" +#include "util/lp/binary_heap_upair_queue.h" +#include "util/lp/sparse_matrix.h" +namespace lean { +template +class square_dense_submatrix : public tail_matrix { + // the submatrix uses the permutations of the parent matrix to access the elements + struct ref { + unsigned m_i_offset; + square_dense_submatrix & m_s; + ref(unsigned i, square_dense_submatrix & s) : + m_i_offset((i - s.m_index_start) * s.m_dim), m_s(s){} + T & operator[] (unsigned j) { + lean_assert(j >= m_s.m_index_start); + return m_s.m_v[m_i_offset + m_s.adjust_column(j) - m_s.m_index_start]; + } + const T & operator[] (unsigned j) const { + lean_assert(j >= m_s.m_index_start); + return m_s.m_v[m_i_offset + m_s.adjust_column(j) - m_s.m_index_start]; + } + }; +public: + unsigned m_index_start; + unsigned m_dim; + vector m_v; + sparse_matrix * m_parent = nullptr; + permutation_matrix m_row_permutation; + indexed_vector m_work_vector; +public: + permutation_matrix m_column_permutation; + bool is_active() const { return m_parent != nullptr; } + + square_dense_submatrix() {} + + square_dense_submatrix (sparse_matrix *parent_matrix, unsigned index_start); + + void init(sparse_matrix *parent_matrix, unsigned index_start); + + bool is_dense() const { return true; } + + ref operator[] (unsigned i) { + lean_assert(i >= m_index_start); + lean_assert(i < m_parent->dimension()); + return ref(i, *this); + } + + int find_pivot_column_in_row(unsigned i) const; + + void swap_columns(unsigned i, unsigned j) { + if (i != j) + m_column_permutation.transpose_from_left(i, j); + } + + unsigned adjust_column(unsigned col) const{ + if (col >= m_column_permutation.size()) + return col; + return m_column_permutation.apply_reverse(col); + } + + unsigned adjust_column_inverse(unsigned col) const{ + if (col >= m_column_permutation.size()) + return col; + return m_column_permutation[col]; + } + unsigned adjust_row(unsigned row) const{ + if (row >= m_row_permutation.size()) + return row; + return m_row_permutation[row]; + } + + unsigned adjust_row_inverse(unsigned row) const{ + if (row >= m_row_permutation.size()) + return row; + return m_row_permutation.apply_reverse(row); + } + + void pivot(unsigned i, lp_settings & settings); + + void pivot_row_to_row(unsigned i, unsigned row, lp_settings & settings);; + + void divide_row_by_pivot(unsigned i); + + void update_parent_matrix(lp_settings & settings); + + void update_existing_or_delete_in_parent_matrix_for_row(unsigned i, lp_settings & settings); + + void push_new_elements_to_parent_matrix(lp_settings & settings); + + template + L row_by_vector_product(unsigned i, const vector & v); + + template + L column_by_vector_product(unsigned j, const vector & v); + + template + L row_by_indexed_vector_product(unsigned i, const indexed_vector & v); + + template + void apply_from_left_local(indexed_vector & w, lp_settings & settings); + + template + void apply_from_left_to_vector(vector & w); + + bool is_L_matrix() const; + + void apply_from_left_to_T(indexed_vector & w, lp_settings & settings) { + apply_from_left_local(w, settings); + } + + + + void apply_from_right(indexed_vector & w) { +#if 1==0 + indexed_vector wcopy = w; + apply_from_right(wcopy.m_data); + wcopy.m_index.clear(); + if (numeric_traits::precise()) { + for (unsigned i = 0; i < m_parent->dimension(); i++) { + if (!is_zero(wcopy.m_data[i])) + wcopy.m_index.push_back(i); + } + } else { + for (unsigned i = 0; i < m_parent->dimension(); i++) { + T & v = wcopy.m_data[i]; + if (!lp_settings::is_eps_small_general(v, 1e-14)){ + wcopy.m_index.push_back(i); + } else { + v = zero_of_type(); + } + } + } + lean_assert(wcopy.is_OK()); + apply_from_right(w.m_data); + w.m_index.clear(); + if (numeric_traits::precise()) { + for (unsigned i = 0; i < m_parent->dimension(); i++) { + if (!is_zero(w.m_data[i])) + w.m_index.push_back(i); + } + } else { + for (unsigned i = 0; i < m_parent->dimension(); i++) { + T & v = w.m_data[i]; + if (!lp_settings::is_eps_small_general(v, 1e-14)){ + w.m_index.push_back(i); + } else { + v = zero_of_type(); + } + } + } +#else + lean_assert(w.is_OK()); + lean_assert(m_work_vector.is_OK()); + m_work_vector.resize(w.data_size()); + m_work_vector.clear(); + lean_assert(m_work_vector.is_OK()); + unsigned end = m_index_start + m_dim; + for (unsigned k : w.m_index) { + // find j such that k = adjust_row_inverse(j) + unsigned j = adjust_row(k); + if (j < m_index_start || j >= end) { + m_work_vector.set_value(w[k], adjust_column_inverse(j)); + } else { // j >= m_index_start and j < end + unsigned offset = (j - m_index_start) * m_dim; // this is the row start + const T& wv = w[k]; + for (unsigned col = m_index_start; col < end; col++, offset ++) { + unsigned adj_col = adjust_column_inverse(col); + m_work_vector.add_value_at_index(adj_col, m_v[offset] * wv); + } + } + } + m_work_vector.clean_up(); + lean_assert(m_work_vector.is_OK()); + w = m_work_vector; +#endif + } + void apply_from_left(vector & w, lp_settings & /*settings*/) { + apply_from_left_to_vector(w);// , settings); + } + + void apply_from_right(vector & w); + +#ifdef LEAN_DEBUG + T get_elem (unsigned i, unsigned j) const; + unsigned row_count() const { return m_parent->row_count();} + unsigned column_count() const { return row_count();} + void set_number_of_rows(unsigned) {} + void set_number_of_columns(unsigned) {}; +#endif + void conjugate_by_permutation(permutation_matrix & q); +}; +} diff --git a/src/util/lp/square_dense_submatrix.hpp b/src/util/lp/square_dense_submatrix.hpp new file mode 100644 index 000000000..365c9d7f0 --- /dev/null +++ b/src/util/lp/square_dense_submatrix.hpp @@ -0,0 +1,353 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include "util/vector.h" +#include "util/lp/square_dense_submatrix.h" +namespace lean { +template +square_dense_submatrix::square_dense_submatrix (sparse_matrix *parent_matrix, unsigned index_start) : + m_index_start(index_start), + m_dim(parent_matrix->dimension() - index_start), + m_v(m_dim * m_dim), + m_parent(parent_matrix), + m_row_permutation(m_parent->dimension()), + m_column_permutation(m_parent->dimension()) { + int row_offset = - static_cast(m_index_start); + for (unsigned i = index_start; i < parent_matrix->dimension(); i++) { + unsigned row = parent_matrix->adjust_row(i); + for (auto & iv : parent_matrix->get_row_values(row)) { + unsigned j = parent_matrix->adjust_column_inverse(iv.m_index); + lean_assert(j>= m_index_start); + m_v[row_offset + j] = iv.m_value; + } + row_offset += m_dim; + } +} + +template void square_dense_submatrix::init(sparse_matrix *parent_matrix, unsigned index_start) { + m_index_start = index_start; + m_dim = parent_matrix->dimension() - index_start; + m_v.resize(m_dim * m_dim); + m_parent = parent_matrix; + m_column_permutation.init(m_parent->dimension()); + for (unsigned i = index_start; i < parent_matrix->dimension(); i++) { + unsigned row = parent_matrix->adjust_row(i); + for (auto & iv : parent_matrix->get_row_values(row)) { + unsigned j = parent_matrix->adjust_column_inverse(iv.m_index); + (*this)[i][j] = iv.m_value; + } + } +} + +template int square_dense_submatrix::find_pivot_column_in_row(unsigned i) const { + int j = -1; + T max = zero_of_type(); + lean_assert(i >= m_index_start); + unsigned row_start = (i - m_index_start) * m_dim; + for (unsigned k = i; k < m_parent->dimension(); k++) { + unsigned col = adjust_column(k); // this is where the column is in the row + unsigned offs = row_start + col - m_index_start; + T t = abs(m_v[offs]); + if (t > max) { + j = k; + max = t; + } + } + return j; +} + +template void square_dense_submatrix::pivot(unsigned i, lp_settings & settings) { + divide_row_by_pivot(i); + for (unsigned k = i + 1; k < m_parent->dimension(); k++) + pivot_row_to_row(i, k, settings); +} + +template void square_dense_submatrix::pivot_row_to_row(unsigned i, unsigned row, lp_settings & settings) { + lean_assert(i < row); + unsigned pj = adjust_column(i); // the pivot column + unsigned pjd = pj - m_index_start; + unsigned pivot_row_offset = (i-m_index_start)*m_dim; + T pivot = m_v[pivot_row_offset + pjd]; + unsigned row_offset= (row-m_index_start)*m_dim; + T m = m_v[row_offset + pjd]; + lean_assert(!is_zero(pivot)); + m_v[row_offset + pjd] = -m * pivot; // creating L matrix + for (unsigned j = m_index_start; j < m_parent->dimension(); j++) { + if (j == pj) { + pivot_row_offset++; + row_offset++; + continue; + } + auto t = m_v[row_offset] - m_v[pivot_row_offset] * m; + if (settings.abs_val_is_smaller_than_drop_tolerance(t)) { + m_v[row_offset] = zero_of_type(); + } else { + m_v[row_offset] = t; + } + row_offset++; pivot_row_offset++; + // at the same time we pivot the L too + } +} + +template void square_dense_submatrix::divide_row_by_pivot(unsigned i) { + unsigned pj = adjust_column(i); // the pivot column + unsigned irow_offset = (i - m_index_start) * m_dim; + T pivot = m_v[irow_offset + pj - m_index_start]; + lean_assert(!is_zero(pivot)); + for (unsigned k = m_index_start; k < m_parent->dimension(); k++) { + if (k == pj){ + m_v[irow_offset++] = one_of_type() / pivot; // creating the L matrix diagonal + continue; + } + m_v[irow_offset++] /= pivot; + } +} + +template void square_dense_submatrix::update_parent_matrix(lp_settings & settings) { + for (unsigned i = m_index_start; i < m_parent->dimension(); i++) + update_existing_or_delete_in_parent_matrix_for_row(i, settings); + push_new_elements_to_parent_matrix(settings); + for (unsigned i = m_index_start; i < m_parent->dimension(); i++) + m_parent->set_max_in_row(m_parent->adjust_row(i)); +} + +template void square_dense_submatrix::update_existing_or_delete_in_parent_matrix_for_row(unsigned i, lp_settings & settings) { + bool diag_updated = false; + unsigned ai = m_parent->adjust_row(i); + auto & row_vals = m_parent->get_row_values(ai); + for (unsigned k = 0; k < row_vals.size(); k++) { + auto & iv = row_vals[k]; + unsigned j = m_parent->adjust_column_inverse(iv.m_index); + if (j < i) { + m_parent->remove_element(row_vals, iv); + k--; + } else if (i == j) { + m_parent->m_columns[iv.m_index].m_values[iv.m_other].set_value(iv.m_value = one_of_type()); + diag_updated = true; + } else { // j > i + T & v = (*this)[i][j]; + if (settings.abs_val_is_smaller_than_drop_tolerance(v)) { + m_parent->remove_element(row_vals, iv); + k--; + } else { + m_parent->m_columns[iv.m_index].m_values[iv.m_other].set_value(iv.m_value = v); + v = zero_of_type(); // only new elements are left above the diagonal + } + } + } + if (!diag_updated) { + unsigned aj = m_parent->adjust_column(i); + m_parent->add_new_element(ai, aj, one_of_type()); + } +} + +template void square_dense_submatrix::push_new_elements_to_parent_matrix(lp_settings & settings) { + for (unsigned i = m_index_start; i < m_parent->dimension() - 1; i++) { + unsigned ai = m_parent->adjust_row(i); + for (unsigned j = i + 1; j < m_parent->dimension(); j++) { + T & v = (*this)[i][j]; + if (!settings.abs_val_is_smaller_than_drop_tolerance(v)) { + unsigned aj = m_parent->adjust_column(j); + m_parent->add_new_element(ai, aj, v); + } + v = zero_of_type(); // leave only L elements now + } + } +} +template +template +L square_dense_submatrix::row_by_vector_product(unsigned i, const vector & v) { + lean_assert(i >= m_index_start); + + unsigned row_in_subm = i - m_index_start; + unsigned row_offset = row_in_subm * m_dim; + L r = zero_of_type(); + for (unsigned j = 0; j < m_dim; j++) + r += m_v[row_offset + j] * v[adjust_column_inverse(m_index_start + j)]; + return r; +} + +template +template +L square_dense_submatrix::column_by_vector_product(unsigned j, const vector & v) { + lean_assert(j >= m_index_start); + + unsigned offset = j - m_index_start; + L r = zero_of_type(); + for (unsigned i = 0; i < m_dim; i++, offset += m_dim) + r += m_v[offset] * v[adjust_row_inverse(m_index_start + i)]; + return r; +} +template +template +L square_dense_submatrix::row_by_indexed_vector_product(unsigned i, const indexed_vector & v) { + lean_assert(i >= m_index_start); + + unsigned row_in_subm = i - m_index_start; + unsigned row_offset = row_in_subm * m_dim; + L r = zero_of_type(); + for (unsigned j = 0; j < m_dim; j++) + r += m_v[row_offset + j] * v[adjust_column_inverse(m_index_start + j)]; + return r; +} +template +template +void square_dense_submatrix::apply_from_left_local(indexed_vector & w, lp_settings & settings) { +#ifdef LEAN_DEBUG + // dense_matrix deb(*this); + // vector deb_w(w.m_data.size()); + // for (unsigned i = 0; i < w.m_data.size(); i++) + // deb_w[i] = w[i]; + + // deb.apply_from_left(deb_w); +#endif // use indexed vector here + +#ifndef DO_NOT_USE_INDEX + vector t(m_parent->dimension(), zero_of_type()); + for (auto k : w.m_index) { + unsigned j = adjust_column(k); // k-th element will contribute only to column j + if (j < m_index_start || j >= this->m_index_start + this->m_dim) { // it is a unit matrix outside + t[adjust_row_inverse(j)] = w[k]; + } else { + const L & v = w[k]; + for (unsigned i = 0; i < m_dim; i++) { + unsigned row = adjust_row_inverse(m_index_start + i); + unsigned offs = i * m_dim + j - m_index_start; + t[row] += m_v[offs] * v; + } + } + } + w.m_index.clear(); + for (unsigned i = 0; i < m_parent->dimension(); i++) { + const L & v = t[i]; + if (!settings.abs_val_is_smaller_than_drop_tolerance(v)){ + w.m_index.push_back(i); + w.m_data[i] = v; + } else { + w.m_data[i] = zero_of_type(); + } + } +#else + vector t(m_parent->dimension()); + for (unsigned i = 0; i < m_index_start; i++) { + t[adjust_row_inverse(i)] = w[adjust_column_inverse(i)]; + } + for (unsigned i = m_index_start; i < m_parent->dimension(); i++){ + t[adjust_row_inverse(i)] = row_by_indexed_vector_product(i, w); + } + for (unsigned i = 0; i < m_parent->dimension(); i++) { + w.set_value(t[i], i); + } + for (unsigned i = 0; i < m_parent->dimension(); i++) { + const L & v = t[i]; + if (!is_zero(v)) + w.m_index.push_back(i); + w.m_data[i] = v; + } +#endif +#ifdef LEAN_DEBUG + // cout << "w final" << endl; + // print_vector(w.m_data); + // lean_assert(vectors_are_equal(deb_w, w.m_data)); + // lean_assert(w.is_OK()); +#endif +} + +template +template +void square_dense_submatrix::apply_from_left_to_vector(vector & w) { + // lp_settings & settings) { + // dense_matrix deb(*this); + // vector deb_w(w); + // deb.apply_from_left_to_X(deb_w, settings); + // // cout << "deb" << endl; + // // print_matrix(deb); + // // cout << "w" << endl; + // // print_vector(w.m_data); + // // cout << "deb_w" << endl; + // // print_vector(deb_w); + vector t(m_parent->dimension()); + for (unsigned i = 0; i < m_index_start; i++) { + t[adjust_row_inverse(i)] = w[adjust_column_inverse(i)]; + } + for (unsigned i = m_index_start; i < m_parent->dimension(); i++){ + t[adjust_row_inverse(i)] = row_by_vector_product(i, w); + } + for (unsigned i = 0; i < m_parent->dimension(); i++) { + w[i] = t[i]; + } +#ifdef LEAN_DEBUG + // cout << "w final" << endl; + // print_vector(w.m_data); + // lean_assert(vectors_are_equal(deb_w, w)); +#endif +} + +template bool square_dense_submatrix::is_L_matrix() const { +#ifdef LEAN_DEBUG + lean_assert(m_row_permutation.is_identity()); + for (unsigned i = 0; i < m_parent->dimension(); i++) { + if (i < m_index_start) { + lean_assert(m_column_permutation[i] == i); + continue; + } + unsigned row_offs = (i-m_index_start)*m_dim; + for (unsigned k = 0; k < m_dim; k++) { + unsigned j = m_index_start + k; + unsigned jex = adjust_column_inverse(j); + if (jex > i) { + lean_assert(is_zero(m_v[row_offs + k])); + } else if (jex == i) { + lean_assert(!is_zero(m_v[row_offs + k])); + } + } + } +#endif + return true; +} + +template void square_dense_submatrix::apply_from_right(vector & w) { +#ifdef LEAN_DEBUG + // dense_matrix deb(*this); + // vector deb_w(w); + // deb.apply_from_right(deb_w); +#endif + vector t(w.size()); + + for (unsigned j = 0; j < m_index_start; j++) { + t[adjust_column_inverse(j)] = w[adjust_row_inverse(j)]; + } + unsigned end = m_index_start + m_dim; + for (unsigned j = end; j < m_parent->dimension(); j++) { + t[adjust_column_inverse(j)] = w[adjust_row_inverse(j)]; + } + for (unsigned j = m_index_start; j < end; j++) { + t[adjust_column_inverse(j)] = column_by_vector_product(j, w); + } + w = t; +#ifdef LEAN_DEBUG + // lean_assert(vector_are_equal(deb_w, w)); +#endif +} + + + + +#ifdef LEAN_DEBUG + +template T square_dense_submatrix::get_elem (unsigned i, unsigned j) const { + i = adjust_row(i); + j = adjust_column(j); + if (i < m_index_start || j < m_index_start) + return i == j? one_of_type() : zero_of_type(); + unsigned offs = (i - m_index_start)* m_dim + j - m_index_start; + return m_v[offs]; +} + +#endif +template void square_dense_submatrix::conjugate_by_permutation(permutation_matrix & q) { + m_row_permutation.multiply_by_permutation_from_left(q); + m_column_permutation.multiply_by_reverse_from_right(q); +} +} diff --git a/src/util/lp/square_dense_submatrix_instances.cpp b/src/util/lp/square_dense_submatrix_instances.cpp new file mode 100644 index 000000000..7d45aaaa1 --- /dev/null +++ b/src/util/lp/square_dense_submatrix_instances.cpp @@ -0,0 +1,33 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include +#include "util/vector.h" +#include "util/lp/square_dense_submatrix.hpp" +template void lean::square_dense_submatrix::init(lean::sparse_matrix*, unsigned int); +template lean::square_dense_submatrix::square_dense_submatrix(lean::sparse_matrix*, unsigned int); +template void lean::square_dense_submatrix::update_parent_matrix(lean::lp_settings&); +template bool lean::square_dense_submatrix::is_L_matrix() const; +template void lean::square_dense_submatrix::conjugate_by_permutation(lean::permutation_matrix&); +template int lean::square_dense_submatrix::find_pivot_column_in_row(unsigned int) const; +template void lean::square_dense_submatrix::pivot(unsigned int, lean::lp_settings&); +template lean::square_dense_submatrix >::square_dense_submatrix(lean::sparse_matrix >*, unsigned int); +template void lean::square_dense_submatrix >::update_parent_matrix(lean::lp_settings&); +template bool lean::square_dense_submatrix >::is_L_matrix() const; +template void lean::square_dense_submatrix >::conjugate_by_permutation(lean::permutation_matrix >&); +template int lean::square_dense_submatrix >::find_pivot_column_in_row(unsigned int) const; +template void lean::square_dense_submatrix >::pivot(unsigned int, lean::lp_settings&); +#ifdef LEAN_DEBUG +template double lean::square_dense_submatrix::get_elem(unsigned int, unsigned int) const; +#endif +template void lean::square_dense_submatrix::apply_from_right(vector&); + +template void lean::square_dense_submatrix::apply_from_left_local(lean::indexed_vector&, lean::lp_settings&); +template void lean::square_dense_submatrix::apply_from_left_to_vector(vector&); +template lean::square_dense_submatrix::square_dense_submatrix(lean::sparse_matrix*, unsigned int); +template void lean::square_dense_submatrix::update_parent_matrix(lean::lp_settings&); +template bool lean::square_dense_submatrix::is_L_matrix() const; +template void lean::square_dense_submatrix::conjugate_by_permutation(lean::permutation_matrix&); +template int lean::square_dense_submatrix::find_pivot_column_in_row(unsigned int) const; +template void lean::square_dense_submatrix::pivot(unsigned int, lean::lp_settings&); diff --git a/src/util/lp/stacked_map.h b/src/util/lp/stacked_map.h new file mode 100644 index 000000000..4692540dd --- /dev/null +++ b/src/util/lp/stacked_map.h @@ -0,0 +1,179 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ + +#pragma once +// this class implements a map with some stack functionality +#include +#include +#include +namespace lean { + + +template , + typename KeyEqual = std::equal_to, + typename Allocator = std::allocator< std::pair > > +class stacked_map { + struct delta { + std::unordered_set m_new; + std::unordered_map m_original_changed; + // std::unordered_map m_deb_copy; + }; + std::unordered_map m_map; + std::stack m_stack; +public: + class ref { + stacked_map & m_map; + const A & m_key; + public: + ref(stacked_map & m, const A & key) :m_map(m), m_key(key) {} + ref & operator=(const B & b) { + m_map.emplace_replace(m_key, b); + return *this; + } + ref & operator=(const ref & b) { lean_assert(false); return *this; } + operator const B&() const { + auto it = m_map.m_map.find(m_key); + lean_assert(it != m_map.m_map.end()); + return it->second; + } + }; +private: + void emplace_replace(const A & a, const B & b) { + if (!m_stack.empty()) { + delta & d = m_stack.top(); + auto it = m_map.find(a); + if (it == m_map.end()) { + d.m_new.insert(a); + m_map.emplace(a, b); + } else if (it->second != b) { + auto nit = d.m_new.find(a); + if (nit == d.m_new.end()) { // we do not have the old key + auto & orig_changed= d.m_original_changed; + auto itt = orig_changed.find(a); + if (itt == orig_changed.end()) { + orig_changed.emplace(a, it->second); + } else if (itt->second == b) { + orig_changed.erase(itt); + } + } + it->second = b; + } + } else { // there is no stack: just emplace or replace + m_map[a] = b; + } + } +public: + ref operator[] (const A & a) { + return ref(*this, a); + } + + const B & operator[]( const A & a) const { + auto it = m_map.find(a); + if (it == m_map.end()) { + lean_assert(false); + } + + return it->second; + } + + bool try_get_value(const A& key, B& val) const { + auto it = m_map.find(key); + if (it == m_map.end()) + return false; + + val = it->second; + return true; + } + bool try_get_value(const A&& key, B& val) const { + auto it = m_map.find(std::move(key)); + if (it == m_map.end()) + return false; + + val = it->second; + return true; + } + + unsigned size() const { + return m_map.size(); + } + + bool contains(const A & key) const { + return m_map.find(key) != m_map.end(); + } + + bool contains(const A && key) const { + return m_map.find(std::move(key)) != m_map.end(); + } + + void push() { + delta d; + // d.m_deb_copy = m_map; + m_stack.push(d); + } + + void pop() { + pop(1); + } + void pop(unsigned k) { + while (k-- > 0) { + if (m_stack.empty()) + return; + delta & d = m_stack.top(); + for (auto & t : d.m_new) { + m_map.erase(t); + } + for (auto & t: d.m_original_changed) { + m_map[t.first] = t.second; + } + // lean_assert(d.m_deb_copy == m_map); + m_stack.pop(); + } + } + + void erase(const A & key) { + if (m_stack.empty()) { + m_map.erase(key); + return; + } + + delta & d = m_stack.top(); + auto it = m_map.find(key); + if (it == m_map.end()) { + lean_assert(d.m_new.find(key) == d.m_new.end()); + return; + } + auto &orig_changed = d.m_original_changed; + auto nit = d.m_new.find(key); + if (nit == d.m_new.end()) { // key is old + if (orig_changed.find(key) == orig_changed.end()) + orig_changed.emplace(it->first, it->second); // need to restore + } else { // k is new + lean_assert(orig_changed.find(key) == orig_changed.end()); + d.m_new.erase(nit); + } + + m_map.erase(it); + } + + void clear() { + if (m_stack.empty()) { + m_map.clear(); + return; + } + + delta & d = m_stack.top(); + auto & oc = d.m_original_changed; + for (auto & p : m_map) { + const auto & it = oc.find(p.first); + if (it == oc.end() && d.m_new.find(p.first) == d.m_new.end()) + oc.emplace(p.first, p.second); + } + m_map.clear(); + } + + const std::unordered_map& operator()() const { return m_map;} +}; +} diff --git a/src/util/lp/stacked_unordered_set.h b/src/util/lp/stacked_unordered_set.h new file mode 100644 index 000000000..69c4cf03b --- /dev/null +++ b/src/util/lp/stacked_unordered_set.h @@ -0,0 +1,92 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ + +#pragma once +// this class implements an unordered_set with some stack functionality +#include +#include +#include +namespace lean { + +template , + typename KeyEqual = std::equal_to, + typename Allocator = std::allocator + > class stacked_unordered_set { + struct delta { + std::unordered_set m_inserted; + std::unordered_set m_erased; + std::unordered_set m_deb_copy; + }; + std::unordered_set m_set; + std::stack m_stack; +public: + void insert(const A & a) { + if (m_stack.empty()) { + m_set.insert(a); + } else if (m_set.find(a) == m_set.end()) { + m_set.insert(a); + size_t in_erased = m_stack.top().m_erased.erase(a); + if (in_erased == 0) { + m_stack.top().m_inserted.insert(a); + } + } + } + + void erase(const A &a) { + if (m_stack.empty()) { + m_set.erase(a); + return; + } + auto erased = m_set.erase(a); + if (erased == 1) { + auto was_new = m_stack.top().m_inserted.erase(a); + if (was_new == 0) { + m_stack.top().m_erased.insert(a); + } + } + } + + unsigned size() const { + return m_set.size(); + } + + bool contains(A & key) const { + return m_set.find(key) != m_set.end(); + } + + bool contains(A && key) const { + return m_set.find(std::move(key)) != m_set.end(); + } + + void push() { + delta d; + d.m_deb_copy = m_set; + m_stack.push(d); + } + + void pop() { + pop(1); + } + void pop(unsigned k) { + while (k-- > 0) { + if (m_stack.empty()) + return; + delta & d = m_stack.top(); + for (auto & t : d.m_inserted) { + m_set.erase(t); + } + for (auto & t : d.m_erased) { + m_set.insert(t); + } + lean_assert(d.m_deb_copy == m_set); + m_stack.pop(); + } + } + + const std::unordered_set& operator()() const { return m_set;} +}; +} + diff --git a/src/util/lp/stacked_value.h b/src/util/lp/stacked_value.h new file mode 100644 index 000000000..2a1e85be7 --- /dev/null +++ b/src/util/lp/stacked_value.h @@ -0,0 +1,65 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ + +#pragma once +// add to value the stack semantics +#include +namespace lean { +template class stacked_value { + T m_value; + std::stack m_stack; +public: + void push() { + m_stack.push(m_value); + } + + unsigned stack_size() const { + return static_cast(m_stack.size()); + } + + void pop() { + pop(1); + } + void pop(unsigned k) { + while (k-- > 0) { + if (m_stack.empty()) + return; + m_value = m_stack.top(); + m_stack.pop(); + } + } + + stacked_value() {} + stacked_value(const T& m) { + m_value = m; + } + stacked_value(const T&& m) { + m_value = std::move(m); + } + + T& operator=(T arg) { // copy/move constructor + m_value = arg; + return m_value; + } + + operator T&() { + return m_value; + } + + operator const T&() const { + return m_value; + } + + T & operator()() { + return m_value; + } + + const T & operator()() const { + return m_value; + } + + +}; +} diff --git a/src/util/lp/stacked_vector.h b/src/util/lp/stacked_vector.h new file mode 100644 index 000000000..3f39dd346 --- /dev/null +++ b/src/util/lp/stacked_vector.h @@ -0,0 +1,167 @@ +/* +Copyright (c) 2017 Microsoft Corporation +Author: Lev Nachmanson +*/ +#pragma once +#include +#include +#include +#include "util/vector.h" +namespace lean { +template < typename B> class stacked_vector { + vector m_stack_of_vector_sizes; + vector m_stack_of_change_sizes; + vector> m_changes; + vector m_vector; +public: + class ref { + stacked_vector & m_vec; + unsigned m_i; + public: + ref(stacked_vector &m, unsigned key) :m_vec(m), m_i(key) { + lean_assert(key < m.size()); + } + ref & operator=(const B & b) { + m_vec.emplace_replace(m_i, b); + return *this; + } + ref & operator=(const ref & b) { + m_vec.emplace_replace(m_i, b.m_vec.m_vector[b.m_i]); + return *this; + } + operator const B&() const { + return m_vec.m_vector[m_i]; + } + + }; + + class ref_const { + const stacked_vector & m_vec; + unsigned m_i; + public: + ref_const(const stacked_vector &m, unsigned key) :m_vec(m), m_i(key) { + lean_assert(key < m.size()); + } + + operator const B&() const { + return m_vec.m_vector[m_i]; + } + + }; + +private: + void emplace_replace(unsigned i,const B & b) { + if (m_vector[i] != b) { + m_changes.push_back(std::make_pair(i, m_vector[i])); + m_vector[i] = b; + } + } +public: + + ref operator[] (unsigned a) { + return ref(*this, a); + } + + ref_const operator[] (unsigned a) const { + return ref_const(*this, a); + } + + /* + const B & operator[](unsigned a) const { + lean_assert(a < m_vector.size()); + return m_vector[a]; + } + */ + unsigned size() const { + return m_vector.size(); + } + + + void push() { + m_stack_of_change_sizes.push_back(m_changes.size()); + m_stack_of_vector_sizes.push_back(m_vector.size()); + } + + void pop() { + pop(1); + } + + template + void pop_tail(vector & v, unsigned k) { + lean_assert(v.size() >= k); + v.resize(v.size() - k); + } + + template + void resize(vector & v, unsigned new_size) { + v.resize(new_size); + } + + void pop(unsigned k) { + lean_assert(m_stack_of_vector_sizes.size() >= k); + lean_assert(k > 0); + resize(m_vector, m_stack_of_vector_sizes[m_stack_of_vector_sizes.size() - k]); + pop_tail(m_stack_of_vector_sizes, k); + unsigned first_change = m_stack_of_change_sizes[m_stack_of_change_sizes.size() - k]; + pop_tail(m_stack_of_change_sizes, k); + for (unsigned j = m_changes.size(); j-- > first_change; ) { + const auto & p = m_changes[j]; + unsigned jc = p.first; + if (jc < m_vector.size()) + m_vector[jc] = p.second; // restore the old value + } + resize(m_changes, first_change); + + /* + while (k-- > 0) { + + if (m_stack.empty()) + return; + + delta & d = m_stack.back(); + lean_assert(m_vector.size() >= d.m_size); + while (m_vector.size() > d.m_size) + m_vector.pop_back(); + + for (auto & t : d.m_original_changed) { + lean_assert(t.first < m_vector.size()); + m_vector[t.first] = t.second; + } + // lean_assert(d.m_deb_copy == m_vector); + m_stack.pop_back();*/ + } + + + // void clear() { + // if (m_stack.empty()) { + // m_vector.clear(); + // return; + // } + + // delta & d = m_stack.top(); + // auto & oc = d.m_original_changed; + // for (auto & p : m_vector) { + // const auto & it = oc.find(p.first); + // if (it == oc.end() && d.m_new.find(p.first) == d.m_new.end()) + // oc.emplace(p.first, p.second); + // } + // m_vector.clear(); + // } + + void push_back(const B & b) { + m_vector.push_back(b); + } + + void increase_size_by_one() { + m_vector.resize(m_vector.size() + 1); + } + + unsigned peek_size(unsigned k) const { + lean_assert(k > 0 && k <= m_stack_of_vector_sizes.size()); + return m_stack_of_vector_sizes[m_stack_of_vector_sizes.size() - k]; + } + + const vector& operator()() const { return m_vector; } +}; +} + diff --git a/src/util/lp/static_matrix.h b/src/util/lp/static_matrix.h new file mode 100644 index 000000000..5ef4b449f --- /dev/null +++ b/src/util/lp/static_matrix.h @@ -0,0 +1,364 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ + +#pragma once +#include "util/vector.h" +#include +#include +#include +#include "util/lp/sparse_vector.h" +#include "util/lp/indexed_vector.h" +#include "util/lp/permutation_matrix.h" +#include "util/lp/linear_combination_iterator.h" +#include +namespace lean { + +struct column_cell { + unsigned m_i; // points to the row + unsigned m_offset; // the offset of the element in the matrix row + column_cell(unsigned i, unsigned offset) : m_i(i), m_offset(offset) { + } +}; + +template +struct row_cell { + unsigned m_j; // points to the column + unsigned m_offset; // offset in column + row_cell(unsigned j, unsigned offset, T const & val) : m_j(j), m_offset(offset), m_value(val) { + } + const T & get_val() const { return m_value;} + T & get_val() { return m_value;} + void set_val(const T& v) { m_value = v; } + T m_value; +}; + +// each assignment for this matrix should be issued only once!!! +template +class static_matrix +#ifdef LEAN_DEBUG + : public matrix +#endif +{ + struct dim { + unsigned m_m; + unsigned m_n; + dim(unsigned m, unsigned n) :m_m(m), m_n(n) {} + }; + std::stack m_stack; + vector m_became_zeros; // the row indices that became zeroes during the pivoting +public: + typedef vector> row_strip; + typedef vector column_strip; + vector m_vector_of_row_offsets; + indexed_vector m_work_vector; + vector m_rows; + vector m_columns; + // starting inner classes + class ref { + static_matrix & m_matrix; + unsigned m_row; + unsigned m_col; + public: + ref(static_matrix & m, unsigned row, unsigned col):m_matrix(m), m_row(row), m_col(col) {} + ref & operator=(T const & v) { m_matrix.set( m_row, m_col, v); return *this; } + + ref & operator=(ref const & v) { m_matrix.set(m_row, m_col, v.m_matrix.get(v.m_row, v.m_col)); return *this; } + + operator T () const { return m_matrix.get_elem(m_row, m_col); } + }; + + class ref_row { + static_matrix & m_matrix; + unsigned m_row; + public: + ref_row(static_matrix & m, unsigned row):m_matrix(m), m_row(row) {} + ref operator[](unsigned col) const { return ref(m_matrix, m_row, col); } + }; + +public: + + const T & get_val(const column_cell & c) const { + return m_rows[c.m_i][c.m_offset].get_val(); + } + + void init_row_columns(unsigned m, unsigned n); + + // constructor with no parameters + static_matrix() {} + + // constructor + static_matrix(unsigned m, unsigned n): m_vector_of_row_offsets(n, -1) { + init_row_columns(m, n); + } + // constructor that copies columns of the basis from A + static_matrix(static_matrix const &A, unsigned * basis); + + void clear(); + + void init_vector_of_row_offsets(); + + void init_empty_matrix(unsigned m, unsigned n); + + unsigned row_count() const { return static_cast(m_rows.size()); } + + unsigned column_count() const { return static_cast(m_columns.size()); } + + unsigned lowest_row_in_column(unsigned col); + + void add_columns_at_the_end(unsigned delta); + void add_new_element(unsigned i, unsigned j, const T & v); + + void add_row() {m_rows.push_back(row_strip());} + void add_column() { + m_columns.push_back(column_strip()); + m_vector_of_row_offsets.push_back(-1); + } + + void forget_last_columns(unsigned how_many_to_forget); + + void remove_last_column(unsigned j); + + void remove_element(vector> & row, row_cell & elem_to_remove); + + void multiply_column(unsigned column, T const & alpha) { + for (auto & t : m_columns[column]) { + auto & r = m_rows[t.m_i][t.m_offset]; + r.m_value *= alpha; + } + } + + +#ifdef LEAN_DEBUG + void regen_domain(); +#endif + + // offs - offset in columns + row_cell make_row_cell(unsigned row, unsigned offs, T const & val) { + return row_cell(row, offs, val); + } + + column_cell make_column_cell(unsigned column, unsigned offset) { + return column_cell(column, offset); + } + + void set(unsigned row, unsigned col, T const & val); + + ref operator()(unsigned row, unsigned col) { return ref(*this, row, col); } + + std::set> get_domain(); + + void copy_column_to_indexed_vector(unsigned j, indexed_vector & v) const; + + T get_max_abs_in_row(unsigned row) const; + void add_column_to_vector (const T & a, unsigned j, T * v) const { + for (const auto & it : m_columns[j]) { + v[it.m_i] += a * get_val(it); + } + } + + T get_min_abs_in_row(unsigned row) const; + T get_max_abs_in_column(unsigned column) const; + + T get_min_abs_in_column(unsigned column) const; + +#ifdef LEAN_DEBUG + void check_consistency(); +#endif + + + void cross_out_row(unsigned k); + + // + void fix_row_indices_in_each_column_for_crossed_row(unsigned k); + + void cross_out_row_from_columns(unsigned k, row_strip & row); + + void cross_out_row_from_column(unsigned col, unsigned k); + + T get_elem(unsigned i, unsigned j) const; + + + unsigned number_of_non_zeroes_in_column(unsigned j) const { return m_columns[j].size(); } + + unsigned number_of_non_zeroes_in_row(unsigned i) const { return m_rows[i].size(); } + + unsigned number_of_non_zeroes() const { + unsigned ret = 0; + for (unsigned i = 0; i < row_count(); i++) + ret += number_of_non_zeroes_in_row(i); + return ret; + } + + void scan_row_to_work_vector(unsigned i); + + void clean_row_work_vector(unsigned i); + + +#ifdef LEAN_DEBUG + unsigned get_number_of_rows() const { return row_count(); } + unsigned get_number_of_columns() const { return column_count(); } + virtual void set_number_of_rows(unsigned /*m*/) { } + virtual void set_number_of_columns(unsigned /*n*/) { } +#endif + + T get_max_val_in_row(unsigned /* i */) const { lean_unreachable(); } + + T get_balance() const; + + T get_row_balance(unsigned row) const; + + bool is_correct() const; + void push() { + dim d(row_count(), column_count()); + m_stack.push(d); + } + + void pop_row_columns(const vector> & row) { + for (auto & c : row) { + unsigned j = c.m_j; + auto & col = m_columns[j]; + lean_assert(col[col.size() - 1].m_i == m_rows.size() -1 ); // todo : start here!!!! + col.pop_back(); + } + } + + + + void pop(unsigned k) { +#ifdef LEAN_DEBUG + std::set> pairs_to_remove_from_domain; +#endif + + + while (k-- > 0) { + if (m_stack.empty()) break; + unsigned m = m_stack.top().m_m; + while (m < row_count()) { + unsigned i = m_rows.size() -1 ; + auto & row = m_rows[i]; + pop_row_columns(row); + m_rows.pop_back(); // delete the last row + } + unsigned n = m_stack.top().m_n; + while (n < column_count()) + m_columns.pop_back(); // delete the last column + m_stack.pop(); + } + lean_assert(is_correct()); + } + + void multiply_row(unsigned row, T const & alpha) { + for (auto & t : m_rows[row]) { + t.m_value *= alpha; + } + } + + void divide_row(unsigned row, T const & alpha) { + for (auto & t : m_rows[row]) { + t.m_value /= alpha; + } + } + + T dot_product_with_column(const vector & y, unsigned j) const { + lean_assert(j < column_count()); + T ret = numeric_traits::zero(); + for (auto & it : m_columns[j]) { + ret += y[it.m_i] * get_val(it); // get_value_of_column_cell(it); + } + return ret; + } + + // pivot row i to row ii + bool pivot_row_to_row_given_cell(unsigned i, column_cell& c, unsigned); + void scan_row_ii_to_offset_vector(unsigned ii); + + void transpose_rows(unsigned i, unsigned ii) { + auto t = m_rows[i]; + m_rows[i] = m_rows[ii]; + m_rows[ii] = t; + // now fix the columns + for (auto & rc : m_rows[i]) { + column_cell & cc = m_columns[rc.m_j][rc.m_offset]; + lean_assert(cc.m_i == ii); + cc.m_i = i; + } + for (auto & rc : m_rows[ii]) { + column_cell & cc = m_columns[rc.m_j][rc.m_offset]; + lean_assert(cc.m_i == i); + cc.m_i = ii; + } + + } + + void fill_last_row_with_pivoting(linear_combination_iterator & it, const vector & basis_heading) { + lean_assert(numeric_traits::precise()); + lean_assert(row_count() > 0); + m_work_vector.resize(column_count()); + T a; + unsigned j; + while (it.next(a, j)) { + m_work_vector.set_value(-a, j); // we use the form -it + 1 = 0 + // but take care of the basis 1 later + } + + it.reset(); + // not iterate with pivoting + while (it.next(j)) { + int row_index = basis_heading[j]; + if (row_index < 0) + continue; + + T & alpha = m_work_vector[j]; // the pivot alpha + if (is_zero(alpha)) + continue; + + for (const auto & c : m_rows[row_index]) { + if (c.m_j == j) { + continue; + } + T & wv = m_work_vector.m_data[c.m_j]; + bool was_zero = is_zero(wv); + wv -= alpha * c.m_value; + if (was_zero) + m_work_vector.m_index.push_back(c.m_j); + else { + if (is_zero(wv)) { + m_work_vector.erase_from_index(c.m_j); + } + } + } + alpha = zero_of_type(); + m_work_vector.erase_from_index(j); + } + lean_assert(m_work_vector.is_OK()); + unsigned last_row = row_count() - 1; + + for (unsigned j : m_work_vector.m_index) { + set (last_row, j, m_work_vector.m_data[j]); + } + lean_assert(column_count() > 0); + set(last_row, column_count() - 1, one_of_type()); + } + + void copy_column_to_vector (unsigned j, vector & v) const { + v.resize(row_count(), numeric_traits::zero()); + for (auto & it : m_columns[j]) { + const T& val = get_val(it); + if (!is_zero(val)) + v[it.m_i] = val; + } + } + + template + L dot_product_with_row(unsigned row, const vector & w) const { + L ret = zero_of_type(); + lean_assert(row < m_rows.size()); + for (auto & it : m_rows[row]) { + ret += w[it.m_j] * it.get_val(); + } + return ret; + } +}; +} diff --git a/src/util/lp/static_matrix.hpp b/src/util/lp/static_matrix.hpp new file mode 100644 index 000000000..29357f296 --- /dev/null +++ b/src/util/lp/static_matrix.hpp @@ -0,0 +1,409 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include "util/vector.h" +#include +#include +#include "util/lp/static_matrix.h" +namespace lean { +// each assignment for this matrix should be issued only once!!! +template +void static_matrix::init_row_columns(unsigned m, unsigned n) { + lean_assert(m_rows.size() == 0 && m_columns.size() == 0); + for (unsigned i = 0; i < m; i++){ + m_rows.push_back(row_strip()); + } + for (unsigned j = 0; j < n; j++){ + m_columns.push_back(column_strip()); + } +} + + +template void static_matrix::scan_row_ii_to_offset_vector(unsigned ii) { + auto & rvals = m_rows[ii]; + unsigned size = rvals.size(); + for (unsigned j = 0; j < size; j++) + m_vector_of_row_offsets[rvals[j].m_j] = j; +} + + +template bool static_matrix::pivot_row_to_row_given_cell(unsigned i, column_cell & c, unsigned pivot_col) { + // std::cout << "ddd = " << ++lp_settings::ddd<< std::endl; + unsigned ii = c.m_i; + lean_assert(i < row_count() && ii < column_count()); + lean_assert(i != ii); + + m_became_zeros.reset(); + T alpha = -get_val(c); + lean_assert(!is_zero(alpha)); + auto & ii_row_vals = m_rows[ii]; + remove_element(ii_row_vals, ii_row_vals[c.m_offset]); + scan_row_ii_to_offset_vector(ii); + lean_assert(!is_zero(alpha)); + unsigned prev_size_ii = ii_row_vals.size(); + // run over the pivot row and update row ii + for (const auto & iv : m_rows[i]) { + unsigned j = iv.m_j; + if (j == pivot_col) continue; + T alv = alpha * iv.m_value; + lean_assert(!is_zero(iv.m_value)); + int j_offs = m_vector_of_row_offsets[j]; + if (j_offs == -1) { // it is a new element + add_new_element(ii, j, alv); + } + else { + auto & row_el_iv = ii_row_vals[j_offs]; + row_el_iv.m_value += alv; + if (is_zero(row_el_iv.m_value)) { + m_became_zeros.push_back(j_offs); + ensure_increasing(m_became_zeros); + } + } + } + + // clean the work vector + for (unsigned k = 0; k < prev_size_ii; k++) { + m_vector_of_row_offsets[ii_row_vals[k].m_j] = -1; + } + + for (unsigned k = m_became_zeros.size(); k-- > 0; ) { + unsigned j = m_became_zeros[k]; + remove_element(ii_row_vals, ii_row_vals[j]); + } + return !ii_row_vals.empty(); +} + + +// constructor that copies columns of the basis from A +template +static_matrix::static_matrix(static_matrix const &A, unsigned * /* basis */) : + m_vector_of_row_offsets(A.column_count(), numeric_traits::zero()) { + unsigned m = A.row_count(); + init_row_columns(m, m); + while (m--) { + for (auto & col : A.m_columns[m]){ + set(col.m_i, m, A.get_value_of_column_cell(col)); + } + } +} + +template void static_matrix::clear() { + m_vector_of_row_offsets.clear(); + m_rows.clear(); + m_columns.clear(); +} + +template void static_matrix::init_vector_of_row_offsets() { + m_vector_of_row_offsets.clear(); + m_vector_of_row_offsets.resize(column_count(), -1); +} + +template void static_matrix::init_empty_matrix(unsigned m, unsigned n) { + init_vector_of_row_offsets(); + init_row_columns(m, n); +} + +template unsigned static_matrix::lowest_row_in_column(unsigned col) { + lean_assert(col < column_count()); + column_strip & colstrip = m_columns[col]; + lean_assert(colstrip.size() > 0); + unsigned ret = 0; + for (auto & t : colstrip) { + if (t.m_i > ret) { + ret = t.m_i; + } + } + return ret; +} + +template void static_matrix::add_columns_at_the_end(unsigned delta) { + for (unsigned i = 0; i < delta; i++) + add_column(); +} + +template void static_matrix::forget_last_columns(unsigned how_many_to_forget) { + lean_assert(m_columns.size() >= how_many_to_forget); + unsigned j = column_count() - 1; + for (; how_many_to_forget > 0; how_many_to_forget--) { + remove_last_column(j --); + } +} + +template void static_matrix::remove_last_column(unsigned j) { + column_strip & col = m_columns.back(); + for (auto & it : col) { + auto & row = m_rows[it.m_i]; + unsigned offset = row.size() - 1; + for (auto row_it = row.rbegin(); row_it != row.rend(); row_it ++) { + if (row_it->m_j == j) { + row.erase(row.begin() + offset); + break; + } + offset--; + } + } + m_columns.pop_back(); + m_vector_of_row_offsets.pop_back(); +} + + + + +template void static_matrix::set(unsigned row, unsigned col, T const & val) { + if (numeric_traits::is_zero(val)) return; + lean_assert(row < row_count() && col < column_count()); + auto & r = m_rows[row]; + unsigned offs_in_cols = static_cast(m_columns[col].size()); + m_columns[col].push_back(make_column_cell(row, static_cast(r.size()))); + r.push_back(make_row_cell(col, offs_in_cols, val)); +} + +template +std::set> static_matrix::get_domain() { + std::set> ret; + for (unsigned i = 0; i < m_rows.size(); i++) { + for (auto it : m_rows[i]) { + ret.insert(std::make_pair(i, it.m_j)); + } + } + return ret; +} + + +template void static_matrix::copy_column_to_indexed_vector (unsigned j, indexed_vector & v) const { + lean_assert(j < m_columns.size()); + for (auto & it : m_columns[j]) { + const T& val = get_val(it); + if (!is_zero(val)) + v.set_value(val, it.m_i); + } +} + + + +template T static_matrix::get_max_abs_in_row(unsigned row) const { + T ret = numeric_traits::zero(); + for (auto & t : m_rows[row]) { + T a = abs(t.get_val()); + if (a > ret) { + ret = a; + } + } + return ret; +} + +template T static_matrix::get_min_abs_in_row(unsigned row) const { + bool first_time = true; + T ret = numeric_traits::zero(); + for (auto & t : m_rows[row]) { + T a = abs(t.get_val()); + if (first_time) { + ret = a; + first_time = false; + } else if (a < ret) { + ret = a; + } + } + return ret; +} + + +template T static_matrix::get_max_abs_in_column(unsigned column) const { + T ret = numeric_traits::zero(); + for (const auto & t : m_columns[column]) { + T a = abs(get_val(t)); + if (a > ret) { + ret = a; + } + } + return ret; +} + +template T static_matrix::get_min_abs_in_column(unsigned column) const { + bool first_time = true; + T ret = numeric_traits::zero(); + for (auto & t : m_columns[column]) { + T a = abs(get_val(t)); + if (first_time) { + first_time = false; + ret = a; + } else if (a < ret) { + ret = a; + } + } + return ret; +} + +#ifdef LEAN_DEBUG +template void static_matrix::check_consistency() { + std::unordered_map, T> by_rows; + for (int i = 0; i < m_rows.size(); i++){ + for (auto & t : m_rows[i]) { + std::pair p(i, t.m_j); + lean_assert(by_rows.find(p) == by_rows.end()); + by_rows[p] = t.get_val(); + } + } + std::unordered_map, T> by_cols; + for (int i = 0; i < m_columns.size(); i++){ + for (auto & t : m_columns[i]) { + std::pair p(t.m_i, i); + lean_assert(by_cols.find(p) == by_cols.end()); + by_cols[p] = get_val(t); + } + } + lean_assert(by_rows.size() == by_cols.size()); + + for (auto & t : by_rows) { + auto ic = by_cols.find(t.first); + if (ic == by_cols.end()){ + //std::cout << "rows have pair (" << t.first.first <<"," << t.first.second + // << "), but columns don't " << std::endl; + } + lean_assert(ic != by_cols.end()); + lean_assert(t.second == ic->second); + } +} +#endif + + +template void static_matrix::cross_out_row(unsigned k) { +#ifdef LEAN_DEBUG + check_consistency(); +#endif + cross_out_row_from_columns(k, m_rows[k]); + fix_row_indices_in_each_column_for_crossed_row(k); + m_rows.erase(m_rows.begin() + k); +#ifdef LEAN_DEBUG + regen_domain(); + check_consistency(); +#endif +} + + +template void static_matrix::fix_row_indices_in_each_column_for_crossed_row(unsigned k) { + for (unsigned j = 0; j < m_columns.size(); j++) { + auto & col = m_columns[j]; + for (int i = 0; i < col.size(); i++) { + if (col[i].m_i > k) { + col[i].m_i--; + } + } + } +} + +template void static_matrix::cross_out_row_from_columns(unsigned k, row_strip & row) { + for (auto & t : row) { + cross_out_row_from_column(t.m_j, k); + } +} + +template void static_matrix::cross_out_row_from_column(unsigned col, unsigned k) { + auto & s = m_columns[col]; + for (unsigned i = 0; i < s.size(); i++) { + if (s[i].m_i == k) { + s.erase(s.begin() + i); + break; + } + } +} + +template T static_matrix::get_elem(unsigned i, unsigned j) const { // should not be used in efficient code !!!! + for (auto & t : m_rows[i]) { + if (t.m_j == j) { + return t.get_val(); + } + } + return numeric_traits::zero(); +} + + +template T static_matrix::get_balance() const { + T ret = zero_of_type(); + for (unsigned i = 0; i < row_count(); i++) { + ret += get_row_balance(i); + } + return ret; +} + +template T static_matrix::get_row_balance(unsigned row) const { + T ret = zero_of_type(); + for (auto & t : m_rows[row]) { + if (numeric_traits::is_zero(t.get_val())) continue; + T a = abs(t.get_val()); + numeric_traits::log(a); + ret += a * a; + } + return ret; +} + +template bool static_matrix::is_correct() const { + for (auto & r : m_rows) { + std::unordered_set s; + for (auto & rc : r) { + if (s.find(rc.m_j) != s.end()) { + std::cout << "found column " << rc.m_j << " twice in a row" << std::endl; + return false; + } + s.insert(rc.m_j); + if (rc.m_j >= m_columns.size()) + return false; + if (rc.m_offset >= m_columns[rc.m_j].size()) + return false; + if (rc.get_val() != get_val(m_columns[rc.m_j][rc.m_offset])) + return false; + } + } + for (auto & c : m_columns) { + std::unordered_set s; + for (auto & cc : c) { + if (s.find(cc.m_i) != s.end()) { + std::cout << "found row " << cc.m_i << " twice in a column" << std::endl; + return false; + } + s.insert(cc.m_i); + if (cc.m_i >= m_rows.size()) + return false; + if (cc.m_offset >= m_rows[cc.m_i].size()) + return false; + if (get_val(cc) != m_rows[cc.m_i][cc.m_offset].get_val()) + return false; + } + } + + + + return true; +} + +template +void static_matrix::remove_element(vector> & row_vals, row_cell & row_el_iv) { + unsigned column_offset = row_el_iv.m_offset; + auto & column_vals = m_columns[row_el_iv.m_j]; + column_cell& cs = m_columns[row_el_iv.m_j][column_offset]; + unsigned row_offset = cs.m_offset; + if (column_offset != column_vals.size() - 1) { + auto & cc = column_vals[column_offset] = column_vals.back(); // copy from the tail + m_rows[cc.m_i][cc.m_offset].m_offset = column_offset; + } + + if (row_offset != row_vals.size() - 1) { + auto & rc = row_vals[row_offset] = row_vals.back(); // copy from the tail + m_columns[rc.m_j][rc.m_offset].m_offset = row_offset; + } + + column_vals.pop_back(); + row_vals.pop_back(); +} +template +void static_matrix::add_new_element(unsigned row, unsigned col, const T& val) { + auto & row_vals = m_rows[row]; + auto & col_vals = m_columns[col]; + unsigned row_el_offs = static_cast(row_vals.size()); + unsigned col_el_offs = static_cast(col_vals.size()); + row_vals.push_back(row_cell(col, col_el_offs, val)); + col_vals.push_back(column_cell(row, row_el_offs)); +} + +} diff --git a/src/util/lp/static_matrix_instances.cpp b/src/util/lp/static_matrix_instances.cpp new file mode 100644 index 000000000..5d6be17ec --- /dev/null +++ b/src/util/lp/static_matrix_instances.cpp @@ -0,0 +1,67 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include "util/vector.h" +#include +#include +#include +#include "util/lp/static_matrix.hpp" +#include "util/lp/lp_core_solver_base.h" +#include "util/lp/lp_dual_core_solver.h" +#include "util/lp/lp_dual_simplex.h" +#include "util/lp/lp_primal_core_solver.h" +#include "util/lp/scaler.h" +#include "util/lp/lar_solver.h" +namespace lean { +template void static_matrix::add_columns_at_the_end(unsigned int); +template void static_matrix::clear(); +#ifdef LEAN_DEBUG +template bool static_matrix::is_correct() const; +#endif +template void static_matrix::copy_column_to_indexed_vector(unsigned int, indexed_vector&) const; + +template double static_matrix::get_balance() const; +template std::set> static_matrix::get_domain(); +template std::set> lean::static_matrix::get_domain(); +template std::set> lean::static_matrix >::get_domain(); +template double static_matrix::get_elem(unsigned int, unsigned int) const; +template double static_matrix::get_max_abs_in_column(unsigned int) const; +template double static_matrix::get_min_abs_in_column(unsigned int) const; +template double static_matrix::get_min_abs_in_row(unsigned int) const; +template void static_matrix::init_empty_matrix(unsigned int, unsigned int); +template void static_matrix::init_row_columns(unsigned int, unsigned int); +template static_matrix::ref & static_matrix::ref::operator=(double const&); +template void static_matrix::set(unsigned int, unsigned int, double const&); +template static_matrix::static_matrix(unsigned int, unsigned int); +template void static_matrix::add_column_to_vector(mpq const&, unsigned int, mpq*) const; +template void static_matrix::add_columns_at_the_end(unsigned int); +template bool static_matrix::is_correct() const; +template void static_matrix::copy_column_to_indexed_vector(unsigned int, indexed_vector&) const; + +template mpq static_matrix::get_balance() const; +template mpq static_matrix::get_elem(unsigned int, unsigned int) const; +template mpq static_matrix::get_max_abs_in_column(unsigned int) const; +template mpq static_matrix::get_max_abs_in_row(unsigned int) const; +template double static_matrix::get_max_abs_in_row(unsigned int) const; +template mpq static_matrix::get_min_abs_in_column(unsigned int) const; +template mpq static_matrix::get_min_abs_in_row(unsigned int) const; +template void static_matrix::init_row_columns(unsigned int, unsigned int); +template static_matrix::ref& static_matrix::ref::operator=(mpq const&); +template void static_matrix::set(unsigned int, unsigned int, mpq const&); + +template static_matrix::static_matrix(unsigned int, unsigned int); +#ifdef LEAN_DEBUG +template bool static_matrix >::is_correct() const; +#endif +template void static_matrix >::copy_column_to_indexed_vector(unsigned int, indexed_vector&) const; +template mpq static_matrix >::get_elem(unsigned int, unsigned int) const; +template void static_matrix >::init_empty_matrix(unsigned int, unsigned int); +template void static_matrix >::set(unsigned int, unsigned int, mpq const&); + + +template bool lean::static_matrix::pivot_row_to_row_given_cell(unsigned int, column_cell &, unsigned int); +template bool lean::static_matrix::pivot_row_to_row_given_cell(unsigned int, column_cell& , unsigned int); +template bool lean::static_matrix >::pivot_row_to_row_given_cell(unsigned int, column_cell&, unsigned int); +} + diff --git a/src/util/lp/tail_matrix.h b/src/util/lp/tail_matrix.h new file mode 100644 index 000000000..c337b0933 --- /dev/null +++ b/src/util/lp/tail_matrix.h @@ -0,0 +1,28 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ + +#pragma once +#include "util/vector.h" +#include "util/lp/indexed_vector.h" +#include "util/lp/matrix.h" +#include "util/lp/lp_settings.h" +// These matrices appear at the end of the list + +namespace lean { +template +class tail_matrix +#ifdef LEAN_DEBUG + : public matrix +#endif +{ +public: + virtual void apply_from_left_to_T(indexed_vector & w, lp_settings & settings) = 0; + virtual void apply_from_left(vector & w, lp_settings & settings) = 0; + virtual void apply_from_right(vector & w) = 0; + virtual void apply_from_right(indexed_vector & w) = 0; + virtual ~tail_matrix() {} + virtual bool is_dense() const = 0; +}; +} diff --git a/src/util/lp/test_bound_analyzer.h b/src/util/lp/test_bound_analyzer.h new file mode 100644 index 000000000..262c610c7 --- /dev/null +++ b/src/util/lp/test_bound_analyzer.h @@ -0,0 +1,260 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#pragma once +#include "util/vector.h" +#include "util/lp/linear_combination_iterator.h" +#include "util/lp/implied_bound.h" +#include "util/lp/lp_settings.h" +#include +// this class is for testing only + +// We have an equality : sum by j of row[j]*x[j] = rs +// We try to pin a var by pushing the total by using the variable bounds +// In a loop we drive the partial sum down, denoting the variables of this process by _u. +// In the same loop trying to pin variables by pushing the partial sum up, denoting the variable related to it by _l + +// here in addition we assume that all coefficient in the row are positive +namespace lean { + +class test_bound_analyzer { + linear_combination_iterator & m_it; + std::function& m_low_bounds; + std::function& m_upper_bounds; + std::function m_column_types; + vector & m_implied_bounds; + vector m_coeffs; + int m_coeff_sign; + vector m_index; + unsigned m_row_or_term_index; + std::function & m_try_get_found_bound; +public : + // constructor + test_bound_analyzer(linear_combination_iterator &it, + std::function & low_bounds, + std::function & upper_bounds, + std::function column_types, + vector & evidence_vector, + unsigned row_or_term_index, + std::function & try_get_found_bound) : + m_it(it), + m_low_bounds(low_bounds), + m_upper_bounds(upper_bounds), + m_column_types(column_types), + m_implied_bounds(evidence_vector), + m_row_or_term_index(row_or_term_index), + m_try_get_found_bound(try_get_found_bound) + { + m_it.reset(); + unsigned i; + mpq a; + while (m_it.next(a, i) ) { + m_coeffs.push_back(a); + m_index.push_back(i); + } + } + + static int sign (const mpq & t) { return is_pos(t) ? 1: -1;} + + void analyze() { + // We have the equality sum by j of row[j]*x[j] = m_rs + // We try to pin a var by pushing the total of the partial sum down, denoting the variable of this process by _u. + for (unsigned i = 0; i < m_index.size(); i++) { + analyze_i(i); + } + } + void analyze_i(unsigned i) { + // set the m_coeff_is_pos + m_coeff_sign = sign(m_coeffs[i]); + analyze_i_for_lower(i); + analyze_i_for_upper(i); + } + + void analyze_i_for_upper(unsigned i) { + mpq l; + bool strict = false; + lean_assert(is_zero(l)); + for (unsigned k = 0; k < m_index.size(); k++) { + if (k == i) + continue; + mpq lb; + bool str; + if (!upper_bound_of_monoid(k, lb, str)) { + return; + } + l += lb; + if (str) + strict = true; + } + l /= m_coeffs[i]; + unsigned j = m_index[i]; + switch(m_column_types(j)) { + case column_type::fixed: + case column_type::boxed: + case column_type::upper_bound: + { + const auto & lb = m_upper_bounds(j); + if (l > lb.x || (l == lb.x && !(is_zero(lb.y) && strict))) { + break; // no improvement on the existing upper bound + } + } + default: + // std::cout << " got an upper bound with " << T_to_string(l) << "\n"; + m_implied_bounds.push_back(implied_bound(l, j, false, is_pos(m_coeffs[i]), m_row_or_term_index, strict)); + } + } + + + bool low_bound_of_monoid(unsigned k, mpq & lb, bool &strict) const { + int s = - m_coeff_sign * sign(m_coeffs[k]); + unsigned j = m_index[k]; + if (s > 0) { + switch(m_column_types(j)) { + case column_type::fixed: + case column_type::boxed: + case column_type::low_bound: + lb = -m_coeffs[k] * m_low_bounds(j).x; + strict = !is_zero(m_low_bounds(j).y); + return true; + default: + return false; + } + } + + switch(m_column_types(j)) { + case column_type::fixed: + case column_type::boxed: + case column_type::upper_bound: + lb = -m_coeffs[k] * m_upper_bounds(j).x; + strict = !is_zero(m_upper_bounds(j).y); + return true; + default: + return false; + } + } + + bool upper_bound_of_monoid(unsigned k, mpq & lb, bool & strict) const { + int s = - m_coeff_sign * sign(m_coeffs[k]); + unsigned j = m_index[k]; + if (s > 0) { + switch(m_column_types(j)) { + case column_type::fixed: + case column_type::boxed: + case column_type::upper_bound: + lb = -m_coeffs[k] * m_upper_bounds(j).x; + strict = !is_zero(m_upper_bounds(j).y); + + return true; + default: + return false; + } + } + + switch(m_column_types(j)) { + case column_type::fixed: + case column_type::boxed: + case column_type::low_bound: + lb = -m_coeffs[k] * m_low_bounds(j).x; + strict = !is_zero(m_low_bounds(j).y); + return true; + default: + return false; + } + } + + void analyze_i_for_lower(unsigned i) { + mpq l; + lean_assert(is_zero(l)); + bool strict = false; + for (unsigned k = 0; k < m_index.size(); k++) { + if (k == i) + continue; + mpq lb; + bool str; + if (!low_bound_of_monoid(k, lb, str)) { + return; + } + if (str) + strict = true; + l += lb; + } + l /= m_coeffs[i]; + unsigned j = m_index[i]; + switch(m_column_types(j)) { + case column_type::fixed: + case column_type::boxed: + case column_type::low_bound: + { + const auto & lb = m_low_bounds(j); + if (l < lb.x || (l == lb.x && !(is_zero(lb.y) && strict))) { + break; // no improvement on the existing upper bound + } + } + default: + // std::cout << " got a lower bound with " << T_to_string(l) << "\n"; + m_implied_bounds.push_back(implied_bound(l, j, true, is_pos(m_coeffs[i]), m_row_or_term_index, strict)); + } + } + + bool low_bound_is_available(unsigned j) const { + switch(m_column_types(j)) { + case column_type::fixed: + case column_type::boxed: + case column_type::low_bound: + return true; + default: + return false; + } + } + + bool upper_bound_is_available(unsigned j) const { + switch(m_column_types(j)) { + case column_type::fixed: + case column_type::boxed: + case column_type::upper_bound: + return true; + default: + return false; + } + } + + bool try_get_best_avail_bound(unsigned j, bool is_lower_bound, mpq & best_bound, bool & strict_of_best_bound) const { + if (m_try_get_found_bound(j, is_lower_bound, best_bound, strict_of_best_bound)) { + return true; + } + if (is_lower_bound) { + if (low_bound_is_available(j)) { + best_bound = m_low_bounds(j).x; + strict_of_best_bound = !is_zero(m_low_bounds(j).y); + return true; + } + } else { + if (upper_bound_is_available(j)) { + best_bound = m_upper_bounds(j).x; + strict_of_best_bound = !is_zero(m_low_bounds(j).y); + return true; + } + } + return false; + } + + bool bound_is_new(unsigned j, const mpq& b, bool is_lower_bound, bool strict) const { + mpq best_bound; + bool strict_of_best_bound; + if (try_get_best_avail_bound(j, is_lower_bound, best_bound, strict_of_best_bound)) { + if (is_lower_bound) { + if (b > best_bound || ( b != best_bound && (strict && !strict_of_best_bound))) // the second clouse stands for strong inequality + return true; + } else { + if (b < best_bound || ( b == best_bound && (strict && !strict_of_best_bound))) + return true; + } + return false; + } + return true; + } + +}; + +} diff --git a/src/util/lp/ul_pair.h b/src/util/lp/ul_pair.h new file mode 100644 index 000000000..0e96364ce --- /dev/null +++ b/src/util/lp/ul_pair.h @@ -0,0 +1,58 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ + +#pragma once +#include "util/vector.h" +#include +#include +#include +#include "util/lp/column_info.h" + +namespace lean { + + enum lconstraint_kind { + LE = -2, LT = -1 , GE = 2, GT = 1, EQ = 0 + }; + + inline std::ostream& operator<<(std::ostream& out, lconstraint_kind k) { + switch (k) { + case LE: return out << "<="; + case LT: return out << "<"; + case GE: return out << ">="; + case GT: return out << ">"; + case EQ: return out << "="; + } + return out << "??"; + } + +inline bool compare(const std::pair & a, const std::pair & b) { + return a.second < b.second; +} + +class ul_pair { + constraint_index m_low_bound_witness = static_cast(-1); + constraint_index m_upper_bound_witness = static_cast(-1); +public: + constraint_index& low_bound_witness() {return m_low_bound_witness;} + constraint_index low_bound_witness() const {return m_low_bound_witness;} + constraint_index& upper_bound_witness() { return m_upper_bound_witness;} + constraint_index upper_bound_witness() const {return m_upper_bound_witness;} + row_index m_i = static_cast(-1); + bool operator!=(const ul_pair & p) const { + return !(*this == p); + } + + bool operator==(const ul_pair & p) const { + return m_low_bound_witness == p.m_low_bound_witness + && m_upper_bound_witness == p.m_upper_bound_witness && + m_i == p.m_i; + } + // empty constructor + ul_pair(){} + ul_pair(row_index ri) : m_i(ri) {} + ul_pair(const ul_pair & o): m_low_bound_witness(o.m_low_bound_witness), m_upper_bound_witness(o.m_upper_bound_witness), m_i(o.m_i) {} +}; + +} From 2a63c56ae0aa3e68052247d2c934d7c6a9840e5a Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Tue, 9 May 2017 14:03:30 -0700 Subject: [PATCH 400/410] A faster and more scalable LRA solver by Lev Nachmanson. It is disabled in the initial merge pending a few bug fixes Signed-off-by: Nikolaj Bjorner --- src/smt/smt_setup.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/smt/smt_setup.cpp b/src/smt/smt_setup.cpp index 1abb3d010..ebb1f87fd 100644 --- a/src/smt/smt_setup.cpp +++ b/src/smt/smt_setup.cpp @@ -720,7 +720,10 @@ namespace smt { } void setup::setup_r_arith() { - m_context.register_plugin(alloc(smt::theory_lra, m_manager, m_params)); + m_context.register_plugin(alloc(smt::theory_mi_arith, m_manager, m_params)); + + // Disabled in initial commit of LRA additions + // m_context.register_plugin(alloc(smt::theory_lra, m_manager, m_params)); } void setup::setup_mi_arith() { From 905cf08e5db1a217163777eb289c2f2d19896f70 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Tue, 9 May 2017 14:11:33 -0700 Subject: [PATCH 401/410] missing files Signed-off-by: Nikolaj Bjorner --- contrib/cmake/src/util/lp/CMakeLists.txt | 35 ++++++++++++++++++++++++ src/util/sstream.h | 21 ++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 contrib/cmake/src/util/lp/CMakeLists.txt create mode 100644 src/util/sstream.h diff --git a/contrib/cmake/src/util/lp/CMakeLists.txt b/contrib/cmake/src/util/lp/CMakeLists.txt new file mode 100644 index 000000000..57ebecc8d --- /dev/null +++ b/contrib/cmake/src/util/lp/CMakeLists.txt @@ -0,0 +1,35 @@ +z3_add_component(lp + SOURCES + lp_utils.cpp + binary_heap_priority_queue_instances.cpp + binary_heap_upair_queue_instances.cpp + bound_propagator.cpp + core_solver_pretty_printer_instances.cpp + dense_matrix_instances.cpp + eta_matrix_instances.cpp + indexed_vector_instances.cpp + lar_core_solver_instances.cpp + lp_core_solver_base_instances.cpp + lp_dual_core_solver_instances.cpp + lp_dual_simplex_instances.cpp + lp_primal_core_solver_instances.cpp + lp_primal_simplex_instances.cpp + lp_settings_instances.cpp + lp_solver_instances.cpp + lu_instances.cpp + matrix_instances.cpp + permutation_matrix_instances.cpp + quick_xplain.cpp + row_eta_matrix_instances.cpp + scaler_instances.cpp + sparse_matrix_instances.cpp + square_dense_submatrix_instances.cpp + static_matrix_instances.cpp + random_updater_instances.cpp + COMPONENT_DEPENDENCIES + util + PYG_FILES + lp_params.pyg +) + +include_directories(${src_SOURCE_DIR}) diff --git a/src/util/sstream.h b/src/util/sstream.h new file mode 100644 index 000000000..23d5bdfbb --- /dev/null +++ b/src/util/sstream.h @@ -0,0 +1,21 @@ + + +/* +Copyright (c) 2013 Microsoft Corporation. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. + +Author: Leonardo de Moura +*/ +#pragma once +#include +#include + +namespace lean { +/** \brief Wrapper for std::ostringstream */ +class sstream { + std::ostringstream m_strm; +public: + std::string str() const { return m_strm.str(); } + template sstream & operator<<(T const & t) { m_strm << t; return *this; } +}; +} From c5f1f8ba59cf859387c61f8b3aa9667824679396 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Tue, 9 May 2017 14:14:58 -0700 Subject: [PATCH 402/410] missing files Signed-off-by: Nikolaj Bjorner --- src/smt/theory_lra.cpp | 2624 ++++++++++++++++++++++++++++ src/smt/theory_lra.h | 96 ++ src/test/argument_parser.h | 144 ++ src/test/lp.cpp | 3232 +++++++++++++++++++++++++++++++++++ src/test/lp_main.cpp | 14 + src/test/smt_reader.h | 392 +++++ src/test/test_file_reader.h | 73 + src/util/lp/lp_params.pyg | 13 + 8 files changed, 6588 insertions(+) create mode 100644 src/smt/theory_lra.cpp create mode 100644 src/smt/theory_lra.h create mode 100644 src/test/argument_parser.h create mode 100644 src/test/lp.cpp create mode 100644 src/test/lp_main.cpp create mode 100644 src/test/smt_reader.h create mode 100644 src/test/test_file_reader.h create mode 100644 src/util/lp/lp_params.pyg diff --git a/src/smt/theory_lra.cpp b/src/smt/theory_lra.cpp new file mode 100644 index 000000000..745aa5da5 --- /dev/null +++ b/src/smt/theory_lra.cpp @@ -0,0 +1,2624 @@ +/*++ +Copyright (c) 2016 Microsoft Corporation + +Module Name: + + theory_lra.cpp + +Abstract: + + + +Author: + + Lev Nachmanson (levnach) 2016-25-3 + Nikolaj Bjorner (nbjorner) + +Revision History: + + +--*/ +#include "util/stopwatch.h" +#include "util/lp/lp_solver.h" +#include "util/lp/lp_primal_simplex.h" +#include "util/lp/lp_dual_simplex.h" +#include "util/lp/indexed_value.h" +#include "util/lp/lar_solver.h" +#include "util/nat_set.h" +#include "util/optional.h" +#include "lp_params.hpp" +#include "util/inf_rational.h" +#include "smt/smt_theory.h" +#include "smt/smt_context.h" +#include "smt/theory_lra.h" +#include "smt/proto_model/numeral_factory.h" +#include "smt/smt_model_generator.h" +#include "smt/arith_eq_adapter.h" +#include "util/nat_set.h" +#include "tactic/filter_model_converter.h" + +namespace lp { + enum bound_kind { lower_t, upper_t }; + + std::ostream& operator<<(std::ostream& out, bound_kind const& k) { + switch (k) { + case lower_t: return out << "<="; + case upper_t: return out << ">="; + } + return out; + } + + class bound { + smt::bool_var m_bv; + smt::theory_var m_var; + rational m_value; + bound_kind m_bound_kind; + + public: + bound(smt::bool_var bv, smt::theory_var v, rational const & val, bound_kind k): + m_bv(bv), + m_var(v), + m_value(val), + m_bound_kind(k) { + } + virtual ~bound() {} + smt::theory_var get_var() const { return m_var; } + smt::bool_var get_bv() const { return m_bv; } + bound_kind get_bound_kind() const { return m_bound_kind; } + rational const& get_value() const { return m_value; } + inf_rational get_value(bool is_true) const { + if (is_true) return inf_rational(m_value); // v >= value or v <= value + if (m_bound_kind == lower_t) return inf_rational(m_value, false); // v <= value - epsilon + return inf_rational(m_value, true); // v >= value + epsilon + } + virtual std::ostream& display(std::ostream& out) const { + return out << "v" << get_var() << " " << get_bound_kind() << " " << m_value; + } + }; + + std::ostream& operator<<(std::ostream& out, bound const& b) { + return b.display(out); + } + + struct stats { + unsigned m_assert_lower; + unsigned m_assert_upper; + unsigned m_add_rows; + unsigned m_bounds_propagations; + unsigned m_num_iterations; + unsigned m_num_iterations_with_no_progress; + unsigned m_num_factorizations; + unsigned m_need_to_solve_inf; + unsigned m_fixed_eqs; + unsigned m_conflicts; + unsigned m_bound_propagations1; + unsigned m_bound_propagations2; + unsigned m_assert_diseq; + unsigned m_make_feasible; + unsigned m_max_cols; + unsigned m_max_rows; + stats() { reset(); } + void reset() { + memset(this, 0, sizeof(*this)); + } + }; + + typedef optional opt_inf_rational; + + +} + +namespace smt { + + typedef ptr_vector lp_bounds; + + class theory_lra::imp { + + struct scope { + unsigned m_bounds_lim; + unsigned m_asserted_qhead; + unsigned m_asserted_atoms_lim; + unsigned m_delayed_terms_lim; + unsigned m_delayed_equalities_lim; + unsigned m_delayed_defs_lim; + unsigned m_underspecified_lim; + unsigned m_var_trail_lim; + expr* m_not_handled; + }; + + struct delayed_atom { + unsigned m_bv; + bool m_is_true; + delayed_atom(unsigned b, bool t): m_bv(b), m_is_true(t) {} + }; + + class resource_limit : public lean::lp_resource_limit { + imp& m_imp; + public: + resource_limit(imp& i): m_imp(i) { } + virtual bool get_cancel_flag() { return m_imp.m.canceled(); } + }; + + + theory_lra& th; + ast_manager& m; + theory_arith_params& m_arith_params; + lp_params m_lp_params; // seeded from global parameters. + arith_util a; + + arith_eq_adapter m_arith_eq_adapter; + + vector m_columns; + int m_print_counter = 0; + + // temporary values kept during internalization + struct internalize_state { + expr_ref_vector m_terms; + vector m_coeffs; + svector m_vars; + rational m_coeff; + ptr_vector m_terms_to_internalize; + internalize_state(ast_manager& m): m_terms(m) {} + void reset() { + m_terms.reset(); + m_coeffs.reset(); + m_coeff.reset(); + m_vars.reset(); + m_terms_to_internalize.reset(); + } + }; + ptr_vector m_internalize_states; + unsigned m_internalize_head; + + class scoped_internalize_state { + imp& m_imp; + internalize_state& m_st; + + internalize_state& push_internalize(imp& i) { + if (i.m_internalize_head == i.m_internalize_states.size()) { + i.m_internalize_states.push_back(alloc(internalize_state, i.m)); + } + internalize_state& st = *i.m_internalize_states[i.m_internalize_head++]; + st.reset(); + return st; + } + public: + scoped_internalize_state(imp& i): m_imp(i), m_st(push_internalize(i)) {} + ~scoped_internalize_state() { --m_imp.m_internalize_head; } + expr_ref_vector& terms() { return m_st.m_terms; } + vector& coeffs() { return m_st.m_coeffs; } + svector& vars() { return m_st.m_vars; } + rational& coeff() { return m_st.m_coeff; } + ptr_vector& terms_to_internalize() { return m_st.m_terms_to_internalize; } + void push(expr* e, rational c) { m_st.m_terms.push_back(e); m_st.m_coeffs.push_back(c); } + void set_back(unsigned i) { + if (terms().size() == i + 1) return; + terms()[i] = terms().back(); + coeffs()[i] = coeffs().back(); + terms().pop_back(); + coeffs().pop_back(); + } + }; + + typedef vector> var_coeffs; + struct delayed_def { + vector m_coeffs; + svector m_vars; + rational m_coeff; + theory_var m_var; + delayed_def(svector const& vars, vector const& coeffs, rational const& r, theory_var v): + m_coeffs(coeffs), m_vars(vars), m_coeff(r), m_var(v) {} + }; + + svector m_theory_var2var_index; // translate from theory variables to lar vars + svector m_var_index2theory_var; // reverse map from lp_solver variables to theory variables + svector m_term_index2theory_var; // reverse map from lp_solver variables to theory variables + var_coeffs m_left_side; // constraint left side + mutable std::unordered_map m_variable_values; // current model + + enum constraint_source { + inequality_source, + equality_source, + definition_source, + null_source + }; + svector m_constraint_sources; + svector m_inequalities; // asserted rows corresponding to inequality literals. + svector m_equalities; // asserted rows corresponding to equalities. + svector m_definitions; // asserted rows corresponding to definitions + + bool m_delay_constraints; // configuration + svector m_asserted_atoms; + app_ref_vector m_delayed_terms; + svector> m_delayed_equalities; + vector m_delayed_defs; + expr* m_not_handled; + ptr_vector m_underspecified; + unsigned_vector m_var_trail; + vector > m_use_list; // bounds where variables are used. + + // attributes for incremental version: + u_map m_bool_var2bound; + vector m_bounds; + unsigned_vector m_unassigned_bounds; + unsigned_vector m_bounds_trail; + unsigned m_asserted_qhead; + + svector m_to_check; // rows that should be checked for theory propagation + + svector > m_assume_eq_candidates; + unsigned m_assume_eq_head; + + unsigned m_num_conflicts; + + + struct var_value_eq { + imp & m_th; + var_value_eq(imp & th):m_th(th) {} + bool operator()(theory_var v1, theory_var v2) const { return m_th.get_ivalue(v1) == m_th.get_ivalue(v2) && m_th.is_int(v1) == m_th.is_int(v2); } + }; + struct var_value_hash { + imp & m_th; + var_value_hash(imp & th):m_th(th) {} + unsigned operator()(theory_var v) const { return std::hash()(m_th.get_ivalue(v)); } + }; + int_hashtable m_model_eqs; + + + svector m_scopes; + lp::stats m_stats; + arith_factory* m_factory; + scoped_ptr m_solver; + resource_limit m_resource_limit; + lp_bounds m_new_bounds; + + + context& ctx() const { return th.get_context(); } + theory_id get_id() const { return th.get_id(); } + bool is_int(theory_var v) const { return is_int(get_enode(v)); } + bool is_int(enode* n) const { return a.is_int(n->get_owner()); } + enode* get_enode(theory_var v) const { return th.get_enode(v); } + enode* get_enode(expr* e) const { return ctx().get_enode(e); } + expr* get_owner(theory_var v) const { return get_enode(v)->get_owner(); } + + void init_solver() { + m_solver = alloc(lean::lar_solver); + m_theory_var2var_index.reset(); + m_solver->settings().set_resource_limit(m_resource_limit); + m_solver->settings().simplex_strategy() = static_cast(m_lp_params.simplex_strategy()); + m_solver->settings().presolve_with_double_solver_for_lar = m_lp_params.presolve_with_dbl(); + reset_variable_values(); + m_solver->settings().bound_propagation() = BP_NONE != propagation_mode(); + m_solver->set_propagate_bounds_on_pivoted_rows_mode(m_lp_params.bprop_on_pivoted_rows()); + //m_solver->settings().set_ostream(0); + } + + void found_not_handled(expr* n) { + m_not_handled = n; + if (is_app(n) && is_underspecified(to_app(n))) { + m_underspecified.push_back(to_app(n)); + } + TRACE("arith", tout << "Unhandled: " << mk_pp(n, m) << "\n";); + } + + bool is_numeral(expr* term, rational& r) { + rational mul(1); + do { + if (a.is_numeral(term, r)) { + r *= mul; + return true; + } + if (a.is_uminus(term, term)) { + mul.neg(); + continue; + } + if (a.is_to_real(term, term)) { + continue; + } + return false; + } + while (false); + return false; + } + + void linearize_term(expr* term, scoped_internalize_state& st) { + st.push(term, rational::one()); + linearize(st); + } + + void linearize_ineq(expr* lhs, expr* rhs, scoped_internalize_state& st) { + st.push(lhs, rational::one()); + st.push(rhs, rational::minus_one()); + linearize(st); + } + + void linearize(scoped_internalize_state& st) { + expr_ref_vector & terms = st.terms(); + svector& vars = st.vars(); + vector& coeffs = st.coeffs(); + rational& coeff = st.coeff(); + rational r; + expr* n1, *n2; + unsigned index = 0; + while (index < terms.size()) { + SASSERT(index >= vars.size()); + expr* n = terms[index].get(); + st.terms_to_internalize().push_back(n); + if (a.is_add(n)) { + unsigned sz = to_app(n)->get_num_args(); + for (unsigned i = 0; i < sz; ++i) { + st.push(to_app(n)->get_arg(i), coeffs[index]); + } + st.set_back(index); + } + else if (a.is_sub(n)) { + unsigned sz = to_app(n)->get_num_args(); + terms[index] = to_app(n)->get_arg(0); + for (unsigned i = 1; i < sz; ++i) { + st.push(to_app(n)->get_arg(i), -coeffs[index]); + } + } + else if (a.is_mul(n, n1, n2) && is_numeral(n1, r)) { + coeffs[index] *= r; + terms[index] = n2; + st.terms_to_internalize().push_back(n1); + } + else if (a.is_mul(n, n1, n2) && is_numeral(n2, r)) { + coeffs[index] *= r; + terms[index] = n1; + st.terms_to_internalize().push_back(n2); + } + else if (a.is_numeral(n, r)) { + coeff += coeffs[index]*r; + ++index; + } + else if (a.is_uminus(n, n1)) { + coeffs[index].neg(); + terms[index] = n1; + } + else if (is_app(n) && a.get_family_id() == to_app(n)->get_family_id()) { + app* t = to_app(n); + found_not_handled(n); + internalize_args(t); + mk_enode(t); + theory_var v = mk_var(n); + coeffs[vars.size()] = coeffs[index]; + vars.push_back(v); + ++index; + } + else { + if (is_app(n)) { + internalize_args(to_app(n)); + } + if (a.is_int(n)) { + found_not_handled(n); + } + theory_var v = mk_var(n); + coeffs[vars.size()] = coeffs[index]; + vars.push_back(v); + ++index; + } + } + for (unsigned i = st.terms_to_internalize().size(); i > 0; ) { + --i; + expr* n = st.terms_to_internalize()[i]; + if (is_app(n)) { + mk_enode(to_app(n)); + } + } + st.terms_to_internalize().reset(); + } + + void internalize_args(app* t) { + for (unsigned i = 0; reflect(t) && i < t->get_num_args(); ++i) { + if (!ctx().e_internalized(t->get_arg(i))) { + ctx().internalize(t->get_arg(i), false); + } + } + } + + enode * mk_enode(app * n) { + if (ctx().e_internalized(n)) { + return get_enode(n); + } + else { + return ctx().mk_enode(n, !reflect(n), false, enable_cgc_for(n)); + } + } + + bool enable_cgc_for(app * n) const { + // Congruence closure is not enabled for (+ ...) and (* ...) applications. + return !(n->get_family_id() == get_id() && (n->get_decl_kind() == OP_ADD || n->get_decl_kind() == OP_MUL)); + } + + + void mk_clause(literal l1, literal l2, unsigned num_params, parameter * params) { + TRACE("arith", literal lits[2]; lits[0] = l1; lits[1] = l2; ctx().display_literals_verbose(tout, 2, lits); tout << "\n";); + ctx().mk_th_axiom(get_id(), l1, l2, num_params, params); + } + + void mk_clause(literal l1, literal l2, literal l3, unsigned num_params, parameter * params) { + TRACE("arith", literal lits[3]; lits[0] = l1; lits[1] = l2; lits[2] = l3; ctx().display_literals_verbose(tout, 3, lits); tout << "\n";); + ctx().mk_th_axiom(get_id(), l1, l2, l3, num_params, params); + } + + bool is_underspecified(app* n) const { + if (n->get_family_id() == get_id()) { + switch (n->get_decl_kind()) { + case OP_DIV: + case OP_IDIV: + case OP_REM: + case OP_MOD: + return true; + default: + break; + } + } + return false; + } + + bool reflect(app* n) const { + return m_arith_params.m_arith_reflect || is_underspecified(n); + } + + theory_var mk_var(expr* n, bool internalize = true) { + if (!ctx().e_internalized(n)) { + ctx().internalize(n, false); + } + enode* e = get_enode(n); + theory_var v; + if (!th.is_attached_to_var(e)) { + v = th.mk_var(e); + SASSERT(m_bounds.size() <= static_cast(v) || m_bounds[v].empty()); + if (m_bounds.size() <= static_cast(v)) { + m_bounds.push_back(lp_bounds()); + m_unassigned_bounds.push_back(0); + } + ctx().attach_th_var(e, &th, v); + } + else { + v = e->get_th_var(get_id()); + } + SASSERT(null_theory_var != v); + return v; + } + + lean::var_index get_var_index(theory_var v) { + lean::var_index result = UINT_MAX; + if (m_theory_var2var_index.size() > static_cast(v)) { + result = m_theory_var2var_index[v]; + } + if (result == UINT_MAX) { + result = m_solver->add_var(v); + m_theory_var2var_index.setx(v, result, UINT_MAX); + m_var_index2theory_var.setx(result, v, UINT_MAX); + m_var_trail.push_back(v); + } + return result; + } + + void init_left_side(scoped_internalize_state& st) { + SASSERT(all_zeros(m_columns)); + svector const& vars = st.vars(); + vector const& coeffs = st.coeffs(); + for (unsigned i = 0; i < vars.size(); ++i) { + theory_var var = vars[i]; + rational const& coeff = coeffs[i]; + if (m_columns.size() <= static_cast(var)) { + m_columns.setx(var, coeff, rational::zero()); + } + else { + m_columns[var] += coeff; + } + } + m_left_side.clear(); + // reset the coefficients after they have been used. + for (unsigned i = 0; i < vars.size(); ++i) { + theory_var var = vars[i]; + rational const& r = m_columns[var]; + if (!r.is_zero()) { + m_left_side.push_back(std::make_pair(r, get_var_index(var))); + m_columns[var].reset(); + } + } + SASSERT(all_zeros(m_columns)); + } + + bool all_zeros(vector const& v) const { + for (unsigned i = 0; i < v.size(); ++i) { + if (!v[i].is_zero()) { + return false; + } + } + return true; + } + + void add_eq_constraint(lean::constraint_index index, enode* n1, enode* n2) { + m_constraint_sources.setx(index, equality_source, null_source); + m_equalities.setx(index, enode_pair(n1, n2), enode_pair(0, 0)); + ++m_stats.m_add_rows; + } + + void add_ineq_constraint(lean::constraint_index index, literal lit) { + m_constraint_sources.setx(index, inequality_source, null_source); + m_inequalities.setx(index, lit, null_literal); + ++m_stats.m_add_rows; + TRACE("arith", m_solver->print_constraint(index, tout); tout << "\n";); + } + + void add_def_constraint(lean::constraint_index index, theory_var v) { + m_constraint_sources.setx(index, definition_source, null_source); + m_definitions.setx(index, v, null_theory_var); + ++m_stats.m_add_rows; + } + + void internalize_eq(delayed_def const& d) { + scoped_internalize_state st(*this); + st.vars().append(d.m_vars); + st.coeffs().append(d.m_coeffs); + init_left_side(st); + add_def_constraint(m_solver->add_constraint(m_left_side, lean::EQ, -d.m_coeff), d.m_var); + } + + void internalize_eq(theory_var v1, theory_var v2) { + enode* n1 = get_enode(v1); + enode* n2 = get_enode(v2); + scoped_internalize_state st(*this); + st.vars().push_back(v1); + st.vars().push_back(v2); + st.coeffs().push_back(rational::one()); + st.coeffs().push_back(rational::minus_one()); + init_left_side(st); + add_eq_constraint(m_solver->add_constraint(m_left_side, lean::EQ, rational::zero()), n1, n2); + TRACE("arith", + tout << "v" << v1 << " = " << "v" << v2 << ": " + << mk_pp(n1->get_owner(), m) << " = " << mk_pp(n2->get_owner(), m) << "\n";); + } + + void del_bounds(unsigned old_size) { + for (unsigned i = m_bounds_trail.size(); i > old_size; ) { + --i; + unsigned v = m_bounds_trail[i]; + lp::bound* b = m_bounds[v].back(); + // del_use_lists(b); + dealloc(b); + m_bounds[v].pop_back(); + } + m_bounds_trail.shrink(old_size); + } + + void updt_unassigned_bounds(theory_var v, int inc) { + TRACE("arith", tout << "v" << v << " " << m_unassigned_bounds[v] << " += " << inc << "\n";); + ctx().push_trail(vector_value_trail(m_unassigned_bounds, v)); + m_unassigned_bounds[v] += inc; + } + + bool is_unit_var(scoped_internalize_state& st) { + return st.coeff().is_zero() && st.vars().size() == 1 && st.coeffs()[0].is_one(); + } + + theory_var internalize_def(app* term, scoped_internalize_state& st) { + linearize_term(term, st); + if (is_unit_var(st)) { + return st.vars()[0]; + } + else { + theory_var v = mk_var(term); + SASSERT(null_theory_var != v); + st.coeffs().resize(st.vars().size() + 1); + st.coeffs()[st.vars().size()] = rational::minus_one(); + st.vars().push_back(v); + return v; + } + } + + // term - v = 0 + theory_var internalize_def(app* term) { + scoped_internalize_state st(*this); + linearize_term(term, st); + if (is_unit_var(st)) { + return st.vars()[0]; + } + else { + init_left_side(st); + theory_var v = mk_var(term); + lean::var_index vi = m_theory_var2var_index.get(v, UINT_MAX); + if (vi == UINT_MAX) { + vi = m_solver->add_term(m_left_side, st.coeff()); + m_theory_var2var_index.setx(v, vi, UINT_MAX); + if (m_solver->is_term(vi)) { + m_term_index2theory_var.setx(m_solver->adjust_term_index(vi), v, UINT_MAX); + } + else { + m_var_index2theory_var.setx(vi, v, UINT_MAX); + } + m_var_trail.push_back(v); + TRACE("arith", tout << "v" << v << " := " << mk_pp(term, m) << " slack: " << vi << " scopes: " << m_scopes.size() << "\n"; + m_solver->print_term(m_solver->get_term(vi), tout); tout << "\n";); + } + rational val; + if (a.is_numeral(term, val)) { + m_fixed_var_table.insert(value_sort_pair(val, is_int(v)), v); + } + return v; + } + } + + + public: + imp(theory_lra& th, ast_manager& m, theory_arith_params& p): + th(th), m(m), m_arith_params(p), a(m), + m_arith_eq_adapter(th, p, a), + m_internalize_head(0), + m_delay_constraints(false), + m_delayed_terms(m), + m_not_handled(0), + m_asserted_qhead(0), + m_assume_eq_head(0), + m_num_conflicts(0), + m_model_eqs(DEFAULT_HASHTABLE_INITIAL_CAPACITY, var_value_hash(*this), var_value_eq(*this)), + m_resource_limit(*this) { + init_solver(); + } + + ~imp() { + del_bounds(0); + std::for_each(m_internalize_states.begin(), m_internalize_states.end(), delete_proc()); + } + + bool internalize_atom(app * atom, bool gate_ctx) { + if (m_delay_constraints) { + return internalize_atom_lazy(atom, gate_ctx); + } + else { + return internalize_atom_strict(atom, gate_ctx); + } + } + + bool internalize_atom_strict(app * atom, bool gate_ctx) { + SASSERT(!ctx().b_internalized(atom)); + bool_var bv = ctx().mk_bool_var(atom); + ctx().set_var_theory(bv, get_id()); + expr* n1, *n2; + rational r; + lp::bound_kind k; + theory_var v = null_theory_var; + if (a.is_le(atom, n1, n2) && is_numeral(n2, r) && is_app(n1)) { + v = internalize_def(to_app(n1)); + k = lp::upper_t; + } + else if (a.is_ge(atom, n1, n2) && is_numeral(n2, r) && is_app(n1)) { + v = internalize_def(to_app(n1)); + k = lp::lower_t; + } + else { + TRACE("arith", tout << "Could not internalize " << mk_pp(atom, m) << "\n";); + found_not_handled(atom); + return true; + } + lp::bound* b = alloc(lp::bound, bv, v, r, k); + m_bounds[v].push_back(b); + updt_unassigned_bounds(v, +1); + m_bounds_trail.push_back(v); + m_bool_var2bound.insert(bv, b); + TRACE("arith", tout << "Internalized " << mk_pp(atom, m) << "\n";); + mk_bound_axioms(*b); + //add_use_lists(b); + return true; + } + + bool internalize_atom_lazy(app * atom, bool gate_ctx) { + SASSERT(!ctx().b_internalized(atom)); + bool_var bv = ctx().mk_bool_var(atom); + ctx().set_var_theory(bv, get_id()); + expr* n1, *n2; + rational r; + lp::bound_kind k; + theory_var v = null_theory_var; + scoped_internalize_state st(*this); + if (a.is_le(atom, n1, n2) && is_numeral(n2, r) && is_app(n1)) { + v = internalize_def(to_app(n1), st); + k = lp::upper_t; + } + else if (a.is_ge(atom, n1, n2) && is_numeral(n2, r) && is_app(n1)) { + v = internalize_def(to_app(n1), st); + k = lp::lower_t; + } + else { + TRACE("arith", tout << "Could not internalize " << mk_pp(atom, m) << "\n";); + found_not_handled(atom); + return true; + } + lp::bound* b = alloc(lp::bound, bv, v, r, k); + m_bounds[v].push_back(b); + updt_unassigned_bounds(v, +1); + m_bounds_trail.push_back(v); + m_bool_var2bound.insert(bv, b); + TRACE("arith", tout << "Internalized " << mk_pp(atom, m) << "\n";); + if (!is_unit_var(st) && m_bounds[v].size() == 1) { + m_delayed_defs.push_back(delayed_def(st.vars(), st.coeffs(), st.coeff(), v)); + } + return true; + } + + bool internalize_term(app * term) { + if (ctx().e_internalized(term) && th.is_attached_to_var(ctx().get_enode(term))) { + // skip + } + else if (m_delay_constraints) { + scoped_internalize_state st(*this); + linearize_term(term, st); // ensure that a theory_var was created. + SASSERT(ctx().e_internalized(term)); + if(!th.is_attached_to_var(ctx().get_enode(term))) { + mk_var(term); + } + m_delayed_terms.push_back(term); + } + else { + internalize_def(term); + } + return true; + } + + void internalize_eq_eh(app * atom, bool_var) { + expr* lhs, *rhs; + VERIFY(m.is_eq(atom, lhs, rhs)); + enode * n1 = get_enode(lhs); + enode * n2 = get_enode(rhs); + if (n1->get_th_var(get_id()) != null_theory_var && + n2->get_th_var(get_id()) != null_theory_var && + n1 != n2) { + TRACE("arith", tout << mk_pp(atom, m) << "\n";); + m_arith_eq_adapter.mk_axioms(n1, n2); + } + } + + void assign_eh(bool_var v, bool is_true) { + TRACE("arith", tout << mk_pp(ctx().bool_var2expr(v), m) << " " << (is_true?"true":"false") << "\n";); + m_asserted_atoms.push_back(delayed_atom(v, is_true)); + } + + void new_eq_eh(theory_var v1, theory_var v2) { + if (m_delay_constraints) { + m_delayed_equalities.push_back(std::make_pair(v1, v2)); + } + else { + // or internalize_eq(v1, v2); + m_arith_eq_adapter.new_eq_eh(v1, v2); + } + } + + bool use_diseqs() const { + return true; + } + + void new_diseq_eh(theory_var v1, theory_var v2) { + TRACE("arith", tout << "v" << v1 << " != " << "v" << v2 << "\n";); + ++m_stats.m_assert_diseq; + m_arith_eq_adapter.new_diseq_eh(v1, v2); + } + + void push_scope_eh() { + m_scopes.push_back(scope()); + scope& s = m_scopes.back(); + s.m_bounds_lim = m_bounds_trail.size(); + s.m_asserted_qhead = m_asserted_qhead; + s.m_asserted_atoms_lim = m_asserted_atoms.size(); + s.m_delayed_terms_lim = m_delayed_terms.size(); + s.m_delayed_equalities_lim = m_delayed_equalities.size(); + s.m_delayed_defs_lim = m_delayed_defs.size(); + s.m_not_handled = m_not_handled; + s.m_underspecified_lim = m_underspecified.size(); + s.m_var_trail_lim = m_var_trail.size(); + if (!m_delay_constraints) m_solver->push(); + } + + void pop_scope_eh(unsigned num_scopes) { + if (num_scopes == 0) { + return; + } + unsigned old_size = m_scopes.size() - num_scopes; + del_bounds(m_scopes[old_size].m_bounds_lim); + for (unsigned i = m_scopes[old_size].m_var_trail_lim; i < m_var_trail.size(); ++i) { + lean::var_index vi = m_theory_var2var_index[m_var_trail[i]]; + if (m_solver->is_term(vi)) { + unsigned ti = m_solver->adjust_term_index(vi); + m_term_index2theory_var[ti] = UINT_MAX; + } + else if (vi < m_var_index2theory_var.size()) { + m_var_index2theory_var[vi] = UINT_MAX; + } + m_theory_var2var_index[m_var_trail[i]] = UINT_MAX; + } + m_asserted_atoms.shrink(m_scopes[old_size].m_asserted_atoms_lim); + m_delayed_terms.shrink(m_scopes[old_size].m_delayed_terms_lim); + m_delayed_defs.shrink(m_scopes[old_size].m_delayed_defs_lim); + m_delayed_equalities.shrink(m_scopes[old_size].m_delayed_equalities_lim); + m_asserted_qhead = m_scopes[old_size].m_asserted_qhead; + m_underspecified.shrink(m_scopes[old_size].m_underspecified_lim); + m_var_trail.shrink(m_scopes[old_size].m_var_trail_lim); + m_not_handled = m_scopes[old_size].m_not_handled; + m_scopes.resize(old_size); + if (!m_delay_constraints) m_solver->pop(num_scopes); + // VERIFY(l_false != make_feasible()); + m_new_bounds.reset(); + m_to_check.reset(); + TRACE("arith", tout << "num scopes: " << num_scopes << " new scope level: " << m_scopes.size() << "\n";); + } + + void restart_eh() { + m_arith_eq_adapter.restart_eh(); + } + + void relevant_eh(app* n) { + TRACE("arith", tout << mk_pp(n, m) << "\n";); + expr* n1, *n2; + if (a.is_mod(n, n1, n2)) + mk_idiv_mod_axioms(n1, n2); + else if (a.is_rem(n, n1, n2)) + mk_rem_axiom(n1, n2); + else if (a.is_div(n, n1, n2)) + mk_div_axiom(n1, n2); + else if (a.is_to_int(n)) + mk_to_int_axiom(n); + else if (a.is_is_int(n)) + mk_is_int_axiom(n); + } + + // n < 0 || rem(a, n) = mod(a, n) + // !n < 0 || rem(a, n) = -mod(a, n) + void mk_rem_axiom(expr* dividend, expr* divisor) { + expr_ref zero(a.mk_int(0), m); + expr_ref rem(a.mk_rem(dividend, divisor), m); + expr_ref mod(a.mk_mod(dividend, divisor), m); + expr_ref mmod(a.mk_uminus(mod), m); + literal dgez = mk_literal(a.mk_ge(divisor, zero)); + mk_axiom(~dgez, th.mk_eq(rem, mod, false)); + mk_axiom( dgez, th.mk_eq(rem, mmod, false)); + } + + // q = 0 or q * (p div q) = p + void mk_div_axiom(expr* p, expr* q) { + if (a.is_zero(q)) return; + literal eqz = th.mk_eq(q, a.mk_real(0), false); + literal eq = th.mk_eq(a.mk_mul(q, a.mk_div(p, q)), p, false); + mk_axiom(eqz, eq); + } + + // to_int (to_real x) = x + // to_real(to_int(x)) <= x < to_real(to_int(x)) + 1 + void mk_to_int_axiom(app* n) { + expr* x, *y; + VERIFY (a.is_to_int(n, x)); + if (a.is_to_real(x, y)) { + mk_axiom(th.mk_eq(y, n, false)); + } + else { + expr_ref to_r(a.mk_to_real(n), m); + expr_ref lo(a.mk_le(a.mk_sub(to_r, x), a.mk_real(0)), m); + expr_ref hi(a.mk_ge(a.mk_sub(x, to_r), a.mk_real(1)), m); + mk_axiom(mk_literal(lo)); + mk_axiom(~mk_literal(hi)); + } + } + + // is_int(x) <=> to_real(to_int(x)) = x + void mk_is_int_axiom(app* n) { + expr* x; + VERIFY(a.is_is_int(n, x)); + literal eq = th.mk_eq(a.mk_to_real(a.mk_to_int(x)), x, false); + literal is_int = ctx().get_literal(n); + mk_axiom(~is_int, eq); + mk_axiom(is_int, ~eq); + } + + void mk_idiv_mod_axioms(expr * p, expr * q) { + if (a.is_zero(q)) { + return; + } + // if q is zero, then idiv and mod are uninterpreted functions. + expr_ref div(a.mk_idiv(p, q), m); + expr_ref mod(a.mk_mod(p, q), m); + expr_ref zero(a.mk_int(0), m); + literal q_ge_0 = mk_literal(a.mk_ge(q, zero)); + literal q_le_0 = mk_literal(a.mk_le(q, zero)); + // literal eqz = th.mk_eq(q, zero, false); + literal eq = th.mk_eq(a.mk_add(a.mk_mul(q, div), mod), p, false); + literal mod_ge_0 = mk_literal(a.mk_ge(mod, zero)); + // q >= 0 or p = (p mod q) + q * (p div q) + // q <= 0 or p = (p mod q) + q * (p div q) + // q >= 0 or (p mod q) >= 0 + // q <= 0 or (p mod q) >= 0 + // q <= 0 or (p mod q) < q + // q >= 0 or (p mod q) < -q + mk_axiom(q_ge_0, eq); + mk_axiom(q_le_0, eq); + mk_axiom(q_ge_0, mod_ge_0); + mk_axiom(q_le_0, mod_ge_0); + mk_axiom(q_le_0, ~mk_literal(a.mk_ge(a.mk_sub(mod, q), zero))); + mk_axiom(q_ge_0, ~mk_literal(a.mk_ge(a.mk_add(mod, q), zero))); + rational k; + if (m_arith_params.m_arith_enum_const_mod && a.is_numeral(q, k) && + k.is_pos() && k < rational(8)) { + unsigned _k = k.get_unsigned(); + literal_buffer lits; + for (unsigned j = 0; j < _k; ++j) { + literal mod_j = th.mk_eq(mod, a.mk_int(j), false); + lits.push_back(mod_j); + ctx().mark_as_relevant(mod_j); + } + ctx().mk_th_axiom(get_id(), lits.size(), lits.begin()); + } + } + + void mk_axiom(literal l) { + ctx().mk_th_axiom(get_id(), false_literal, l); + if (ctx().relevancy()) { + ctx().mark_as_relevant(l); + } + } + + void mk_axiom(literal l1, literal l2) { + if (l1 == false_literal) { + mk_axiom(l2); + return; + } + ctx().mk_th_axiom(get_id(), l1, l2); + if (ctx().relevancy()) { + ctx().mark_as_relevant(l1); + expr_ref e(m); + ctx().literal2expr(l2, e); + ctx().add_rel_watch(~l1, e); + } + } + + void mk_axiom(literal l1, literal l2, literal l3) { + ctx().mk_th_axiom(get_id(), l1, l2, l3); + if (ctx().relevancy()) { + expr_ref e(m); + ctx().mark_as_relevant(l1); + ctx().literal2expr(l2, e); + ctx().add_rel_watch(~l1, e); + ctx().literal2expr(l3, e); + ctx().add_rel_watch(~l2, e); + } + } + + literal mk_literal(expr* e) { + expr_ref pinned(e, m); + if (!ctx().e_internalized(e)) { + ctx().internalize(e, false); + } + return ctx().get_literal(e); + } + + + void init_search_eh() { + m_arith_eq_adapter.init_search_eh(); + m_num_conflicts = 0; + } + + bool can_get_value(theory_var v) const { + return + (v != null_theory_var) && + (v < static_cast(m_theory_var2var_index.size())) && + (UINT_MAX != m_theory_var2var_index[v]) && + (m_solver->is_term(m_theory_var2var_index[v]) || m_variable_values.count(m_theory_var2var_index[v]) > 0); + } + + + bool can_get_ivalue(theory_var v) const { + if (v == null_theory_var || (v >= static_cast(m_theory_var2var_index.size()))) + return false; + return m_solver->var_is_registered(m_theory_var2var_index[v]); + } + + lean::impq get_ivalue(theory_var v) const { + lean_assert(can_get_ivalue(v)); + lean::var_index vi = m_theory_var2var_index[v]; + if (!m_solver->is_term(vi)) + return m_solver->get_value(vi); + + const lean::lar_term& term = m_solver->get_term(vi); + lean::impq result(term.m_v); + for (const auto & i: term.m_coeffs) { + result += m_solver->get_value(i.first) * i.second; + } + return result; + } + + + rational get_value(theory_var v) const { + if (!can_get_value(v)) return rational::zero(); + lean::var_index vi = m_theory_var2var_index[v]; + if (m_variable_values.count(vi) > 0) { + return m_variable_values[vi]; + } + if (m_solver->is_term(vi)) { + const lean::lar_term& term = m_solver->get_term(vi); + rational result = term.m_v; + for (auto i = term.m_coeffs.begin(); i != term.m_coeffs.end(); ++i) { + result += m_variable_values[i->first] * i->second; + } + m_variable_values[vi] = result; + return result; + } + UNREACHABLE(); + return m_variable_values[vi]; + } + + void init_variable_values() { + if (m_solver.get() && th.get_num_vars() > 0) { + m_solver->get_model(m_variable_values); + } + } + + void reset_variable_values() { + m_variable_values.clear(); + } + + bool assume_eqs() { + svector vars; + theory_var sz = static_cast(th.get_num_vars()); + for (theory_var v = 0; v < sz; ++v) { + if (th.is_relevant_and_shared(get_enode(v))) { + vars.push_back(m_theory_var2var_index[v]); + } + } + if (vars.empty()) { + return false; + } + TRACE("arith", + for (theory_var v = 0; v < sz; ++v) { + if (th.is_relevant_and_shared(get_enode(v))) { + tout << "v" << v << " " << m_theory_var2var_index[v] << " "; + } + } + tout << "\n"; + ); + m_solver->random_update(vars.size(), vars.c_ptr()); + m_model_eqs.reset(); + TRACE("arith", display(tout);); + + unsigned old_sz = m_assume_eq_candidates.size(); + bool result = false; + int start = ctx().get_random_value(); + for (theory_var i = 0; i < sz; ++i) { + theory_var v = (i + start) % sz; + enode* n1 = get_enode(v); + if (!th.is_relevant_and_shared(n1)) { + continue; + } + if (!can_get_ivalue(v)) { + continue; + } + theory_var other = m_model_eqs.insert_if_not_there(v); + if (other == v) { + continue; + } + enode* n2 = get_enode(other); + if (n1->get_root() != n2->get_root()) { + TRACE("arith", tout << mk_pp(n1->get_owner(), m) << " = " << mk_pp(n2->get_owner(), m) << "\n"; + tout << mk_pp(n1->get_owner(), m) << " = " << mk_pp(n2->get_owner(), m) << "\n"; + tout << "v" << v << " = " << "v" << other << "\n";); + m_assume_eq_candidates.push_back(std::make_pair(v, other)); + result = true; + } + } + + if (result) { + ctx().push_trail(restore_size_trail, false>(m_assume_eq_candidates, old_sz)); + } + + return delayed_assume_eqs(); + } + + bool delayed_assume_eqs() { + if (m_assume_eq_head == m_assume_eq_candidates.size()) + return false; + + ctx().push_trail(value_trail(m_assume_eq_head)); + while (m_assume_eq_head < m_assume_eq_candidates.size()) { + std::pair const & p = m_assume_eq_candidates[m_assume_eq_head]; + theory_var v1 = p.first; + theory_var v2 = p.second; + enode* n1 = get_enode(v1); + enode* n2 = get_enode(v2); + m_assume_eq_head++; + CTRACE("arith", + get_ivalue(v1) == get_ivalue(v2) && n1->get_root() != n2->get_root(), + tout << "assuming eq: v" << v1 << " = v" << v2 << "\n";); + if (get_ivalue(v1) == get_ivalue(v2) && n1->get_root() != n2->get_root() && th.assume_eq(n1, n2)) { + return true; + } + } + return false; + } + + bool has_delayed_constraints() const { + return !(m_asserted_atoms.empty() && m_delayed_terms.empty() && m_delayed_equalities.empty()); + } + + final_check_status final_check_eh() { + lbool is_sat = l_true; + if (m_delay_constraints) { + init_solver(); + for (unsigned i = 0; i < m_asserted_atoms.size(); ++i) { + bool_var bv = m_asserted_atoms[i].m_bv; + assert_bound(bv, m_asserted_atoms[i].m_is_true, *m_bool_var2bound.find(bv)); + } + for (unsigned i = 0; i < m_delayed_terms.size(); ++i) { + internalize_def(m_delayed_terms[i].get()); + } + for (unsigned i = 0; i < m_delayed_defs.size(); ++i) { + internalize_eq(m_delayed_defs[i]); + } + for (unsigned i = 0; i < m_delayed_equalities.size(); ++i) { + std::pair const& eq = m_delayed_equalities[i]; + internalize_eq(eq.first, eq.second); + } + is_sat = make_feasible(); + } + else if (m_solver->get_status() != lean::lp_status::OPTIMAL) { + is_sat = make_feasible(); + } + switch (is_sat) { + case l_true: + if (delayed_assume_eqs()) { + return FC_CONTINUE; + } + if (assume_eqs()) { + return FC_CONTINUE; + } + if (m_not_handled != 0) { + return FC_GIVEUP; + } + return FC_DONE; + case l_false: + set_conflict(); + return FC_CONTINUE; + case l_undef: + return m.canceled() ? FC_CONTINUE : FC_GIVEUP; + default: + UNREACHABLE(); + break; + } + return FC_GIVEUP; + } + + + /** + \brief We must redefine this method, because theory of arithmetic contains + underspecified operators such as division by 0. + (/ a b) is essentially an uninterpreted function when b = 0. + Thus, 'a' must be considered a shared var if it is the child of an underspecified operator. + + if merge(a / b, x + y) and a / b is root, then x + y become shared and all z + u in equivalence class of x + y. + + + TBD: when the set of underspecified subterms is small, compute the shared variables below it. + Recompute the set if there are merges that invalidate it. + Use the set to determine if a variable is shared. + */ + bool is_shared(theory_var v) const { + if (m_underspecified.empty()) { + return false; + } + enode * n = get_enode(v); + enode * r = n->get_root(); + unsigned usz = m_underspecified.size(); + TRACE("shared", tout << ctx().get_scope_level() << " " << v << " " << r->get_num_parents() << "\n";); + if (r->get_num_parents() > 2*usz) { + for (unsigned i = 0; i < usz; ++i) { + app* u = m_underspecified[i]; + unsigned sz = u->get_num_args(); + for (unsigned j = 0; j < sz; ++j) { + if (ctx().get_enode(u->get_arg(j))->get_root() == r) { + return true; + } + } + } + } + else { + enode_vector::const_iterator it = r->begin_parents(); + enode_vector::const_iterator end = r->end_parents(); + for (; it != end; ++it) { + enode * parent = *it; + if (is_underspecified(parent->get_owner())) { + return true; + } + } + } + return false; + } + + bool can_propagate() { +#if 0 + if (ctx().at_base_level() && has_delayed_constraints()) { + // we could add the delayed constraints here directly to the tableau instead of using bounds variables. + } +#endif + return m_asserted_atoms.size() > m_asserted_qhead; + } + + void propagate() { + flush_bound_axioms(); + if (!can_propagate()) { + return; + } + unsigned qhead = m_asserted_qhead; + while (m_asserted_qhead < m_asserted_atoms.size() && !ctx().inconsistent()) { + bool_var bv = m_asserted_atoms[m_asserted_qhead].m_bv; + bool is_true = m_asserted_atoms[m_asserted_qhead].m_is_true; + +#if 1 + m_to_check.push_back(bv); +#else + propagate_bound(bv, is_true, b); +#endif + if (!m_delay_constraints) { + lp::bound& b = *m_bool_var2bound.find(bv); + assert_bound(bv, is_true, b); + } + + ++m_asserted_qhead; + } + if (m_delay_constraints || ctx().inconsistent()) { + m_to_check.reset(); + return; + } + /*for (; qhead < m_asserted_atoms.size() && !ctx().inconsistent(); ++qhead) { + bool_var bv = m_asserted_atoms[qhead].m_bv; + bool is_true = m_asserted_atoms[qhead].m_is_true; + lp::bound& b = *m_bool_var2bound.find(bv); + propagate_bound_compound(bv, is_true, b); + }*/ + + lbool lbl = make_feasible(); + + switch(lbl) { + case l_false: + TRACE("arith", tout << "propagation conflict\n";); + set_conflict(); + break; + case l_true: + propagate_basic_bounds(); + propagate_bounds_with_lp_solver(); + break; + case l_undef: + break; + } + + } + + void propagate_bounds_with_lp_solver() { + if (BP_NONE == propagation_mode()) { + return; + } + int num_of_p = m_solver->settings().st().m_num_of_implied_bounds; + local_bound_propagator bp(*this); + m_solver->propagate_bounds_for_touched_rows(bp); + if (m.canceled()) { + return; + } + int new_num_of_p = m_solver->settings().st().m_num_of_implied_bounds; + CTRACE("arith", new_num_of_p > num_of_p, tout << "found " << new_num_of_p << " implied bounds\n";); + if (m_solver->get_status() == lean::lp_status::INFEASIBLE) { + set_conflict(); + } + else { + for (size_t i = 0; !m.canceled() && !ctx().inconsistent() && i < bp.m_ibounds.size(); ++i) { + propagate_lp_solver_bound(bp.m_ibounds[i]); + } + } + } + + bool bound_is_interesting(unsigned vi, lean::lconstraint_kind kind, const rational & bval) const { + theory_var v; + if (m_solver->is_term(vi)) { + v = m_term_index2theory_var.get(m_solver->adjust_term_index(vi), null_theory_var); + } + else { + v = m_var_index2theory_var.get(vi, null_theory_var); + } + + if (v == null_theory_var) return false; + + if (m_unassigned_bounds[v] == 0 || m_bounds.size() <= static_cast(v)) { + TRACE("arith", tout << "return\n";); + return false; + } + lp_bounds const& bounds = m_bounds[v]; + for (unsigned i = 0; i < bounds.size(); ++i) { + lp::bound* b = bounds[i]; + if (ctx().get_assignment(b->get_bv()) != l_undef) { + continue; + } + literal lit = is_bound_implied(kind, bval, *b); + if (lit == null_literal) { + continue; + } + return true; + } + return false; + } + + struct local_bound_propagator: public lean::bound_propagator { + imp & m_imp; + local_bound_propagator(imp& i) : bound_propagator(*i.m_solver), m_imp(i) {} + + bool bound_is_interesting(unsigned j, lean::lconstraint_kind kind, const rational & v) { + return m_imp.bound_is_interesting(j, kind, v); + } + + virtual void consume(rational const& v, unsigned j) { + m_imp.set_evidence(j); + } + }; + + + void propagate_lp_solver_bound(lean::implied_bound& be) { + + theory_var v; + lean::var_index vi = be.m_j; + if (m_solver->is_term(vi)) { + v = m_term_index2theory_var.get(m_solver->adjust_term_index(vi), null_theory_var); + } + else { + v = m_var_index2theory_var.get(vi, null_theory_var); + } + + if (v == null_theory_var) return; + TRACE("arith", tout << "v" << v << " " << be.kind() << " " << be.m_bound << "\n"; + // if (m_unassigned_bounds[v] == 0) m_solver->print_bound_evidence(be, tout); + ); + + + if (m_unassigned_bounds[v] == 0 || m_bounds.size() <= static_cast(v)) { + TRACE("arith", tout << "return\n";); + return; + } + lp_bounds const& bounds = m_bounds[v]; + bool first = true; + for (unsigned i = 0; i < bounds.size(); ++i) { + lp::bound* b = bounds[i]; + if (ctx().get_assignment(b->get_bv()) != l_undef) { + continue; + } + literal lit = is_bound_implied(be.kind(), be.m_bound, *b); + if (lit == null_literal) { + continue; + } + + m_solver->settings().st().m_num_of_implied_bounds ++; + if (first) { + first = false; + m_core.reset(); + m_eqs.reset(); + m_params.reset(); + local_bound_propagator bp(*this); + m_solver->explain_implied_bound(be, bp); + } + CTRACE("arith", m_unassigned_bounds[v] == 0, tout << "missed bound\n";); + updt_unassigned_bounds(v, -1); + TRACE("arith", + ctx().display_literals_verbose(tout, m_core); + tout << "\n --> "; + ctx().display_literal_verbose(tout, lit); + tout << "\n"; + // display_evidence(tout, m_explanation); + m_solver->print_implied_bound(be, tout); + ); + DEBUG_CODE( + for (auto& lit : m_core) { + SASSERT(ctx().get_assignment(lit) == l_true); + }); + ++m_stats.m_bound_propagations1; + assign(lit); + } + } + + literal_vector m_core2; + + void assign(literal lit) { + SASSERT(validate_assign(lit)); + if (m_core.size() < small_lemma_size() && m_eqs.empty()) { + m_core2.reset(); + for (unsigned i = 0; i < m_core.size(); ++i) { + m_core2.push_back(~m_core[i]); + } + m_core2.push_back(lit); + justification * js = 0; + if (proofs_enabled()) { + js = alloc(theory_lemma_justification, get_id(), ctx(), m_core2.size(), m_core2.c_ptr(), + m_params.size(), m_params.c_ptr()); + } + ctx().mk_clause(m_core2.size(), m_core2.c_ptr(), js, CLS_AUX_LEMMA, 0); + } + else { + ctx().assign( + lit, ctx().mk_justification( + ext_theory_propagation_justification( + get_id(), ctx().get_region(), m_core.size(), m_core.c_ptr(), + m_eqs.size(), m_eqs.c_ptr(), lit, m_params.size(), m_params.c_ptr()))); + } + } + + literal is_bound_implied(lean::lconstraint_kind k, rational const& value, lp::bound const& b) const { + if ((k == lean::LE || k == lean::LT) && b.get_bound_kind() == lp::upper_t && value <= b.get_value()) { + // v <= value <= b.get_value() => v <= b.get_value() + return literal(b.get_bv(), false); + } + if ((k == lean::GE || k == lean::GT) && b.get_bound_kind() == lp::lower_t && b.get_value() <= value) { + // b.get_value() <= value <= v => b.get_value() <= v + return literal(b.get_bv(), false); + } + if (k == lean::LE && b.get_bound_kind() == lp::lower_t && value < b.get_value()) { + // v <= value < b.get_value() => v < b.get_value() + return literal(b.get_bv(), true); + } + if (k == lean::LT && b.get_bound_kind() == lp::lower_t && value <= b.get_value()) { + // v < value <= b.get_value() => v < b.get_value() + return literal(b.get_bv(), true); + } + if (k == lean::GE && b.get_bound_kind() == lp::upper_t && b.get_value() < value) { + // b.get_value() < value <= v => b.get_value() < v + return literal(b.get_bv(), true); + } + if (k == lean::GT && b.get_bound_kind() == lp::upper_t && b.get_value() <= value) { + // b.get_value() <= value < v => b.get_value() < v + return literal(b.get_bv(), true); + } + + return null_literal; + } + + void mk_bound_axioms(lp::bound& b) { + if (!ctx().is_searching()) { + // + // NB. We make an assumption that user push calls propagation + // before internal scopes are pushed. This flushes all newly + // asserted atoms into the right context. + // + m_new_bounds.push_back(&b); + return; + } + theory_var v = b.get_var(); + lp::bound_kind kind1 = b.get_bound_kind(); + rational const& k1 = b.get_value(); + lp_bounds & bounds = m_bounds[v]; + + lp::bound* end = 0; + lp::bound* lo_inf = end, *lo_sup = end; + lp::bound* hi_inf = end, *hi_sup = end; + + for (unsigned i = 0; i < bounds.size(); ++i) { + lp::bound& other = *bounds[i]; + if (&other == &b) continue; + if (b.get_bv() == other.get_bv()) continue; + lp::bound_kind kind2 = other.get_bound_kind(); + rational const& k2 = other.get_value(); + if (k1 == k2 && kind1 == kind2) { + // the bounds are equivalent. + continue; + } + + SASSERT(k1 != k2 || kind1 != kind2); + if (kind2 == lp::lower_t) { + if (k2 < k1) { + if (lo_inf == end || k2 > lo_inf->get_value()) { + lo_inf = &other; + } + } + else if (lo_sup == end || k2 < lo_sup->get_value()) { + lo_sup = &other; + } + } + else if (k2 < k1) { + if (hi_inf == end || k2 > hi_inf->get_value()) { + hi_inf = &other; + } + } + else if (hi_sup == end || k2 < hi_sup->get_value()) { + hi_sup = &other; + } + } + if (lo_inf != end) mk_bound_axiom(b, *lo_inf); + if (lo_sup != end) mk_bound_axiom(b, *lo_sup); + if (hi_inf != end) mk_bound_axiom(b, *hi_inf); + if (hi_sup != end) mk_bound_axiom(b, *hi_sup); + } + + + void mk_bound_axiom(lp::bound& b1, lp::bound& b2) { + theory_var v = b1.get_var(); + literal l1(b1.get_bv()); + literal l2(b2.get_bv()); + rational const& k1 = b1.get_value(); + rational const& k2 = b2.get_value(); + lp::bound_kind kind1 = b1.get_bound_kind(); + lp::bound_kind kind2 = b2.get_bound_kind(); + bool v_is_int = is_int(v); + SASSERT(v == b2.get_var()); + if (k1 == k2 && kind1 == kind2) return; + SASSERT(k1 != k2 || kind1 != kind2); + parameter coeffs[3] = { parameter(symbol("farkas")), + parameter(rational(1)), parameter(rational(1)) }; + + if (kind1 == lp::lower_t) { + if (kind2 == lp::lower_t) { + if (k2 <= k1) { + mk_clause(~l1, l2, 3, coeffs); + } + else { + mk_clause(l1, ~l2, 3, coeffs); + } + } + else if (k1 <= k2) { + // k1 <= k2, k1 <= x or x <= k2 + mk_clause(l1, l2, 3, coeffs); + } + else { + // k1 > hi_inf, k1 <= x => ~(x <= hi_inf) + mk_clause(~l1, ~l2, 3, coeffs); + if (v_is_int && k1 == k2 + rational(1)) { + // k1 <= x or x <= k1-1 + mk_clause(l1, l2, 3, coeffs); + } + } + } + else if (kind2 == lp::lower_t) { + if (k1 >= k2) { + // k1 >= lo_inf, k1 >= x or lo_inf <= x + mk_clause(l1, l2, 3, coeffs); + } + else { + // k1 < k2, k2 <= x => ~(x <= k1) + mk_clause(~l1, ~l2, 3, coeffs); + if (v_is_int && k1 == k2 - rational(1)) { + // x <= k1 or k1+l <= x + mk_clause(l1, l2, 3, coeffs); + } + + } + } + else { + // kind1 == A_UPPER, kind2 == A_UPPER + if (k1 >= k2) { + // k1 >= k2, x <= k2 => x <= k1 + mk_clause(l1, ~l2, 3, coeffs); + } + else { + // k1 <= hi_sup , x <= k1 => x <= hi_sup + mk_clause(~l1, l2, 3, coeffs); + } + } + } + + typedef lp_bounds::iterator iterator; + + void flush_bound_axioms() { + CTRACE("arith", !m_new_bounds.empty(), tout << "flush bound axioms\n";); + + while (!m_new_bounds.empty()) { + lp_bounds atoms; + atoms.push_back(m_new_bounds.back()); + m_new_bounds.pop_back(); + theory_var v = atoms.back()->get_var(); + for (unsigned i = 0; i < m_new_bounds.size(); ++i) { + if (m_new_bounds[i]->get_var() == v) { + atoms.push_back(m_new_bounds[i]); + m_new_bounds[i] = m_new_bounds.back(); + m_new_bounds.pop_back(); + --i; + } + } + CTRACE("arith_verbose", !atoms.empty(), + for (unsigned i = 0; i < atoms.size(); ++i) { + atoms[i]->display(tout); tout << "\n"; + }); + lp_bounds occs(m_bounds[v]); + + std::sort(atoms.begin(), atoms.end(), compare_bounds()); + std::sort(occs.begin(), occs.end(), compare_bounds()); + + iterator begin1 = occs.begin(); + iterator begin2 = occs.begin(); + iterator end = occs.end(); + begin1 = first(lp::lower_t, begin1, end); + begin2 = first(lp::upper_t, begin2, end); + + iterator lo_inf = begin1, lo_sup = begin1; + iterator hi_inf = begin2, hi_sup = begin2; + iterator lo_inf1 = begin1, lo_sup1 = begin1; + iterator hi_inf1 = begin2, hi_sup1 = begin2; + bool flo_inf, fhi_inf, flo_sup, fhi_sup; + ptr_addr_hashtable visited; + for (unsigned i = 0; i < atoms.size(); ++i) { + lp::bound* a1 = atoms[i]; + lo_inf1 = next_inf(a1, lp::lower_t, lo_inf, end, flo_inf); + hi_inf1 = next_inf(a1, lp::upper_t, hi_inf, end, fhi_inf); + lo_sup1 = next_sup(a1, lp::lower_t, lo_sup, end, flo_sup); + hi_sup1 = next_sup(a1, lp::upper_t, hi_sup, end, fhi_sup); + if (lo_inf1 != end) lo_inf = lo_inf1; + if (lo_sup1 != end) lo_sup = lo_sup1; + if (hi_inf1 != end) hi_inf = hi_inf1; + if (hi_sup1 != end) hi_sup = hi_sup1; + if (!flo_inf) lo_inf = end; + if (!fhi_inf) hi_inf = end; + if (!flo_sup) lo_sup = end; + if (!fhi_sup) hi_sup = end; + visited.insert(a1); + if (lo_inf1 != end && lo_inf != end && !visited.contains(*lo_inf)) mk_bound_axiom(*a1, **lo_inf); + if (lo_sup1 != end && lo_sup != end && !visited.contains(*lo_sup)) mk_bound_axiom(*a1, **lo_sup); + if (hi_inf1 != end && hi_inf != end && !visited.contains(*hi_inf)) mk_bound_axiom(*a1, **hi_inf); + if (hi_sup1 != end && hi_sup != end && !visited.contains(*hi_sup)) mk_bound_axiom(*a1, **hi_sup); + } + } + } + + struct compare_bounds { + bool operator()(lp::bound* a1, lp::bound* a2) const { return a1->get_value() < a2->get_value(); } + }; + + + lp_bounds::iterator first( + lp::bound_kind kind, + iterator it, + iterator end) { + for (; it != end; ++it) { + lp::bound* a = *it; + if (a->get_bound_kind() == kind) return it; + } + return end; + } + + lp_bounds::iterator next_inf( + lp::bound* a1, + lp::bound_kind kind, + iterator it, + iterator end, + bool& found_compatible) { + rational const & k1(a1->get_value()); + iterator result = end; + found_compatible = false; + for (; it != end; ++it) { + lp::bound * a2 = *it; + if (a1 == a2) continue; + if (a2->get_bound_kind() != kind) continue; + rational const & k2(a2->get_value()); + found_compatible = true; + if (k2 <= k1) { + result = it; + } + else { + break; + } + } + return result; + } + + lp_bounds::iterator next_sup( + lp::bound* a1, + lp::bound_kind kind, + iterator it, + iterator end, + bool& found_compatible) { + rational const & k1(a1->get_value()); + found_compatible = false; + for (; it != end; ++it) { + lp::bound * a2 = *it; + if (a1 == a2) continue; + if (a2->get_bound_kind() != kind) continue; + rational const & k2(a2->get_value()); + found_compatible = true; + if (k1 < k2) { + return it; + } + } + return end; + } + + void propagate_basic_bounds() { + for (auto const& bv : m_to_check) { + lp::bound& b = *m_bool_var2bound.find(bv); + propagate_bound(bv, ctx().get_assignment(bv) == l_true, b); + if (ctx().inconsistent()) break; + + } + m_to_check.reset(); + } + + // for glb lo': lo' < lo: + // lo <= x -> lo' <= x + // lo <= x -> ~(x <= lo') + // for lub hi': hi' > hi + // x <= hi -> x <= hi' + // x <= hi -> ~(x >= hi') + + void propagate_bound(bool_var bv, bool is_true, lp::bound& b) { + if (BP_NONE == propagation_mode()) { + return; + } + lp::bound_kind k = b.get_bound_kind(); + theory_var v = b.get_var(); + inf_rational val = b.get_value(is_true); + lp_bounds const& bounds = m_bounds[v]; + SASSERT(!bounds.empty()); + if (bounds.size() == 1) return; + if (m_unassigned_bounds[v] == 0) return; + + literal lit1(bv, !is_true); + literal lit2 = null_literal; + bool find_glb = (is_true == (k == lp::lower_t)); + if (find_glb) { + rational glb; + lp::bound* lb = 0; + for (unsigned i = 0; i < bounds.size(); ++i) { + lp::bound* b2 = bounds[i]; + if (b2 == &b) continue; + rational const& val2 = b2->get_value(); + if ((is_true ? val2 < val : val2 <= val) && (!lb || glb < val2)) { + lb = b2; + glb = val2; + } + } + if (!lb) return; + bool sign = lb->get_bound_kind() != lp::lower_t; + lit2 = literal(lb->get_bv(), sign); + } + else { + rational lub; + lp::bound* ub = 0; + for (unsigned i = 0; i < bounds.size(); ++i) { + lp::bound* b2 = bounds[i]; + if (b2 == &b) continue; + rational const& val2 = b2->get_value(); + if ((is_true ? val < val2 : val <= val2) && (!ub || val2 < lub)) { + ub = b2; + lub = val2; + } + } + if (!ub) return; + bool sign = ub->get_bound_kind() != lp::upper_t; + lit2 = literal(ub->get_bv(), sign); + } + TRACE("arith", + ctx().display_literal_verbose(tout, lit1); + ctx().display_literal_verbose(tout << " => ", lit2); + tout << "\n";); + updt_unassigned_bounds(v, -1); + ++m_stats.m_bound_propagations2; + m_params.reset(); + m_core.reset(); + m_eqs.reset(); + m_core.push_back(lit2); + m_params.push_back(parameter(symbol("farkas"))); + m_params.push_back(parameter(rational(1))); + m_params.push_back(parameter(rational(1))); + assign(lit2); + ++m_stats.m_bounds_propagations; + } + + void add_use_lists(lp::bound* b) { + theory_var v = b->get_var(); + lean::var_index vi = get_var_index(v); + if (m_solver->is_term(vi)) { + lean::lar_term const& term = m_solver->get_term(vi); + for (auto i = term.m_coeffs.begin(); i != term.m_coeffs.end(); ++i) { + lean::var_index wi = i->first; + unsigned w = m_var_index2theory_var[wi]; + m_use_list.reserve(w + 1, ptr_vector()); + m_use_list[w].push_back(b); + } + } + } + + void del_use_lists(lp::bound* b) { + theory_var v = b->get_var(); + lean::var_index vi = m_theory_var2var_index[v]; + if (m_solver->is_term(vi)) { + lean::lar_term const& term = m_solver->get_term(vi); + for (auto i = term.m_coeffs.begin(); i != term.m_coeffs.end(); ++i) { + lean::var_index wi = i->first; + unsigned w = m_var_index2theory_var[wi]; + SASSERT(m_use_list[w].back() == b); + m_use_list[w].pop_back(); + } + } + } + + // + // propagate bounds to compound terms + // The idea is that if bounds on all variables in an inequality ax + by + cz >= k + // have been assigned we may know the truth value of the inequality by using simple + // bounds propagation. + // + void propagate_bound_compound(bool_var bv, bool is_true, lp::bound& b) { + lp::bound_kind k = b.get_bound_kind(); + theory_var v = b.get_var(); + TRACE("arith", tout << mk_pp(get_owner(v), m) << "\n";); + if (static_cast(v) >= m_use_list.size()) { + return; + } + for (auto const& vb : m_use_list[v]) { + if (ctx().get_assignment(vb->get_bv()) != l_undef) { + TRACE("arith_verbose", display_bound(tout << "assigned ", *vb) << "\n";); + continue; + } + inf_rational r; + // x + y + // x >= 0, y >= 1 -> x + y >= 1 + // x <= 0, y <= 2 -> x + y <= 2 + literal lit = null_literal; + if (lp::lower_t == vb->get_bound_kind()) { + if (get_glb(*vb, r) && r >= vb->get_value()) { // vb is assigned true + lit = literal(vb->get_bv(), false); + } + else if (get_lub(*vb, r) && r < vb->get_value()) { // vb is assigned false + lit = literal(vb->get_bv(), true); + } + } + else { + if (get_glb(*vb, r) && r > vb->get_value()) { // VB <= value < val(VB) + lit = literal(vb->get_bv(), true); + } + else if (get_lub(*vb, r) && r <= vb->get_value()) { // val(VB) <= value + lit = literal(vb->get_bv(), false); + } + } + + if (lit != null_literal) { + TRACE("arith", + ctx().display_literals_verbose(tout, m_core); + tout << "\n --> "; + ctx().display_literal_verbose(tout, lit); + tout << "\n"; + ); + + + assign(lit); + } + else { + TRACE("arith_verbose", display_bound(tout << "skip ", *vb) << "\n";); + } + } + } + + bool get_lub(lp::bound const& b, inf_rational& lub) { + return get_bound(b, lub, true); + } + + bool get_glb(lp::bound const& b, inf_rational& glb) { + return get_bound(b, glb, false); + } + + std::ostream& display_bound(std::ostream& out, lp::bound const& b) { + return out << mk_pp(ctx().bool_var2expr(b.get_bv()), m); + } + + bool get_bound(lp::bound const& b, inf_rational& r, bool is_lub) { + m_core.reset(); + m_eqs.reset(); + m_params.reset(); + r.reset(); + theory_var v = b.get_var(); + lean::var_index vi = m_theory_var2var_index[v]; + SASSERT(m_solver->is_term(vi)); + lean::lar_term const& term = m_solver->get_term(vi); + for (auto const coeff : term.m_coeffs) { + lean::var_index wi = coeff.first; + lean::constraint_index ci; + rational value; + bool is_strict; + if (coeff.second.is_neg() == is_lub) { + // -3*x ... <= lub based on lower bound for x. + if (!m_solver->has_lower_bound(wi, ci, value, is_strict)) { + return false; + } + if (is_strict) { + r += inf_rational(rational::zero(), coeff.second.is_pos()); + } + } + else { + if (!m_solver->has_upper_bound(wi, ci, value, is_strict)) { + return false; + } + if (is_strict) { + r += inf_rational(rational::zero(), coeff.second.is_pos()); + } + } + r += value * coeff.second; + set_evidence(ci); + } + TRACE("arith_verbose", tout << (is_lub?"lub":"glb") << " is " << r << "\n";); + return true; + } + + void assert_bound(bool_var bv, bool is_true, lp::bound& b) { + if (m_solver->get_status() == lean::lp_status::INFEASIBLE) { + return; + } + scoped_internalize_state st(*this); + st.vars().push_back(b.get_var()); + st.coeffs().push_back(rational::one()); + init_left_side(st); + lean::lconstraint_kind k = lean::EQ; + switch (b.get_bound_kind()) { + case lp::lower_t: + k = is_true ? lean::GE : lean::LT; + break; + case lp::upper_t: + k = is_true ? lean::LE : lean::GT; + break; + } + if (k == lean::LT || k == lean::LE) { + ++m_stats.m_assert_lower; + } + else { + ++m_stats.m_assert_upper; + } + auto vi = get_var_index(b.get_var()); + auto ci = m_solver->add_var_bound(vi, k, b.get_value()); + TRACE("arith", tout << "v" << b.get_var() << "\n";); + add_ineq_constraint(ci, literal(bv, !is_true)); + + propagate_eqs(vi, ci, k, b); + } + + // + // fixed equalities. + // A fixed equality is inferred if there are two variables v1, v2 whose + // upper and lower bounds coincide. + // Then the equality v1 == v2 is propagated to the core. + // + + typedef std::pair constraint_bound; + vector m_lower_terms; + vector m_upper_terms; + typedef std::pair value_sort_pair; + typedef pair_hash, bool_hash> value_sort_pair_hash; + typedef map > value2var; + value2var m_fixed_var_table; + const lean::constraint_index null_index = static_cast(-1); + + void propagate_eqs(lean::var_index vi, lean::constraint_index ci, lean::lconstraint_kind k, lp::bound& b) { + if (propagate_eqs()) { + rational const& value = b.get_value(); + if (k == lean::GE) { + set_lower_bound(vi, ci, value); + if (has_upper_bound(vi, ci, value)) { + fixed_var_eh(b.get_var(), value); + } + } + else if (k == lean::LE) { + set_upper_bound(vi, ci, value); + if (has_lower_bound(vi, ci, value)) { + fixed_var_eh(b.get_var(), value); + } + } + } + } + + bool dump_lemmas() const { return m_arith_params.m_arith_dump_lemmas; } + + bool propagate_eqs() const { return m_arith_params.m_arith_propagate_eqs && m_num_conflicts < m_arith_params.m_arith_propagation_threshold; } + + bound_prop_mode propagation_mode() const { return m_num_conflicts < m_arith_params.m_arith_propagation_threshold ? m_arith_params.m_arith_bound_prop : BP_NONE; } + + unsigned small_lemma_size() const { return m_arith_params.m_arith_small_lemma_size; } + + bool proofs_enabled() const { return m.proofs_enabled(); } + + bool use_tableau() const { return m_lp_params.simplex_strategy() < 2; } + + void set_upper_bound(lean::var_index vi, lean::constraint_index ci, rational const& v) { set_bound(vi, ci, v, false); } + + void set_lower_bound(lean::var_index vi, lean::constraint_index ci, rational const& v) { set_bound(vi, ci, v, true); } + + void set_bound(lean::var_index vi, lean::constraint_index ci, rational const& v, bool is_lower) { + if (!m_solver->is_term(vi)) { + // m_solver already tracks bounds on proper variables, but not on terms. + return; + } + lean::var_index ti = m_solver->adjust_term_index(vi); + auto& vec = is_lower ? m_lower_terms : m_upper_terms; + if (vec.size() <= ti) { + vec.resize(ti + 1, constraint_bound(null_index, rational())); + } + constraint_bound& b = vec[ti]; + if (b.first == null_index || (is_lower? b.second < v : b.second > v)) { + ctx().push_trail(vector_value_trail(vec, ti)); + b.first = ci; + b.second = v; + } + } + + bool has_upper_bound(lean::var_index vi, lean::constraint_index& ci, rational const& bound) { return has_bound(vi, ci, bound, false); } + + bool has_lower_bound(lean::var_index vi, lean::constraint_index& ci, rational const& bound) { return has_bound(vi, ci, bound, true); } + + bool has_bound(lean::var_index vi, lean::constraint_index& ci, rational const& bound, bool is_lower) { + + if (m_solver->is_term(vi)) { + + lean::var_index ti = m_solver->adjust_term_index(vi); + theory_var v = m_term_index2theory_var.get(ti, null_theory_var); + rational val; + TRACE("arith", tout << vi << " " << v << "\n";); + if (v != null_theory_var && a.is_numeral(get_owner(v), val) && bound == val) { + ci = null_constraint_index; + return bound == val; + } + + auto& vec = is_lower ? m_lower_terms : m_upper_terms; + if (vec.size() > ti) { + constraint_bound& b = vec[ti]; + ci = b.first; + return ci != null_index && bound == b.second; + } + else { + return false; + + } + } + else { + bool is_strict = false; + rational b; + if (is_lower) { + return m_solver->has_lower_bound(vi, ci, b, is_strict) && b == bound && !is_strict; + } + else { + return m_solver->has_upper_bound(vi, ci, b, is_strict) && b == bound && !is_strict; + } + } + } + + + bool is_equal(theory_var x, theory_var y) const { return get_enode(x)->get_root() == get_enode(y)->get_root(); } + + + void fixed_var_eh(theory_var v1, rational const& bound) { + theory_var v2; + value_sort_pair key(bound, is_int(v1)); + if (m_fixed_var_table.find(key, v2)) { + if (static_cast(v2) < th.get_num_vars() && !is_equal(v1, v2)) { + auto vi1 = get_var_index(v1); + auto vi2 = get_var_index(v2); + lean::constraint_index ci1, ci2, ci3, ci4; + TRACE("arith", tout << "fixed: " << mk_pp(get_owner(v1), m) << " " << mk_pp(get_owner(v2), m) << " " << bound << " " << has_lower_bound(vi2, ci3, bound) << "\n";); + if (has_lower_bound(vi2, ci3, bound) && has_upper_bound(vi2, ci4, bound)) { + VERIFY (has_lower_bound(vi1, ci1, bound)); + VERIFY (has_upper_bound(vi1, ci2, bound)); + ++m_stats.m_fixed_eqs; + m_core.reset(); + m_eqs.reset(); + set_evidence(ci1); + set_evidence(ci2); + set_evidence(ci3); + set_evidence(ci4); + enode* x = get_enode(v1); + enode* y = get_enode(v2); + justification* js = + ctx().mk_justification( + ext_theory_eq_propagation_justification( + get_id(), ctx().get_region(), m_core.size(), m_core.c_ptr(), m_eqs.size(), m_eqs.c_ptr(), x, y, 0, 0)); + + TRACE("arith", + for (unsigned i = 0; i < m_core.size(); ++i) { + ctx().display_detailed_literal(tout, m_core[i]); + tout << "\n"; + } + for (unsigned i = 0; i < m_eqs.size(); ++i) { + tout << mk_pp(m_eqs[i].first->get_owner(), m) << " = " << mk_pp(m_eqs[i].second->get_owner(), m) << "\n"; + } + tout << " ==> "; + tout << mk_pp(x->get_owner(), m) << " = " << mk_pp(y->get_owner(), m) << "\n"; + ); + + // parameters are TBD. + SASSERT(validate_eq(x, y)); + ctx().assign_eq(x, y, eq_justification(js)); + } + } + else { + // bounds on v2 were changed. + m_fixed_var_table.insert(key, v1); + } + } + else { + m_fixed_var_table.insert(key, v1); + } + } + + lbool make_feasible() { + reset_variable_values(); + ++m_stats.m_make_feasible; + if (m_solver->A_r().column_count() > m_stats.m_max_cols) + m_stats.m_max_cols = m_solver->A_r().column_count(); + if (m_solver->A_r().row_count() > m_stats.m_max_rows) + m_stats.m_max_rows = m_solver->A_r().row_count(); + TRACE("arith_verbose", display(tout);); + bool print = false && m_print_counter++ % 1000 == 0; + stopwatch sw; + if (print) { + sw.start(); + } + lean::lp_status status = m_solver->find_feasible_solution(); + if (print) { + sw.stop(); + } + m_stats.m_num_iterations = m_solver->settings().st().m_total_iterations; + m_stats.m_num_factorizations = m_solver->settings().st().m_num_factorizations; + m_stats.m_need_to_solve_inf = m_solver->settings().st().m_need_to_solve_inf; + if (print) { + IF_VERBOSE(0, verbose_stream() << status << " " << sw.get_seconds() << " " << m_stats.m_num_iterations << " " << m_print_counter << "\n";); + } + //m_stats.m_num_iterations_with_no_progress += m_solver->settings().st().m_iters_with_no_cost_growing; + + switch (status) { + case lean::lp_status::INFEASIBLE: + return l_false; + case lean::lp_status::FEASIBLE: + case lean::lp_status::OPTIMAL: + // SASSERT(m_solver->all_constraints_hold()); + return l_true; + case lean::lp_status::TIME_EXHAUSTED: + + default: + TRACE("arith", tout << "status treated as inconclusive: " << status << "\n";); + // TENTATIVE_UNBOUNDED, UNBOUNDED, TENTATIVE_DUAL_UNBOUNDED, DUAL_UNBOUNDED, + // FLOATING_POINT_ERROR, TIME_EXAUSTED, ITERATIONS_EXHAUSTED, EMPTY, UNSTABLE + return l_undef; + } + } + + vector> m_explanation; + literal_vector m_core; + svector m_eqs; + vector m_params; + lean::constraint_index const null_constraint_index = UINT_MAX; + + void set_evidence(lean::constraint_index idx) { + if (idx == null_constraint_index) { + return; + } + switch (m_constraint_sources[idx]) { + case inequality_source: { + literal lit = m_inequalities[idx]; + SASSERT(lit != null_literal); + m_core.push_back(lit); + break; + } + case equality_source: { + SASSERT(m_equalities[idx].first != nullptr); + SASSERT(m_equalities[idx].second != nullptr); + m_eqs.push_back(m_equalities[idx]); + break; + } + case definition_source: { + // skip definitions (these are treated as hard constraints) + break; + } + default: + UNREACHABLE(); + break; + } + } + + void set_conflict() { + m_eqs.reset(); + m_core.reset(); + m_params.reset(); + m_explanation.clear(); + m_solver->get_infeasibility_explanation(m_explanation); + // m_solver->shrink_explanation_to_minimum(m_explanation); // todo, enable when perf is fixed + /* + static unsigned cn = 0; + static unsigned num_l = 0; + num_l+=m_explanation.size(); + std::cout << num_l / (++cn) << "\n"; + */ + ++m_num_conflicts; + ++m_stats.m_conflicts; + TRACE("arith", tout << "scope: " << ctx().get_scope_level() << "\n"; display_evidence(tout, m_explanation); ); + TRACE("arith", display(tout);); + for (auto const& ev : m_explanation) { + if (!ev.first.is_zero()) { + set_evidence(ev.second); + } + } + SASSERT(validate_conflict()); + ctx().set_conflict( + ctx().mk_justification( + ext_theory_conflict_justification( + get_id(), ctx().get_region(), + m_core.size(), m_core.c_ptr(), + m_eqs.size(), m_eqs.c_ptr(), m_params.size(), m_params.c_ptr()))); + } + + justification * why_is_diseq(theory_var v1, theory_var v2) { + return 0; + } + + void reset_eh() { + m_arith_eq_adapter.reset_eh(); + m_solver = 0; + m_not_handled = nullptr; + del_bounds(0); + m_unassigned_bounds.reset(); + m_asserted_qhead = 0; + m_scopes.reset(); + m_stats.reset(); + m_to_check.reset(); + } + + void init_model(model_generator & mg) { + init_variable_values(); + m_factory = alloc(arith_factory, m); + mg.register_factory(m_factory); + TRACE("arith", display(tout);); + } + + model_value_proc * mk_value(enode * n, model_generator & mg) { + theory_var v = n->get_th_var(get_id()); + return alloc(expr_wrapper_proc, m_factory->mk_value(get_value(v), m.get_sort(n->get_owner()))); + } + + bool get_value(enode* n, expr_ref& r) { + theory_var v = n->get_th_var(get_id()); + if (can_get_value(v)) { + r = a.mk_numeral(get_value(v), is_int(n)); + return true; + } + else { + return false; + } + } + + bool validate_eq_in_model(theory_var v1, theory_var v2, bool is_true) const { + SASSERT(v1 != null_theory_var); + SASSERT(v2 != null_theory_var); + return (get_value(v1) == get_value(v2)) == is_true; + } + + // Auxiliary verification utilities. + + bool validate_conflict() { + if (dump_lemmas()) { + ctx().display_lemma_as_smt_problem(m_core.size(), m_core.c_ptr(), m_eqs.size(), m_eqs.c_ptr(), false_literal); + } + context nctx(m, ctx().get_fparams(), ctx().get_params()); + add_background(nctx); + bool result = l_true != nctx.check(); + CTRACE("arith", !result, ctx().display_lemma_as_smt_problem(tout, m_core.size(), m_core.c_ptr(), m_eqs.size(), m_eqs.c_ptr(), false_literal); + display(tout);); + return result; + } + + bool validate_assign(literal lit) { + if (dump_lemmas()) { + ctx().display_lemma_as_smt_problem(m_core.size(), m_core.c_ptr(), m_eqs.size(), m_eqs.c_ptr(), lit); + } + context nctx(m, ctx().get_fparams(), ctx().get_params()); + m_core.push_back(~lit); + add_background(nctx); + m_core.pop_back(); + bool result = l_true != nctx.check(); + CTRACE("arith", !result, ctx().display_lemma_as_smt_problem(tout, m_core.size(), m_core.c_ptr(), m_eqs.size(), m_eqs.c_ptr(), lit); + display(tout);); + return result; + } + + bool validate_eq(enode* x, enode* y) { + context nctx(m, ctx().get_fparams(), ctx().get_params()); + add_background(nctx); + nctx.assert_expr(m.mk_not(m.mk_eq(x->get_owner(), y->get_owner()))); + return l_true != nctx.check(); + } + + void add_background(context& nctx) { + for (unsigned i = 0; i < m_core.size(); ++i) { + expr_ref tmp(m); + ctx().literal2expr(m_core[i], tmp); + nctx.assert_expr(tmp); + } + for (unsigned i = 0; i < m_eqs.size(); ++i) { + nctx.assert_expr(m.mk_eq(m_eqs[i].first->get_owner(), m_eqs[i].second->get_owner())); + } + } + + theory_lra::inf_eps value(theory_var v) { + lean::impq ival = get_ivalue(v); + return inf_eps(0, inf_rational(ival.x, ival.y)); + } + + theory_lra::inf_eps maximize(theory_var v, expr_ref& blocker, bool& has_shared) { + lean::var_index vi = m_theory_var2var_index.get(v, UINT_MAX); + vector > coeffs; + rational coeff; + if (m_solver->is_term(vi)) { + const lean::lar_term& term = m_solver->get_term(vi); + for (auto & ti : term.m_coeffs) { + coeffs.push_back(std::make_pair(ti.second, ti.first)); + } + coeff = term.m_v; + } + else { + coeffs.push_back(std::make_pair(rational::one(), vi)); + coeff = rational::zero(); + } + lean::impq term_max; + if (m_solver->maximize_term(coeffs, term_max)) { + blocker = mk_gt(v); + inf_rational val(term_max.x + coeff, term_max.y); + return inf_eps(rational::zero(), val); + } + else { + TRACE("arith", tout << "Unbounded " << v << "\n";); + has_shared = false; + blocker = m.mk_false(); + return inf_eps(rational::one(), inf_rational()); + } + } + + expr_ref mk_gt(theory_var v) { + lean::impq val = get_ivalue(v); + expr* obj = get_enode(v)->get_owner(); + rational r = val.x; + expr_ref e(m); + if (a.is_int(m.get_sort(obj))) { + if (r.is_int()) { + r += rational::one(); + } + else { + r = ceil(r); + } + e = a.mk_numeral(r, m.get_sort(obj)); + e = a.mk_ge(obj, e); + } + else { + e = a.mk_numeral(r, m.get_sort(obj)); + if (val.y.is_neg()) { + e = a.mk_ge(obj, e); + } + else { + e = a.mk_gt(obj, e); + } + } + TRACE("opt", tout << "v" << v << " " << val << " " << r << " " << e << "\n";); + return e; + } + + theory_var add_objective(app* term) { + return internalize_def(term); + } + + app_ref mk_obj(theory_var v) { + lean::var_index vi = m_theory_var2var_index[v]; + if (m_solver->is_term(vi)) { + expr_ref_vector args(m); + const lean::lar_term& term = m_solver->get_term(vi); + for (auto & ti : term.m_coeffs) { + theory_var w = m_var_index2theory_var[ti.first]; + expr* o = get_enode(w)->get_owner(); + args.push_back(a.mk_mul(a.mk_numeral(ti.second, a.is_int(o)), o)); + } + rational r = term.m_v; + args.push_back(a.mk_numeral(r, r.is_int())); + return app_ref(a.mk_add(args.size(), args.c_ptr()), m); + } + else { + theory_var w = m_var_index2theory_var[vi]; + return app_ref(get_enode(w)->get_owner(), m); + } + } + + expr_ref mk_ge(filter_model_converter& fm, theory_var v, inf_rational const& val) { + rational r = val.get_rational(); + bool is_strict = val.get_infinitesimal().is_pos(); + app_ref b(m); + if (is_strict) { + b = a.mk_le(mk_obj(v), a.mk_numeral(r, r.is_int())); + } + else { + b = a.mk_ge(mk_obj(v), a.mk_numeral(r, r.is_int())); + } + if (!ctx().b_internalized(b)) { + fm.insert(b->get_decl()); + bool_var bv = ctx().mk_bool_var(b); + ctx().set_var_theory(bv, get_id()); + // ctx().set_enode_flag(bv, true); + lp::bound_kind bkind = lp::bound_kind::lower_t; + if (is_strict) bkind = lp::bound_kind::upper_t; + lp::bound* a = alloc(lp::bound, bv, v, r, bkind); + mk_bound_axioms(*a); + updt_unassigned_bounds(v, +1); + m_bounds[v].push_back(a); + m_bounds_trail.push_back(v); + m_bool_var2bound.insert(bv, a); + TRACE("arith", tout << mk_pp(b, m) << "\n";); + } + if (is_strict) { + b = m.mk_not(b); + } + TRACE("arith", tout << b << "\n";); + return expr_ref(b, m); + + } + + + void display(std::ostream & out) const { + if (m_solver) { + m_solver->print_constraints(out); + m_solver->print_terms(out); + } + unsigned nv = th.get_num_vars(); + for (unsigned v = 0; v < nv; ++v) { + out << "v" << v; + if (can_get_value(v)) out << ", value: " << get_value(v); + out << ", shared: " << ctx().is_shared(get_enode(v)) + << ", rel: " << ctx().is_relevant(get_enode(v)) + << ", def: "; th.display_var_flat_def(out, v) << "\n"; + } + } + + void display_evidence(std::ostream& out, vector> const& evidence) { + for (auto const& ev : evidence) { + expr_ref e(m); + SASSERT(!ev.first.is_zero()); + if (ev.first.is_zero()) { + continue; + } + unsigned idx = ev.second; + switch (m_constraint_sources.get(idx, null_source)) { + case inequality_source: { + literal lit = m_inequalities[idx]; + ctx().literal2expr(lit, e); + out << e << " " << ctx().get_assignment(lit) << "\n"; + break; + } + case equality_source: + out << mk_pp(m_equalities[idx].first->get_owner(), m) << " = " + << mk_pp(m_equalities[idx].second->get_owner(), m) << "\n"; + break; + case definition_source: { + theory_var v = m_definitions[idx]; + out << "def: v" << v << " := " << mk_pp(th.get_enode(v)->get_owner(), m) << "\n"; + break; + } + case null_source: + default: + UNREACHABLE(); + break; + } + } + for (auto const& ev : evidence) { + m_solver->print_constraint(ev.second, out << ev.first << ": "); + } + } + + void collect_statistics(::statistics & st) const { + m_arith_eq_adapter.collect_statistics(st); + st.update("arith-lower", m_stats.m_assert_lower); + st.update("arith-upper", m_stats.m_assert_upper); + st.update("arith-rows", m_stats.m_add_rows); + st.update("arith-propagations", m_stats.m_bounds_propagations); + st.update("arith-iterations", m_stats.m_num_iterations); + st.update("arith-factorizations", m_stats.m_num_factorizations); + st.update("arith-pivots", m_stats.m_need_to_solve_inf); + st.update("arith-plateau-iterations", m_stats.m_num_iterations_with_no_progress); + st.update("arith-fixed-eqs", m_stats.m_fixed_eqs); + st.update("arith-conflicts", m_stats.m_conflicts); + st.update("arith-bound-propagations-lp", m_stats.m_bound_propagations1); + st.update("arith-bound-propagations-cheap", m_stats.m_bound_propagations2); + st.update("arith-diseq", m_stats.m_assert_diseq); + st.update("arith-make-feasible", m_stats.m_make_feasible); + st.update("arith-max-columns", m_stats.m_max_cols); + st.update("arith-max-rows", m_stats.m_max_rows); + } + }; + + theory_lra::theory_lra(ast_manager& m, theory_arith_params& p): + theory(m.get_family_id("arith")) { + m_imp = alloc(imp, *this, m, p); + } + theory_lra::~theory_lra() { + dealloc(m_imp); + } + theory* theory_lra::mk_fresh(context* new_ctx) { + return alloc(theory_lra, new_ctx->get_manager(), new_ctx->get_fparams()); + } + void theory_lra::init(context * ctx) { + theory::init(ctx); + } + bool theory_lra::internalize_atom(app * atom, bool gate_ctx) { + return m_imp->internalize_atom(atom, gate_ctx); + } + bool theory_lra::internalize_term(app * term) { + return m_imp->internalize_term(term); + } + void theory_lra::internalize_eq_eh(app * atom, bool_var v) { + m_imp->internalize_eq_eh(atom, v); + } + void theory_lra::assign_eh(bool_var v, bool is_true) { + m_imp->assign_eh(v, is_true); + } + void theory_lra::new_eq_eh(theory_var v1, theory_var v2) { + m_imp->new_eq_eh(v1, v2); + } + bool theory_lra::use_diseqs() const { + return m_imp->use_diseqs(); + } + void theory_lra::new_diseq_eh(theory_var v1, theory_var v2) { + m_imp->new_diseq_eh(v1, v2); + } + void theory_lra::push_scope_eh() { + theory::push_scope_eh(); + m_imp->push_scope_eh(); + } + void theory_lra::pop_scope_eh(unsigned num_scopes) { + m_imp->pop_scope_eh(num_scopes); + theory::pop_scope_eh(num_scopes); + } + void theory_lra::restart_eh() { + m_imp->restart_eh(); + } + void theory_lra::relevant_eh(app* e) { + m_imp->relevant_eh(e); + } + void theory_lra::init_search_eh() { + m_imp->init_search_eh(); + } + final_check_status theory_lra::final_check_eh() { + return m_imp->final_check_eh(); + } + bool theory_lra::is_shared(theory_var v) const { + return m_imp->is_shared(v); + } + bool theory_lra::can_propagate() { + return m_imp->can_propagate(); + } + void theory_lra::propagate() { + m_imp->propagate(); + } + justification * theory_lra::why_is_diseq(theory_var v1, theory_var v2) { + return m_imp->why_is_diseq(v1, v2); + } + void theory_lra::reset_eh() { + m_imp->reset_eh(); + } + void theory_lra::init_model(model_generator & m) { + m_imp->init_model(m); + } + model_value_proc * theory_lra::mk_value(enode * n, model_generator & mg) { + return m_imp->mk_value(n, mg); + } + bool theory_lra::get_value(enode* n, expr_ref& r) { + return m_imp->get_value(n, r); + } + bool theory_lra::validate_eq_in_model(theory_var v1, theory_var v2, bool is_true) const { + return m_imp->validate_eq_in_model(v1, v2, is_true); + } + void theory_lra::display(std::ostream & out) const { + m_imp->display(out); + } + void theory_lra::collect_statistics(::statistics & st) const { + m_imp->collect_statistics(st); + } + theory_lra::inf_eps theory_lra::value(theory_var v) { + return m_imp->value(v); + } + theory_lra::inf_eps theory_lra::maximize(theory_var v, expr_ref& blocker, bool& has_shared) { + return m_imp->maximize(v, blocker, has_shared); + } + theory_var theory_lra::add_objective(app* term) { + return m_imp->add_objective(term); + } + expr_ref theory_lra::mk_ge(filter_model_converter& fm, theory_var v, inf_rational const& val) { + return m_imp->mk_ge(fm, v, val); + } + + + +} diff --git a/src/smt/theory_lra.h b/src/smt/theory_lra.h new file mode 100644 index 000000000..c91363848 --- /dev/null +++ b/src/smt/theory_lra.h @@ -0,0 +1,96 @@ +/*++ +Copyright (c) 2016 Microsoft Corporation + +Module Name: + + theory_lra.h + +Abstract: + + + +Author: + + Lev Nachmanson (levnach) 2016-25-3 + Nikolaj Bjorner (nbjorner) + +Revision History: + + +--*/ +#pragma once + +#include "theory_opt.h" + +namespace smt { + class theory_lra : public theory, public theory_opt { + class imp; + imp* m_imp; + public: + theory_lra(ast_manager& m, theory_arith_params& params); + virtual ~theory_lra(); + virtual theory* mk_fresh(context* new_ctx); + virtual char const* get_name() const { return "lra"; } + + virtual void init(context * ctx); + + virtual bool internalize_atom(app * atom, bool gate_ctx); + + virtual bool internalize_term(app * term); + + virtual void internalize_eq_eh(app * atom, bool_var v); + + virtual void assign_eh(bool_var v, bool is_true); + + virtual void new_eq_eh(theory_var v1, theory_var v2); + + virtual bool use_diseqs() const; + + virtual void new_diseq_eh(theory_var v1, theory_var v2); + + virtual void push_scope_eh(); + + virtual void pop_scope_eh(unsigned num_scopes); + + virtual void restart_eh(); + + virtual void relevant_eh(app* e); + + virtual void init_search_eh(); + + virtual final_check_status final_check_eh(); + + virtual bool is_shared(theory_var v) const; + + virtual bool can_propagate(); + + virtual void propagate(); + + virtual justification * why_is_diseq(theory_var v1, theory_var v2); + + // virtual void flush_eh(); + + virtual void reset_eh(); + + virtual void init_model(model_generator & m); + + virtual model_value_proc * mk_value(enode * n, model_generator & mg); + + virtual bool get_value(enode* n, expr_ref& r); + + virtual bool validate_eq_in_model(theory_var v1, theory_var v2, bool is_true) const; + + virtual void display(std::ostream & out) const; + + virtual void collect_statistics(::statistics & st) const; + + // optimization + virtual inf_eps value(theory_var); + virtual inf_eps maximize(theory_var v, expr_ref& blocker, bool& has_shared); + virtual theory_var add_objective(app* term); + virtual expr_ref mk_ge(filter_model_converter& fm, theory_var v, inf_rational const& val); + + + }; + +} diff --git a/src/test/argument_parser.h b/src/test/argument_parser.h new file mode 100644 index 000000000..706167f49 --- /dev/null +++ b/src/test/argument_parser.h @@ -0,0 +1,144 @@ +/* +Copyright (c) 2013 Microsoft Corporation. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. + +Author: Lev Nachmanson +*/ + +#include +#include +#include +#include +#include + +namespace lean { +class argument_parser { + std::unordered_map m_options; + std::unordered_map m_options_with_after_string; + std::set m_used_options; + std::unordered_map m_used_options_with_after_string; + std::vector m_free_args; + std::vector m_args; + +public: + std::string m_error_message; + argument_parser(unsigned argn, char * const* args) { + for (unsigned i = 0; i < argn; i++) { + m_args.push_back(std::string(args[i])); + } + } + + void add_option(std::string s) { + add_option_with_help_string(s, ""); + } + + void add_option_with_help_string(std::string s, std::string help_string) { + m_options[s]=help_string; + } + + void add_option_with_after_string(std::string s) { + add_option_with_after_string_with_help(s, ""); + } + + void add_option_with_after_string_with_help(std::string s, std::string help_string) { + m_options_with_after_string[s]=help_string; + } + + + bool parse() { + bool status_is_ok = true; + for (unsigned i = 0; i < m_args.size(); i++) { + std::string ar = m_args[i]; + if (m_options.find(ar) != m_options.end() ) + m_used_options.insert(ar); + else if (m_options_with_after_string.find(ar) != m_options_with_after_string.end()) { + if (i == m_args.size() - 1) { + m_error_message = "Argument is missing after "+ar; + return false; + } + i++; + m_used_options_with_after_string[ar] = m_args[i]; + } else { + if (starts_with(ar, "-") || starts_with(ar, "//")) + status_is_ok = false; + + m_free_args.push_back(ar); + } + } + return status_is_ok; + } + + bool contains(std::unordered_map & m, std::string s) { + return m.find(s) != m.end(); + } + + bool contains(std::set & m, std::string s) { + return m.find(s) != m.end(); + } + + bool option_is_used(std::string option) { + return contains(m_used_options, option) || contains(m_used_options_with_after_string, option); + } + + std::string get_option_value(std::string option) { + auto t = m_used_options_with_after_string.find(option); + if (t != m_used_options_with_after_string.end()){ + return t->second; + } + return std::string(); + } + + bool starts_with(std::string s, char const * prefix) { + return starts_with(s, std::string(prefix)); + } + + bool starts_with(std::string s, std::string prefix) { + return s.substr(0, prefix.size()) == prefix; + } + + std::string usage_string() { + std::string ret = ""; + std::vector unknown_options; + for (auto t : m_free_args) { + if (starts_with(t, "-") || starts_with(t, "\\")) { + unknown_options.push_back(t); + } + } + if (unknown_options.size()) { + ret = "Unknown options:"; + } + for (auto unknownOption : unknown_options) { + ret += unknownOption; + ret += ","; + } + ret += "\n"; + ret += "Usage:\n"; + for (auto allowed_option : m_options) + ret += allowed_option.first + " " + (allowed_option.second.size() == 0 ? std::string("") : std::string("/") + allowed_option.second) + std::string("\n"); + for (auto s : m_options_with_after_string) { + ret += s.first + " " + (s.second.size() == 0? " \"option value\"":("\""+ s.second+"\"")) + "\n"; + } + return ret; + } + + void print() { + if (m_used_options.size() == 0 && m_used_options_with_after_string.size() == 0 && m_free_args.size() == 0) { + std::cout << "no options are given" << std::endl; + return; + } + std::cout << "options are: " << std::endl; + for (std::string s : m_used_options) { + std::cout << s << std::endl; + } + for (auto & t : m_used_options_with_after_string) { + std::cout << t.first << " " << t.second << std::endl; + } + if (m_free_args.size() > 0) { + std::cout << "free arguments are: " << std::endl; + for (auto & t : m_free_args) { + std::cout << t << " " << std::endl; + } + } + } +}; +} diff --git a/src/test/lp.cpp b/src/test/lp.cpp new file mode 100644 index 000000000..7829d81f5 --- /dev/null +++ b/src/test/lp.cpp @@ -0,0 +1,3232 @@ +/* +Copyright (c) 2017 Microsoft Corporation. All rights reserved. +Author: Lev Nachmanson +*/ +#include +#if _LINUX_ +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "util/lp/lp_utils.h" +#include "util/lp/lp_primal_simplex.h" +#include "util/lp/mps_reader.h" +#include "smt_reader.h" +#include "util/lp/binary_heap_priority_queue.h" +#include "argument_parser.h" +#include "test_file_reader.h" +#include "util/lp/indexed_value.h" +#include "util/lp/lar_solver.h" +#include "util/lp/numeric_pair.h" +#include "util/lp/binary_heap_upair_queue.h" +#include "util/lp/stacked_value.h" +#include "util/lp/stacked_unordered_set.h" +#include "util/lp/int_set.h" +#include "util/stopwatch.h" +namespace lean { +unsigned seed = 1; + +struct simple_column_namer:public column_namer +{ + std::string get_column_name(unsigned j) const override { + return std::string("x") + T_to_string(j); + } +}; + + +template +void test_matrix(sparse_matrix & a) { + auto m = a.dimension(); + +// copy a to b in the reversed order + sparse_matrix b(m); + std::cout << "copy b to a"<< std::endl; + for (int row = m - 1; row >= 0; row--) + for (int col = m - 1; col >= 0; col --) { + b(row, col) = (T const&) a(row, col); + } + + + std::cout << "zeroing b in the reverse order"<< std::endl; + for (int row = m - 1; row >= 0; row--) + for (int col = m - 1; col >= 0; col --) + b.set(row, col, T(0)); + + + + for (unsigned row = 0; row < m; row ++) + for (unsigned col = 0; col < m; col ++) + a.set(row, col, T(0)); + + + unsigned i = my_random() % m; + unsigned j = my_random() % m; + + auto t = T(1); + + a.set(i, j, t); + + lean_assert(a.get(i, j) == t); + + unsigned j1; + if (j < m - 1) { + j1 = m - 1; + a.set(i, j1, T(2)); + } +} + +void tst1() { + std::cout << "testing the minimial matrix with 1 row and 1 column" << std::endl; + sparse_matrix m0(1); + m0.set(0, 0, 1); + // print_matrix(m0); + m0.set(0, 0, 0); + // print_matrix(m0); + test_matrix(m0); + + unsigned rows = 2; + sparse_matrix m(rows); + std::cout << "setting m(0,1)=" << std::endl; + + m.set(0, 1, 11); + m.set(0, 0, 12); + + // print_matrix(m); + + test_matrix(m); + + sparse_matrix m1(2); + m1.set(0, 0, 2); + m1.set(1, 0, 3); + // print_matrix(m1); + std::cout << " zeroing matrix 2 by 2" << std::endl; + m1.set(0, 0, 0); + m1.set(1, 0, 0); + // print_matrix(m1); + + test_matrix(m1); + + + std::cout << "printing zero matrix 3 by 1" << std::endl; + sparse_matrix m2(3); + // print_matrix(m2); + + m2.set(0, 0, 1); + m2.set(2, 0, 2); + std::cout << "printing matrix 3 by 1 with a gap" << std::endl; + // print_matrix(m2); + + test_matrix(m2); + + sparse_matrix m10by9(10); + m10by9.set(0, 1, 1); + + m10by9(0, 1) = 4; + + double test = m10by9(0, 1); + + std::cout << "got " << test << std::endl; + + + m10by9.set(0, 8, 8); + m10by9.set(3, 4, 7); + m10by9.set(3, 2, 5); + m10by9.set(3, 8, 99); + m10by9.set(3, 2, 6); + m10by9.set(1, 8, 9); + m10by9.set(4, 0, 40); + m10by9.set(0, 0, 10); + + std::cout << "printing matrix 10 by 9" << std::endl; + // print_matrix(m10by9); + + + test_matrix(m10by9); + std::cout <<"zeroing m10by9\n"; +#ifdef LEAN_DEBUG + for (unsigned int i = 0; i < m10by9.dimension(); i++) + for (unsigned int j = 0; j < m10by9.column_count(); j++) + m10by9.set(i, j, 0); +#endif + // print_matrix(m10by9); +} + +vector allocate_basis_heading(unsigned count) { // the rest of initilization will be handled by lu_QR + vector basis_heading(count, -1); + return basis_heading; +} + + +void init_basic_part_of_basis_heading(vector & basis, vector & basis_heading) { + lean_assert(basis_heading.size() >= basis.size()); + unsigned m = basis.size(); + for (unsigned i = 0; i < m; i++) { + unsigned column = basis[i]; + basis_heading[column] = i; + } +} + +void init_non_basic_part_of_basis_heading(vector & basis_heading, vector & non_basic_columns) { + non_basic_columns.clear(); + for (int j = basis_heading.size(); j--;){ + if (basis_heading[j] < 0) { + non_basic_columns.push_back(j); + // the index of column j in m_nbasis is (- basis_heading[j] - 1) + basis_heading[j] = - static_cast(non_basic_columns.size()); + } + } +} +void init_basis_heading_and_non_basic_columns_vector(vector & basis, + vector & basis_heading, + vector & non_basic_columns) { + init_basic_part_of_basis_heading(basis, basis_heading); + init_non_basic_part_of_basis_heading(basis_heading, non_basic_columns); +} + +void change_basis(unsigned entering, unsigned leaving, vector& basis, vector& nbasis, vector & basis_heading) { + int place_in_basis = basis_heading[leaving]; + int place_in_non_basis = - basis_heading[entering] - 1; + basis_heading[entering] = place_in_basis; + basis_heading[leaving] = -place_in_non_basis - 1; + basis[place_in_basis] = entering; + nbasis[place_in_non_basis] = leaving; +} + + +#ifdef LEAN_DEBUG +void test_small_lu(lp_settings & settings) { + std::cout << " test_small_lu" << std::endl; + static_matrix m(3, 6); + vector basis(3); + basis[0] = 0; + basis[1] = 1; + basis[2] = 3; + + m(0, 0) = 1; m(0, 2)= 3.9; m(2, 3) = 11; m(0, 5) = -3; + m(1, 1) = 4; m(1, 4) = 7; + m(2, 0) = 1.8; m(2, 2) = 5; m(2, 4) = 2; m(2, 5) = 8; + +#ifdef LEAN_DEBUG + print_matrix(m, std::cout); +#endif + vector heading = allocate_basis_heading(m.column_count()); + vector non_basic_columns; + init_basis_heading_and_non_basic_columns_vector(basis, heading, non_basic_columns); + lu l(m, basis, settings); + lean_assert(l.is_correct(basis)); + indexed_vector w(m.row_count()); + std::cout << "entering 2, leaving 0" << std::endl; + l.prepare_entering(2, w); // to init vector w + l.replace_column(0, w, heading[0]); + change_basis(2, 0, basis, non_basic_columns, heading); + // #ifdef LEAN_DEBUG + // std::cout << "we were factoring " << std::endl; + // print_matrix(get_B(l)); + // #endif + lean_assert(l.is_correct(basis)); + std::cout << "entering 4, leaving 3" << std::endl; + l.prepare_entering(4, w); // to init vector w + l.replace_column(0, w, heading[3]); + change_basis(4, 3, basis, non_basic_columns, heading); + std::cout << "we were factoring " << std::endl; +#ifdef LEAN_DEBUG + { + auto bl = get_B(l, basis); + print_matrix(&bl, std::cout); + } +#endif + lean_assert(l.is_correct(basis)); + + std::cout << "entering 5, leaving 1" << std::endl; + l.prepare_entering(5, w); // to init vector w + l.replace_column(0, w, heading[1]); + change_basis(5, 1, basis, non_basic_columns, heading); + std::cout << "we were factoring " << std::endl; +#ifdef LEAN_DEBUG + { + auto bl = get_B(l, basis); + print_matrix(&bl, std::cout); + } +#endif + lean_assert(l.is_correct(basis)); + std::cout << "entering 3, leaving 2" << std::endl; + l.prepare_entering(3, w); // to init vector w + l.replace_column(0, w, heading[2]); + change_basis(3, 2, basis, non_basic_columns, heading); + std::cout << "we were factoring " << std::endl; +#ifdef LEAN_DEBUG + { + auto bl = get_B(l, basis); + print_matrix(&bl, std::cout); + } +#endif + lean_assert(l.is_correct(basis)); + + m.add_row(); + m.add_column(); + m.add_row(); + m.add_column(); + for (unsigned i = 0; i < m.column_count(); i++) { + m(3, i) = i; + m(4, i) = i * i; // to make the rows linearly independent + } + unsigned j = m.column_count() ; + basis.push_back(j-2); + heading.push_back(basis.size() - 1); + basis.push_back(j-1); + heading.push_back(basis.size() - 1); + + auto columns_to_replace = l.get_set_of_columns_to_replace_for_add_last_rows(heading); + l.add_last_rows_to_B(heading, columns_to_replace); + std::cout << "here" << std::endl; + lean_assert(l.is_correct(basis)); +} + +#endif + +void fill_long_row(sparse_matrix &m, int i) { + int n = m.dimension(); + for (int j = 0; j < n; j ++) { + m (i, (j + i) % n) = j * j; + } +} + +void fill_long_row(static_matrix &m, int i) { + int n = m.column_count(); + for (int j = 0; j < n; j ++) { + m (i, (j + i) % n) = j * j; + } +} + + +void fill_long_row_exp(sparse_matrix &m, int i) { + int n = m.dimension(); + + for (int j = 0; j < n; j ++) { + m(i, j) = my_random() % 20; + } +} + +void fill_long_row_exp(static_matrix &m, int i) { + int n = m.column_count(); + + for (int j = 0; j < n; j ++) { + m(i, j) = my_random() % 20; + } +} + +void fill_larger_sparse_matrix_exp(sparse_matrix & m){ + for ( unsigned i = 0; i < m.dimension(); i++ ) + fill_long_row_exp(m, i); +} + +void fill_larger_sparse_matrix_exp(static_matrix & m){ + for ( unsigned i = 0; i < m.row_count(); i++ ) + fill_long_row_exp(m, i); +} + + +void fill_larger_sparse_matrix(sparse_matrix & m){ + for ( unsigned i = 0; i < m.dimension(); i++ ) + fill_long_row(m, i); +} + +void fill_larger_sparse_matrix(static_matrix & m){ + for ( unsigned i = 0; i < m.row_count(); i++ ) + fill_long_row(m, i); +} + + +int perm_id = 0; + +#ifdef LEAN_DEBUG +void test_larger_lu_exp(lp_settings & settings) { + std::cout << " test_larger_lu_exp" << std::endl; + static_matrix m(6, 12); + vector basis(6); + basis[0] = 1; + basis[1] = 3; + basis[2] = 0; + basis[3] = 4; + basis[4] = 5; + basis[5] = 6; + + + fill_larger_sparse_matrix_exp(m); + // print_matrix(m); + vector heading = allocate_basis_heading(m.column_count()); + vector non_basic_columns; + init_basis_heading_and_non_basic_columns_vector(basis, heading, non_basic_columns); + lu l(m, basis, settings); + + dense_matrix left_side = l.get_left_side(basis); + dense_matrix right_side = l.get_right_side(); + lean_assert(left_side == right_side); + int leaving = 3; + int entering = 8; + for (unsigned i = 0; i < m.row_count(); i++) { + std::cout << static_cast(m(i, entering)) << std::endl; + } + + indexed_vector w(m.row_count()); + + l.prepare_entering(entering, w); + l.replace_column(0, w, heading[leaving]); + change_basis(entering, leaving, basis, non_basic_columns, heading); + lean_assert(l.is_correct(basis)); + + l.prepare_entering(11, w); // to init vector w + l.replace_column(0, w, heading[0]); + change_basis(11, 0, basis, non_basic_columns, heading); + lean_assert(l.is_correct(basis)); +} + +void test_larger_lu_with_holes(lp_settings & settings) { + std::cout << " test_larger_lu_with_holes" << std::endl; + static_matrix m(8, 9); + vector basis(8); + for (unsigned i = 0; i < m.row_count(); i++) { + basis[i] = i; + } + m(0, 0) = 1; m(0, 1) = 2; m(0, 2) = 3; m(0, 3) = 4; m(0, 4) = 5; m(0, 8) = 99; + /* */ m(1, 1) =- 6; m(1, 2) = 7; m(1, 3) = 8; m(1, 4) = 9; + /* */ m(2, 2) = 10; + /* */ m(3, 2) = 11; m(3, 3) = -12; + /* */ m(4, 2) = 13; m(4, 3) = 14; m(4, 4) = 15; + // the rest of the matrix is denser + m(5, 4) = 28; m(5, 5) = -18; m(5, 6) = 19; m(5, 7) = 25; + /* */ m(6, 5) = 20; m(6, 6) = -21; + /* */ m(7, 5) = 22; m(7, 6) = 23; m(7, 7) = 24; m(7, 8) = 88; + print_matrix(m, std::cout); + vector heading = allocate_basis_heading(m.column_count()); + vector non_basic_columns; + init_basis_heading_and_non_basic_columns_vector(basis, heading, non_basic_columns); + lu l(m, basis, settings); + std::cout << "printing factorization" << std::endl; + for (int i = l.tail_size() - 1; i >=0; i--) { + auto lp = l.get_lp_matrix(i); + lp->set_number_of_columns(m.row_count()); + lp->set_number_of_rows(m.row_count()); + print_matrix( lp, std::cout); + } + + dense_matrix left_side = l.get_left_side(basis); + dense_matrix right_side = l.get_right_side(); + if (!(left_side == right_side)) { + std::cout << "different sides" << std::endl; + } + + indexed_vector w(m.row_count()); + l.prepare_entering(8, w); // to init vector w + l.replace_column(0, w, heading[0]); + change_basis(8, 0, basis, non_basic_columns, heading); + lean_assert(l.is_correct(basis)); +} + + +void test_larger_lu(lp_settings& settings) { + std::cout << " test_larger_lu" << std::endl; + static_matrix m(6, 12); + vector basis(6); + basis[0] = 1; + basis[1] = 3; + basis[2] = 0; + basis[3] = 4; + basis[4] = 5; + basis[5] = 6; + + + fill_larger_sparse_matrix(m); + print_matrix(m, std::cout); + + vector heading = allocate_basis_heading(m.column_count()); + vector non_basic_columns; + init_basis_heading_and_non_basic_columns_vector(basis, heading, non_basic_columns); + auto l = lu (m, basis, settings); + // std::cout << "printing factorization" << std::endl; + // for (int i = lu.tail_size() - 1; i >=0; i--) { + // auto lp = lu.get_lp_matrix(i); + // lp->set_number_of_columns(m.row_count()); + // lp->set_number_of_rows(m.row_count()); + // print_matrix(* lp); + // } + + dense_matrix left_side = l.get_left_side(basis); + dense_matrix right_side = l.get_right_side(); + if (!(left_side == right_side)) { + std::cout << "left side" << std::endl; + print_matrix(&left_side, std::cout); + std::cout << "right side" << std::endl; + print_matrix(&right_side, std::cout); + + std::cout << "different sides" << std::endl; + std::cout << "initial factorization is incorrect" << std::endl; + exit(1); + } + indexed_vector w(m.row_count()); + l.prepare_entering(9, w); // to init vector w + l.replace_column(0, w, heading[0]); + change_basis(9, 0, basis, non_basic_columns, heading); + lean_assert(l.is_correct(basis)); +} + + +void test_lu(lp_settings & settings) { + test_small_lu(settings); + test_larger_lu(settings); + test_larger_lu_with_holes(settings); + test_larger_lu_exp(settings); +} +#endif + + + + + + +void init_b(vector & b, sparse_matrix & m, vector& x) { + for (unsigned i = 0; i < m.dimension(); i++) { + b.push_back(m.dot_product_with_row(i, x)); + } +} + +void init_b(vector & b, static_matrix & m, vector & x) { + for (unsigned i = 0; i < m.row_count(); i++) { + b.push_back(m.dot_product_with_row(i, x)); + } +} + + +void test_lp_0() { + std::cout << " test_lp_0 " << std::endl; + static_matrix m_(3, 7); + m_(0, 0) = 3; m_(0, 1) = 2; m_(0, 2) = 1; m_(0, 3) = 2; m_(0, 4) = 1; + m_(1, 0) = 1; m_(1, 1) = 1; m_(1, 2) = 1; m_(1, 3) = 1; m_(1, 5) = 1; + m_(2, 0) = 4; m_(2, 1) = 3; m_(2, 2) = 3; m_(2, 3) = 4; m_(2, 6) = 1; + vector x_star(7); + x_star[0] = 225; x_star[1] = 117; x_star[2] = 420; + x_star[3] = x_star[4] = x_star[5] = x_star[6] = 0; + vector b; + init_b(b, m_, x_star); + vector basis(3); + basis[0] = 0; basis[1] = 1; basis[2] = 2; + vector costs(7); + costs[0] = 19; + costs[1] = 13; + costs[2] = 12; + costs[3] = 17; + costs[4] = 0; + costs[5] = 0; + costs[6] = 0; + + vector column_types(7, column_type::low_bound); + vector upper_bound_values; + lp_settings settings; + simple_column_namer cn; + vector nbasis; + vector heading; + + lp_primal_core_solver lpsolver(m_, b, x_star, basis, nbasis, heading, costs, column_types, upper_bound_values, settings, cn); + + lpsolver.solve(); +} + +void test_lp_1() { + std::cout << " test_lp_1 " << std::endl; + static_matrix m(4, 7); + m(0, 0) = 1; m(0, 1) = 3; m(0, 2) = 1; m(0, 3) = 1; + m(1, 0) = -1; m(1, 2) = 3; m(1, 4) = 1; + m(2, 0) = 2; m(2, 1) = -1; m(2, 2) = 2; m(2, 5) = 1; + m(3, 0) = 2; m(3, 1) = 3; m(3, 2) = -1; m(3, 6) = 1; +#ifdef LEAN_DEBUG + print_matrix(m, std::cout); +#endif + vector x_star(7); + x_star[0] = 0; x_star[1] = 0; x_star[2] = 0; + x_star[3] = 3; x_star[4] = 2; x_star[5] = 4; x_star[6] = 2; + + vector basis(4); + basis[0] = 3; basis[1] = 4; basis[2] = 5; basis[3] = 6; + + vector b; + b.push_back(3); + b.push_back(2); + b.push_back(4); + b.push_back(2); + + vector costs(7); + costs[0] = 5; + costs[1] = 5; + costs[2] = 3; + costs[3] = 0; + costs[4] = 0; + costs[5] = 0; + costs[6] = 0; + + + + vector column_types(7, column_type::low_bound); + vector upper_bound_values; + + std::cout << "calling lp\n"; + lp_settings settings; + simple_column_namer cn; + + vector nbasis; + vector heading; + + lp_primal_core_solver lpsolver(m, b, + x_star, + basis, + nbasis, heading, + costs, + column_types, upper_bound_values, settings, cn); + + lpsolver.solve(); +} + + +void test_lp_primal_core_solver() { + test_lp_0(); + test_lp_1(); +} + + +#ifdef LEAN_DEBUG +template +void test_swap_rows_with_permutation(sparse_matrix& m){ + std::cout << "testing swaps" << std::endl; + unsigned dim = m.row_count(); + dense_matrix original(&m); + permutation_matrix q(dim); + print_matrix(m, std::cout); + lean_assert(original == q * m); + for (int i = 0; i < 100; i++) { + unsigned row1 = my_random() % dim; + unsigned row2 = my_random() % dim; + if (row1 == row2) continue; + std::cout << "swap " << row1 << " " << row2 << std::endl; + m.swap_rows(row1, row2); + q.transpose_from_left(row1, row2); + lean_assert(original == q * m); + print_matrix(m, std::cout); + std::cout << std::endl; + } +} +#endif +template +void fill_matrix(sparse_matrix& m); // forward definition +#ifdef LEAN_DEBUG +template +void test_swap_cols_with_permutation(sparse_matrix& m){ + std::cout << "testing swaps" << std::endl; + unsigned dim = m.row_count(); + dense_matrix original(&m); + permutation_matrix q(dim); + print_matrix(m, std::cout); + lean_assert(original == q * m); + for (int i = 0; i < 100; i++) { + unsigned row1 = my_random() % dim; + unsigned row2 = my_random() % dim; + if (row1 == row2) continue; + std::cout << "swap " << row1 << " " << row2 << std::endl; + m.swap_rows(row1, row2); + q.transpose_from_right(row1, row2); + lean_assert(original == q * m); + print_matrix(m, std::cout); + std::cout << std::endl; + } +} + + +template +void test_swap_rows(sparse_matrix& m, unsigned i0, unsigned i1){ + std::cout << "test_swap_rows(" << i0 << "," << i1 << ")" << std::endl; + sparse_matrix mcopy(m.dimension()); + for (unsigned i = 0; i < m.dimension(); i++) + for (unsigned j = 0; j < m.dimension(); j++) { + mcopy(i, j)= m(i, j); + } + std::cout << "swapping rows "<< i0 << "," << i1 << std::endl; + m.swap_rows(i0, i1); + + for (unsigned j = 0; j < m.dimension(); j++) { + lean_assert(mcopy(i0, j) == m(i1, j)); + lean_assert(mcopy(i1, j) == m(i0, j)); + } +} +template +void test_swap_columns(sparse_matrix& m, unsigned i0, unsigned i1){ + std::cout << "test_swap_columns(" << i0 << "," << i1 << ")" << std::endl; + sparse_matrix mcopy(m.dimension()); + for (unsigned i = 0; i < m.dimension(); i++) + for (unsigned j = 0; j < m.dimension(); j++) { + mcopy(i, j)= m(i, j); + } + m.swap_columns(i0, i1); + + for (unsigned j = 0; j < m.dimension(); j++) { + lean_assert(mcopy(j, i0) == m(j, i1)); + lean_assert(mcopy(j, i1) == m(j, i0)); + } + + for (unsigned i = 0; i < m.dimension(); i++) { + if (i == i0 || i == i1) + continue; + for (unsigned j = 0; j < m.dimension(); j++) { + lean_assert(mcopy(j, i)== m(j, i)); + } + } +} +#endif + +template +void fill_matrix(sparse_matrix& m){ + int v = 0; + for (int i = m.dimension() - 1; i >= 0; i--) { + for (int j = m.dimension() - 1; j >=0; j--){ + m(i, j) = v++; + } + } +} + +void test_pivot_like_swaps_and_pivot(){ + sparse_matrix m(10); + fill_matrix(m); + // print_matrix(m); +// pivot at 2,7 + m.swap_columns(0, 7); + // print_matrix(m); + m.swap_rows(2, 0); + // print_matrix(m); + for (unsigned i = 1; i < m.dimension(); i++) { + m(i, 0) = 0; + } + // print_matrix(m); + +// say pivot at 3,4 + m.swap_columns(1, 4); + // print_matrix(m); + m.swap_rows(1, 3); + // print_matrix(m); + + vector row; + double alpha = 2.33; + unsigned pivot_row = 1; + unsigned target_row = 2; + unsigned pivot_row_0 = 3; + double beta = 3.1; + m(target_row, 3) = 0; + m(target_row, 5) = 0; + m(pivot_row, 6) = 0; +#ifdef LEAN_DEBUG + print_matrix(m, std::cout); +#endif + + for (unsigned j = 0; j < m.dimension(); j++) { + row.push_back(m(target_row, j) + alpha * m(pivot_row, j) + beta * m(pivot_row_0, j)); + } + + for (auto & t : row) { + std::cout << t << ","; + } + std::cout << std::endl; + lp_settings settings; + m.pivot_row_to_row(pivot_row, alpha, target_row, settings); + m.pivot_row_to_row(pivot_row_0, beta, target_row, settings); + // print_matrix(m); + for (unsigned j = 0; j < m.dimension(); j++) { + lean_assert(abs(row[j] - m(target_row, j)) < 0.00000001); + } +} + +#ifdef LEAN_DEBUG +void test_swap_rows() { + sparse_matrix m(10); + fill_matrix(m); + // print_matrix(m); + test_swap_rows(m, 3, 5); + + test_swap_rows(m, 1, 3); + + + test_swap_rows(m, 1, 3); + + test_swap_rows(m, 1, 7); + + test_swap_rows(m, 3, 7); + + test_swap_rows(m, 0, 7); + + m(0, 4) = 1; + // print_matrix(m); + test_swap_rows(m, 0, 7); + +// go over some corner cases + sparse_matrix m0(2); + test_swap_rows(m0, 0, 1); + m0(0, 0) = 3; + test_swap_rows(m0, 0, 1); + m0(1, 0) = 3; + test_swap_rows(m0, 0, 1); + + + sparse_matrix m1(10); + test_swap_rows(m1, 0, 1); + m1(0, 0) = 3; + test_swap_rows(m1, 0, 1); + m1(1, 0) = 3; + m1(0, 3) = 5; + m1(1, 3) = 4; + m1(1, 8) = 8; + m1(1, 9) = 8; + + test_swap_rows(m1, 0, 1); + + sparse_matrix m2(3); + test_swap_rows(m2, 0, 1); + m2(0, 0) = 3; + test_swap_rows(m2, 0, 1); + m2(2, 0) = 3; + test_swap_rows(m2, 0, 2); +} + +void fill_uniformly(sparse_matrix & m, unsigned dim) { + int v = 0; + for (unsigned i = 0; i < dim; i++) { + for (unsigned j = 0; j < dim; j++) { + m(i, j) = v++; + } + } +} + +void fill_uniformly(dense_matrix & m, unsigned dim) { + int v = 0; + for (unsigned i = 0; i < dim; i++) { + for (unsigned j = 0; j < dim; j++) { + m.set_elem(i, j, v++); + } + } +} + +void sparse_matrix_with_permutaions_test() { + unsigned dim = 4; + sparse_matrix m(dim); + fill_uniformly(m, dim); + dense_matrix dm(dim, dim); + fill_uniformly(dm, dim); + dense_matrix dm0(dim, dim); + fill_uniformly(dm0, dim); + permutation_matrix q0(dim); + q0[0] = 1; + q0[1] = 0; + q0[2] = 3; + q0[3] = 2; + permutation_matrix q1(dim); + q1[0] = 1; + q1[1] = 2; + q1[2] = 3; + q1[3] = 0; + permutation_matrix p0(dim); + p0[0] = 1; + p0[1] = 0; + p0[2] = 3; + p0[3] = 2; + permutation_matrix p1(dim); + p1[0] = 1; + p1[1] = 2; + p1[2] = 3; + p1[3] = 0; + + m.multiply_from_left(q0); + for (unsigned i = 0; i < dim; i++) { + for (unsigned j = 0; j < dim; j++) { + lean_assert(m(i, j) == dm0.get_elem(q0[i], j)); + } + } + + auto q0_dm = q0 * dm; + lean_assert(m == q0_dm); + + m.multiply_from_left(q1); + for (unsigned i = 0; i < dim; i++) { + for (unsigned j = 0; j < dim; j++) { + lean_assert(m(i, j) == dm0.get_elem(q0[q1[i]], j)); + } + } + + + auto q1_q0_dm = q1 * q0_dm; + + lean_assert(m == q1_q0_dm); + + m.multiply_from_right(p0); + + for (unsigned i = 0; i < dim; i++) { + for (unsigned j = 0; j < dim; j++) { + lean_assert(m(i, j) == dm0.get_elem(q0[q1[i]], p0[j])); + } + } + + auto q1_q0_dm_p0 = q1_q0_dm * p0; + + lean_assert(m == q1_q0_dm_p0); + + m.multiply_from_right(p1); + + for (unsigned i = 0; i < dim; i++) { + for (unsigned j = 0; j < dim; j++) { + lean_assert(m(i, j) == dm0.get_elem(q0[q1[i]], p1[p0[j]])); + } + } + + auto q1_q0_dm_p0_p1 = q1_q0_dm_p0 * p1; + lean_assert(m == q1_q0_dm_p0_p1); + + m.multiply_from_right(p1); + for (unsigned i = 0; i < dim; i++) { + for (unsigned j = 0; j < dim; j++) { + lean_assert(m(i, j) == dm0.get_elem(q0[q1[i]], p1[p1[p0[j]]])); + } + } + auto q1_q0_dm_p0_p1_p1 = q1_q0_dm_p0_p1 * p1; + + lean_assert(m == q1_q0_dm_p0_p1_p1); +} + +void test_swap_columns() { + sparse_matrix m(10); + fill_matrix(m); + // print_matrix(m); + + test_swap_columns(m, 3, 5); + + test_swap_columns(m, 1, 3); + + test_swap_columns(m, 1, 3); + + // print_matrix(m); + test_swap_columns(m, 1, 7); + + test_swap_columns(m, 3, 7); + + test_swap_columns(m, 0, 7); + + test_swap_columns(m, 0, 7); + +// go over some corner cases + sparse_matrix m0(2); + test_swap_columns(m0, 0, 1); + m0(0, 0) = 3; + test_swap_columns(m0, 0, 1); + m0(0, 1) = 3; + test_swap_columns(m0, 0, 1); + + + sparse_matrix m1(10); + test_swap_columns(m1, 0, 1); + m1(0, 0) = 3; + test_swap_columns(m1, 0, 1); + m1(0, 1) = 3; + m1(3, 0) = 5; + m1(3, 1) = 4; + m1(8, 1) = 8; + m1(9, 1) = 8; + + test_swap_columns(m1, 0, 1); + + sparse_matrix m2(3); + test_swap_columns(m2, 0, 1); + m2(0, 0) = 3; + test_swap_columns(m2, 0, 1); + m2(0, 2) = 3; + test_swap_columns(m2, 0, 2); +} + + + +void test_swap_operations() { + test_swap_rows(); + test_swap_columns(); +} + +void test_dense_matrix() { + dense_matrix d(3, 2); + d.set_elem(0, 0, 1); + d.set_elem(1, 1, 2); + d.set_elem(2, 0, 3); + // print_matrix(d); + + dense_matrix unit(2, 2); + d.set_elem(0, 0, 1); + d.set_elem(1, 1, 1); + + dense_matrix c = d * unit; + + // print_matrix(d); + + dense_matrix perm(3, 3); + perm.set_elem(0, 1, 1); + perm.set_elem(1, 0, 1); + perm.set_elem(2, 2, 1); + auto c1 = perm * d; + // print_matrix(c1); + + + dense_matrix p2(2, 2); + p2.set_elem(0, 1, 1); + p2.set_elem(1, 0, 1); + auto c2 = d * p2; +} +#endif + + + +vector> vector_of_permutaions() { + vector> ret; + { + permutation_matrix p0(5); + p0[0] = 1; p0[1] = 2; p0[2] = 3; p0[3] = 4; + p0[4] = 0; + ret.push_back(p0); + } + { + permutation_matrix p0(5); + p0[0] = 2; p0[1] = 0; p0[2] = 1; p0[3] = 4; + p0[4] = 3; + ret.push_back(p0); + } + return ret; +} + +void test_apply_reverse_from_right_to_perm(permutation_matrix & l) { + permutation_matrix p(5); + p[0] = 4; p[1] = 2; p[2] = 0; p[3] = 3; + p[4] = 1; + + permutation_matrix pclone(5); + pclone[0] = 4; pclone[1] = 2; pclone[2] = 0; pclone[3] = 3; + pclone[4] = 1; + + p.multiply_by_reverse_from_right(l); +#ifdef LEAN_DEBUG + auto rev = l.get_inverse(); + auto rs = pclone * rev; + lean_assert(p == rs) +#endif +} + +void test_apply_reverse_from_right() { + auto vec = vector_of_permutaions(); + for (unsigned i = 0; i < vec.size(); i++) { + test_apply_reverse_from_right_to_perm(vec[i]); + } +} + +void test_permutations() { + std::cout << "test permutations" << std::endl; + test_apply_reverse_from_right(); + vector v; v.resize(5, 0); + v[1] = 1; + v[3] = 3; + permutation_matrix p(5); + p[0] = 4; p[1] = 2; p[2] = 0; p[3] = 3; + p[4] = 1; + + indexed_vector vi(5); + vi.set_value(1, 1); + vi.set_value(3, 3); + + p.apply_reverse_from_right_to_T(v); + p.apply_reverse_from_right_to_T(vi); + lean_assert(vectors_are_equal(v, vi.m_data)); + lean_assert(vi.is_OK()); +} + +void lp_solver_test() { + // lp_revised_solver lp_revised; + // lp_revised.get_minimal_solution(); +} + +bool get_int_from_args_parser(const char * option, argument_parser & args_parser, unsigned & n) { + std::string s = args_parser.get_option_value(option); + if (s.size() > 0) { + n = atoi(s.c_str()); + return true; + } + return false; +} + +bool get_double_from_args_parser(const char * option, argument_parser & args_parser, double & n) { + std::string s = args_parser.get_option_value(option); + if (s.size() > 0) { + n = atof(s.c_str()); + return true; + } + return false; +} + + +void update_settings(argument_parser & args_parser, lp_settings& settings) { + unsigned n; + settings.m_simplex_strategy = simplex_strategy_enum::no_tableau; + if (get_int_from_args_parser("--rep_frq", args_parser, n)) + settings.report_frequency = n; + else + settings.report_frequency = args_parser.option_is_used("--mpq")? 80: 1000; + + settings.print_statistics = true; + + if (get_int_from_args_parser("--percent_for_enter", args_parser, n)) + settings.percent_of_entering_to_check = n; + if (get_int_from_args_parser("--partial_pivot", args_parser, n)) { + std::cout << "setting partial pivot constant to " << n << std::endl; + settings.c_partial_pivoting = n; + } + if (get_int_from_args_parser("--density", args_parser, n)) { + double density = static_cast(n) / 100.0; + std::cout << "setting density to " << density << std::endl; + settings.density_threshold = density; + } + if (get_int_from_args_parser("--maxng", args_parser, n)) + settings.max_number_of_iterations_with_no_improvements = n; + double d; + if (get_double_from_args_parser("--harris_toler", args_parser, d)) { + std::cout << "setting harris_feasibility_tolerance to " << d << std::endl; + settings.harris_feasibility_tolerance = d; + } + if (get_int_from_args_parser("--random_seed", args_parser, n)) { + settings.random_seed = n; + } + if (get_int_from_args_parser("--simplex_strategy", args_parser, n)) { + settings.simplex_strategy() = static_cast(n); + } +} + +template +void setup_solver(unsigned max_iterations, unsigned time_limit, bool look_for_min, argument_parser & args_parser, lp_solver * solver) { + if (max_iterations > 0) + solver->set_max_iterations_per_stage(max_iterations); + + if (time_limit > 0) + solver->set_time_limit(time_limit); + + if (look_for_min) + solver->flip_costs(); + + update_settings(args_parser, solver->settings()); +} + +bool values_are_one_percent_close(double a, double b); + +void print_x(mps_reader & reader, lp_solver * solver) { + for (auto name : reader.column_names()) { + std::cout << name << "=" << solver->get_column_value_by_name(name) << ' '; + } + std::cout << std::endl; +} + +void compare_solutions(mps_reader & reader, lp_solver * solver, lp_solver * solver0) { + for (auto name : reader.column_names()) { + double a = solver->get_column_value_by_name(name); + double b = solver0->get_column_value_by_name(name); + if (!values_are_one_percent_close(a, b)) { + std::cout << "different values for " << name << ":" << a << " and " << b << std::endl; + } + } +} + + +void solve_mps_double(std::string file_name, bool look_for_min, unsigned max_iterations, unsigned time_limit, bool dual, bool compare_with_primal, argument_parser & args_parser) { + mps_reader reader(file_name); + reader.read(); + if (!reader.is_ok()) { + std::cout << "cannot process " << file_name << std::endl; + return; + } + + lp_solver * solver = reader.create_solver(dual); + setup_solver(max_iterations, time_limit, look_for_min, args_parser, solver); + int begin = get_millisecond_count(); + if (dual) { + std::cout << "solving for dual" << std::endl; + } + solver->find_maximal_solution(); + int span = get_millisecond_span(begin); + std::cout << "Status: " << lp_status_to_string(solver->get_status()) << std::endl; + if (solver->get_status() == lp_status::OPTIMAL) { + if (reader.column_names().size() < 20) { + print_x(reader, solver); + } + double cost = solver->get_current_cost(); + if (look_for_min) { + cost = -cost; + } + std::cout << "cost = " << cost << std::endl; + } + std::cout << "processed in " << span / 1000.0 << " seconds, running for " << solver->m_total_iterations << " iterations" << " one iter for " << (double)span/solver->m_total_iterations << " ms" << std::endl; + if (compare_with_primal) { + auto * primal_solver = reader.create_solver(false); + setup_solver(max_iterations, time_limit, look_for_min, args_parser, primal_solver); + primal_solver->find_maximal_solution(); + if (solver->get_status() != primal_solver->get_status()) { + std::cout << "statuses are different: dual " << lp_status_to_string(solver->get_status()) << " primal = " << lp_status_to_string(primal_solver->get_status()) << std::endl; + } else { + if (solver->get_status() == lp_status::OPTIMAL) { + double cost = solver->get_current_cost(); + if (look_for_min) { + cost = -cost; + } + double primal_cost = primal_solver->get_current_cost(); + if (look_for_min) { + primal_cost = -primal_cost; + } + std::cout << "primal cost = " << primal_cost << std::endl; + if (!values_are_one_percent_close(cost, primal_cost)) { + compare_solutions(reader, primal_solver, solver); + print_x(reader, primal_solver); + std::cout << "dual cost is " << cost << ", but primal cost is " << primal_cost << std::endl; + lean_assert(false); + } + } + } + delete primal_solver; + } + delete solver; +} + +void solve_mps_rational(std::string file_name, bool look_for_min, unsigned max_iterations, unsigned time_limit, bool dual, argument_parser & args_parser) { + mps_reader reader(file_name); + reader.read(); + if (reader.is_ok()) { + auto * solver = reader.create_solver(dual); + setup_solver(max_iterations, time_limit, look_for_min, args_parser, solver); + int begin = get_millisecond_count(); + solver->find_maximal_solution(); + std::cout << "Status: " << lp_status_to_string(solver->get_status()) << std::endl; + + if (solver->get_status() == lp_status::OPTIMAL) { + // for (auto name: reader.column_names()) { + // std::cout << name << "=" << solver->get_column_value_by_name(name) << ' '; + // } + lean::mpq cost = solver->get_current_cost(); + if (look_for_min) { + cost = -cost; + } + std::cout << "cost = " << cost.get_double() << std::endl; + } + std::cout << "processed in " << get_millisecond_span(begin) / 1000.0 << " seconds, running for " << solver->m_total_iterations << " iterations" << std::endl; + delete solver; + } else { + std::cout << "cannot process " << file_name << std::endl; + } +} +void get_time_limit_and_max_iters_from_parser(argument_parser & args_parser, unsigned & time_limit, unsigned & max_iters); // forward definition + +void solve_mps(std::string file_name, bool look_for_min, unsigned max_iterations, unsigned time_limit, bool solve_for_rational, bool dual, bool compare_with_primal, argument_parser & args_parser) { + if (!solve_for_rational) { + std::cout << "solving " << file_name << std::endl; + solve_mps_double(file_name, look_for_min, max_iterations, time_limit, dual, compare_with_primal, args_parser); + } + else { + std::cout << "solving " << file_name << " in rationals " << std::endl; + solve_mps_rational(file_name, look_for_min, max_iterations, time_limit, dual, args_parser); + } +} + +void solve_mps(std::string file_name, argument_parser & args_parser) { + bool look_for_min = args_parser.option_is_used("--min"); + unsigned max_iterations, time_limit; + bool solve_for_rational = args_parser.option_is_used("--mpq"); + bool dual = args_parser.option_is_used("--dual"); + bool compare_with_primal = args_parser.option_is_used("--compare_with_primal"); + get_time_limit_and_max_iters_from_parser(args_parser, time_limit, max_iterations); + solve_mps(file_name, look_for_min, max_iterations, time_limit, solve_for_rational, dual, compare_with_primal, args_parser); +} + +void solve_mps_in_rational(std::string file_name, bool dual, argument_parser & /*args_parser*/) { + std::cout << "solving " << file_name << std::endl; + + mps_reader reader(file_name); + reader.read(); + if (reader.is_ok()) { + auto * solver = reader.create_solver(dual); + solver->find_maximal_solution(); + std::cout << "status is " << lp_status_to_string(solver->get_status()) << std::endl; + if (solver->get_status() == lp_status::OPTIMAL) { + if (reader.column_names().size() < 20) { + for (auto name : reader.column_names()) { + std::cout << name << "=" << solver->get_column_value_by_name(name).get_double() << ' '; + } + } + std::cout << std::endl << "cost = " << numeric_traits::get_double(solver->get_current_cost()) << std::endl; + } + delete solver; + } else { + std::cout << "cannot process " << file_name << std::endl; + } +} + +void test_upair_queue() { + int n = 10; + binary_heap_upair_queue q(2); + std::unordered_map m; + for (int k = 0; k < 100; k++) { + int i = my_random()%n; + int j = my_random()%n; + q.enqueue(i, j, my_random()%n); + } + + q.remove(5, 5); + + while (!q.is_empty()) { + unsigned i, j; + q.dequeue(i, j); + } +} + +void test_binary_priority_queue() { + std::cout << "testing binary_heap_priority_queue..."; + auto q = binary_heap_priority_queue(10); + q.enqueue(2, 2); + q.enqueue(1, 1); + q.enqueue(9, 9); + q.enqueue(8, 8); + q.enqueue(5, 25); + q.enqueue(3, 3); + q.enqueue(4, 4); + q.enqueue(7, 30); + q.enqueue(6, 6); + q.enqueue(0, 0); + q.enqueue(5, 5); + q.enqueue(7, 7); + + for (unsigned i = 0; i < 10; i++) { + unsigned de = q.dequeue(); + lean_assert(i == de); + std::cout << de << std::endl; + } + q.enqueue(2, 2); + q.enqueue(1, 1); + q.enqueue(9, 9); + q.enqueue(8, 8); + q.enqueue(5, 5); + q.enqueue(3, 3); + q.enqueue(4, 4); + q.enqueue(7, 2); + q.enqueue(0, 1); + q.enqueue(6, 6); + q.enqueue(7, 7); + q.enqueue(33, 1000); + q.enqueue(20, 0); + q.dequeue(); + q.remove(33); + q.enqueue(0, 0); +#ifdef LEAN_DEBUG + unsigned t = 0; + while (q.size() > 0) { + unsigned d =q.dequeue(); + lean_assert(t++ == d); + std::cout << d << std::endl; + } +#endif + test_upair_queue(); + std::cout << " done" << std::endl; +} + +bool solution_is_feasible(std::string file_name, const std::unordered_map & solution) { + mps_reader reader(file_name); + reader.read(); + if (reader.is_ok()) { + lp_primal_simplex * solver = static_cast *>(reader.create_solver(false)); + return solver->solution_is_feasible(solution); + } + return false; +} + + +void solve_mps_with_known_solution(std::string file_name, std::unordered_map * solution, lp_status status, bool dual) { + std::cout << "solving " << file_name << std::endl; + mps_reader reader(file_name); + reader.read(); + if (reader.is_ok()) { + auto * solver = reader.create_solver(dual); + solver->find_maximal_solution(); + std::cout << "status is " << lp_status_to_string(solver->get_status()) << std::endl; + if (status != solver->get_status()){ + std::cout << "status should be " << lp_status_to_string(status) << std::endl; + lean_assert(status == solver->get_status()); + throw "status is wrong"; + } + if (solver->get_status() == lp_status::OPTIMAL) { + std::cout << "cost = " << solver->get_current_cost() << std::endl; + if (solution != nullptr) { + for (auto it : *solution) { + if (fabs(it.second - solver->get_column_value_by_name(it.first)) >= 0.000001) { + std::cout << "expected:" << it.first << "=" << + it.second <<", got " << solver->get_column_value_by_name(it.first) << std::endl; + } + lean_assert(fabs(it.second - solver->get_column_value_by_name(it.first)) < 0.000001); + } + } + if (reader.column_names().size() < 20) { + for (auto name : reader.column_names()) { + std::cout << name << "=" << solver->get_column_value_by_name(name) << ' '; + } + std::cout << std::endl; + } + } + delete solver; + } else { + std::cout << "cannot process " << file_name << std::endl; + } +} + +int get_random_rows() { + return 5 + my_random() % 2; +} + +int get_random_columns() { + return 5 + my_random() % 3; +} + +int get_random_int() { + return -1 + my_random() % 2; // (1.0 + RAND_MAX); +} + +void add_random_row(lp_primal_simplex * solver, int cols, int row) { + solver->add_constraint(lp_relation::Greater_or_equal, 1, row); + for (int i = 0; i < cols; i++) { + solver->set_row_column_coefficient(row, i, get_random_int()); + } +} + +void add_random_cost(lp_primal_simplex * solver, int cols) { + for (int i = 0; i < cols; i++) { + solver->set_cost_for_column(i, get_random_int()); + } +} + +lp_primal_simplex * generate_random_solver() { + int rows = get_random_rows(); + int cols = get_random_columns(); + auto * solver = new lp_primal_simplex(); + for (int i = 0; i < rows; i++) { + add_random_row(solver, cols, i); + } + add_random_cost(solver, cols); + return solver; +} + + + +void random_test_on_i(unsigned i) { + if (i % 1000 == 0) { + std::cout << "."; + } + srand(i); + auto *solver = generate_random_solver(); + solver->find_maximal_solution(); + // std::cout << lp_status_to_string(solver->get_status()) << std::endl; + delete solver; +} + +void random_test() { + for (unsigned i = 0; i < std::numeric_limits::max(); i++) { + try { + random_test_on_i(i); + } + catch (const char * error) { + std::cout << "i = " << i << ", throwing at ' " << error << "'" << std::endl; + break; + } + } +} + +#if _LINUX_ +void fill_file_names(vector &file_names, std::set & minimums) { + char *home_dir = getenv("HOME"); + if (home_dir == nullptr) { + std::cout << "cannot find home directory, don't know how to find the files"; + return; + } + std::string home_dir_str(home_dir); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/l0redund.mps"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/l1.mps"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/l2.mps"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/l3.mps"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/l4.mps"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/l4fix.mps"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/plan.mps"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/samp2.mps"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/murtagh.mps"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/l0.mps"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/AFIRO.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/SC50B.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/SC50A.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/KB2.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/SC105.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/STOCFOR1.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/ADLITTLE.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/BLEND.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/SCAGR7.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/SC205.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/SHARE2B.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/RECIPELP.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/LOTFI.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/VTP-BASE.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/SHARE1B.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/BOEING2.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/BORE3D.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/SCORPION.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/CAPRI.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/BRANDY.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/SCAGR25.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/SCTAP1.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/ISRAEL.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/SCFXM1.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/BANDM.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/E226.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/AGG.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/GROW7.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/ETAMACRO.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/FINNIS.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/SCSD1.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/STANDATA.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/STANDGUB.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/BEACONFD.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/STAIR.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/STANDMPS.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/GFRD-PNC.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/SCRS8.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/BOEING1.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/MODSZK1.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/DEGEN2.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/FORPLAN.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/AGG2.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/AGG3.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/SCFXM2.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/SHELL.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/PILOT4.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/SCSD6.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/SHIP04S.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/SEBA.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/GROW15.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/FFFFF800.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/BNL1.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/PEROLD.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/QAP8.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/SCFXM3.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/SHIP04L.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/GANGES.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/SCTAP2.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/GROW22.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/SHIP08S.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/PILOT-WE.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/MAROS.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/STOCFOR2.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/25FV47.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/SHIP12S.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/SCSD8.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/FIT1P.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/SCTAP3.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/SIERRA.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/PILOTNOV.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/CZPROB.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/FIT1D.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/PILOT-JA.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/SHIP08L.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/BNL2.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/NESM.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/CYCLE.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/acc-tight5.mps"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/SHIP12L.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/DEGEN3.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/GREENBEA.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/GREENBEB.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/80BAU3B.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/TRUSS.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/D2Q06C.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/WOODW.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/QAP12.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/D6CUBE.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/PILOT.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/DFL001.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/WOOD1P.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/FIT2P.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/PILOT87.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/STOCFOR3.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/QAP15.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/FIT2D.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/MAROS-R7.SIF"); + minimums.insert("/projects/lean/src/tests/util/lp/test_files/netlib/FIT2P.SIF"); + minimums.insert("/projects/lean/src/tests/util/lp/test_files/netlib/DFL001.SIF"); + minimums.insert("/projects/lean/src/tests/util/lp/test_files/netlib/D2Q06C.SIF"); + minimums.insert("/projects/lean/src/tests/util/lp/test_files/netlib/80BAU3B.SIF"); + minimums.insert("/projects/lean/src/tests/util/lp/test_files/netlib/GREENBEB.SIF"); + minimums.insert("/projects/lean/src/tests/util/lp/test_files/netlib/GREENBEA.SIF"); + minimums.insert("/projects/lean/src/tests/util/lp/test_files/netlib/BNL2.SIF"); + minimums.insert("/projects/lean/src/tests/util/lp/test_files/netlib/SHIP08L.SIF"); + minimums.insert("/projects/lean/src/tests/util/lp/test_files/netlib/FIT1D.SIF"); + minimums.insert("/projects/lean/src/tests/util/lp/test_files/netlib/SCTAP3.SIF"); + minimums.insert("/projects/lean/src/tests/util/lp/test_files/netlib/SCSD8.SIF"); + minimums.insert("/projects/lean/src/tests/util/lp/test_files/netlib/SCSD6.SIF"); + minimums.insert("/projects/lean/src/tests/util/lp/test_files/netlib/MAROS-R7.SIF"); +} + +void test_out_dir(std::string out_dir) { + auto *out_dir_p = opendir(out_dir.c_str()); + if (out_dir_p == nullptr) { + std::cout << "creating directory " << out_dir << std::endl; +#ifdef LEAN_WINDOWS + int res = mkdir(out_dir.c_str()); +#else + int res = mkdir(out_dir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); +#endif + if (res) { + std::cout << "Cannot open output directory \"" << out_dir << "\"" << std::endl; + } + return; + } + closedir(out_dir_p); +} + +void find_dir_and_file_name(std::string a, std::string & dir, std::string& fn) { + // todo: make it system independent + size_t last_slash_pos = a.find_last_of("/"); + if (last_slash_pos >= a.size()) { + std::cout << "cannot find file name in " << a << std::endl; + throw; + } + dir = a.substr(0, last_slash_pos); + // std::cout << "dir = " << dir << std::endl; + fn = a.substr(last_slash_pos + 1); + // std::cout << "fn = " << fn << std::endl; +} + +void process_test_file(std::string test_dir, std::string test_file_name, argument_parser & args_parser, std::string out_dir, unsigned max_iters, unsigned time_limit, unsigned & successes, unsigned & failures, unsigned & inconclusives); + +void solve_some_mps(argument_parser & args_parser) { + unsigned max_iters, time_limit; + get_time_limit_and_max_iters_from_parser(args_parser, time_limit, max_iters); + unsigned successes = 0; + unsigned failures = 0; + unsigned inconclusives = 0; + std::set minimums; + vector file_names; + fill_file_names(file_names, minimums); + bool solve_for_rational = args_parser.option_is_used("--mpq"); + bool dual = args_parser.option_is_used("--dual"); + bool compare_with_primal = args_parser.option_is_used("--compare_with_primal"); + bool compare_with_glpk = args_parser.option_is_used("--compare_with_glpk"); + if (compare_with_glpk) { + std::string out_dir = args_parser.get_option_value("--out_dir"); + if (out_dir.size() == 0) { + out_dir = "/tmp/test"; + } + test_out_dir(out_dir); + for (auto& a : file_names) { + try { + std::string file_dir; + std::string file_name; + find_dir_and_file_name(a, file_dir, file_name); + process_test_file(file_dir, file_name, args_parser, out_dir, max_iters, time_limit, successes, failures, inconclusives); + } + catch(const char *s){ + std::cout<< "exception: "<< s << std::endl; + } + } + std::cout << "comparing with glpk: successes " << successes << ", failures " << failures << ", inconclusives " << inconclusives << std::endl; + return; + } + if (!solve_for_rational) { + solve_mps(file_names[6], false, 0, time_limit, false, dual, compare_with_primal, args_parser); + solve_mps_with_known_solution(file_names[3], nullptr, INFEASIBLE, dual); // chvatal: 135(d) + std::unordered_map sol; + sol["X1"] = 0; + sol["X2"] = 6; + sol["X3"] = 0; + sol["X4"] = 15; + sol["X5"] = 2; + sol["X6"] = 1; + sol["X7"] = 1; + sol["X8"] = 0; + solve_mps_with_known_solution(file_names[9], &sol, OPTIMAL, dual); + solve_mps_with_known_solution(file_names[0], &sol, OPTIMAL, dual); + sol.clear(); + sol["X1"] = 25.0/14.0; + // sol["X2"] = 0; + // sol["X3"] = 0; + // sol["X4"] = 0; + // sol["X5"] = 0; + // sol["X6"] = 0; + // sol["X7"] = 9.0/14.0; + solve_mps_with_known_solution(file_names[5], &sol, OPTIMAL, dual); // chvatal: 135(e) + solve_mps_with_known_solution(file_names[4], &sol, OPTIMAL, dual); // chvatal: 135(e) + solve_mps_with_known_solution(file_names[2], nullptr, UNBOUNDED, dual); // chvatal: 135(c) + solve_mps_with_known_solution(file_names[1], nullptr, UNBOUNDED, dual); // chvatal: 135(b) + solve_mps(file_names[8], false, 0, time_limit, false, dual, compare_with_primal, args_parser); + // return; + for (auto& s : file_names) { + try { + solve_mps(s, minimums.find(s) != minimums.end(), max_iters, time_limit, false, dual, compare_with_primal, args_parser); + } + catch(const char *s){ + std::cout<< "exception: "<< s << std::endl; + } + } + } else { + // unsigned i = 0; + for (auto& s : file_names) { + // if (i++ > 9) return; + try { + solve_mps_in_rational(s, dual, args_parser); + } + catch(const char *s){ + std::cout<< "exception: "<< s << std::endl; + } + } + } +} +#endif + +void solve_rational() { + lp_primal_simplex solver; + solver.add_constraint(lp_relation::Equal, lean::mpq(7), 0); + solver.add_constraint(lp_relation::Equal, lean::mpq(-3), 1); + + // setting the cost + int cost[] = {-3, -1, -1, 2, -1, 1, 1, -4}; + std::string var_names[8] = {"x1", "x2", "x3", "x4", "x5", "x6", "x7", "x8"}; + + for (unsigned i = 0; i < 8; i++) { + solver.set_cost_for_column(i, lean::mpq(cost[i])); + solver.give_symbolic_name_to_column(var_names[i], i); + } + + int row0[] = {1, 0, 3, 1, -5, -2 , 4, -6}; + for (unsigned i = 0; i < 8; i++) { + solver.set_row_column_coefficient(0, i, lean::mpq(row0[i])); + } + + int row1[] = {0, 1, -2, -1, 4, 1, -3, 5}; + for (unsigned i = 0; i < 8; i++) { + solver.set_row_column_coefficient(1, i, lean::mpq(row1[i])); + } + + int bounds[] = {8, 6, 4, 15, 2, 10, 10, 3}; + for (unsigned i = 0; i < 8; i++) { + solver.set_low_bound(i, lean::mpq(0)); + solver.set_upper_bound(i, lean::mpq(bounds[i])); + } + + std::unordered_map expected_sol; + expected_sol["x1"] = lean::mpq(0); + expected_sol["x2"] = lean::mpq(6); + expected_sol["x3"] = lean::mpq(0); + expected_sol["x4"] = lean::mpq(15); + expected_sol["x5"] = lean::mpq(2); + expected_sol["x6"] = lean::mpq(1); + expected_sol["x7"] = lean::mpq(1); + expected_sol["x8"] = lean::mpq(0); + solver.find_maximal_solution(); + lean_assert(solver.get_status() == OPTIMAL); + for (auto it : expected_sol) { + lean_assert(it.second == solver.get_column_value_by_name(it.first)); + } +} + + +std::string read_line(bool & end, std::ifstream & file) { + std::string s; + if (!getline(file, s)) { + end = true; + return std::string(); + } + end = false; + return s; +} + +bool contains(std::string const & s, char const * pattern) { + return s.find(pattern) != std::string::npos; +} + + +std::unordered_map * get_solution_from_glpsol_output(std::string & file_name) { + std::ifstream file(file_name); + if (!file.is_open()){ + std::cerr << "cannot open " << file_name << std::endl; + return nullptr; + } + std::string s; + bool end; + do { + s = read_line(end, file); + if (end) { + std::cerr << "unexpected file end " << file_name << std::endl; + return nullptr; + } + if (contains(s, "Column name")){ + break; + } + } while (true); + + read_line(end, file); + if (end) { + std::cerr << "unexpected file end " << file_name << std::endl; + return nullptr; + } + + auto ret = new std::unordered_map(); + + do { + s = read_line(end, file); + if (end) { + std::cerr << "unexpected file end " << file_name << std::endl; + return nullptr; + } + auto split = string_split(s, " \t", false); + if (split.size() == 0) { + return ret; + } + + lean_assert(split.size() > 3); + (*ret)[split[1]] = atof(split[3].c_str()); + } while (true); +} + + + +void test_init_U() { + static_matrix m(3, 7); + m(0, 0) = 10; m(0, 1) = 11; m(0, 2) = 12; m(0, 3) = 13; m(0, 4) = 14; + m(1, 0) = 20; m(1, 1) = 21; m(1, 2) = 22; m(1, 3) = 23; m(1, 5) = 24; + m(2, 0) = 30; m(2, 1) = 31; m(2, 2) = 32; m(2, 3) = 33; m(2, 6) = 34; +#ifdef LEAN_DEBUG + print_matrix(m, std::cout); +#endif + vector basis(3); + basis[0] = 1; + basis[1] = 2; + basis[2] = 4; + + sparse_matrix u(m, basis); + + for (unsigned i = 0; i < 3; i++) { + for (unsigned j = 0; j < 3; j ++) { + lean_assert(m(i, basis[j]) == u(i, j)); + } + } + + // print_matrix(m); + // print_matrix(u); +} + +void test_replace_column() { + sparse_matrix m(10); + fill_matrix(m); + m.swap_columns(0, 7); + m.swap_columns(6, 3); + m.swap_rows(2, 0); + for (unsigned i = 1; i < m.dimension(); i++) { + m(i, 0) = 0; + } + + indexed_vector w(m.dimension()); + for (unsigned i = 0; i < m.dimension(); i++) { + w.set_value(i % 3, i); + } + + lp_settings settings; + + for (unsigned column_to_replace = 0; column_to_replace < m.dimension(); column_to_replace ++) { + m.replace_column(column_to_replace, w, settings); + for (unsigned i = 0; i < m.dimension(); i++) { + lean_assert(abs(w[i] - m(i, column_to_replace)) < 0.00000001); + } + } +} + + +void setup_args_parser(argument_parser & parser) { + parser.add_option_with_help_string("-xyz_sample", "run a small interactive scenario"); + parser.add_option_with_after_string_with_help("--density", "the percentage of non-zeroes in the matrix below which it is not dense"); + parser.add_option_with_after_string_with_help("--harris_toler", "harris tolerance"); + parser.add_option_with_help_string("--test_swaps", "test row swaps with a permutation"); + parser.add_option_with_help_string("--test_perm", "test permutaions"); + parser.add_option_with_after_string_with_help("--checklu", "the file name for lu checking"); + parser.add_option_with_after_string_with_help("--partial_pivot", "the partial pivot constant, a number somewhere between 10 and 100"); + parser.add_option_with_after_string_with_help("--percent_for_enter", "which percent of columns check for entering column"); + parser.add_option_with_help_string("--totalinf", "minimizes the total infeasibility instead of diminishin infeasibility of the rows"); + parser.add_option_with_after_string_with_help("--rep_frq", "the report frequency, in how many iterations print the cost and other info "); + parser.add_option_with_help_string("--smt", "smt file format"); + parser.add_option_with_after_string_with_help("--filelist", "the file containing the list of files"); + parser.add_option_with_after_string_with_help("--file", "the input file name"); + parser.add_option_with_after_string_with_help("--random_seed", "random seed"); + parser.add_option_with_help_string("--bp", "bound propogation"); + parser.add_option_with_help_string("--min", "will look for the minimum for the given file if --file is used; the default is looking for the max"); + parser.add_option_with_help_string("--max", "will look for the maximum for the given file if --file is used; it is the default behavior"); + parser.add_option_with_after_string_with_help("--max_iters", "maximum total iterations in a core solver stage"); + parser.add_option_with_after_string_with_help("--time_limit", "time limit in seconds"); + parser.add_option_with_help_string("--mpq", "solve for rational numbers"); + parser.add_option_with_after_string_with_help("--simplex_strategy", "sets simplex strategy for rational number"); + parser.add_option_with_help_string("--test_lu", "test the work of the factorization"); + parser.add_option_with_help_string("--test_small_lu", "test the work of the factorization on a smallish matrix"); + parser.add_option_with_help_string("--test_larger_lu", "test the work of the factorization"); + parser.add_option_with_help_string("--test_larger_lu_with_holes", "test the work of the factorization"); + parser.add_option_with_help_string("--test_lp_0", "solve a small lp"); + parser.add_option_with_help_string("--solve_some_mps", "solves a list of mps problems"); + parser.add_option_with_after_string_with_help("--test_file_directory", "loads files from the directory for testing"); + parser.add_option_with_help_string("--compare_with_glpk", "compares the results by running glpsol"); + parser.add_option_with_after_string_with_help("--out_dir", "setting the output directory for tests, if not set /tmp is used"); + parser.add_option_with_help_string("--dual", "using the dual simplex solver"); + parser.add_option_with_help_string("--compare_with_primal", "using the primal simplex solver for comparison"); + parser.add_option_with_help_string("--lar", "test lar_solver"); + parser.add_option_with_after_string_with_help("--maxng", "max iterations without progress"); + parser.add_option_with_help_string("-tbq", "test binary queue"); + parser.add_option_with_help_string("--randomize_lar", "test randomize funclionality"); + parser.add_option_with_help_string("--smap", "test stacked_map"); + parser.add_option_with_help_string("--term", "simple term test"); + parser.add_option_with_help_string("--eti"," run a small evidence test for total infeasibility scenario"); + parser.add_option_with_help_string("--row_inf", "forces row infeasibility search"); + parser.add_option_with_help_string("-pd", "presolve with double solver"); + parser.add_option_with_help_string("--test_int_set", "test int_set"); + parser.add_option_with_help_string("--test_mpq", "test rationals"); + parser.add_option_with_help_string("--test_mpq_np", "test rationals"); + parser.add_option_with_help_string("--test_mpq_np_plus", "test rationals using plus instead of +="); +} + +struct fff { int a; int b;}; + +void test_stacked_map_itself() { + vector v(3,0); + for(auto u : v) + std::cout << u << std::endl; + + std::unordered_map foo; + fff l; + l.a = 0; + l.b =1; + foo[1] = l; + int r = 1; + int k = foo[r].a; + std::cout << k << std::endl; + + stacked_map m; + m[0] = 3; + m[1] = 4; + m.push(); + m[1] = 5; + m[2] = 2; + m.pop(); + m.erase(2); + m[2] = 3; + m.erase(1); + m.push(); + m[3] = 100; + m[4] = 200; + m.erase(1); + m.push(); + m[5] = 300; + m[6] = 400; + m[5] = 301; + m.erase(5); + m[3] = 122; + + m.pop(2); + m.pop(); +} + +void test_stacked_unsigned() { + std::cout << "test stacked unsigned" << std::endl; + stacked_value v(0); + v = 1; + v = 2; + v.push(); + v = 3; + v = 4; + v.pop(); + lean_assert(v == 2); + v ++; + v++; + std::cout << "before push v=" << v << std::endl; + v.push(); + v++; + v.push(); + v+=1; + std::cout << "v = " << v << std::endl; + v.pop(2); + lean_assert(v == 4); + const unsigned & rr = v; + std::cout << rr << std:: endl; + +} + +void test_stacked_value() { + test_stacked_unsigned(); +} + +void test_stacked_vector() { + std::cout << "test_stacked_vector" << std::endl; + stacked_vector v; + v.push(); + v.push_back(0); + v.push_back(1); + v.push(); + v[0] = 3; + v[0] = 0; + v.push_back(2); + v.push_back(3); + v.push_back(34); + v.push(); + v[1]=3; + v[2] = 3; + v.push(); + v[0]= 7; + v[1] = 9; + v.pop(2); + if (v.size()) + v[v.size() -1 ] = 7; + v.push(); + v.push_back(33); + v[0] = 13; + v.pop(); + +} + +void test_stacked_set() { +#ifdef LEAN_DEBUG + std::cout << "test_stacked_set" << std::endl; + stacked_unordered_set s; + s.insert(1); + s.insert(2); + s.insert(3); + std::unordered_set scopy = s(); + s.push(); + s.insert(4); + s.pop(); + lean_assert(s() == scopy); + s.push(); + s.push(); + s.insert(4); + s.insert(5); + s.push(); + s.insert(4); + s.pop(3); + lean_assert(s() == scopy); +#endif +} + +void test_stacked() { + std::cout << "test_stacked_map()" << std::endl; + test_stacked_map_itself(); + test_stacked_value(); + test_stacked_vector(); + test_stacked_set(); + +} + +char * find_home_dir() { + #ifdef _WINDOWS + #else + char * home_dir = getenv("HOME"); + if (home_dir == nullptr) { + std::cout << "cannot find home directory" << std::endl; + return nullptr; + } + #endif + return nullptr; +} + + +template +void print_chunk(T * arr, unsigned len) { + for (unsigned i = 0; i < len; i++) { + std::cout << arr[i] << ", "; + } + std::cout << std::endl; +} + +struct mem_cpy_place_holder { + static void mem_copy_hook(int * destination, unsigned num) { + if (destination == nullptr || num == 0) { + throw "bad parameters"; + } + } +}; + +void finalize(unsigned ret) { + /* + finalize_util_module(); + finalize_numerics_module(); + */ + // return ret; +} + +void get_time_limit_and_max_iters_from_parser(argument_parser & args_parser, unsigned & time_limit, unsigned & max_iters) { + std::string s = args_parser.get_option_value("--max_iters"); + if (s.size() > 0) { + max_iters = atoi(s.c_str()); + } else { + max_iters = 0; + } + + std::string time_limit_string = args_parser.get_option_value("--time_limit"); + if (time_limit_string.size() > 0) { + time_limit = atoi(time_limit_string.c_str()); + } else { + time_limit = 0; + } +} + + +std::string create_output_file_name(bool minimize, std::string file_name, bool use_mpq) { + std::string ret = file_name + "_lp_tst_" + (minimize?"min":"max"); + if (use_mpq) return ret + "_mpq.out"; + return ret + ".out"; +} + +std::string create_output_file_name_for_glpsol(bool minimize, std::string file_name){ + return file_name + (minimize?"_min":"_max") + "_glpk_out"; +} + +int run_glpk(std::string file_name, std::string glpk_out_file_name, bool minimize, unsigned time_limit) { + std::string minmax(minimize?"--min":"--max"); + std::string tmlim = time_limit > 0 ? std::string(" --tmlim ") + std::to_string(time_limit)+ " ":std::string(); + std::string command_line = std::string("glpsol --nointopt --nomip ") + minmax + tmlim + + " -o " + glpk_out_file_name +" " + file_name + " > /dev/null"; + return system(command_line.c_str()); +} + +std::string get_status(std::string file_name) { + std::ifstream f(file_name); + if (!f.is_open()) { + std::cout << "cannot open " << file_name << std::endl; + throw 0; + } + std::string str; + while (getline(f, str)) { + if (str.find("Status") != std::string::npos) { + vector tokens = split_and_trim(str); + if (tokens.size() != 2) { + std::cout << "unexpected Status string " << str << std::endl; + throw 0; + } + return tokens[1]; + } + } + std::cout << "cannot find the status line in " << file_name << std::endl; + throw 0; +} + +// returns true if the costs should be compared too +bool compare_statuses(std::string glpk_out_file_name, std::string lp_out_file_name, unsigned & successes, unsigned & failures) { + std::string glpk_status = get_status(glpk_out_file_name); + std::string lp_tst_status = get_status(lp_out_file_name); + + if (glpk_status != lp_tst_status) { + if (glpk_status == "UNDEFINED" && (lp_tst_status == "UNBOUNDED" || lp_tst_status == "INFEASIBLE")) { + successes++; + return false; + } else { + std::cout << "glpsol and lp_tst disagree: glpsol status is " << glpk_status; + std::cout << " but lp_tst status is " << lp_tst_status << std::endl; + failures++; + return false; + } + } + return lp_tst_status == "OPTIMAL"; +} + +double get_glpk_cost(std::string file_name) { + std::ifstream f(file_name); + if (!f.is_open()) { + std::cout << "cannot open " << file_name << std::endl; + throw 0; + } + std::string str; + while (getline(f, str)) { + if (str.find("Objective") != std::string::npos) { + vector tokens = split_and_trim(str); + if (tokens.size() != 5) { + std::cout << "unexpected Objective std::string " << str << std::endl; + throw 0; + } + return atof(tokens[3].c_str()); + } + } + std::cout << "cannot find the Objective line in " << file_name << std::endl; + throw 0; +} + +double get_lp_tst_cost(std::string file_name) { + std::ifstream f(file_name); + if (!f.is_open()) { + std::cout << "cannot open " << file_name << std::endl; + throw 0; + } + std::string str; + std::string cost_string; + while (getline(f, str)) { + if (str.find("cost") != std::string::npos) { + cost_string = str; + } + } + if (cost_string.size() == 0) { + std::cout << "cannot find the cost line in " << file_name << std::endl; + throw 0; + } + + vector tokens = split_and_trim(cost_string); + if (tokens.size() != 3) { + std::cout << "unexpected cost string " << cost_string << std::endl; + throw 0; + } + return atof(tokens[2].c_str()); +} + +bool values_are_one_percent_close(double a, double b) { + double maxval = std::max(fabs(a), fabs(b)); + if (maxval < 0.000001) { + return true; + } + + double one_percent = maxval / 100; + return fabs(a - b) <= one_percent; +} + +// returns true if both are optimal +void compare_costs(std::string glpk_out_file_name, + std::string lp_out_file_name, + unsigned & successes, + unsigned & failures) { + double a = get_glpk_cost(glpk_out_file_name); + double b = get_lp_tst_cost(lp_out_file_name); + + if (values_are_one_percent_close(a, b)) { + successes++; + } else { + failures++; + std::cout << "glpsol cost is " << a << " lp_tst cost is " << b << std::endl; + } +} + + + +void compare_with_glpk(std::string glpk_out_file_name, std::string lp_out_file_name, unsigned & successes, unsigned & failures, std::string /*lp_file_name*/) { +#ifdef CHECK_GLPK_SOLUTION + std::unordered_map * solution_table = get_solution_from_glpsol_output(glpk_out_file_name); + if (solution_is_feasible(lp_file_name, *solution_table)) { + std::cout << "glpk solution is feasible" << std::endl; + } else { + std::cout << "glpk solution is infeasible" << std::endl; + } + delete solution_table; +#endif + if (compare_statuses(glpk_out_file_name, lp_out_file_name, successes, failures)) { + compare_costs(glpk_out_file_name, lp_out_file_name, successes, failures); + } +} +void test_lar_on_file(std::string file_name, argument_parser & args_parser); + +void process_test_file(std::string test_dir, std::string test_file_name, argument_parser & args_parser, std::string out_dir, unsigned max_iters, unsigned time_limit, unsigned & successes, unsigned & failures, unsigned & inconclusives) { + bool use_mpq = args_parser.option_is_used("--mpq"); + bool minimize = args_parser.option_is_used("--min"); + std::string full_lp_tst_out_name = out_dir + "/" + create_output_file_name(minimize, test_file_name, use_mpq); + + std::string input_file_name = test_dir + "/" + test_file_name; + if (input_file_name[input_file_name.size() - 1] == '~') { + // std::cout << "ignoring " << input_file_name << std::endl; + return; + } + std::cout <<"processing " << input_file_name << std::endl; + + std::ofstream out(full_lp_tst_out_name); + if (!out.is_open()) { + std::cout << "cannot open file " << full_lp_tst_out_name << std::endl; + throw 0; + } + std::streambuf *coutbuf = std::cout.rdbuf(); // save old buffer + std::cout.rdbuf(out.rdbuf()); // redirect std::cout to dir_entry->d_name! + bool dual = args_parser.option_is_used("--dual"); + try { + if (args_parser.option_is_used("--lar")) + test_lar_on_file(input_file_name, args_parser); + else + solve_mps(input_file_name, minimize, max_iters, time_limit, use_mpq, dual, false, args_parser); + } + catch(...) { + std::cout << "catching the failure" << std::endl; + failures++; + std::cout.rdbuf(coutbuf); // reset to standard output again + return; + } + std::cout.rdbuf(coutbuf); // reset to standard output again + + if (args_parser.option_is_used("--compare_with_glpk")) { + std::string glpk_out_file_name = out_dir + "/" + create_output_file_name_for_glpsol(minimize, std::string(test_file_name)); + int glpk_exit_code = run_glpk(input_file_name, glpk_out_file_name, minimize, time_limit); + if (glpk_exit_code != 0) { + std::cout << "glpk failed" << std::endl; + inconclusives++; + } else { + compare_with_glpk(glpk_out_file_name, full_lp_tst_out_name, successes, failures, input_file_name); + } + } +} +/* + int my_readdir(DIR *dirp, struct dirent * +#ifndef LEAN_WINDOWS + entry +#endif + , struct dirent **result) { +#ifdef LEAN_WINDOWS + *result = readdir(dirp); // NOLINT + return *result != nullptr? 0 : 1; +#else + return readdir_r(dirp, entry, result); +#endif +} +*/ +/* +vector> get_file_list_of_dir(std::string test_file_dir) { + DIR *dir; + if ((dir = opendir(test_file_dir.c_str())) == nullptr) { + std::cout << "Cannot open directory " << test_file_dir << std::endl; + throw 0; + } + vector> ret; + struct dirent entry; + struct dirent* result; + int return_code; + for (return_code = my_readdir(dir, &entry, &result); +#ifndef LEAN_WINDOWS + result != nullptr && +#endif + return_code == 0; + return_code = my_readdir(dir, &entry, &result)) { + DIR *tmp_dp = opendir(result->d_name); + struct stat file_record; + if (tmp_dp == nullptr) { + std::string s = test_file_dir+ "/" + result->d_name; + int stat_ret = stat(s.c_str(), & file_record); + if (stat_ret!= -1) { + ret.push_back(make_pair(result->d_name, file_record.st_size)); + } else { + perror("stat"); + exit(1); + } + } else { + closedir(tmp_dp); + } + } + closedir(dir); + return ret; +} +*/ +/* +struct file_size_comp { + unordered_map& m_file_sizes; + file_size_comp(unordered_map& fs) :m_file_sizes(fs) {} + int operator()(std::string a, std::string b) { + std::cout << m_file_sizes.size() << std::endl; + std::cout << a << std::endl; + std::cout << b << std::endl; + + auto ls = m_file_sizes.find(a); + std::cout << "fa" << std::endl; + auto rs = m_file_sizes.find(b); + std::cout << "fb" << std::endl; + if (ls != m_file_sizes.end() && rs != m_file_sizes.end()) { + std::cout << "fc " << std::endl; + int r = (*ls < *rs? -1: (*ls > *rs)? 1 : 0); + std::cout << "calc r " << std::endl; + return r; + } else { + std::cout << "sc " << std::endl; + return 0; + } + } +}; + +*/ +struct sort_pred { + bool operator()(const std::pair &left, const std::pair &right) { + return left.second < right.second; + } +}; + + +void test_files_from_directory(std::string test_file_dir, argument_parser & args_parser) { + /* + std::cout << "loading files from directory \"" << test_file_dir << "\"" << std::endl; + std::string out_dir = args_parser.get_option_value("--out_dir"); + if (out_dir.size() == 0) { + out_dir = "/tmp/test"; + } + DIR *out_dir_p = opendir(out_dir.c_str()); + if (out_dir_p == nullptr) { + std::cout << "Cannot open output directory \"" << out_dir << "\"" << std::endl; + return; + } + closedir(out_dir_p); + vector> files = get_file_list_of_dir(test_file_dir); + std::sort(files.begin(), files.end(), sort_pred()); + unsigned max_iters, time_limit; + get_time_limit_and_max_iters_from_parser(args_parser, time_limit, max_iters); + unsigned successes = 0, failures = 0, inconclusives = 0; + for (auto & t : files) { + process_test_file(test_file_dir, t.first, args_parser, out_dir, max_iters, time_limit, successes, failures, inconclusives); + } + std::cout << "comparing with glpk: successes " << successes << ", failures " << failures << ", inconclusives " << inconclusives << std::endl; + */ +} + + +std::unordered_map get_solution_map(lp_solver * lps, mps_reader & reader) { + std::unordered_map ret; + for (auto it : reader.column_names()) { + ret[it] = lps->get_column_value_by_name(it); + } + return ret; +} + +void run_lar_solver(argument_parser & args_parser, lar_solver * solver, mps_reader * reader) { + std::string maxng = args_parser.get_option_value("--maxng"); + if (maxng.size() > 0) { + solver->settings().max_number_of_iterations_with_no_improvements = atoi(maxng.c_str()); + } + if (args_parser.option_is_used("-pd")){ + solver->settings().presolve_with_double_solver_for_lar = true; + } + + std::string iter = args_parser.get_option_value("--max_iters"); + if (iter.size() > 0) { + solver->settings().max_total_number_of_iterations = atoi(iter.c_str()); + } + if (args_parser.option_is_used("--compare_with_primal")){ + if (reader == nullptr) { + std::cout << "cannot compare with primal, the reader is null " << std::endl; + return; + } + auto * lps = reader->create_solver(false); + lps->find_maximal_solution(); + std::unordered_map sol = get_solution_map(lps, *reader); + std::cout << "status = " << lp_status_to_string(solver->get_status()) << std::endl; + return; + } + int begin = get_millisecond_count(); + lp_status status = solver->solve(); + std::cout << "status is " << lp_status_to_string(status) << ", processed for " << get_millisecond_span(begin) / 1000.0 <<" seconds, and " << solver->get_total_iterations() << " iterations" << std::endl; + if (solver->get_status() == INFEASIBLE) { + vector> evidence; + solver->get_infeasibility_explanation(evidence); + } + if (args_parser.option_is_used("--randomize_lar")) { + if (solver->get_status() != OPTIMAL) { + std::cout << "cannot check randomize on an infeazible problem" << std::endl; + return; + } + std::cout << "checking randomize" << std::endl; + vector all_vars = solver->get_list_of_all_var_indices(); + unsigned m = all_vars.size(); + if (m > 100) + m = 100; + + var_index *vars = new var_index[m]; + for (unsigned i = 0; i < m; i++) + vars[i]=all_vars[i]; + + solver->random_update(m, vars); + delete []vars; + } +} + +lar_solver * create_lar_solver_from_file(std::string file_name, argument_parser & args_parser) { + if (args_parser.option_is_used("--smt")) { + smt_reader reader(file_name); + reader.read(); + if (!reader.is_ok()){ + std::cout << "cannot process " << file_name << std::endl; + return nullptr; + } + return reader.create_lar_solver(); + } + mps_reader reader(file_name); + reader.read(); + if (!reader.is_ok()) { + std::cout << "cannot process " << file_name << std::endl; + return nullptr; + } + return reader.create_lar_solver(); +} + +void test_lar_on_file(std::string file_name, argument_parser & args_parser) { + lar_solver * solver = create_lar_solver_from_file(file_name, args_parser); + mps_reader reader(file_name); + mps_reader * mps_reader = nullptr; + reader.read(); + if (reader.is_ok()) { + mps_reader = & reader; + run_lar_solver(args_parser, solver, mps_reader); + } + delete solver; +} + +vector get_file_names_from_file_list(std::string filelist) { + std::ifstream file(filelist); + if (!file.is_open()) { + std::cout << "cannot open " << filelist << std::endl; + return vector(); + } + vector ret; + bool end; + do { + std::string s = read_line(end, file); + if (end) + break; + if (s.size() == 0) + break; + ret.push_back(s); + } while (true); + return ret; +} + +void test_lar_solver(argument_parser & args_parser) { + + std::string file_name = args_parser.get_option_value("--file"); + if (file_name.size() > 0) { + test_lar_on_file(file_name, args_parser); + return; + } + + std::string file_list = args_parser.get_option_value("--filelist"); + if (file_list.size() > 0) { + for (std::string fn : get_file_names_from_file_list(file_list)) + test_lar_on_file(fn, args_parser); + return; + } +} + +void test_numeric_pair() { + numeric_pair a; + numeric_pair b(2, lean::mpq(6, 2)); + a = b; + numeric_pair c(0.1, 0.5); + a += 2*c; + a -= c; + lean_assert (a == b + c); + numeric_pair d = a * 2; + std::cout << a << std::endl; + lean_assert(b == b); + lean_assert(b < a); + lean_assert(b <= a); + lean_assert(a > b); + lean_assert(a != b); + lean_assert(a >= b); + lean_assert(-a < b); + lean_assert(a < 2 * b); + lean_assert(b + b > a); + lean_assert(lean::mpq(2.1) * b + b > a); + lean_assert(-b * lean::mpq(2.1) - b < lean::mpq(0.99) * a); + std::cout << - b * lean::mpq(2.1) - b << std::endl; + lean_assert(-b *(lean::mpq(2.1) + 1) == - b * lean::mpq(2.1) - b); +} + +void get_matrix_dimensions(std::ifstream & f, unsigned & m, unsigned & n) { + std::string line; + getline(f, line); + getline(f, line); + vector r = split_and_trim(line); + m = atoi(r[1].c_str()); + getline(f, line); + r = split_and_trim(line); + n = atoi(r[1].c_str()); +} + +void read_row_cols(unsigned i, static_matrix& A, std::ifstream & f) { + do { + std::string line; + getline(f, line); + if (line== "row_end") + break; + auto r = split_and_trim(line); + lean_assert(r.size() == 4); + unsigned j = atoi(r[1].c_str()); + double v = atof(r[3].c_str()); + A.set(i, j, v); + } while (true); +} + +bool read_row(static_matrix & A, std::ifstream & f) { + std::string line; + getline(f, line); + if (static_cast(line.find("row")) == -1) + return false; + auto r = split_and_trim(line); + if (r[0] != "row") + std::cout << "wrong row line" << line << std::endl; + unsigned i = atoi(r[1].c_str()); + read_row_cols(i, A, f); + return true; +} + +void read_rows(static_matrix& A, std::ifstream & f) { + while (read_row(A, f)) {} +} + +void read_basis(vector & basis, std::ifstream & f) { + std::cout << "reading basis" << std::endl; + std::string line; + getline(f, line); + lean_assert(line == "basis_start"); + do { + getline(f, line); + if (line == "basis_end") + break; + unsigned j = atoi(line.c_str()); + basis.push_back(j); + } while (true); +} + +void read_indexed_vector(indexed_vector & v, std::ifstream & f) { + std::string line; + getline(f, line); + lean_assert(line == "vector_start"); + do { + getline(f, line); + if (line == "vector_end") break; + auto r = split_and_trim(line); + unsigned i = atoi(r[0].c_str()); + double val = atof(r[1].c_str()); + v.set_value(val, i); + std::cout << "setting value " << i << " = " << val << std::endl; + } while (true); +} + +void check_lu_from_file(std::string lufile_name) { + std::ifstream f(lufile_name); + if (!f.is_open()) { + std::cout << "cannot open file " << lufile_name << std::endl; + } + unsigned m, n; + get_matrix_dimensions(f, m, n); + std::cout << "init matrix " << m << " by " << n << std::endl; + static_matrix A(m, n); + read_rows(A, f); + vector basis; + read_basis(basis, f); + indexed_vector v(m); + // read_indexed_vector(v, f); + f.close(); + vector basis_heading; + lp_settings settings; + vector non_basic_columns; + lu lsuhl(A, basis, settings); + indexed_vector d(A.row_count()); + unsigned entering = 26; + lsuhl.solve_Bd(entering, d, v); +#ifdef LEAN_DEBUG + auto B = get_B(lsuhl, basis); + vector a(m); + A.copy_column_to_vector(entering, a); + indexed_vector cd(d); + B.apply_from_left(cd.m_data, settings); + lean_assert(vectors_are_equal(cd.m_data , a)); +#endif +} + +void test_square_dense_submatrix() { + std::cout << "testing square_dense_submatrix" << std::endl; + unsigned parent_dim = 7; + sparse_matrix parent(parent_dim); + fill_matrix(parent); + unsigned index_start = 3; + square_dense_submatrix d; + d.init(&parent, index_start); + for (unsigned i = index_start; i < parent_dim; i++) + for (unsigned j = index_start; j < parent_dim; j++) + d[i][j] = i*3+j*2; +#ifdef LEAN_DEBUG + unsigned dim = parent_dim - index_start; + dense_matrix m(dim, dim); + for (unsigned i = index_start; i < parent_dim; i++) + for (unsigned j = index_start; j < parent_dim; j++) + m[i-index_start][j-index_start] = d[i][j]; + print_matrix(&m, std::cout); +#endif + for (unsigned i = index_start; i < parent_dim; i++) + for (unsigned j = index_start; j < parent_dim; j++) + d[i][j] = d[j][i]; +#ifdef LEAN_DEBUG + for (unsigned i = index_start; i < parent_dim; i++) + for (unsigned j = index_start; j < parent_dim; j++) + m[i-index_start][j-index_start] = d[i][j]; + + print_matrix(&m, std::cout); + std::cout << std::endl; +#endif +} + + + +void print_st(lp_status status) { + std::cout << lp_status_to_string(status) << std::endl; +} + + + +void test_term() { + lar_solver solver; + unsigned _x = 0; + unsigned _y = 1; + var_index x = solver.add_var(_x); + var_index y = solver.add_var(_y); + + vector> term_ls; + term_ls.push_back(std::pair((int)1, x)); + term_ls.push_back(std::pair((int)1, y)); + var_index z = solver.add_term(term_ls, mpq(3)); + + vector> ls; + ls.push_back(std::pair((int)1, x)); + ls.push_back(std::pair((int)1, y)); + ls.push_back(std::pair((int)1, z)); + + solver.add_constraint(ls, lconstraint_kind::EQ, mpq(0)); + auto status = solver.solve(); + std::cout << lp_status_to_string(status) << std::endl; + std::unordered_map model; + solver.get_model(model); + + for (auto & t : model) { + std::cout << solver.get_variable_name(t.first) << " = " << t.second.get_double() << ","; + } + std::cout << std::endl; + +} + +void test_evidence_for_total_inf_simple(argument_parser & args_parser) { + lar_solver solver; + var_index x = solver.add_var(0); + var_index y = solver.add_var(1); + solver.add_var_bound(x, LE, -mpq(1)); + solver.add_var_bound(y, GE, mpq(0)); + vector> ls; + + ls.push_back(std::pair((int)1, x)); + ls.push_back(std::pair((int)1, y)); + solver.add_constraint(ls, GE, mpq(1)); + ls.pop_back(); + ls.push_back(std::pair(-(int)1, y)); + solver.add_constraint(ls, lconstraint_kind::GE, mpq(0)); + auto status = solver.solve(); + std::cout << lp_status_to_string(status) << std::endl; + std::unordered_map model; + lean_assert(solver.get_status() == INFEASIBLE); +} +void test_bound_propagation_one_small_sample1() { + /* +(<= (+ a (* (- 1.0) b)) 0.0) +(<= (+ b (* (- 1.0) x_13)) 0.0) +--> (<= (+ a (* (- 1.0) c)) 0.0) + +the inequality on (<= a c) is obtained from a triangle inequality (<= a b) (<= b c). +If b becomes basic variable, then it is likely the old solver ends up with a row that implies (<= a c). + a - b <= 0.0 + b - c <= 0.0 + + got to get a <= c + */ + std::function bound_is_relevant = + [&](unsigned j, bool is_low_bound, bool strict, const rational& bound_val) { + return true; + }; + lar_solver ls; + unsigned a = ls.add_var(0); + unsigned b = ls.add_var(1); + unsigned c = ls.add_var(2); + vector> coeffs; + coeffs.push_back(std::pair(1, a)); + coeffs.push_back(std::pair(-1, c)); + ls.add_term(coeffs, zero_of_type()); + coeffs.pop_back(); + coeffs.push_back(std::pair(-1, b)); + ls.add_term(coeffs, zero_of_type()); + coeffs.clear(); + coeffs.push_back(std::pair(1, a)); + coeffs.push_back(std::pair(-1, b)); + ls.add_constraint(coeffs, LE, zero_of_type()); + coeffs.clear(); + coeffs.push_back(std::pair(1, b)); + coeffs.push_back(std::pair(-1, c)); + ls.add_constraint(coeffs, LE, zero_of_type()); + vector ev; + ls.add_var_bound(a, LE, mpq(1)); + ls.solve(); + bound_propagator bp(ls); + ls.propagate_bounds_for_touched_rows(bp); + std::cout << " bound ev from test_bound_propagation_one_small_sample1" << std::endl; + for (auto & be : bp.m_ibounds) { + std::cout << "bound\n"; + ls.print_implied_bound(be, std::cout); + } +} + +void test_bound_propagation_one_small_samples() { + test_bound_propagation_one_small_sample1(); + /* + (>= x_46 0.0) +(<= x_29 0.0) +(not (<= x_68 0.0)) +(<= (+ (* (/ 1001.0 1998.0) x_10) (* (- 1.0) x_151) x_68) (- (/ 1001.0 999.0))) +(<= (+ (* (/ 1001.0 999.0) x_9) + (* (- 1.0) x_152) + (* (/ 1001.0 999.0) x_151) + (* (/ 1001.0 999.0) x_68)) + (- (/ 1502501.0 999000.0))) +(not (<= (+ (* (/ 999.0 2.0) x_10) (* (- 1.0) x_152) (* (- (/ 999.0 2.0)) x_151)) + (/ 1001.0 2.0))) +(not (<= x_153 0.0))z +(>= (+ x_9 (* (- (/ 1001.0 999.0)) x_10) (* (- 1.0) x_153) (* (- 1.0) x_68)) + (/ 5003.0 1998.0)) +--> (not (<= (+ x_10 x_46 (* (- 1.0) x_29)) 0.0)) + +and + +(<= (+ a (* (- 1.0) b)) 0.0) +(<= (+ b (* (- 1.0) x_13)) 0.0) +--> (<= (+ a (* (- 1.0) x_13)) 0.0) + +In the first case, there typically are no atomic formulas for bounding x_10. So there is never some +basic lemma of the form (>= x46 0), (<= x29 0), (>= x10 0) -> (not (<= (+ x10 x46 (- x29)) 0)). +Instead the bound on x_10 falls out from a bigger blob of constraints. + +In the second case, the inequality on (<= x19 x13) is obtained from a triangle inequality (<= x19 x9) (<= x9 x13). +If x9 becomes basic variable, then it is likely the old solver ends up with a row that implies (<= x19 x13). + */ +} +void test_bound_propagation_one_row() { + lar_solver ls; + unsigned x0 = ls.add_var(0); + unsigned x1 = ls.add_var(1); + vector> c; + c.push_back(std::pair(1, x0)); + c.push_back(std::pair(-1, x1)); + ls.add_constraint(c, EQ, one_of_type()); + vector ev; + ls.add_var_bound(x0, LE, mpq(1)); + ls.solve(); + bound_propagator bp(ls); + ls.propagate_bounds_for_touched_rows(bp); +} +void test_bound_propagation_one_row_with_bounded_vars() { + lar_solver ls; + unsigned x0 = ls.add_var(0); + unsigned x1 = ls.add_var(1); + vector> c; + c.push_back(std::pair(1, x0)); + c.push_back(std::pair(-1, x1)); + ls.add_constraint(c, EQ, one_of_type()); + vector ev; + ls.add_var_bound(x0, GE, mpq(-3)); + ls.add_var_bound(x0, LE, mpq(3)); + ls.add_var_bound(x0, LE, mpq(1)); + ls.solve(); + bound_propagator bp(ls); + ls.propagate_bounds_for_touched_rows(bp); +} +void test_bound_propagation_one_row_mixed() { + lar_solver ls; + unsigned x0 = ls.add_var(0); + unsigned x1 = ls.add_var(1); + vector> c; + c.push_back(std::pair(1, x0)); + c.push_back(std::pair(-1, x1)); + ls.add_constraint(c, EQ, one_of_type()); + vector ev; + ls.add_var_bound(x1, LE, mpq(1)); + ls.solve(); + bound_propagator bp(ls); + ls.propagate_bounds_for_touched_rows(bp); +} + +void test_bound_propagation_two_rows() { + lar_solver ls; + unsigned x = ls.add_var(0); + unsigned y = ls.add_var(1); + unsigned z = ls.add_var(2); + vector> c; + c.push_back(std::pair(1, x)); + c.push_back(std::pair(2, y)); + c.push_back(std::pair(3, z)); + ls.add_constraint(c, GE, one_of_type()); + c.clear(); + c.push_back(std::pair(3, x)); + c.push_back(std::pair(2, y)); + c.push_back(std::pair(1, z)); + ls.add_constraint(c, GE, one_of_type()); + ls.add_var_bound(x, LE, mpq(2)); + vector ev; + ls.add_var_bound(y, LE, mpq(1)); + ls.solve(); + bound_propagator bp(ls); + ls.propagate_bounds_for_touched_rows(bp); +} + +void test_total_case_u() { + std::cout << "test_total_case_u\n"; + lar_solver ls; + unsigned x = ls.add_var(0); + unsigned y = ls.add_var(1); + unsigned z = ls.add_var(2); + vector> c; + c.push_back(std::pair(1, x)); + c.push_back(std::pair(2, y)); + c.push_back(std::pair(3, z)); + ls.add_constraint(c, LE, one_of_type()); + ls.add_var_bound(x, GE, zero_of_type()); + ls.add_var_bound(y, GE, zero_of_type()); + vector ev; + ls.add_var_bound(z, GE, zero_of_type()); + ls.solve(); + bound_propagator bp(ls); + ls.propagate_bounds_for_touched_rows(bp); +} +bool contains_j_kind(unsigned j, lconstraint_kind kind, const mpq & rs, const vector & ev) { + for (auto & e : ev) { + if (e.m_j == j && e.m_bound == rs && e.kind() == kind) + return true; + } + return false; +} +void test_total_case_l(){ + std::cout << "test_total_case_l\n"; + lar_solver ls; + unsigned x = ls.add_var(0); + unsigned y = ls.add_var(1); + unsigned z = ls.add_var(2); + vector> c; + c.push_back(std::pair(1, x)); + c.push_back(std::pair(2, y)); + c.push_back(std::pair(3, z)); + ls.add_constraint(c, GE, one_of_type()); + ls.add_var_bound(x, LE, one_of_type()); + ls.add_var_bound(y, LE, one_of_type()); + ls.settings().presolve_with_double_solver_for_lar = true; + vector ev; + ls.add_var_bound(z, LE, zero_of_type()); + ls.solve(); + bound_propagator bp(ls); + ls.propagate_bounds_for_touched_rows(bp); + lean_assert(ev.size() == 4); + lean_assert(contains_j_kind(x, GE, - one_of_type(), ev)); +} +void test_bound_propagation() { + test_total_case_u(); + test_bound_propagation_one_small_samples(); + test_bound_propagation_one_row(); + test_bound_propagation_one_row_with_bounded_vars(); + test_bound_propagation_two_rows(); + test_bound_propagation_one_row_mixed(); + test_total_case_l(); + +} + +void test_int_set() { + int_set s(4); + s.insert(2); + s.print(std::cout); + s.insert(1); + s.insert(2); + s.print(std::cout); + lean_assert(s.contains(2)); + lean_assert(s.size() == 2); + s.erase(2); + lean_assert(s.size() == 1); + s.erase(2); + lean_assert(s.size() == 1); + s.print(std::cout); + s.insert(3); + s.insert(2); + s.clear(); + lean_assert(s.size() == 0); + + +} + +void test_rationals_no_numeric_pairs() { + stopwatch sw; + + vector c; + for (unsigned j = 0; j < 10; j ++) + c.push_back(mpq(my_random()%100, 1 + my_random()%100 )); + + vector x; + for (unsigned j = 0; j < 10; j ++) + x.push_back(mpq(my_random()%100, 1 + my_random()%100 )); + + unsigned k = 500000; + mpq r=zero_of_type(); + sw.start(); + + for (unsigned j = 0; j < k; j++){ + mpq val = zero_of_type(); + for (unsigned j=0;j< c.size(); j++){ + val += c[j]*x[j]; + } + + r += val; + } + + sw.stop(); + std::cout << "operation with rationals no pairs " << sw.get_seconds() << std::endl; + std::cout << T_to_string(r) << std::endl; +} + +void test_rationals_no_numeric_pairs_plus() { + stopwatch sw; + + vector c; + for (unsigned j = 0; j < 10; j ++) + c.push_back(mpq(my_random()%100, 1 + my_random()%100 )); + + vector x; + for (unsigned j = 0; j < 10; j ++) + x.push_back(mpq(my_random()%100, 1 + my_random()%100 )); + + unsigned k = 500000; + mpq r=zero_of_type(); + sw.start(); + + for (unsigned j = 0; j < k; j++){ + mpq val = zero_of_type(); + for (unsigned j=0;j< c.size(); j++){ + val = val + c[j]*x[j]; + } + + r = r + val; + } + + sw.stop(); + std::cout << "operation with rationals no pairs " << sw.get_seconds() << std::endl; + std::cout << T_to_string(r) << std::endl; +} + + + +void test_rationals() { + stopwatch sw; + + vector c; + for (unsigned j = 0; j < 10; j ++) + c.push_back(mpq(my_random()%100, 1 + my_random()%100)); + + + + vector> x; + for (unsigned j = 0; j < 10; j ++) + x.push_back(mpq(my_random()%100, 1 + my_random()%100 )); + + std::cout << "x = "; + print_vector(x, std::cout); + + unsigned k = 1000000; + numeric_pair r=zero_of_type>(); + sw.start(); + + for (unsigned j = 0; j < k; j++) { + for (unsigned i = 0; i < c.size(); i++) { + r+= c[i] * x[i]; + } + } + sw.stop(); + std::cout << "operation with rationals " << sw.get_seconds() << std::endl; + std::cout << T_to_string(r) << std::endl; +} + +void test_lp_local(int argn, char**argv) { + std::cout << "resize\n"; + vector r; + r.resize(1); + + // initialize_util_module(); + // initialize_numerics_module(); + int ret; + argument_parser args_parser(argn, argv); + setup_args_parser(args_parser); + if (!args_parser.parse()) { + std::cout << args_parser.m_error_message << std::endl; + std::cout << args_parser.usage_string(); + ret = 1; + return finalize(ret); + } + + args_parser.print(); + + if (args_parser.option_is_used("--test_mpq")) { + test_rationals(); + return finalize(0); + } + + if (args_parser.option_is_used("--test_mpq_np")) { + test_rationals_no_numeric_pairs(); + return finalize(0); + } + + if (args_parser.option_is_used("--test_mpq_np_plus")) { + test_rationals_no_numeric_pairs_plus(); + return finalize(0); + } + + + + if (args_parser.option_is_used("--test_int_set")) { + test_int_set(); + return finalize(0); + } + if (args_parser.option_is_used("--bp")) { + test_bound_propagation(); + return finalize(0); + } + + + std::string lufile = args_parser.get_option_value("--checklu"); + if (lufile.size()) { + check_lu_from_file(lufile); + return finalize(0); + } + +#ifdef LEAN_DEBUG + if (args_parser.option_is_used("--test_swaps")) { + sparse_matrix m(10); + fill_matrix(m); + test_swap_rows_with_permutation(m); + test_swap_cols_with_permutation(m); + return finalize(0); + } +#endif + if (args_parser.option_is_used("--test_perm")) { + test_permutations(); + return finalize(0); + } + if (args_parser.option_is_used("--test_file_directory")) { + test_files_from_directory(args_parser.get_option_value("--test_file_directory"), args_parser); + return finalize(0); + } + std::string file_list = args_parser.get_option_value("--filelist"); + if (file_list.size() > 0) { + for (std::string fn : get_file_names_from_file_list(file_list)) + solve_mps(fn, args_parser); + return finalize(0); + } + + if (args_parser.option_is_used("-tbq")) { + test_binary_priority_queue(); + ret = 0; + return finalize(ret); + } + +#ifdef LEAN_DEBUG + lp_settings settings; + update_settings(args_parser, settings); + if (args_parser.option_is_used("--test_lu")) { + test_lu(settings); + ret = 0; + return finalize(ret); + } + + if (args_parser.option_is_used("--test_small_lu")) { + test_small_lu(settings); + ret = 0; + return finalize(ret); + } + + if (args_parser.option_is_used("--lar")){ + std::cout <<"calling test_lar_solver" << std::endl; + test_lar_solver(args_parser); + return finalize(0); + } + + + + if (args_parser.option_is_used("--test_larger_lu")) { + test_larger_lu(settings); + ret = 0; + return finalize(ret); + } + + if (args_parser.option_is_used("--test_larger_lu_with_holes")) { + test_larger_lu_with_holes(settings); + ret = 0; + return finalize(ret); + } +#endif + if (args_parser.option_is_used("--eti")) { + test_evidence_for_total_inf_simple(args_parser); + ret = 0; + return finalize(ret); + } + + + if (args_parser.option_is_used("--test_lp_0")) { + test_lp_0(); + ret = 0; + return finalize(ret); + } + + if (args_parser.option_is_used("--smap")) { + test_stacked(); + ret = 0; + return finalize(ret); + } + if (args_parser.option_is_used("--term")) { + test_term(); + ret = 0; + return finalize(ret); + } + unsigned max_iters; + unsigned time_limit; + get_time_limit_and_max_iters_from_parser(args_parser, time_limit, max_iters); + bool dual = args_parser.option_is_used("--dual"); + bool solve_for_rational = args_parser.option_is_used("--mpq"); + std::string file_name = args_parser.get_option_value("--file"); + if (file_name.size() > 0) { + solve_mps(file_name, args_parser.option_is_used("--min"), max_iters, time_limit, solve_for_rational, dual, args_parser.option_is_used("--compare_with_primal"), args_parser); + ret = 0; + return finalize(ret); + } + + if (args_parser.option_is_used("--solve_some_mps")) { +#if _LINUX_ + solve_some_mps(args_parser); +#endif + ret = 0; + return finalize(ret); + } + // lean::ccc = 0; + return finalize(0); + test_init_U(); + test_replace_column(); +#ifdef LEAN_DEBUG + sparse_matrix_with_permutaions_test(); + test_dense_matrix(); + test_swap_operations(); + test_permutations(); + test_pivot_like_swaps_and_pivot(); +#endif + tst1(); + std::cout << "done with LP tests\n"; + return finalize(0); // has_violations() ? 1 : 0); +} +} +void tst_lp(char ** argv, int argc, int& i) { + lean::test_lp_local(argc - 2, argv + 2); +} diff --git a/src/test/lp_main.cpp b/src/test/lp_main.cpp new file mode 100644 index 000000000..a301f38c6 --- /dev/null +++ b/src/test/lp_main.cpp @@ -0,0 +1,14 @@ +void gparams_register_modules(){} +void mem_initialize() {} +void mem_finalize() {} +#include "util/rational.h" +namespace lean { +void test_lp_local(int argc, char**argv); +} +int main(int argn, char**argv){ + rational::initialize(); + lean::test_lp_local(argn, argv); + rational::finalize(); + return 0; +} + diff --git a/src/test/smt_reader.h b/src/test/smt_reader.h new file mode 100644 index 000000000..481985ed4 --- /dev/null +++ b/src/test/smt_reader.h @@ -0,0 +1,392 @@ +/* + Copyright (c) 2013 Microsoft Corporation. All rights reserved. + Released under Apache 2.0 license as described in the file LICENSE. + + Author: Lev Nachmanson +*/ + +#pragma once + +// reads an MPS file reperesenting a Mixed Integer Program +#include +#include +#include +#include "util/lp/lp_primal_simplex.h" +#include "util/lp/lp_dual_simplex.h" +#include "util/lp/lar_solver.h" +#include +#include +#include +#include +#include "util/lp/mps_reader.h" +#include "util/lp/ul_pair.h" +#include "util/lp/lar_constraints.h" +#include +#include +namespace lean { + + template + T from_string(const std::string& str) { + std::istringstream ss(str); + T ret; + ss >> ret; + return ret; + } + + class smt_reader { + public: + struct lisp_elem { + std::string m_head; + std::vector m_elems; + void print() { + if (m_elems.size()) { + std::cout << '('; + std::cout << m_head << ' '; + for (auto & el : m_elems) + el.print(); + + std::cout << ')'; + } else { + std::cout << " " << m_head; + } + } + unsigned size() const { return static_cast(m_elems.size()); } + bool is_simple() const { return size() == 0; } + }; + struct formula_constraint { + lconstraint_kind m_kind; + std::vector> m_coeffs; + mpq m_right_side = numeric_traits::zero(); + void add_pair(mpq c, std::string name) { + m_coeffs.push_back(make_pair(c, name)); + } + }; + + lisp_elem m_formula_lisp_elem; + + std::unordered_map m_name_to_var_index; + std::vector m_constraints; + std::string m_file_name; + std::ifstream m_file_stream; + std::string m_line; + bool m_is_OK = true; + unsigned m_line_number = 0; + smt_reader(std::string file_name): + m_file_name(file_name), m_file_stream(file_name) { + } + + void set_error() { + std::cout << "setting error" << std::endl; + m_is_OK = false; + } + + bool is_ok() { + return m_is_OK; + } + + bool prefix(const char * pr) { + return m_line.find(pr) == 0; + } + + int first_separator() { + unsigned blank_pos = static_cast(m_line.find(' ')); + unsigned br_pos = static_cast(m_line.find('(')); + unsigned reverse_br_pos = static_cast(m_line.find(')')); + return std::min(blank_pos, std::min(br_pos, reverse_br_pos)); + } + + void fill_lisp_elem(lisp_elem & lm) { + if (m_line[0] == '(') + fill_nested_elem(lm); + else + fill_simple_elem(lm); + } + + void fill_simple_elem(lisp_elem & lm) { + int separator = first_separator(); + lean_assert(-1 != separator && separator != 0); + lm.m_head = m_line.substr(0, separator); + m_line = m_line.substr(separator); + } + + void fill_nested_elem(lisp_elem & lm) { + lean_assert(m_line[0] == '('); + m_line = m_line.substr(1); + int separator = first_separator(); + lm.m_head = m_line.substr(0, separator); + m_line = m_line.substr(lm.m_head.size()); + eat_blanks(); + while (m_line.size()) { + if (m_line[0] == '(') { + lisp_elem el; + fill_nested_elem(el); + lm.m_elems.push_back(el); + } else { + if (m_line[0] == ')') { + m_line = m_line.substr(1); + break; + } + lisp_elem el; + fill_simple_elem(el); + lm.m_elems.push_back(el); + } + eat_blanks(); + } + } + + void eat_blanks() { + while (m_line.size()) { + if (m_line[0] == ' ') + m_line = m_line.substr(1); + else + break; + } + } + + void fill_formula_elem() { + fill_lisp_elem(m_formula_lisp_elem); + } + + void parse_line() { + if (m_line.find(":formula") == 0) { + int first_br = static_cast(m_line.find('(')); + if (first_br == -1) { + std::cout << "empty formula" << std::endl; + return; + } + m_line = m_line.substr(first_br); + fill_formula_elem(); + } + } + + void set_constraint_kind(formula_constraint & c, lisp_elem & el) { + if (el.m_head == "=") { + c.m_kind = EQ; + } else if (el.m_head == ">=") { + c.m_kind = GE; + } else if (el.m_head == "<=") { + c.m_kind = LE; + } else if (el.m_head == ">") { + c.m_kind = GT; + } else if (el.m_head == "<") { + c.m_kind = LT; + } else { + std::cout << "kind " << el.m_head << " is not supported " << std::endl; + set_error(); + } + } + + void adjust_rigth_side(formula_constraint & /* c*/, lisp_elem & /*el*/) { + // lean_assert(el.m_head == "0"); // do nothing for the time being + } + + void set_constraint_coeffs(formula_constraint & c, lisp_elem & el) { + lean_assert(el.m_elems.size() == 2); + set_constraint_coeffs_on_coeff_element(c, el.m_elems[0]); + adjust_rigth_side(c, el.m_elems[1]); + } + + + bool is_integer(std::string & s) { + if (s.size() == 0) return false; + return atoi(s.c_str()) != 0 || isdigit(s.c_str()[0]); + } + + void add_complex_sum_elem(formula_constraint & c, lisp_elem & el) { + if (el.m_head == "*") { + add_mult_elem(c, el.m_elems); + } else if (el.m_head == "~") { + lisp_elem & minel = el.m_elems[0]; + lean_assert(minel.is_simple()); + c.m_right_side += mpq(str_to_int(minel.m_head)); + } else { + std::cout << "unexpected input " << el.m_head << std::endl; + set_error(); + return; + } + } + + std::string get_name(lisp_elem & name) { + lean_assert(name.is_simple()); + lean_assert(!is_integer(name.m_head)); + return name.m_head; + } + + + void add_mult_elem(formula_constraint & c, std::vector & els) { + lean_assert(els.size() == 2); + mpq coeff = get_coeff(els[0]); + std::string col_name = get_name(els[1]); + c.add_pair(coeff, col_name); + } + + mpq get_coeff(lisp_elem & le) { + if (le.is_simple()) { + return mpq(str_to_int(le.m_head)); + } else { + lean_assert(le.m_head == "~"); + lean_assert(le.size() == 1); + lisp_elem & el = le.m_elems[0]; + lean_assert(el.is_simple()); + return -mpq(str_to_int(el.m_head)); + } + } + + int str_to_int(std::string & s) { + lean_assert(is_integer(s)); + return atoi(s.c_str()); + } + + void add_sum_elem(formula_constraint & c, lisp_elem & el) { + if (el.size()) { + add_complex_sum_elem(c, el); + } else { + lean_assert(is_integer(el.m_head)); + int v = atoi(el.m_head.c_str()); + mpq vr(v); + c.m_right_side -= vr; + } + } + + void add_sum(formula_constraint & c, std::vector & sum_els) { + for (auto & el : sum_els) + add_sum_elem(c, el); + } + + void set_constraint_coeffs_on_coeff_element(formula_constraint & c, lisp_elem & el) { + if (el.m_head == "*") { + add_mult_elem(c, el.m_elems); + } else if (el.m_head == "+") { + add_sum(c, el.m_elems); + } else { + lean_assert(false); // unexpected input + } + } + + void create_constraint(lisp_elem & el) { + formula_constraint c; + set_constraint_kind(c, el); + set_constraint_coeffs(c, el); + m_constraints.push_back(c); + } + + void fill_constraints() { + if (m_formula_lisp_elem.m_head != "and") { + std::cout << "unexpected top element " << m_formula_lisp_elem.m_head << std::endl; + set_error(); + return; + } + for (auto & el : m_formula_lisp_elem.m_elems) + create_constraint(el); + } + + void read() { + if (!m_file_stream.is_open()){ + std::cout << "cannot open file " << m_file_name << std::endl; + set_error(); + return; + } + while (m_is_OK && getline(m_file_stream, m_line)) { + parse_line(); + m_line_number++; + } + + m_file_stream.close(); + fill_constraints(); + } + + /* + void fill_lar_solver_on_row(row * row, lar_solver *solver) { + if (row->m_name != m_cost_row_name) { + lar_constraint c(get_lar_relation_from_row(row->m_type), row->m_right_side); + for (auto s : row->m_row_columns) { + var_index i = solver->add_var(s.first); + c.add_variable_to_constraint(i, s.second); + } + solver->add_constraint(&c); + } else { + // ignore the cost row + } + } + + + void fill_lar_solver_on_rows(lar_solver * solver) { + for (auto row_it : m_rows) { + fill_lar_solver_on_row(row_it.second, solver); + } + } + + void create_low_constraint_for_var(column* col, bound * b, lar_solver *solver) { + lar_constraint c(GE, b->m_low); + var_index i = solver->add_var(col->m_name); + c.add_variable_to_constraint(i, numeric_traits::one()); + solver->add_constraint(&c); + } + + void create_upper_constraint_for_var(column* col, bound * b, lar_solver *solver) { + lar_constraint c(LE, b->m_upper); + var_index i = solver->add_var(col->m_name); + c.add_variable_to_constraint(i, numeric_traits::one()); + solver->add_constraint(&c); + } + + void create_equality_contraint_for_var(column* col, bound * b, lar_solver *solver) { + lar_constraint c(EQ, b->m_fixed_value); + var_index i = solver->add_var(col->m_name); + c.add_variable_to_constraint(i, numeric_traits::one()); + solver->add_constraint(&c); + } + + void fill_lar_solver_on_columns(lar_solver * solver) { + for (auto s : m_columns) { + mps_reader::column * col = s.second; + solver->add_var(col->m_name); + auto b = col->m_bound; + if (b == nullptr) return; + + if (b->m_free) continue; + + if (b->m_low_is_set) { + create_low_constraint_for_var(col, b, solver); + } + if (b->m_upper_is_set) { + create_upper_constraint_for_var(col, b, solver); + } + if (b->m_value_is_fixed) { + create_equality_contraint_for_var(col, b, solver); + } + } + } + */ + + unsigned register_name(std::string s) { + auto it = m_name_to_var_index.find(s); + if (it!= m_name_to_var_index.end()) + return it->second; + + unsigned ret= m_name_to_var_index.size(); + m_name_to_var_index[s] = ret; + return ret; + } + + void add_constraint_to_solver(lar_solver * solver, formula_constraint & fc) { + vector> ls; + for (auto & it : fc.m_coeffs) { + ls.push_back(std::make_pair(it.first, solver->add_var(register_name(it.second)))); + } + solver->add_constraint(ls, fc.m_kind, fc.m_right_side); + } + + void fill_lar_solver(lar_solver * solver) { + for (formula_constraint & fc : m_constraints) + add_constraint_to_solver(solver, fc); + } + + + lar_solver * create_lar_solver() { + lar_solver * ls = new lar_solver(); + fill_lar_solver(ls); + return ls; + } + }; +} diff --git a/src/test/test_file_reader.h b/src/test/test_file_reader.h new file mode 100644 index 000000000..c7a9e3b8b --- /dev/null +++ b/src/test/test_file_reader.h @@ -0,0 +1,73 @@ +/* +Copyright (c) 2013 Microsoft Corporation. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. + +Author: Lev Nachmanson +*/ +#pragma once + +// reads a text file +#include +#include +#include +#include +#include +#include "util/lp/lp_utils.h" +#include "util/lp/lp_solver.h" + +namespace lean { + +template +struct test_result { + lp_status m_status; + T m_cost; + std::unordered_map column_values; +}; + +template +class test_file_reader { + struct raw_blob { + std::vector m_unparsed_strings; + std::vector m_blobs; + }; + + struct test_file_blob { + std::string m_name; + std::string m_content; + std::unordered_map m_table; + std::unordered_map m_blobs; + + test_result * get_test_result() { + test_result * tr = new test_result(); + throw "not impl"; + return tr; + } + }; + std::ifstream m_file_stream; +public: + // constructor + test_file_reader(std::string file_name) : m_file_stream(file_name) { + if (!m_file_stream.is_open()) { + std::cout << "cannot open file " << "\'" << file_name << "\'" << std::endl; + } + } + + raw_blob scan_to_row_blob() { + } + + test_file_blob scan_row_blob_to_test_file_blob(raw_blob /* rblob */) { + } + + test_result * get_test_result() { + if (!m_file_stream.is_open()) { + return nullptr; + } + + raw_blob rblob = scan_to_row_blob(); + + test_file_blob tblob = scan_row_blob_to_test_file_blob(rblob); + + return tblob.get_test_result(); + } +}; +} diff --git a/src/util/lp/lp_params.pyg b/src/util/lp/lp_params.pyg new file mode 100644 index 000000000..3d31d0bdf --- /dev/null +++ b/src/util/lp/lp_params.pyg @@ -0,0 +1,13 @@ +def_module_params('lp', + export=True, + params=( + ('rep_freq', UINT, 0, 'the report frequency, in how many iterations print the cost and other info '), + ('min', BOOL, False, 'minimize cost'), + ('presolve_with_dbl', BOOL, True, 'presolve with double'), + ('print_stats', BOOL, False, 'print statistic'), + ('simplex_strategy', UINT, 0, 'simplex strategy for the solver'), + ('bprop_on_pivoted_rows', BOOL, True, 'propagate bounds on rows changed by the pivot operation') + )) + + + From 561522f882f6467864f25ae65b8d8a9136b89586 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Tue, 9 May 2017 14:20:13 -0700 Subject: [PATCH 403/410] missing file Signed-off-by: Nikolaj Bjorner --- src/shell/lp_frontend.h | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 src/shell/lp_frontend.h diff --git a/src/shell/lp_frontend.h b/src/shell/lp_frontend.h new file mode 100644 index 000000000..68e6a736e --- /dev/null +++ b/src/shell/lp_frontend.h @@ -0,0 +1,8 @@ +/* + Copyright (c) 2013 Microsoft Corporation. All rights reserved. + Released under Apache 2.0 license as described in the file LICENSE. + + Author: Lev Nachmanson +*/ +#pragma once +unsigned read_mps_file(char const * mps_file_name); From 7731163d1193fdc955d4c774f2c797488374aca0 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Tue, 9 May 2017 14:42:43 -0700 Subject: [PATCH 404/410] use forward slash on src include Signed-off-by: Nikolaj Bjorner --- scripts/mk_util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/mk_util.py b/scripts/mk_util.py index edaffcd1e..8f024c224 100644 --- a/scripts/mk_util.py +++ b/scripts/mk_util.py @@ -1004,7 +1004,7 @@ class Component: out.write('%s =' % include_defs) for dep in self.deps: out.write(' -I%s' % get_component(dep).to_src_dir) - out.write(' -I..\src') + out.write(' -I../src') out.write('\n') mk_dir(os.path.join(BUILD_DIR, self.build_dir)) if VS_PAR and IS_WINDOWS: From f12f83af838253bdf46cef45e368497246e630d6 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Tue, 9 May 2017 14:56:38 -0700 Subject: [PATCH 405/410] fix warnings, avoid class qualification in static function Signed-off-by: Nikolaj Bjorner --- src/smt/theory_lra.cpp | 4 ++-- src/util/lp/lar_solver.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/smt/theory_lra.cpp b/src/smt/theory_lra.cpp index 745aa5da5..2bdaf66c5 100644 --- a/src/smt/theory_lra.cpp +++ b/src/smt/theory_lra.cpp @@ -260,7 +260,7 @@ namespace smt { struct var_value_hash { imp & m_th; var_value_hash(imp & th):m_th(th) {} - unsigned operator()(theory_var v) const { return std::hash()(m_th.get_ivalue(v)); } + unsigned operator()(theory_var v) const { return (unsigned)std::hash()(m_th.get_ivalue(v)); } }; int_hashtable m_model_eqs; @@ -1307,7 +1307,7 @@ namespace smt { set_conflict(); } else { - for (size_t i = 0; !m.canceled() && !ctx().inconsistent() && i < bp.m_ibounds.size(); ++i) { + for (unsigned i = 0; !m.canceled() && !ctx().inconsistent() && i < bp.m_ibounds.size(); ++i) { propagate_lp_solver_bound(bp.m_ibounds[i]); } } diff --git a/src/util/lp/lar_solver.h b/src/util/lp/lar_solver.h index 1fe5c6ca1..3c10e7626 100644 --- a/src/util/lp/lar_solver.h +++ b/src/util/lp/lar_solver.h @@ -43,7 +43,7 @@ struct conversion_helper { template<> struct conversion_helper { - static double conversion_helper ::get_upper_bound(const column_info & ci) { + static double get_upper_bound(const column_info & ci) { if (!ci.upper_bound_is_strict()) return ci.get_upper_bound().get_double(); double eps = 0.00001; From 445a2280d3dfdc4f88b2580755c7e186adac6591 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Tue, 9 May 2017 15:18:15 -0700 Subject: [PATCH 406/410] missing file Signed-off-by: Nikolaj Bjorner --- src/shell/lp_frontend.cpp | 111 ++++++++++++++++++++++++++++++++++++++ src/shell/lp_frontend.h | 1 - 2 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 src/shell/lp_frontend.cpp diff --git a/src/shell/lp_frontend.cpp b/src/shell/lp_frontend.cpp new file mode 100644 index 000000000..9fad426a0 --- /dev/null +++ b/src/shell/lp_frontend.cpp @@ -0,0 +1,111 @@ +/*++ +Copyright (c) 2016 Microsoft Corporation + +Author: + + Lev Nachmanson 2016-10-27 + +--*/ + +#include "lp_params.hpp" +#include "util/lp/lp_settings.h" +#include "util/lp/mps_reader.h" +#include "timeout.h" +#include "cancel_eh.h" +#include "scoped_timer.h" +#include "rlimit.h" +#include "gparams.h" +#include + +static lean::lp_solver* g_solver = 0; + +static void display_statistics() { + if (g_solver && g_solver->settings().print_statistics) { + // TBD display relevant information about statistics + } +} + +static void STD_CALL on_ctrl_c(int) { + signal (SIGINT, SIG_DFL); + #pragma omp critical (g_display_stats) + { + display_statistics(); + } + raise(SIGINT); +} + +static void on_timeout() { + #pragma omp critical (g_display_stats) + { + display_statistics(); + exit(0); + } +} + +struct front_end_resource_limit : public lean::lp_resource_limit { + reslimit& m_reslim; + + front_end_resource_limit(reslimit& lim): + m_reslim(lim) + {} + + virtual bool get_cancel_flag() { return !m_reslim.inc(); } +}; + +void run_solver(lp_params & params, char const * mps_file_name) { + + reslimit rlim; + unsigned timeout = gparams::get().get_uint("timeout", 0); + unsigned rlimit = gparams::get().get_uint("rlimit", 0); + front_end_resource_limit lp_limit(rlim); + + scoped_rlimit _rlimit(rlim, rlimit); + cancel_eh eh(rlim); + scoped_timer timer(timeout, &eh); + + std::string fn(mps_file_name); + lean::mps_reader reader(fn); + reader.set_message_stream(&std::cout); // can be redirected + reader.read(); + if (!reader.is_ok()) { + std::cerr << "cannot process " << mps_file_name << std::endl; + return; + } + lean::lp_solver * solver = reader.create_solver(false); // false - to create the primal solver + solver->settings().set_resource_limit(lp_limit); + g_solver = solver; + if (params.min()) { + solver->flip_costs(); + } + solver->settings().set_message_ostream(&std::cout); + solver->settings().report_frequency = params.rep_freq(); + solver->settings().print_statistics = params.print_stats(); + solver->settings().presolve_with_double_solver_for_lar = params.presolve_with_dbl(); + solver->find_maximal_solution(); + + *(solver->settings().get_message_ostream()) << "status is " << lp_status_to_string(solver->get_status()) << std::endl; + if (solver->get_status() == lean::OPTIMAL) { + if (params.min()) { + solver->flip_costs(); + } + solver->print_model(std::cout); + } + +// #pragma omp critical (g_display_stats) + { + display_statistics(); + register_on_timeout_proc(0); + g_solver = 0; + } + delete solver; +} + +unsigned read_mps_file(char const * mps_file_name) { + signal(SIGINT, on_ctrl_c); + register_on_timeout_proc(on_timeout); + lp_params p; + param_descrs r; + p.collect_param_descrs(r); + run_solver(p, mps_file_name); + return 0; +} diff --git a/src/shell/lp_frontend.h b/src/shell/lp_frontend.h index 68e6a736e..b24be811f 100644 --- a/src/shell/lp_frontend.h +++ b/src/shell/lp_frontend.h @@ -1,6 +1,5 @@ /* Copyright (c) 2013 Microsoft Corporation. All rights reserved. - Released under Apache 2.0 license as described in the file LICENSE. Author: Lev Nachmanson */ From d43c12413d0e7d8e505cfb499c308c08f89a70be Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Tue, 9 May 2017 15:27:42 -0700 Subject: [PATCH 407/410] add disambiguation, avoid uninitialzed variable passing in debug mode Signed-off-by: Nikolaj Bjorner --- src/util/lp/lar_solver.h | 1 - src/util/lp/random_updater.hpp | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/util/lp/lar_solver.h b/src/util/lp/lar_solver.h index 3c10e7626..4ce1da7c3 100644 --- a/src/util/lp/lar_solver.h +++ b/src/util/lp/lar_solver.h @@ -1692,7 +1692,6 @@ public: bool explanation_is_correct(const vector>& explanation) const { #ifdef LEAN_DEBUG lconstraint_kind kind; - lean_assert(the_relations_are_of_same_type(explanation, kind)); lean_assert(the_left_sides_sum_to_zero(explanation)); mpq rs = sum_of_right_sides_of_explanation(explanation); switch (kind) { diff --git a/src/util/lp/random_updater.hpp b/src/util/lp/random_updater.hpp index 67bfcc49b..56593b493 100644 --- a/src/util/lp/random_updater.hpp +++ b/src/util/lp/random_updater.hpp @@ -180,7 +180,7 @@ void random_updater::add_value(numeric_pair& v) { } void random_updater::remove_value(numeric_pair& v) { - auto it = m_values.find(v); + std::unordered_map, unsigned>::iterator it = m_values.find(v); lean_assert(it != m_values.end()); it->second--; if (it->second == 0) From f4544eb0609ff9cab2e6e3b3be77f1f99164f898 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Tue, 9 May 2017 15:35:00 -0700 Subject: [PATCH 408/410] disambiguating arguments to unordered map erase and dealing with unused and uninitialized variables Signed-off-by: Nikolaj Bjorner --- src/smt/theory_lra.cpp | 2 -- src/util/lp/lar_solver.h | 4 ++++ src/util/lp/random_updater.hpp | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/smt/theory_lra.cpp b/src/smt/theory_lra.cpp index 2bdaf66c5..12d34c516 100644 --- a/src/smt/theory_lra.cpp +++ b/src/smt/theory_lra.cpp @@ -1246,7 +1246,6 @@ namespace smt { if (!can_propagate()) { return; } - unsigned qhead = m_asserted_qhead; while (m_asserted_qhead < m_asserted_atoms.size() && !ctx().inconsistent()) { bool_var bv = m_asserted_atoms[m_asserted_qhead].m_bv; bool is_true = m_asserted_atoms[m_asserted_qhead].m_is_true; @@ -1835,7 +1834,6 @@ namespace smt { // bounds propagation. // void propagate_bound_compound(bool_var bv, bool is_true, lp::bound& b) { - lp::bound_kind k = b.get_bound_kind(); theory_var v = b.get_var(); TRACE("arith", tout << mk_pp(get_owner(v), m) << "\n";); if (static_cast(v) >= m_use_list.size()) { diff --git a/src/util/lp/lar_solver.h b/src/util/lp/lar_solver.h index 4ce1da7c3..57b5f49cf 100644 --- a/src/util/lp/lar_solver.h +++ b/src/util/lp/lar_solver.h @@ -1691,7 +1691,10 @@ public: bool explanation_is_correct(const vector>& explanation) const { #ifdef LEAN_DEBUG +#if 0 + // disabled as 'kind' is not assigned lconstraint_kind kind; + the_relations_are_of_same_type(explanation, kind); lean_assert(the_left_sides_sum_to_zero(explanation)); mpq rs = sum_of_right_sides_of_explanation(explanation); switch (kind) { @@ -1709,6 +1712,7 @@ public: lean_assert(false); return false; } +#endif #endif return true; } diff --git a/src/util/lp/random_updater.hpp b/src/util/lp/random_updater.hpp index 56593b493..68e2f5bc9 100644 --- a/src/util/lp/random_updater.hpp +++ b/src/util/lp/random_updater.hpp @@ -184,7 +184,7 @@ void random_updater::remove_value(numeric_pair& v) { lean_assert(it != m_values.end()); it->second--; if (it->second == 0) - m_values.erase(it); + m_values.erase((std::unordered_map, unsigned>::const_iterator)it); } void random_updater::add_column_to_sets(unsigned j) { From a64a73255e9a180e833de013ef436da090be912d Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Tue, 9 May 2017 15:46:24 -0700 Subject: [PATCH 409/410] make source directory relative to build directory Signed-off-by: Nikolaj Bjorner --- scripts/mk_util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/mk_util.py b/scripts/mk_util.py index 8f024c224..da00f6209 100644 --- a/scripts/mk_util.py +++ b/scripts/mk_util.py @@ -1004,7 +1004,7 @@ class Component: out.write('%s =' % include_defs) for dep in self.deps: out.write(' -I%s' % get_component(dep).to_src_dir) - out.write(' -I../src') + out.write(' -I%s' % os.path.join(REV_BUILD_DIR,"src")) out.write('\n') mk_dir(os.path.join(BUILD_DIR, self.build_dir)) if VS_PAR and IS_WINDOWS: From f572c79cdb29e508b918f0456fe6d05e22f27ab0 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Tue, 9 May 2017 19:55:24 -0700 Subject: [PATCH 410/410] add instances Signed-off-by: Nikolaj Bjorner --- src/util/lp/lp_primal_core_solver_instances.cpp | 1 + src/util/lp/static_matrix_instances.cpp | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/util/lp/lp_primal_core_solver_instances.cpp b/src/util/lp/lp_primal_core_solver_instances.cpp index 3b5a3fedb..32364a963 100644 --- a/src/util/lp/lp_primal_core_solver_instances.cpp +++ b/src/util/lp/lp_primal_core_solver_instances.cpp @@ -17,6 +17,7 @@ template void lean::lp_primal_core_solver::solve(); template unsigned lp_primal_core_solver::solve_with_tableau(); template unsigned lp_primal_core_solver::solve(); +template unsigned lp_primal_core_solver >::solve(); template void lean::lp_primal_core_solver::clear_breakpoints(); template bool lean::lp_primal_core_solver::update_basis_and_x_tableau(int, int, lean::mpq const&); template bool lean::lp_primal_core_solver::update_basis_and_x_tableau(int, int, double const&); diff --git a/src/util/lp/static_matrix_instances.cpp b/src/util/lp/static_matrix_instances.cpp index 5d6be17ec..d0e2045c0 100644 --- a/src/util/lp/static_matrix_instances.cpp +++ b/src/util/lp/static_matrix_instances.cpp @@ -63,5 +63,7 @@ template void static_matrix >::set(unsigned int, unsigned template bool lean::static_matrix::pivot_row_to_row_given_cell(unsigned int, column_cell &, unsigned int); template bool lean::static_matrix::pivot_row_to_row_given_cell(unsigned int, column_cell& , unsigned int); template bool lean::static_matrix >::pivot_row_to_row_given_cell(unsigned int, column_cell&, unsigned int); +template void lean::static_matrix >::remove_element(vector, true, unsigned int>&, lean::row_cell&); + }