diff --git a/CMakeLists.txt b/CMakeLists.txt index 19c56a413..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>) @@ -212,6 +213,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 ################################################################################ @@ -244,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 @@ -287,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. @@ -325,6 +338,8 @@ if (("${TARGET_ARCHITECTURE}" STREQUAL "x86_64") OR ("${TARGET_ARCHITECTURE}" ST unset(SSE_FLAGS) 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 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 bd8ad3f31..41890dd05 100644 --- a/contrib/cmake/src/smt/CMakeLists.txt +++ b/contrib/cmake/src/smt/CMakeLists.txt @@ -55,9 +55,11 @@ 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 + theory_str.cpp theory_utvpi.cpp theory_wmaxsat.cpp uses_theory.cpp @@ -68,6 +70,7 @@ z3_add_component(smt euclid fpa grobner + lp macros normal_forms parser_util 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 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/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/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..da00f6209 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%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: 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/ast/rewriter/rewriter.txt b/src/ast/rewriter/rewriter.txt index cdfba9f0f..9eb016af2 100644 --- a/src/ast/rewriter/rewriter.txt +++ b/src/ast/rewriter/rewriter.txt @@ -7,6 +7,7 @@ The following classes implement theory specific rewriting rules: - array_rewriter - datatype_rewriter - fpa_rewriter + - seq_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/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/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/parsers/smt2/smt2parser.cpp b/src/parsers/smt2/smt2parser.cpp index 8e72e50db..1486f6e6c 100644 --- a/src/parsers/smt2/smt2parser.cpp +++ b/src/parsers/smt2/smt2parser.cpp @@ -66,7 +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/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 new file mode 100644 index 000000000..b24be811f --- /dev/null +++ b/src/shell/lp_frontend.h @@ -0,0 +1,7 @@ +/* + Copyright (c) 2013 Microsoft Corporation. All rights reserved. + + Author: Lev Nachmanson +*/ +#pragma once +unsigned read_mps_file(char const * mps_file_name); 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/params/smt_params.cpp b/src/smt/params/smt_params.cpp index 3d0b59c88..92ff1de90 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 e9ae5755c..4b7c924e4 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" @@ -76,6 +77,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; @@ -111,6 +113,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; // ----------------------------------- // @@ -248,6 +251,8 @@ 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_theory_aware_branching(false), m_delay_units(false), m_delay_units_threshold(32), m_theory_resolve(false), @@ -290,7 +295,7 @@ struct smt_params : public preprocessor_params, m_check_at_labels(false), m_dump_goal_as_smt(false), m_auto_config(true), - m_string_solver(symbol("seq")){ + 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 801a79470..6c2ff4962 100644 --- a/src/smt/params/smt_params_helper.pyg +++ b/src/smt/params/smt_params_helper.pyg @@ -64,6 +64,18 @@ def_module_params(module_name='smt', ('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.'), ('string_solver', SYMBOL, 'seq', 'solver for string/sequence theories. options are: \'z3str3\' (specialized string solver), \'seq\' (sequence solver), \'auto\' (use static features to choose best solver)'), ('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_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'), + ('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_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.overlap_priority', DOUBLE, -0.1, 'theory-aware priority for overlapping variable cases; use smt.theory_aware_branching=true'), ('core.minimize', BOOL, False, 'minimize unsat core produced by SMT context'), ('core.extend_patterns', BOOL, False, 'extend unsat core with literals that trigger (potential) quantifier instances'), ('core.extend_patterns.max_distance', UINT, UINT_MAX, 'limits the distance of a pattern-extended unsat core'), diff --git a/src/smt/params/theory_str_params.cpp b/src/smt/params/theory_str_params.cpp new file mode 100644 index 000000000..6090086b8 --- /dev/null +++ b/src/smt/params/theory_str_params.cpp @@ -0,0 +1,34 @@ +/*++ +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_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(); + 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(); + m_OverlapTheoryAwarePriority = p.str_overlap_priority(); +} diff --git a/src/smt/params/theory_str_params.h b/src/smt/params/theory_str_params.h new file mode 100644 index 000000000..207b635d7 --- /dev/null +++ b/src/smt/params/theory_str_params.h @@ -0,0 +1,102 @@ +/*++ +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_StrongArrangements; + + /* + * 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; + + /* + * 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; + + /* + * If StringConstantCache is set to true, + * all string constants in theory_str generated from anywhere will be cached and saved. + */ + 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; + + double m_OverlapTheoryAwarePriority; + + theory_str_params(params_ref const & p = params_ref()): + m_StrongArrangements(true), + m_AggressiveLengthTesting(false), + m_AggressiveValueTesting(false), + m_AggressiveUnrollTesting(true), + m_UseFastLengthTesterCache(false), + m_UseFastValueTesterCache(true), + m_StringConstantCache(true), + m_FiniteOverlapModels(false), + m_UseBinarySearch(false), + m_BinarySearchInitialUpperBound(64), + m_OverlapTheoryAwarePriority(-0.1) + { + updt_params(p); + } + + void updt_params(params_ref const & p); +}; + +#endif /* THEORY_STR_PARAMS_H */ diff --git a/src/smt/smt_context.cpp b/src/smt/smt_context.cpp index a6cef548f..50b957331 100644 --- a/src/smt/smt_context.cpp +++ b/src/smt/smt_context.cpp @@ -2448,8 +2448,9 @@ 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) { (*it)->pop_scope_eh(num_scopes); + } del_justifications(m_justifications, s.m_justifications_lim); @@ -3013,6 +3014,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())) { @@ -3022,10 +3027,6 @@ 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); - } - bool context::propagate_th_case_split(unsigned qhead) { if (m_all_th_case_split_literals.empty()) return true; @@ -3034,7 +3035,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]; @@ -3114,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/smt_setup.cpp b/src/smt/smt_setup.cpp index 820159d18..ebb1f87fd 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" @@ -33,6 +34,7 @@ Revision History: #include"theory_seq.h" #include"theory_pb.h" #include"theory_fpa.h" +#include"theory_str.h" namespace smt { @@ -205,7 +207,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) { @@ -441,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) { @@ -466,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() { @@ -538,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() { @@ -705,7 +707,8 @@ namespace smt { } void setup::setup_QF_S() { - setup_seq(); + 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)); } bool is_arith(static_features const & st) { @@ -716,6 +719,13 @@ 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_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() { if (m_params.m_arith_mode == AS_OPTINF) { m_context.register_plugin(alloc(smt::theory_inf_arith, m_manager, m_params)); @@ -853,7 +863,8 @@ namespace smt { } void setup::setup_str() { - m_context.register_plugin(alloc(smt::theory_seq, m_manager)); + setup_arith(); + m_context.register_plugin(alloc(theory_str, m_manager, m_params)); } void setup::setup_seq() { 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 2745a6efd..c943a85d8 100644 --- a/src/smt/smt_theory.h +++ b/src/smt/smt_theory.h @@ -186,13 +186,13 @@ namespace smt { } /** - \brief This method is called from smt_context when an unsat core is generated. + \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. */ @@ -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/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; diff --git a/src/smt/theory_lra.cpp b/src/smt/theory_lra.cpp new file mode 100644 index 000000000..12d34c516 --- /dev/null +++ b/src/smt/theory_lra.cpp @@ -0,0 +1,2622 @@ +/*++ +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 (unsigned)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; + } + 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 (unsigned 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) { + 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/smt/theory_str.cpp b/src/smt/theory_str.cpp new file mode 100644 index 000000000..df1418aea --- /dev/null +++ b/src/smt/theory_str.cpp @@ -0,0 +1,10574 @@ +/*++ + Module Name: + + theory_str.cpp + + Abstract: + + String Theory Plugin + + Author: + + Murphy Berzish and Yunhui Zheng + + Revision History: + + --*/ +#include"ast_smt2_pp.h" +#include"smt_context.h" +#include"theory_str.h" +#include"smt_model_generator.h" +#include"ast_pp.h" +#include"ast_ll_pp.h" +#include +#include +#include +#include"theory_seq_empty.h" +#include"theory_arith.h" + +namespace smt { + + theory_str::theory_str(ast_manager & m, theory_str_params const & params): + theory(m.mk_family_id("seq")), + m_params(params), + /* Options */ + opt_EagerStringConstantLengthAssertions(true), + opt_VerifyFinalCheckProgress(true), + opt_LCMUnrollStep(2), + opt_NoQuickReturn_IntegerTheory(false), + opt_DisableIntegerTheoryIntegration(false), + opt_DeferEQCConsistencyCheck(false), + opt_CheckVariableScope(true), + opt_ConcatOverlapAvoid(true), + /* Internal setup */ + search_started(false), + m_autil(m), + u(m), + sLevel(0), + finalCheckProgressIndicator(false), + m_trail(m), + m_delayed_axiom_setup_terms(m), + tmpStringVarCount(0), + tmpXorVarCount(0), + tmpLenTestVarCount(0), + tmpValTestVarCount(0), + avoidLoopCut(true), + loopDetected(false), + m_theoryStrOverlapAssumption_term(m), + contains_map(m), + string_int_conversion_terms(m), + totalCacheAccessCount(0), + cacheHitCount(0), + cacheMissCount(0), + m_find(*this), + m_trail_stack(*this) + { + initialize_charset(); + } + + 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; + } else { + val = u.str.mk_string(str); + m_trail.push_back(val); + stringConstantCache.insert(str, val); + return val; + } + } 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;); + } + + 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].empty()) { + T_cut * varInfo = alloc(T_cut); + varInfo->level = slevel; + 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) { + 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"); + } + + 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) { + 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"); + } + } + } + + 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_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(); + + 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 = 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 = 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 = 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 = 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); + } + 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; + } + } + 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); + } + + 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 { + 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); + } + } + + /* + * 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; + } + + // 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); + } + + } + } + + /* + * 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; + } + + { // 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); + } + + 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); + 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); + } + + // 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; + } + + // 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) { + 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; + } + + /************************************************************* + * 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); + + 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; + } + } + + 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); + + 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; + } else { + splitType = 2; + } + } + + 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; + } + } + + 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; + ); + + expr * t1 = NULL; + expr * t2 = 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() + /*|| 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("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); + } 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) { + // 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)); + + 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)) { + // 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 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)); + + 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); + } 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))); + } + + 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 + 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 (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 (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) { + 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) { + 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;); + } + + // 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 (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 + 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; + } + } + 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); + } + } + // 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) { + 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); + } + } + + // 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 + } + + 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;); + } + + // 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;); + + 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 (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 { + 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("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) { + 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); + } + } + } 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("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); + } + } + } + } + } + } + } + // *************************** + // 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; + } + ); + + 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 (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("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); + } + } + + { + 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 (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; + } + + 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(); + + // 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()); + } + } + } + } + } + // 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; + ); + } + } 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; + } + + 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 - 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 { + 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; + 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; + } + } + } + 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; + } + } + // 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 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; + 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) { + 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; + } + } + + 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 = (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;); + 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()); + assumptions.push_back(get_manager().mk_not(m_theoryStrOverlapAssumption_term)); + } + + lbool theory_str::validate_unsat_core(expr_ref_vector & unsat_core) { + 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;); + 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) { + 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;); + 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; + } + } + 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)) { + 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 ;); + + 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) ; + 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); + 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(); + + 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 = 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 + 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 << "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) { + + expr * eqcNode = n; + do { + if (u.str.is_concat(to_app(eqcNode))) { + concats.insert(eqcNode); + } + eqcNode = get_eqc_next(eqcNode); + } while (eqcNode != n); + } + + void theory_str::get_var_in_eqc(expr * n, std::set & varSet) { + expr * eqcNode = n; + do { + if (variable_set.find(eqcNode) != variable_set.end()) { + varSet.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::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(); + + 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); + } + + // 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(); + + 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::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); + } + + /* + * 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(); + app_ref owner(m); + owner = n->get_owner(); + + // If the owner is not internalized, it doesn't have an enode associated. + SASSERT(get_context().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 */ diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h new file mode 100644 index 000000000..0403b0623 --- /dev/null +++ b/src/smt/theory_str.h @@ -0,0 +1,653 @@ +/*++ + Module Name: + + theory_str.h + + Abstract: + + String Theory Plugin + + Author: + + Murphy Berzish and Yunhui Zheng + + Revision History: + + --*/ +#ifndef _THEORY_STR_H_ +#define _THEORY_STR_H_ + +#include"smt_theory.h" +#include"theory_str_params.h" +#include"trail.h" +#include"th_rewriter.h" +#include"value_factory.h" +#include"smt_model_generator.h" +#include"arith_decl_plugin.h" +#include +#include +#include +#include +#include"seq_decl_plugin.h" +#include"union_find.h" + +namespace smt { + +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); + } + } + 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; + } 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_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 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; + } + }; + + 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; + + /* + * 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; + + /* + * 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)) ); + } + }; + // 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); + app * mk_fresh_const(char const* name, sort* s); + + 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, + 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 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 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); + + 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 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); + + 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); + + 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); + + 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); + + bool finalcheck_str2int(app * a); + bool finalcheck_int2str(app * a); + + // 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 dump_assignments(); + void initialize_charset(); + + 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); + + 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(); + + virtual char const * get_name() const { return "seq"; } + 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); + 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 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 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); +}; + +}; + +#endif /* _THEORY_STR_H_ */ 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/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/test/smt2print_parse.cpp b/src/test/smt2print_parse.cpp index 982e2e3f5..c79a80ae5 100644 --- a/src/test/smt2print_parse.cpp +++ b/src/test/smt2print_parse.cpp @@ -13,8 +13,9 @@ Copyright (c) 2015 Microsoft Corporation 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, @@ -24,14 +25,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, @@ -43,8 +44,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); } @@ -104,6 +109,12 @@ void tst_smt2print_parse() { test_parseprint(spec5); + // Test strings + char const* spec6 = + "(assert (= \"abc\" \"abc\"))"; + + test_parseprint(spec6); + // Test ? } 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/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..57b5f49cf --- /dev/null +++ b/src/util/lp/lar_solver.h @@ -0,0 +1,2138 @@ +/* + 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 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 +#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) { + 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 +#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_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') + )) + + + 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..32364a963 --- /dev/null +++ b/src/util/lp/lp_primal_core_solver_instances.cpp @@ -0,0 +1,25 @@ +/* + 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 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..68e2f5bc9 --- /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) { + std::unordered_map, unsigned>::iterator it = m_values.find(v); + lean_assert(it != m_values.end()); + it->second--; + if (it->second == 0) + m_values.erase((std::unordered_map, unsigned>::const_iterator)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..d0e2045c0 --- /dev/null +++ b/src/util/lp/static_matrix_instances.cpp @@ -0,0 +1,69 @@ +/* + 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); +template void lean::static_matrix >::remove_element(vector, true, unsigned int>&, lean::row_cell&); + +} + 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) {} +}; + +} 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; 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); } 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; } +}; +} 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_ */