diff --git a/CMakeLists.txt b/CMakeLists.txt index 778e56e0a..734aee660 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 c344e936f..41890dd05 100644 --- a/contrib/cmake/src/smt/CMakeLists.txt +++ b/contrib/cmake/src/smt/CMakeLists.txt @@ -55,6 +55,7 @@ z3_add_component(smt theory_dl.cpp theory_dummy.cpp theory_fpa.cpp + theory_lra.cpp theory_opt.cpp theory_pb.cpp theory_seq.cpp @@ -69,6 +70,7 @@ z3_add_component(smt euclid fpa grobner + lp macros normal_forms parser_util diff --git a/contrib/cmake/src/test/CMakeLists.txt b/contrib/cmake/src/test/CMakeLists.txt index 5458344d1..5d9935313 100644 --- a/contrib/cmake/src/test/CMakeLists.txt +++ b/contrib/cmake/src/test/CMakeLists.txt @@ -119,6 +119,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}) @@ -130,3 +131,14 @@ target_link_libraries(test-z3 PRIVATE ${Z3_DEPENDENT_LIBS}) target_include_directories(test-z3 PRIVATE ${Z3_COMPONENT_EXTRA_INCLUDE_DIRS}) z3_append_linker_flag_list_to_target(test-z3 ${Z3_DEPENDENT_EXTRA_CXX_LINK_FLAGS}) z3_add_component_dependencies_to_target(test-z3 ${z3_test_expanded_deps}) + +add_executable(lp_tst lp_main.cpp lp.cpp $ $) +target_compile_definitions(lp_tst PRIVATE ${Z3_COMPONENT_CXX_DEFINES}) +target_compile_options(lp_tst PRIVATE ${Z3_COMPONENT_CXX_FLAGS}) +target_include_directories(lp_tst PRIVATE ${Z3_COMPONENT_EXTRA_INCLUDE_DIRS}) +target_link_libraries(lp_tst PRIVATE ${Z3_DEPENDENT_LIBS}) +z3_append_linker_flag_list_to_target(lp_tst ${Z3_DEPENDENT_EXTRA_CXX_LINK_FLAGS}) + + + + diff --git a/scripts/mk_project.py b/scripts/mk_project.py index 7ba88c1b3..8655346f2 100644 --- a/scripts/mk_project.py +++ b/scripts/mk_project.py @@ -11,6 +11,7 @@ from mk_util import * def init_project_def(): set_version(4, 5, 1, 0) add_lib('util', []) + add_lib('lp', ['util'], 'util/lp') add_lib('polynomial', ['util'], 'math/polynomial') add_lib('sat', ['util']) add_lib('nlsat', ['polynomial', 'sat']) @@ -52,7 +53,7 @@ def init_project_def(): add_lib('smt_params', ['ast', 'simplifier', 'pattern', 'bit_blaster'], 'smt/params') add_lib('proto_model', ['model', 'simplifier', 'smt_params'], 'smt/proto_model') add_lib('smt', ['bit_blaster', 'macros', 'normal_forms', 'cmd_context', 'proto_model', - 'substitution', 'grobner', 'euclid', 'simplex', 'proof_checker', 'pattern', 'parser_util', 'fpa']) + 'substitution', 'grobner', 'euclid', 'simplex', 'proof_checker', 'pattern', 'parser_util', 'fpa', 'lp']) add_lib('bv_tactics', ['tactic', 'bit_blaster', 'core_tactics'], 'tactic/bv') add_lib('fuzzing', ['ast'], 'test/fuzzing') add_lib('smt_tactic', ['smt'], 'smt/tactic') diff --git a/scripts/mk_util.py b/scripts/mk_util.py index d1bbd6ca4..edaffcd1e 100644 --- a/scripts/mk_util.py +++ b/scripts/mk_util.py @@ -774,8 +774,13 @@ def extract_c_includes(fname): linenum = 1 for line in f: m1 = std_inc_pat.match(line) - if m1: - result.append(m1.group(1)) + if m1: + root_file_name = m1.group(1) + slash_pos = root_file_name.rfind('/') + if slash_pos >= 0 and root_file_name.find("..") < 0 : #it is a hack for lp include files that behave as continued from "src" + print(root_file_name) + root_file_name = root_file_name[slash_pos+1:] + result.append(root_file_name) elif not system_inc_pat.match(line) and non_std_inc_pat.match(line): raise MKException("Invalid #include directive at '%s':%s" % (fname, line)) linenum = linenum + 1 @@ -999,6 +1004,7 @@ class Component: out.write('%s =' % include_defs) for dep in self.deps: out.write(' -I%s' % get_component(dep).to_src_dir) + out.write(' -I..\src') out.write('\n') mk_dir(os.path.join(BUILD_DIR, self.build_dir)) if VS_PAR and IS_WINDOWS: diff --git a/src/ast/arith_decl_plugin.h b/src/ast/arith_decl_plugin.h index 668eebcc9..beb227c33 100644 --- a/src/ast/arith_decl_plugin.h +++ b/src/ast/arith_decl_plugin.h @@ -305,6 +305,7 @@ public: MATCH_UNARY(is_uminus); MATCH_UNARY(is_to_real); MATCH_UNARY(is_to_int); + MATCH_UNARY(is_is_int); MATCH_BINARY(is_sub); MATCH_BINARY(is_add); MATCH_BINARY(is_mul); @@ -377,6 +378,9 @@ public: app * mk_real(int i) { return mk_numeral(rational(i), false); } + app * mk_real(rational const& r) { + return mk_numeral(r, false); + } app * mk_le(expr * arg1, expr * arg2) const { return m_manager.mk_app(m_afid, OP_LE, arg1, arg2); } app * mk_ge(expr * arg1, expr * arg2) const { return m_manager.mk_app(m_afid, OP_GE, arg1, arg2); } app * mk_lt(expr * arg1, expr * arg2) const { return m_manager.mk_app(m_afid, OP_LT, arg1, arg2); } diff --git a/src/opt/opt_solver.cpp b/src/opt/opt_solver.cpp index bc6462a18..458a5c540 100644 --- a/src/opt/opt_solver.cpp +++ b/src/opt/opt_solver.cpp @@ -26,6 +26,7 @@ Notes: #include "theory_diff_logic.h" #include "theory_dense_diff_logic.h" #include "theory_pb.h" +#include "theory_lra.h" #include "ast_pp.h" #include "ast_smt_pp.h" #include "pp_params.hpp" @@ -143,6 +144,9 @@ namespace opt { else if (typeid(smt::theory_dense_si&) == typeid(*arith_theory)) { return dynamic_cast(*arith_theory); } + else if (typeid(smt::theory_lra&) == typeid(*arith_theory)) { + return dynamic_cast(*arith_theory); + } else { UNREACHABLE(); return dynamic_cast(*arith_theory); @@ -401,6 +405,14 @@ namespace opt { return th.mk_ge(m_fm, v, val); } + + if (typeid(smt::theory_lra) == typeid(opt)) { + smt::theory_lra& th = dynamic_cast(opt); + SASSERT(val.is_finite()); + return th.mk_ge(m_fm, v, val.get_numeral()); + } + + // difference logic? if (typeid(smt::theory_dense_si) == typeid(opt) && val.get_infinitesimal().is_zero()) { smt::theory_dense_si& th = dynamic_cast(opt); diff --git a/src/shell/main.cpp b/src/shell/main.cpp index fddce4f69..b29586f8d 100644 --- a/src/shell/main.cpp +++ b/src/shell/main.cpp @@ -35,8 +35,9 @@ Revision History: #include"error_codes.h" #include"gparams.h" #include"env_params.h" +#include "lp_frontend.h" -typedef enum { IN_UNSPECIFIED, IN_SMTLIB, IN_SMTLIB_2, IN_DATALOG, IN_DIMACS, IN_WCNF, IN_OPB, IN_Z3_LOG } input_kind; +typedef enum { IN_UNSPECIFIED, IN_SMTLIB, IN_SMTLIB_2, IN_DATALOG, IN_DIMACS, IN_WCNF, IN_OPB, IN_Z3_LOG, IN_MPS } input_kind; std::string g_aux_input_file; char const * g_input_file = 0; @@ -342,6 +343,10 @@ int STD_CALL main(int argc, char ** argv) { else if (strcmp(ext, "smt") == 0) { g_input_kind = IN_SMTLIB; } + else if (strcmp(ext, "mps") == 0 || strcmp(ext, "sif") == 0 || + strcmp(ext, "MPS") == 0 || strcmp(ext, "SIF") == 0) { + g_input_kind = IN_MPS; + } } } switch (g_input_kind) { @@ -367,6 +372,9 @@ int STD_CALL main(int argc, char ** argv) { case IN_Z3_LOG: replay_z3_log(g_input_file); break; + case IN_MPS: + return_value = read_mps_file(g_input_file); + break; default: UNREACHABLE(); } diff --git a/src/smt/smt_setup.cpp b/src/smt/smt_setup.cpp index a4f2ee646..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" @@ -442,7 +443,7 @@ namespace smt { m_params.m_arith_propagate_eqs = false; m_params.m_eliminate_term_ite = true; m_params.m_nnf_cnf = false; - setup_mi_arith(); + setup_r_arith(); } void setup::setup_QF_LRA(static_features const & st) { @@ -467,7 +468,7 @@ namespace smt { m_params.m_restart_adaptive = false; } m_params.m_arith_small_lemma_size = 32; - setup_mi_arith(); + setup_r_arith(); } void setup::setup_QF_LIA() { @@ -539,7 +540,7 @@ namespace smt { m_params.m_relevancy_lvl = 0; m_params.m_arith_reflect = false; m_params.m_nnf_cnf = false; - setup_mi_arith(); + setup_r_arith(); } void setup::setup_QF_BV() { @@ -718,6 +719,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)); diff --git a/src/smt/smt_setup.h b/src/smt/smt_setup.h index 80d5d7d1b..cffc96bb8 100644 --- a/src/smt/smt_setup.h +++ b/src/smt/smt_setup.h @@ -99,6 +99,7 @@ namespace smt { void setup_card(); void setup_i_arith(); void setup_mi_arith(); + void setup_r_arith(); void setup_fpa(); void setup_str(); diff --git a/src/smt/smt_theory.cpp b/src/smt/smt_theory.cpp index fd853d6a6..2263699f9 100644 --- a/src/smt/smt_theory.cpp +++ b/src/smt/smt_theory.cpp @@ -54,7 +54,7 @@ namespace smt { } } - void theory::display_app(std::ostream & out, app * n) const { + std::ostream& theory::display_app(std::ostream & out, app * n) const { func_decl * d = n->get_decl(); if (n->get_num_args() == 0) { out << d->get_name(); @@ -73,9 +73,10 @@ namespace smt { else { out << "#" << n->get_id(); } + return out; } - void theory::display_flat_app(std::ostream & out, app * n) const { + std::ostream& theory::display_flat_app(std::ostream & out, app * n) const { func_decl * d = n->get_decl(); if (n->get_num_args() == 0) { out << d->get_name(); @@ -106,6 +107,7 @@ namespace smt { else { out << "#" << n->get_id(); } + return out; } bool theory::is_relevant_and_shared(enode * n) const { diff --git a/src/smt/smt_theory.h b/src/smt/smt_theory.h index 67091c601..c943a85d8 100644 --- a/src/smt/smt_theory.h +++ b/src/smt/smt_theory.h @@ -337,14 +337,14 @@ namespace smt { virtual void collect_statistics(::statistics & st) const { } - - void display_app(std::ostream & out, app * n) const; - - void display_flat_app(std::ostream & out, app * n) const; - void display_var_def(std::ostream & out, theory_var v) const { return display_app(out, get_enode(v)->get_owner()); } + std::ostream& display_app(std::ostream & out, app * n) const; - void display_var_flat_def(std::ostream & out, theory_var v) const { return display_flat_app(out, get_enode(v)->get_owner()); } + std::ostream& display_flat_app(std::ostream & out, app * n) const; + + std::ostream& display_var_def(std::ostream & out, theory_var v) const { return display_app(out, get_enode(v)->get_owner()); } + + std::ostream& display_var_flat_def(std::ostream & out, theory_var v) const { return display_flat_app(out, get_enode(v)->get_owner()); } /** \brief Assume eqs between variable that are equal with respect to the given table. diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 818aca29a..df1418aea 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -10059,7 +10059,7 @@ namespace smt { } TRACE("str", tout << "last bounds are [" << lastBounds.lowerBound << " | " << lastBounds.midPoint << " | " << lastBounds.upperBound << "]!" << lastBounds.windowSize << std::endl;); binary_search_info newBounds; - expr * newTester; + expr * newTester = 0; if (lastTesterConstant == "more") { // special case: if the midpoint, upper bound, and window size are all equal, // we double the window size and adjust the bounds diff --git a/src/test/main.cpp b/src/test/main.cpp index a561ab8b8..bfd2ad57e 100644 --- a/src/test/main.cpp +++ b/src/test/main.cpp @@ -240,6 +240,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(sat_lookahead); diff --git a/src/util/lp/binary_heap_priority_queue.h b/src/util/lp/binary_heap_priority_queue.h new file mode 100644 index 000000000..1a1f89592 --- /dev/null +++ b/src/util/lp/binary_heap_priority_queue.h @@ -0,0 +1,71 @@ + +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#pragma once +#include "util/vector.h" +#include "util/debug.h" +#include "util/lp/lp_utils.h" +namespace lean { +// the elements with the smallest priority are dequeued first +template +class binary_heap_priority_queue { + vector m_priorities; + + // indexing for A starts from 1 + vector m_heap; // keeps the elements of the queue + vector m_heap_inverse; // o = m_heap[m_heap_inverse[o]] + unsigned m_heap_size = 0; + + // is is the child place in heap + void swap_with_parent(unsigned i); + void put_at(unsigned i, unsigned h); + void decrease_priority(unsigned o, T newPriority); +public: +#ifdef LEAN_DEBUG + bool is_consistent() const; +#endif +public: + void remove(unsigned o); + unsigned size() const { return m_heap_size; } + binary_heap_priority_queue(): m_heap(1) {} // the empty constructror + // n is the initial queue capacity. + // The capacity will be enlarged two times automatically if needed + binary_heap_priority_queue(unsigned n); + + void clear() { + for (unsigned i = 0; i < m_heap_size; i++) { + unsigned o = m_heap[i+1]; + m_heap_inverse[o] = -1; + } + m_heap_size = 0; + } + + void resize(unsigned n); + void put_to_heap(unsigned i, unsigned o); + + void enqueue_new(unsigned o, const T& priority); + + // This method can work with an element that is already in the queue. + // In this case the priority will be changed and the queue adjusted. + void enqueue(unsigned o, const T & priority); + void change_priority_for_existing(unsigned o, const T & priority); + T get_priority(unsigned o) const { return m_priorities[o]; } + bool is_empty() const { return m_heap_size == 0; } + + /// return the first element of the queue and removes it from the queue + unsigned dequeue_and_get_priority(T & priority); + void fix_heap_under(unsigned i); + void put_the_last_at_the_top_and_fix_the_heap(); + /// return the first element of the queue and removes it from the queue + unsigned dequeue(); + unsigned peek() const { + lean_assert(m_heap_size > 0); + return m_heap[1]; + } +#ifdef LEAN_DEBUG + void print(std::ostream & out); +#endif +}; +} diff --git a/src/util/lp/binary_heap_priority_queue.hpp b/src/util/lp/binary_heap_priority_queue.hpp new file mode 100644 index 000000000..2ad65c536 --- /dev/null +++ b/src/util/lp/binary_heap_priority_queue.hpp @@ -0,0 +1,193 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include "util/vector.h" +#include "util/lp/binary_heap_priority_queue.h" +namespace lean { +// is is the child place in heap +template void binary_heap_priority_queue::swap_with_parent(unsigned i) { + unsigned parent = m_heap[i >> 1]; + put_at(i >> 1, m_heap[i]); + put_at(i, parent); +} + +template void binary_heap_priority_queue::put_at(unsigned i, unsigned h) { + m_heap[i] = h; + m_heap_inverse[h] = i; +} + +template void binary_heap_priority_queue::decrease_priority(unsigned o, T newPriority) { + m_priorities[o] = newPriority; + int i = m_heap_inverse[o]; + while (i > 1) { + if (m_priorities[m_heap[i]] < m_priorities[m_heap[i >> 1]]) + swap_with_parent(i); + else + break; + i >>= 1; + } +} + +#ifdef LEAN_DEBUG +template bool binary_heap_priority_queue::is_consistent() const { + for (int i = 0; i < m_heap_inverse.size(); i++) { + int i_index = m_heap_inverse[i]; + lean_assert(i_index <= static_cast(m_heap_size)); + lean_assert(i_index == -1 || m_heap[i_index] == i); + } + for (unsigned i = 1; i < m_heap_size; i++) { + unsigned ch = i << 1; + for (int k = 0; k < 2; k++) { + if (ch > m_heap_size) break; + if (!(m_priorities[m_heap[i]] <= m_priorities[m_heap[ch]])){ + return false; + } + ch++; + } + } + return true; +} +#endif +template void binary_heap_priority_queue::remove(unsigned o) { + T priority_of_o = m_priorities[o]; + int o_in_heap = m_heap_inverse[o]; + if (o_in_heap == -1) { + return; // nothing to do + } + lean_assert(static_cast(o_in_heap) <= m_heap_size); + if (static_cast(o_in_heap) < m_heap_size) { + put_at(o_in_heap, m_heap[m_heap_size--]); + if (m_priorities[m_heap[o_in_heap]] > priority_of_o) { + fix_heap_under(o_in_heap); + } else { // we need to propogate the m_heap[o_in_heap] up + unsigned i = o_in_heap; + while (i > 1) { + unsigned ip = i >> 1; + if (m_priorities[m_heap[i]] < m_priorities[m_heap[ip]]) + swap_with_parent(i); + else + break; + i = ip; + } + } + } else { + lean_assert(static_cast(o_in_heap) == m_heap_size); + m_heap_size--; + } + m_heap_inverse[o] = -1; + // lean_assert(is_consistent()); +} +// n is the initial queue capacity. +// The capacity will be enlarged two times automatically if needed +template binary_heap_priority_queue::binary_heap_priority_queue(unsigned n) : + m_priorities(n), + m_heap(n + 1), // because the indexing for A starts from 1 + m_heap_inverse(n, -1) +{ } + + +template void binary_heap_priority_queue::resize(unsigned n) { + m_priorities.resize(n); + m_heap.resize(n + 1); + m_heap_inverse.resize(n, -1); +} + +template void binary_heap_priority_queue::put_to_heap(unsigned i, unsigned o) { + m_heap[i] = o; + m_heap_inverse[o] = i; +} + +template void binary_heap_priority_queue::enqueue_new(unsigned o, const T& priority) { + m_heap_size++; + int i = m_heap_size; + lean_assert(o < m_priorities.size()); + m_priorities[o] = priority; + put_at(i, o); + while (i > 1 && m_priorities[m_heap[i >> 1]] > priority) { + swap_with_parent(i); + i >>= 1; + } +} +// This method can work with an element that is already in the queue. +// In this case the priority will be changed and the queue adjusted. +template void binary_heap_priority_queue::enqueue(unsigned o, const T & priority) { + if (o >= m_priorities.size()) { + resize(o << 1); // make the size twice larger + } + if (m_heap_inverse[o] == -1) + enqueue_new(o, priority); + else + change_priority_for_existing(o, priority); +} + +template void binary_heap_priority_queue::change_priority_for_existing(unsigned o, const T & priority) { + if (m_priorities[o] > priority) { + decrease_priority(o, priority); + } else { + m_priorities[o] = priority; + fix_heap_under(m_heap_inverse[o]); + } +} + + +/// return the first element of the queue and removes it from the queue +template unsigned binary_heap_priority_queue::dequeue_and_get_priority(T & priority) { + lean_assert(m_heap_size != 0); + int ret = m_heap[1]; + priority = m_priorities[ret]; + put_the_last_at_the_top_and_fix_the_heap(); + return ret; +} + +template void binary_heap_priority_queue::fix_heap_under(unsigned i) { + while (true) { + unsigned smallest = i; + unsigned l = i << 1; + if (l <= m_heap_size && m_priorities[m_heap[l]] < m_priorities[m_heap[i]]) + smallest = l; + unsigned r = l + 1; + if (r <= m_heap_size && m_priorities[m_heap[r]] < m_priorities[m_heap[smallest]]) + smallest = r; + if (smallest != i) + swap_with_parent(smallest); + else + break; + i = smallest; + } +} + +template void binary_heap_priority_queue::put_the_last_at_the_top_and_fix_the_heap() { + if (m_heap_size > 1) { + put_at(1, m_heap[m_heap_size--]); + fix_heap_under(1); + } else { + m_heap_size--; + } +} +/// return the first element of the queue and removes it from the queue +template unsigned binary_heap_priority_queue::dequeue() { + lean_assert(m_heap_size > 0); + int ret = m_heap[1]; + put_the_last_at_the_top_and_fix_the_heap(); + m_heap_inverse[ret] = -1; + return ret; +} +#ifdef LEAN_DEBUG +template void binary_heap_priority_queue::print(std::ostream & out) { + vector index; + vector prs; + while (size()) { + T prior; + int j = dequeue_and_get_priority(prior); + index.push_back(j); + prs.push_back(prior); + out << "(" << j << ", " << prior << ")"; + } + out << std::endl; + // restore the queue + for (int i = 0; i < index.size(); i++) + enqueue(index[i], prs[i]); +} +#endif +} diff --git a/src/util/lp/binary_heap_priority_queue_instances.cpp b/src/util/lp/binary_heap_priority_queue_instances.cpp new file mode 100644 index 000000000..567494d6f --- /dev/null +++ b/src/util/lp/binary_heap_priority_queue_instances.cpp @@ -0,0 +1,26 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include "util/lp/numeric_pair.h" +#include "util/lp/binary_heap_priority_queue.hpp" +namespace lean { +template binary_heap_priority_queue::binary_heap_priority_queue(unsigned int); +template unsigned binary_heap_priority_queue::dequeue(); +template void binary_heap_priority_queue::enqueue(unsigned int, int const&); +template void binary_heap_priority_queue::enqueue(unsigned int, double const&); +template void binary_heap_priority_queue::enqueue(unsigned int, mpq const&); +template void binary_heap_priority_queue::remove(unsigned int); +template unsigned binary_heap_priority_queue >::dequeue(); +template unsigned binary_heap_priority_queue::dequeue(); +template unsigned binary_heap_priority_queue::dequeue(); +template void binary_heap_priority_queue >::enqueue(unsigned int, numeric_pair const&); +template void binary_heap_priority_queue >::resize(unsigned int); +template void lean::binary_heap_priority_queue::resize(unsigned int); +template binary_heap_priority_queue::binary_heap_priority_queue(unsigned int); +template void binary_heap_priority_queue::resize(unsigned int); +template unsigned binary_heap_priority_queue::dequeue(); +template void binary_heap_priority_queue::enqueue(unsigned int, unsigned int const&); +template void binary_heap_priority_queue::remove(unsigned int); +template void lean::binary_heap_priority_queue::resize(unsigned int); +} diff --git a/src/util/lp/binary_heap_upair_queue.h b/src/util/lp/binary_heap_upair_queue.h new file mode 100644 index 000000000..d5df7affc --- /dev/null +++ b/src/util/lp/binary_heap_upair_queue.h @@ -0,0 +1,50 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ + +#pragma once +#include +#include +#include +#include "util/vector.h" +#include +#include +#include "util/lp/binary_heap_priority_queue.h" + + +typedef std::pair upair; + +namespace lean { +template +class binary_heap_upair_queue { + binary_heap_priority_queue m_q; + std::unordered_map m_pairs_to_index; + vector m_pairs; // inverse to index + vector m_available_spots; +public: + binary_heap_upair_queue(unsigned size); + + unsigned dequeue_available_spot(); + bool is_empty() const { return m_q.is_empty(); } + + unsigned size() const {return m_q.size(); } + + bool contains(unsigned i, unsigned j) const { return m_pairs_to_index.find(std::make_pair(i, j)) != m_pairs_to_index.end(); + } + + void remove(unsigned i, unsigned j); + bool ij_index_is_new(unsigned ij_index) const; + void enqueue(unsigned i, unsigned j, const T & priority); + void dequeue(unsigned & i, unsigned &j); + T get_priority(unsigned i, unsigned j) const; +#ifdef LEAN_DEBUG + bool pair_to_index_is_a_bijection() const; + bool available_spots_are_correct() const; + bool is_correct() const { + return m_q.is_consistent() && pair_to_index_is_a_bijection() && available_spots_are_correct(); + } +#endif + void resize(unsigned size) { m_q.resize(size); } +}; +} diff --git a/src/util/lp/binary_heap_upair_queue.hpp b/src/util/lp/binary_heap_upair_queue.hpp new file mode 100644 index 000000000..a48bdb5b7 --- /dev/null +++ b/src/util/lp/binary_heap_upair_queue.hpp @@ -0,0 +1,110 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ + +#include +#include "util/lp/lp_utils.h" +#include "util/lp/binary_heap_upair_queue.h" +namespace lean { +template binary_heap_upair_queue::binary_heap_upair_queue(unsigned size) : m_q(size), m_pairs(size) { + for (unsigned i = 0; i < size; i++) + m_available_spots.push_back(i); +} + +template unsigned +binary_heap_upair_queue::dequeue_available_spot() { + lean_assert(m_available_spots.empty() == false); + unsigned ret = m_available_spots.back(); + m_available_spots.pop_back(); + return ret; +} + +template void binary_heap_upair_queue::remove(unsigned i, unsigned j) { + upair p(i, j); + auto it = m_pairs_to_index.find(p); + if (it == m_pairs_to_index.end()) + return; // nothing to do + m_q.remove(it->second); + m_available_spots.push_back(it->second); + m_pairs_to_index.erase(it); +} + + +template bool binary_heap_upair_queue::ij_index_is_new(unsigned ij_index) const { + for (auto it : m_pairs_to_index) { + if (it.second == ij_index) + return false; + } + return true; +} + +template void binary_heap_upair_queue::enqueue(unsigned i, unsigned j, const T & priority) { + upair p(i, j); + auto it = m_pairs_to_index.find(p); + unsigned ij_index; + if (it == m_pairs_to_index.end()) { + // it is a new pair, let us find a spot for it + if (m_available_spots.empty()) { + // we ran out of empty spots + unsigned size_was = static_cast(m_pairs.size()); + unsigned new_size = size_was << 1; + for (unsigned i = size_was; i < new_size; i++) + m_available_spots.push_back(i); + m_pairs.resize(new_size); + } + ij_index = dequeue_available_spot(); + // lean_assert(ij_indexsecond; + } + m_q.enqueue(ij_index, priority); +} + +template void binary_heap_upair_queue::dequeue(unsigned & i, unsigned &j) { + lean_assert(!m_q.is_empty()); + unsigned ij_index = m_q.dequeue(); + upair & p = m_pairs[ij_index]; + i = p.first; + j = p.second; + m_available_spots.push_back(ij_index); + m_pairs_to_index.erase(p); +} + + +template T binary_heap_upair_queue::get_priority(unsigned i, unsigned j) const { + auto it = m_pairs_to_index.find(std::make_pair(i, j)); + if (it == m_pairs_to_index.end()) + return T(0xFFFFFF); // big number + return m_q.get_priority(it->second); +} + +#ifdef LEAN_DEBUG +template bool binary_heap_upair_queue::pair_to_index_is_a_bijection() const { + std::set tmp; + for (auto p : m_pairs_to_index) { + unsigned j = p.second; + unsigned size = tmp.size(); + tmp.insert(j); + if (tmp.size() == size) + return false; + } + return true; +} + +template bool binary_heap_upair_queue::available_spots_are_correct() const { + std::set tmp; + for (auto p : m_available_spots){ + tmp.insert(p); + } + if (tmp.size() != m_available_spots.size()) + return false; + for (auto it : m_pairs_to_index) + if (tmp.find(it.second) != tmp.end()) + return false; + return true; +} +#endif +} diff --git a/src/util/lp/binary_heap_upair_queue_instances.cpp b/src/util/lp/binary_heap_upair_queue_instances.cpp new file mode 100644 index 000000000..4c4603110 --- /dev/null +++ b/src/util/lp/binary_heap_upair_queue_instances.cpp @@ -0,0 +1,17 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include "util/lp/binary_heap_upair_queue.hpp" +namespace lean { +template binary_heap_upair_queue::binary_heap_upair_queue(unsigned int); +template binary_heap_upair_queue::binary_heap_upair_queue(unsigned int); +template unsigned binary_heap_upair_queue::dequeue_available_spot(); +template unsigned binary_heap_upair_queue::dequeue_available_spot(); +template void binary_heap_upair_queue::enqueue(unsigned int, unsigned int, int const&); +template void binary_heap_upair_queue::remove(unsigned int, unsigned int); +template void binary_heap_upair_queue::remove(unsigned int, unsigned int); +template void binary_heap_upair_queue::dequeue(unsigned int&, unsigned int&); +template void binary_heap_upair_queue::enqueue(unsigned int, unsigned int, unsigned int const&); +template void binary_heap_upair_queue::dequeue(unsigned int&, unsigned int&); +} diff --git a/src/util/lp/bound_analyzer_on_row.h b/src/util/lp/bound_analyzer_on_row.h new file mode 100644 index 000000000..7987c89e9 --- /dev/null +++ b/src/util/lp/bound_analyzer_on_row.h @@ -0,0 +1,333 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#pragma once +#include "util/vector.h" +#include "util/lp/linear_combination_iterator.h" +#include "implied_bound.h" +#include "test_bound_analyzer.h" +#include +#include "util/lp/bound_propagator.h" +// We have an equality : sum by j of row[j]*x[j] = rs +// We try to pin a var by pushing the total by using the variable bounds +// In a loop we drive the partial sum down, denoting the variables of this process by _u. +// In the same loop trying to pin variables by pushing the partial sum up, denoting the variable related to it by _l +namespace lean { + +class bound_analyzer_on_row { + + linear_combination_iterator & m_it; + unsigned m_row_or_term_index; + int m_column_of_u = -1; // index of an unlimited from above monoid + // -1 means that such a value is not found, -2 means that at least two of such monoids were found + int m_column_of_l = -1; // index of an unlimited from below monoid + impq m_rs; + bound_propagator & m_bp; +public : + // constructor + bound_analyzer_on_row( + linear_combination_iterator &it, + const numeric_pair& rs, + unsigned row_or_term_index, + bound_propagator & bp + ) + : + m_it(it), + m_row_or_term_index(row_or_term_index), + m_rs(rs), + m_bp(bp) + {} + + + unsigned j; + void analyze() { + + mpq a; unsigned j; + while (((m_column_of_l != -2) || (m_column_of_u != -2)) && m_it.next(a, j)) + analyze_bound_on_var_on_coeff(j, a); + + if (m_column_of_u >= 0) + limit_monoid_u_from_below(); + else if (m_column_of_u == -1) + limit_all_monoids_from_below(); + + if (m_column_of_l >= 0) + limit_monoid_l_from_above(); + else if (m_column_of_l == -1) + limit_all_monoids_from_above(); + } + + bool bound_is_available(unsigned j, bool low_bound) { + return (low_bound && low_bound_is_available(j)) || + (!low_bound && upper_bound_is_available(j)); + } + + bool upper_bound_is_available(unsigned j) const { + switch (m_bp.get_column_type(j)) + { + case column_type::fixed: + case column_type::boxed: + case column_type::upper_bound: + return true; + default: + return false; + } + } + + bool low_bound_is_available(unsigned j) const { + switch (m_bp.get_column_type(j)) + { + case column_type::fixed: + case column_type::boxed: + case column_type::low_bound: + return true; + default: + return false; + } + } + + const impq & ub(unsigned j) const { + lean_assert(upper_bound_is_available(j)); + return m_bp.get_upper_bound(j); + } + const impq & lb(unsigned j) const { + lean_assert(low_bound_is_available(j)); + return m_bp.get_low_bound(j); + } + + + const mpq & monoid_max_no_mult(bool a_is_pos, unsigned j, bool & strict) const { + if (a_is_pos) { + strict = !is_zero(ub(j).y); + return ub(j).x; + } + strict = !is_zero(lb(j).y); + return lb(j).x; + } + mpq monoid_max(const mpq & a, unsigned j) const { + if (is_pos(a)) { + return a * ub(j).x; + } + return a * lb(j).x; + } + mpq monoid_max(const mpq & a, unsigned j, bool & strict) const { + if (is_pos(a)) { + strict = !is_zero(ub(j).y); + return a * ub(j).x; + } + strict = !is_zero(lb(j).y); + return a * lb(j).x; + } + const mpq & monoid_min_no_mult(bool a_is_pos, unsigned j, bool & strict) const { + if (!a_is_pos) { + strict = !is_zero(ub(j).y); + return ub(j).x; + } + strict = !is_zero(lb(j).y); + return lb(j).x; + } + + mpq monoid_min(const mpq & a, unsigned j, bool& strict) const { + if (is_neg(a)) { + strict = !is_zero(ub(j).y); + return a * ub(j).x; + } + + strict = !is_zero(lb(j).y); + return a * lb(j).x; + } + + mpq monoid_min(const mpq & a, unsigned j) const { + if (is_neg(a)) { + return a * ub(j).x; + } + + return a * lb(j).x; + } + + + void limit_all_monoids_from_above() { + int strict = 0; + mpq total; + lean_assert(is_zero(total)); + m_it.reset(); + mpq a; unsigned j; + while (m_it.next(a, j)) { + bool str; + total -= monoid_min(a, j, str); + if (str) + strict++; + } + + m_it.reset(); + while (m_it.next(a, j)) { + bool str; + bool a_is_pos = is_pos(a); + mpq bound = total / a + monoid_min_no_mult(a_is_pos, j, str); + if (a_is_pos) { + limit_j(j, bound, true, false, strict - static_cast(str) > 0); + } + else { + limit_j(j, bound, false, true, strict - static_cast(str) > 0); + } + } + } + + void limit_all_monoids_from_below() { + int strict = 0; + mpq total; + lean_assert(is_zero(total)); + m_it.reset(); + mpq a; unsigned j; + while (m_it.next(a, j)) { + bool str; + total -= monoid_max(a, j, str); + if (str) + strict++; + } + m_it.reset(); + while (m_it.next(a, j)) { + bool str; + bool a_is_pos = is_pos(a); + mpq bound = total / a + monoid_max_no_mult(a_is_pos, j, str); + bool astrict = strict - static_cast(str) > 0; + if (a_is_pos) { + limit_j(j, bound, true, true, astrict); + } + else { + limit_j(j, bound, false, false, astrict); + } + } + } + + + void limit_monoid_u_from_below() { + // we are going to limit from below the monoid m_column_of_u, + // every other monoid is impossible to limit from below + mpq u_coeff, a; + unsigned j; + mpq bound = -m_rs.x; + m_it.reset(); + bool strict = false; + while (m_it.next(a, j)) { + if (j == static_cast(m_column_of_u)) { + u_coeff = a; + continue; + } + bool str; + bound -= monoid_max(a, j, str); + if (str) + strict = true; + } + + bound /= u_coeff; + + if (numeric_traits::is_pos(u_coeff)) { + limit_j(m_column_of_u, bound, true, true, strict); + } else { + limit_j(m_column_of_u, bound, false, false, strict); + } + } + + + void limit_monoid_l_from_above() { + // we are going to limit from above the monoid m_column_of_l, + // every other monoid is impossible to limit from above + mpq l_coeff, a; + unsigned j; + mpq bound = -m_rs.x; + bool strict = false; + m_it.reset(); + while (m_it.next(a, j)) { + if (j == static_cast(m_column_of_l)) { + l_coeff = a; + continue; + } + + bool str; + bound -= monoid_min(a, j, str); + if (str) + strict = true; + } + + bound /= l_coeff; + if (is_pos(l_coeff)) { + limit_j(m_column_of_l, bound, true, false, strict); + } else { + limit_j(m_column_of_l, bound, false, true, strict); + } + } + + // // it is the coefficent before the bounded column + // void provide_evidence(bool coeff_is_pos) { + // /* + // auto & be = m_ibounds.back(); + // bool low_bound = be.m_low_bound; + // if (!coeff_is_pos) + // low_bound = !low_bound; + // auto it = m_it.clone(); + // mpq a; unsigned j; + // while (it->next(a, j)) { + // if (be.m_j == j) continue; + // lean_assert(bound_is_available(j, is_neg(a) ? low_bound : !low_bound)); + // be.m_vector_of_bound_signatures.emplace_back(a, j, numeric_traits:: + // is_neg(a)? low_bound: !low_bound); + // } + // delete it; + // */ + // } + + void limit_j(unsigned j, const mpq& u, bool coeff_before_j_is_pos, bool is_low_bound, bool strict){ + m_bp.try_add_bound(u, j, is_low_bound, coeff_before_j_is_pos, m_row_or_term_index, strict); + } + + + void advance_u(unsigned j) { + if (m_column_of_u == -1) + m_column_of_u = j; + else + m_column_of_u = -2; + } + + void advance_l(unsigned j) { + if (m_column_of_l == -1) + m_column_of_l = j; + else + m_column_of_l = -2; + } + + void analyze_bound_on_var_on_coeff(int j, const mpq &a) { + switch (m_bp.get_column_type(j)) { + case column_type::low_bound: + if (numeric_traits::is_pos(a)) + advance_u(j); + else + advance_l(j); + break; + case column_type::upper_bound: + if(numeric_traits::is_neg(a)) + advance_u(j); + else + advance_l(j); + break; + case column_type::free_column: + advance_u(j); + advance_l(j); + break; + default: + break; + } + } + + static void analyze_row(linear_combination_iterator &it, + const numeric_pair& rs, + unsigned row_or_term_index, + bound_propagator & bp + ) { + bound_analyzer_on_row a(it, rs, row_or_term_index, bp); + a.analyze(); + } + +}; +} diff --git a/src/util/lp/bound_propagator.cpp b/src/util/lp/bound_propagator.cpp new file mode 100644 index 000000000..0d58ec2be --- /dev/null +++ b/src/util/lp/bound_propagator.cpp @@ -0,0 +1,47 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include "util/lp/lar_solver.h" +namespace lean { +bound_propagator::bound_propagator(lar_solver & ls): + m_lar_solver(ls) {} +column_type bound_propagator::get_column_type(unsigned j) const { + return m_lar_solver.m_mpq_lar_core_solver.m_column_types()[j]; +} +const impq & bound_propagator::get_low_bound(unsigned j) const { + return m_lar_solver.m_mpq_lar_core_solver.m_r_low_bounds()[j]; +} +const impq & bound_propagator::get_upper_bound(unsigned j) const { + return m_lar_solver.m_mpq_lar_core_solver.m_r_upper_bounds()[j]; +} +void bound_propagator::try_add_bound(const mpq & v, unsigned j, bool is_low, bool coeff_before_j_is_pos, unsigned row_or_term_index, bool strict) { + j = m_lar_solver.adjust_column_index_to_term_index(j); + lconstraint_kind kind = is_low? GE : LE; + if (strict) + kind = static_cast(kind / 2); + + if (!bound_is_interesting(j, kind, v)) + return; + unsigned k; // index to ibounds + if (is_low) { + if (try_get_val(m_improved_low_bounds, j, k)) { + auto & found_bound = m_ibounds[k]; + if (v > found_bound.m_bound || (v == found_bound.m_bound && found_bound.m_strict == false && strict)) + found_bound = implied_bound(v, j, is_low, coeff_before_j_is_pos, row_or_term_index, strict); + } else { + m_improved_low_bounds[j] = m_ibounds.size(); + m_ibounds.push_back(implied_bound(v, j, is_low, coeff_before_j_is_pos, row_or_term_index, strict)); + } + } else { // the upper bound case + if (try_get_val(m_improved_upper_bounds, j, k)) { + auto & found_bound = m_ibounds[k]; + if (v < found_bound.m_bound || (v == found_bound.m_bound && found_bound.m_strict == false && strict)) + found_bound = implied_bound(v, j, is_low, coeff_before_j_is_pos, row_or_term_index, strict); + } else { + m_improved_upper_bounds[j] = m_ibounds.size(); + m_ibounds.push_back(implied_bound(v, j, is_low, coeff_before_j_is_pos, row_or_term_index, strict)); + } + } +} +} diff --git a/src/util/lp/bound_propagator.h b/src/util/lp/bound_propagator.h new file mode 100644 index 000000000..92523d75f --- /dev/null +++ b/src/util/lp/bound_propagator.h @@ -0,0 +1,27 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#pragma once +#include "util/lp/lp_settings.h" +namespace lean { +class lar_solver; +class bound_propagator { + std::unordered_map m_improved_low_bounds; // these maps map a column index to the corresponding index in ibounds + std::unordered_map m_improved_upper_bounds; + lar_solver & m_lar_solver; +public: + vector m_ibounds; +public: + bound_propagator(lar_solver & ls); + column_type get_column_type(unsigned) const; + const impq & get_low_bound(unsigned) const; + const impq & get_upper_bound(unsigned) const; + void try_add_bound(const mpq & v, unsigned j, bool is_low, bool coeff_before_j_is_pos, unsigned row_or_term_index, bool strict); + virtual bool bound_is_interesting(unsigned vi, + lean::lconstraint_kind kind, + const rational & bval) {return true;} + unsigned number_of_found_bounds() const { return m_ibounds.size(); } + virtual void consume(mpq const& v, unsigned j) { std::cout << "doh\n"; } +}; +} diff --git a/src/util/lp/breakpoint.h b/src/util/lp/breakpoint.h new file mode 100644 index 000000000..e5454db0e --- /dev/null +++ b/src/util/lp/breakpoint.h @@ -0,0 +1,20 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ + +#pragma once + +namespace lean { +enum breakpoint_type { + low_break, upper_break, fixed_break +}; +template +struct breakpoint { + unsigned m_j; // the basic column + breakpoint_type m_type; + X m_delta; + breakpoint(){} + breakpoint(unsigned j, X delta, breakpoint_type type):m_j(j), m_type(type), m_delta(delta) {} +}; +} diff --git a/src/util/lp/column_info.h b/src/util/lp/column_info.h new file mode 100644 index 000000000..7e44ccf43 --- /dev/null +++ b/src/util/lp/column_info.h @@ -0,0 +1,235 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ + +#pragma once +#include "util/vector.h" +#include +#include +#include +#include "util/lp/lp_settings.h" +namespace lean { +inline bool is_valid(unsigned j) { return static_cast(j) >= 0;} + +template +class column_info { + std::string m_name; + bool m_low_bound_is_set = false; + bool m_low_bound_is_strict = false; + bool m_upper_bound_is_set = false; + bool m_upper_bound_is_strict = false; + T m_low_bound; + T m_upper_bound; + T m_cost = numeric_traits::zero(); + T m_fixed_value; + bool m_is_fixed = false; + unsigned m_column_index = static_cast(-1); +public: + bool operator==(const column_info & c) const { + return m_name == c.m_name && + m_low_bound_is_set == c.m_low_bound_is_set && + m_low_bound_is_strict == c.m_low_bound_is_strict && + m_upper_bound_is_set == c.m_upper_bound_is_set&& + m_upper_bound_is_strict == c.m_upper_bound_is_strict&& + (!m_low_bound_is_set || m_low_bound == c.m_low_bound) && + (!m_upper_bound_is_set || m_upper_bound == c.m_upper_bound) && + m_cost == c.m_cost&& + m_is_fixed == c.m_is_fixed && + (!m_is_fixed || m_fixed_value == c.m_fixed_value) && + m_column_index == c.m_column_index; + } + bool operator!=(const column_info & c) const { return !((*this) == c); } + void set_column_index(unsigned j) { + m_column_index = j; + } + // the default constructor + column_info() {} + + column_info(unsigned column_index) : m_column_index(column_index) { + } + + column_info(const column_info & ci) { + m_name = ci.m_name; + m_low_bound_is_set = ci.m_low_bound_is_set; + m_low_bound_is_strict = ci.m_low_bound_is_strict; + m_upper_bound_is_set = ci.m_upper_bound_is_set; + m_upper_bound_is_strict = ci.m_upper_bound_is_strict; + m_low_bound = ci.m_low_bound; + m_upper_bound = ci.m_upper_bound; + m_cost = ci.m_cost; + m_fixed_value = ci.m_fixed_value; + m_is_fixed = ci.m_is_fixed; + m_column_index = ci.m_column_index; + } + + unsigned get_column_index() const { + return m_column_index; + } + + column_type get_column_type() const { + return m_is_fixed? column_type::fixed : (m_low_bound_is_set? (m_upper_bound_is_set? column_type::boxed : column_type::low_bound) : (m_upper_bound_is_set? column_type::upper_bound: column_type::free_column)); + } + + column_type get_column_type_no_flipping() const { + if (m_is_fixed) { + return column_type::fixed; + } + + if (m_low_bound_is_set) { + return m_upper_bound_is_set? column_type::boxed: column_type::low_bound; + } + // we are flipping the bounds! + return m_upper_bound_is_set? column_type::upper_bound + : column_type::free_column; + } + + T get_low_bound() const { + lean_assert(m_low_bound_is_set); + return m_low_bound; + } + T get_upper_bound() const { + lean_assert(m_upper_bound_is_set); + return m_upper_bound; + } + + bool low_bound_is_set() const { + return m_low_bound_is_set; + } + + bool upper_bound_is_set() const { + return m_upper_bound_is_set; + } + + T get_shift() { + if (is_fixed()) { + return m_fixed_value; + } + if (is_flipped()){ + return m_upper_bound; + } + return m_low_bound_is_set? m_low_bound : numeric_traits::zero(); + } + + bool is_flipped() { + return m_upper_bound_is_set && !m_low_bound_is_set; + } + + bool adjusted_low_bound_is_set() { + return !is_flipped()? low_bound_is_set(): upper_bound_is_set(); + } + + bool adjusted_upper_bound_is_set() { + return !is_flipped()? upper_bound_is_set(): low_bound_is_set(); + } + + T get_adjusted_upper_bound() { + return get_upper_bound() - get_low_bound(); + } + + bool is_fixed() const { + return m_is_fixed; + } + + bool is_free() { + return !m_low_bound_is_set && !m_upper_bound_is_set; + } + + void set_fixed_value(T v) { + m_is_fixed = true; + m_fixed_value = v; + } + + T get_fixed_value() const { + lean_assert(m_is_fixed); + return m_fixed_value; + } + + T get_cost() const { + return m_cost; + } + + void set_cost(T const & cost) { + m_cost = cost; + } + + void set_name(std::string const & s) { + m_name = s; + } + + std::string get_name() const { + return m_name; + } + + void set_low_bound(T const & l) { + m_low_bound = l; + m_low_bound_is_set = true; + } + + void set_upper_bound(T const & l) { + m_upper_bound = l; + m_upper_bound_is_set = true; + } + + void unset_low_bound() { + m_low_bound_is_set = false; + } + + void unset_upper_bound() { + m_upper_bound_is_set = false; + } + + void unset_fixed() { + m_is_fixed = false; + } + + bool low_bound_holds(T v) { + return !low_bound_is_set() || v >= m_low_bound -T(0.0000001); + } + + bool upper_bound_holds(T v) { + return !upper_bound_is_set() || v <= m_upper_bound + T(0.000001); + } + + bool bounds_hold(T v) { + return low_bound_holds(v) && upper_bound_holds(v); + } + + bool adjusted_bounds_hold(T v) { + return adjusted_low_bound_holds(v) && adjusted_upper_bound_holds(v); + } + + bool adjusted_low_bound_holds(T v) { + return !adjusted_low_bound_is_set() || v >= -T(0.0000001); + } + + bool adjusted_upper_bound_holds(T v) { + return !adjusted_upper_bound_is_set() || v <= get_adjusted_upper_bound() + T(0.000001); + } + bool is_infeasible() { + if ((!upper_bound_is_set()) || (!low_bound_is_set())) + return false; + // ok, both bounds are set + bool at_least_one_is_strict = upper_bound_is_strict() || low_bound_is_strict(); + if (!at_least_one_is_strict) + return get_upper_bound() < get_low_bound(); + // at least on bound is strict + return get_upper_bound() <= get_low_bound(); // the equality is impossible + } + bool low_bound_is_strict() const { + return m_low_bound_is_strict; + } + + void set_low_bound_strict(bool val) { + m_low_bound_is_strict = val; + } + + bool upper_bound_is_strict() const { + return m_upper_bound_is_strict; + } + + void set_upper_bound_strict(bool val) { + m_upper_bound_is_strict = val; + } +}; +} diff --git a/src/util/lp/column_namer.h b/src/util/lp/column_namer.h new file mode 100644 index 000000000..1a10a5a23 --- /dev/null +++ b/src/util/lp/column_namer.h @@ -0,0 +1,82 @@ +#pragma once +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include +#include "util/lp/linear_combination_iterator.h" +namespace lean { +class column_namer { +public: + virtual std::string get_column_name(unsigned j) const = 0; + template + void print_linear_iterator(linear_combination_iterator* it, std::ostream & out) const { + vector> coeff; + T a; + unsigned i; + while (it->next(a, i)) { + coeff.emplace_back(a, i); + } + print_linear_combination_of_column_indices(coeff, out); + } + template + void print_linear_iterator_indices_only(linear_combination_iterator* it, std::ostream & out) const { + vector> coeff; + T a; + unsigned i; + while (it->next(a, i)) { + coeff.emplace_back(a, i); + } + print_linear_combination_of_column_indices_only(coeff, out); + } + + template + void print_linear_combination_of_column_indices_only(const vector> & coeffs, std::ostream & out) const { + bool first = true; + for (const auto & it : coeffs) { + auto val = it.first; + if (first) { + first = false; + } else { + if (numeric_traits::is_pos(val)) { + out << " + "; + } else { + out << " - "; + val = -val; + } + } + if (val == -numeric_traits::one()) + out << " - "; + else if (val != numeric_traits::one()) + out << T_to_string(val); + + out << "_" << it.second; + } + } + + template + void print_linear_combination_of_column_indices(const vector> & coeffs, std::ostream & out) const { + bool first = true; + for (const auto & it : coeffs) { + auto val = it.first; + if (first) { + first = false; + } else { + if (numeric_traits::is_pos(val)) { + out << " + "; + } else { + out << " - "; + val = -val; + } + } + if (val == -numeric_traits::one()) + out << " - "; + else if (val != numeric_traits::one()) + out << val; + + out << get_column_name(it.second); + } + } + +}; +} diff --git a/src/util/lp/core_solver_pretty_printer.h b/src/util/lp/core_solver_pretty_printer.h new file mode 100644 index 000000000..6dbeb28cf --- /dev/null +++ b/src/util/lp/core_solver_pretty_printer.h @@ -0,0 +1,118 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#pragma once +#include +#include +#include +#include "util/vector.h" +#include +#include "util/lp/lp_settings.h" +#include "util/lp/indexed_vector.h" +namespace lean { +template class lp_core_solver_base; // forward definition + +template +class core_solver_pretty_printer { + std::ostream & m_out; + template using vector = vector; + typedef std::string string; + lp_core_solver_base & m_core_solver; + vector m_column_widths; + vector> m_A; + vector> m_signs; + vector m_costs; + vector m_cost_signs; + vector m_lows; // low bounds + vector m_upps; // upper bounds + vector m_lows_signs; + vector m_upps_signs; + unsigned m_rs_width; + vector m_rs; + unsigned m_title_width; + std::string m_cost_title; + std::string m_basis_heading_title; + std::string m_x_title; + std::string m_low_bounds_title = "low"; + std::string m_upp_bounds_title = "upp"; + std::string m_exact_norm_title = "exact cn"; + std::string m_approx_norm_title = "approx cn"; + + + unsigned ncols() { return m_core_solver.m_A.column_count(); } + unsigned nrows() { return m_core_solver.m_A.row_count(); } + unsigned m_artificial_start = std::numeric_limits::max(); + indexed_vector m_w_buff; + indexed_vector m_ed_buff; + vector m_exact_column_norms; + +public: + core_solver_pretty_printer(lp_core_solver_base & core_solver, std::ostream & out); + + void init_costs(); + + ~core_solver_pretty_printer(); + void init_rs_width(); + + T current_column_norm(); + + void init_m_A_and_signs(); + + void init_column_widths(); + + void adjust_width_with_low_bound(unsigned column, unsigned & w); + void adjust_width_with_upper_bound(unsigned column, unsigned & w); + + void adjust_width_with_bounds(unsigned column, unsigned & w); + + void adjust_width_with_basis_heading(unsigned column, unsigned & w) { + w = std::max(w, (unsigned)T_to_string(m_core_solver.m_basis_heading[column]).size()); + } + + unsigned get_column_width(unsigned column); + + unsigned regular_cell_width(unsigned row, unsigned column, std::string name) { + return regular_cell_string(row, column, name).size(); + } + + std::string regular_cell_string(unsigned row, unsigned column, std::string name); + + + void set_coeff(vector& row, vector & row_signs, unsigned col, const T & t, string name); + + void print_x(); + + std::string get_low_bound_string(unsigned j); + + std::string get_upp_bound_string(unsigned j); + + + void print_lows(); + + void print_upps(); + + string get_exact_column_norm_string(unsigned col) { + return T_to_string(m_exact_column_norms[col]); + } + + void print_exact_norms(); + + void print_approx_norms(); + + void print(); + + void print_basis_heading(); + + void print_bottom_line() { + m_out << "----------------------" << std::endl; + } + + void print_cost(); + + void print_given_rows(vector & row, vector & signs, X rst); + + void print_row(unsigned i); + +}; +} diff --git a/src/util/lp/core_solver_pretty_printer.hpp b/src/util/lp/core_solver_pretty_printer.hpp new file mode 100644 index 000000000..aa963b220 --- /dev/null +++ b/src/util/lp/core_solver_pretty_printer.hpp @@ -0,0 +1,377 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include +#include +#include +#include "util/lp/lp_utils.h" +#include "util/lp/lp_core_solver_base.h" +#include "util/lp/core_solver_pretty_printer.h" +#include "util/lp/numeric_pair.h" +namespace lean { + + +template +core_solver_pretty_printer::core_solver_pretty_printer(lp_core_solver_base & core_solver, std::ostream & out): + m_out(out), + m_core_solver(core_solver), + m_A(core_solver.m_A.row_count(), vector(core_solver.m_A.column_count(), "")), + m_signs(core_solver.m_A.row_count(), vector(core_solver.m_A.column_count(), " ")), + m_costs(ncols(), ""), + m_cost_signs(ncols(), " "), + m_rs(ncols(), zero_of_type()), + m_w_buff(core_solver.m_w), + m_ed_buff(core_solver.m_ed) { + m_column_widths.resize(core_solver.m_A.column_count(), 0), + init_m_A_and_signs(); + init_costs(); + init_column_widths(); + init_rs_width(); + m_cost_title = "costs"; + m_basis_heading_title = "heading"; + m_x_title = "x*"; + m_title_width = static_cast(std::max(std::max(m_cost_title.size(), std::max(m_basis_heading_title.size(), m_x_title.size())), m_approx_norm_title.size())); +} + +template void core_solver_pretty_printer::init_costs() { + if (!m_core_solver.use_tableau()) { + vector local_y(m_core_solver.m_m()); + m_core_solver.solve_yB(local_y); + for (unsigned i = 0; i < ncols(); i++) { + if (m_core_solver.m_basis_heading[i] < 0) { + T t = m_core_solver.m_costs[i] - m_core_solver.m_A.dot_product_with_column(local_y, i); + set_coeff(m_costs, m_cost_signs, i, t, m_core_solver.column_name(i)); + } + } + } else { + for (unsigned i = 0; i < ncols(); i++) { + if (m_core_solver.m_basis_heading[i] < 0) { + set_coeff(m_costs, m_cost_signs, i, m_core_solver.m_d[i], m_core_solver.column_name(i)); + } + } + } +} + +template core_solver_pretty_printer::~core_solver_pretty_printer() { + m_core_solver.m_w = m_w_buff; + m_core_solver.m_ed = m_ed_buff; +} +template void core_solver_pretty_printer::init_rs_width() { + m_rs_width = static_cast(T_to_string(m_core_solver.get_cost()).size()); + for (unsigned i = 0; i < nrows(); i++) { + unsigned wt = static_cast(T_to_string(m_rs[i]).size()); + if (wt > m_rs_width) { + m_rs_width = wt; + } + } +} + +template T core_solver_pretty_printer::current_column_norm() { + T ret = zero_of_type(); + for (auto i : m_core_solver.m_ed.m_index) + ret += m_core_solver.m_ed[i] * m_core_solver.m_ed[i]; + return ret; +} + +template void core_solver_pretty_printer::init_m_A_and_signs() { + if (numeric_traits::precise() && m_core_solver.m_settings.use_tableau()) { + for (unsigned column = 0; column < ncols(); column++) { + vector t(nrows(), zero_of_type()); + for (const auto & c : m_core_solver.m_A.m_columns[column]){ + t[c.m_i] = m_core_solver.m_A.get_val(c); + } + + string name = m_core_solver.column_name(column); + for (unsigned row = 0; row < nrows(); row ++) { + set_coeff( + m_A[row], + m_signs[row], + column, + t[row], + name); + m_rs[row] += t[row] * m_core_solver.m_x[column]; + } + } + } else { + for (unsigned column = 0; column < ncols(); column++) { + m_core_solver.solve_Bd(column); // puts the result into m_core_solver.m_ed + string name = m_core_solver.column_name(column); + for (unsigned row = 0; row < nrows(); row ++) { + set_coeff( + m_A[row], + m_signs[row], + column, + m_core_solver.m_ed[row], + name); + m_rs[row] += m_core_solver.m_ed[row] * m_core_solver.m_x[column]; + } + if (!m_core_solver.use_tableau()) + m_exact_column_norms.push_back(current_column_norm() + T(1)); // a conversion missing 1 -> T + } + } +} + +template void core_solver_pretty_printer::init_column_widths() { + for (unsigned i = 0; i < ncols(); i++) { + m_column_widths[i] = get_column_width(i); + } +} + +template void core_solver_pretty_printer::adjust_width_with_low_bound(unsigned column, unsigned & w) { + if (!m_core_solver.low_bounds_are_set()) return; + w = std::max(w, (unsigned)T_to_string(m_core_solver.low_bound_value(column)).size()); +} +template void core_solver_pretty_printer::adjust_width_with_upper_bound(unsigned column, unsigned & w) { + w = std::max(w, (unsigned)T_to_string(m_core_solver.upper_bound_value(column)).size()); +} + +template void core_solver_pretty_printer::adjust_width_with_bounds(unsigned column, unsigned & w) { + switch (m_core_solver.get_column_type(column)) { + case column_type::fixed: + case column_type::boxed: + adjust_width_with_low_bound(column, w); + adjust_width_with_upper_bound(column, w); + break; + case column_type::low_bound: + adjust_width_with_low_bound(column, w); + break; + case column_type::upper_bound: + adjust_width_with_upper_bound(column, w); + break; + case column_type::free_column: + break; + default: + lean_assert(false); + break; + } +} + + +template unsigned core_solver_pretty_printer:: get_column_width(unsigned column) { + unsigned w = static_cast(std::max((size_t)m_costs[column].size(), T_to_string(m_core_solver.m_x[column]).size())); + adjust_width_with_bounds(column, w); + adjust_width_with_basis_heading(column, w); + for (unsigned i = 0; i < nrows(); i++) { + unsigned cellw = static_cast(m_A[i][column].size()); + if (cellw > w) { + w = cellw; + } + } + if (!m_core_solver.use_tableau()) { + w = std::max(w, (unsigned)T_to_string(m_exact_column_norms[column]).size()); + if (m_core_solver.m_column_norms.size() > 0) + w = std::max(w, (unsigned)T_to_string(m_core_solver.m_column_norms[column]).size()); + } + return w; +} + +template std::string core_solver_pretty_printer::regular_cell_string(unsigned row, unsigned /* column */, std::string name) { + T t = fabs(m_core_solver.m_ed[row]); + if ( t == 1) return name; + return T_to_string(t) + name; +} + + +template void core_solver_pretty_printer::set_coeff(vector& row, vector & row_signs, unsigned col, const T & t, string name) { + if (numeric_traits::is_zero(t)) { + return; + } + if (col > 0) { + if (t > 0) { + row_signs[col] = "+"; + row[col] = t != 1? T_to_string(t) + name : name; + } else { + row_signs[col] = "-"; + row[col] = t != -1? T_to_string(-t) + name: name; + } + } else { // col == 0 + if (t == -1) { + row[col] = "-" + name; + } else if (t == 1) { + row[col] = name; + } else { + row[col] = T_to_string(t) + name; + } + } +} + +template void core_solver_pretty_printer::print_x() { + if (ncols() == 0) { + return; + } + + int blanks = m_title_width + 1 - static_cast(m_x_title.size()); + m_out << m_x_title; + print_blanks(blanks, m_out); + + auto bh = m_core_solver.m_x; + for (unsigned i = 0; i < ncols(); i++) { + string s = T_to_string(bh[i]); + int blanks = m_column_widths[i] - static_cast(s.size()); + print_blanks(blanks, m_out); + m_out << s << " "; // the column interval + } + m_out << std::endl; +} + +template std::string core_solver_pretty_printer::get_low_bound_string(unsigned j) { + switch (m_core_solver.get_column_type(j)){ + case column_type::boxed: + case column_type::low_bound: + case column_type::fixed: + if (m_core_solver.low_bounds_are_set()) + return T_to_string(m_core_solver.low_bound_value(j)); + else + return std::string("0"); + break; + default: + return std::string(); + } +} + +template std::string core_solver_pretty_printer::get_upp_bound_string(unsigned j) { + switch (m_core_solver.get_column_type(j)){ + case column_type::boxed: + case column_type::upper_bound: + case column_type::fixed: + return T_to_string(m_core_solver.upper_bound_value(j)); + break; + default: + return std::string(); + } +} + + +template void core_solver_pretty_printer::print_lows() { + if (ncols() == 0) { + return; + } + int blanks = m_title_width + 1 - static_cast(m_low_bounds_title.size()); + m_out << m_low_bounds_title; + print_blanks(blanks, m_out); + + for (unsigned i = 0; i < ncols(); i++) { + string s = get_low_bound_string(i); + int blanks = m_column_widths[i] - static_cast(s.size()); + print_blanks(blanks, m_out); + m_out << s << " "; // the column interval + } + m_out << std::endl; +} + +template void core_solver_pretty_printer::print_upps() { + if (ncols() == 0) { + return; + } + int blanks = m_title_width + 1 - static_cast(m_upp_bounds_title.size()); + m_out << m_upp_bounds_title; + print_blanks(blanks, m_out); + + for (unsigned i = 0; i < ncols(); i++) { + string s = get_upp_bound_string(i); + int blanks = m_column_widths[i] - static_cast(s.size()); + print_blanks(blanks, m_out); + m_out << s << " "; // the column interval + } + m_out << std::endl; +} + +template void core_solver_pretty_printer::print_exact_norms() { + if (m_core_solver.use_tableau()) return; + int blanks = m_title_width + 1 - static_cast(m_exact_norm_title.size()); + m_out << m_exact_norm_title; + print_blanks(blanks, m_out); + for (unsigned i = 0; i < ncols(); i++) { + string s = get_exact_column_norm_string(i); + int blanks = m_column_widths[i] - static_cast(s.size()); + print_blanks(blanks, m_out); + m_out << s << " "; + } + m_out << std::endl; +} + +template void core_solver_pretty_printer::print_approx_norms() { + if (m_core_solver.use_tableau()) return; + int blanks = m_title_width + 1 - static_cast(m_approx_norm_title.size()); + m_out << m_approx_norm_title; + print_blanks(blanks, m_out); + for (unsigned i = 0; i < ncols(); i++) { + string s = T_to_string(m_core_solver.m_column_norms[i]); + int blanks = m_column_widths[i] - static_cast(s.size()); + print_blanks(blanks, m_out); + m_out << s << " "; + } + m_out << std::endl; +} + +template void core_solver_pretty_printer::print() { + for (unsigned i = 0; i < nrows(); i++) { + print_row(i); + } + print_bottom_line(); + print_cost(); + print_x(); + print_basis_heading(); + print_lows(); + print_upps(); + print_exact_norms(); + if (m_core_solver.m_column_norms.size() > 0) + print_approx_norms(); + m_out << std::endl; +} + +template void core_solver_pretty_printer::print_basis_heading() { + int blanks = m_title_width + 1 - static_cast(m_basis_heading_title.size()); + m_out << m_basis_heading_title; + print_blanks(blanks, m_out); + + if (ncols() == 0) { + return; + } + auto bh = m_core_solver.m_basis_heading; + for (unsigned i = 0; i < ncols(); i++) { + string s = T_to_string(bh[i]); + int blanks = m_column_widths[i] - static_cast(s.size()); + print_blanks(blanks, m_out); + m_out << s << " "; // the column interval + } + m_out << std::endl; +} + +template void core_solver_pretty_printer::print_cost() { + int blanks = m_title_width + 1 - static_cast(m_cost_title.size()); + m_out << m_cost_title; + print_blanks(blanks, m_out); + print_given_rows(m_costs, m_cost_signs, m_core_solver.get_cost()); +} + +template void core_solver_pretty_printer::print_given_rows(vector & row, vector & signs, X rst) { + for (unsigned col = 0; col < row.size(); col++) { + unsigned width = m_column_widths[col]; + string s = row[col]; + int number_of_blanks = width - static_cast(s.size()); + lean_assert(number_of_blanks >= 0); + print_blanks(number_of_blanks, m_out); + m_out << s << ' '; + if (col < row.size() - 1) { + m_out << signs[col + 1] << ' '; + } + } + m_out << '='; + + string rs = T_to_string(rst); + int nb = m_rs_width - static_cast(rs.size()); + lean_assert(nb >= 0); + print_blanks(nb + 1, m_out); + m_out << rs << std::endl; +} + +template void core_solver_pretty_printer::print_row(unsigned i){ + print_blanks(m_title_width + 1, m_out); + auto row = m_A[i]; + auto sign_row = m_signs[i]; + auto rs = m_rs[i]; + print_given_rows(row, sign_row, rs); +} +} diff --git a/src/util/lp/core_solver_pretty_printer_instances.cpp b/src/util/lp/core_solver_pretty_printer_instances.cpp new file mode 100644 index 000000000..cfa72f725 --- /dev/null +++ b/src/util/lp/core_solver_pretty_printer_instances.cpp @@ -0,0 +1,15 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include "util/lp/numeric_pair.h" +#include "util/lp/core_solver_pretty_printer.hpp" +template lean::core_solver_pretty_printer::core_solver_pretty_printer(lean::lp_core_solver_base &, std::ostream & out); +template void lean::core_solver_pretty_printer::print(); +template lean::core_solver_pretty_printer::~core_solver_pretty_printer(); +template lean::core_solver_pretty_printer::core_solver_pretty_printer(lean::lp_core_solver_base &, std::ostream & out); +template void lean::core_solver_pretty_printer::print(); +template lean::core_solver_pretty_printer::~core_solver_pretty_printer(); +template lean::core_solver_pretty_printer >::core_solver_pretty_printer(lean::lp_core_solver_base > &, std::ostream & out); +template lean::core_solver_pretty_printer >::~core_solver_pretty_printer(); +template void lean::core_solver_pretty_printer >::print(); diff --git a/src/util/lp/dense_matrix.h b/src/util/lp/dense_matrix.h new file mode 100644 index 000000000..233f74016 --- /dev/null +++ b/src/util/lp/dense_matrix.h @@ -0,0 +1,92 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#pragma once +#ifdef LEAN_DEBUG +#include "util/vector.h" +#include "util/lp/matrix.h" +namespace lean { +// used for debugging purposes only +template +class dense_matrix: public matrix { +public: + struct ref { + unsigned m_i; + dense_matrix & m_s; + ref(unsigned i, dense_matrix & s) :m_i(i * s.m_n), m_s(s){} + T & operator[] (unsigned j) { + return m_s.m_values[m_i + j]; + } + const T & operator[] (unsigned j) const { + return m_s.m_v[m_i + j]; + } + }; + ref operator[] (unsigned i) { + return ref(i, *this); + } + unsigned m_m; // number of rows + unsigned m_n; // number of const + vector m_values; + dense_matrix(unsigned m, unsigned n); + + dense_matrix operator*=(matrix const & a) { + lean_assert(column_count() == a.row_count()); + dense_matrix c(row_count(), a.column_count()); + for (unsigned i = 0; i < row_count(); i++) { + for (unsigned j = 0; j < a.column_count(); j++) { + T v = numeric_traits::zero(); + for (unsigned k = 0; k < a.column_count(); k++) { + v += get_elem(i, k) * a(k, j); + } + c.set_elem(i, j, v); + } + } + *this = c; + return *this; + } + + dense_matrix & operator=(matrix const & other); + + dense_matrix & operator=(dense_matrix const & other); + + dense_matrix(matrix const * other); + void apply_from_right(T * w); + + void apply_from_right(vector & w); + + T * apply_from_left_with_different_dims(vector & w); + void apply_from_left(vector & w , lp_settings & ) { apply_from_left(w); } + + void apply_from_left(vector & w); + + void apply_from_left(X * w, lp_settings & ); + + void apply_from_left_to_X(vector & w, lp_settings & ); + + virtual void set_number_of_rows(unsigned /*m*/) {} + virtual void set_number_of_columns(unsigned /*n*/) { } + + T get_elem(unsigned i, unsigned j) const { return m_values[i * m_n + j]; } + + unsigned row_count() const { return m_m; } + unsigned column_count() const { return m_n; } + + void set_elem(unsigned i, unsigned j, const T& val) { m_values[i * m_n + j] = val; } + + // This method pivots row i to row i0 by muliplying row i by + // alpha and adding it to row i0. + void pivot_row_to_row(unsigned i, const T& alpha, unsigned i0, + const double & pivot_epsilon); + + void swap_columns(unsigned a, unsigned b); + + void swap_rows(unsigned a, unsigned b); + + void multiply_row_by_constant(unsigned row, T & t); + +}; +template +dense_matrix operator* (matrix & a, matrix & b); +} +#endif diff --git a/src/util/lp/dense_matrix.hpp b/src/util/lp/dense_matrix.hpp new file mode 100644 index 000000000..e42d9e3a4 --- /dev/null +++ b/src/util/lp/dense_matrix.hpp @@ -0,0 +1,186 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include "util/lp/lp_settings.h" +#ifdef LEAN_DEBUG +#include "util/vector.h" +#include "util/lp/numeric_pair.h" +#include "util/lp/dense_matrix.h" +namespace lean { +template void print_vector(const vector & t, std::ostream & out); +template dense_matrix::dense_matrix(unsigned m, unsigned n) : m_m(m), m_n(n), m_values(m * n, numeric_traits::zero()) { +} + +template dense_matrix& +dense_matrix::operator=(matrix const & other){ + if ( this == & other) + return *this; + m_values = new T[m_m * m_n]; + for (unsigned i = 0; i < m_m; i ++) + for (unsigned j = 0; j < m_n; j++) + m_values[i * m_n + j] = other.get_elem(i, j); + return *this; +} + +template dense_matrix& +dense_matrix::operator=(dense_matrix const & other){ + if ( this == & other) + return *this; + m_m = other.m_m; + m_n = other.m_n; + m_values.resize(m_m * m_n); + for (unsigned i = 0; i < m_m; i ++) + for (unsigned j = 0; j < m_n; j++) + m_values[i * m_n + j] = other.get_elem(i, j); + return *this; +} + +template dense_matrix::dense_matrix(matrix const * other) : + m_m(other->row_count()), + m_n(other->column_count()) { + m_values.resize(m_m*m_n); + for (unsigned i = 0; i < m_m; i++) + for (unsigned j = 0; j < m_n; j++) + m_values[i * m_n + j] = other->get_elem(i, j); +} + +template void dense_matrix::apply_from_right(T * w) { + T * t = new T[m_m]; + for (int i = 0; i < m_m; i ++) { + T v = numeric_traits::zero(); + for (int j = 0; j < m_m; j++) { + v += w[j]* get_elem(j, i); + } + t[i] = v; + } + + for (int i = 0; i < m_m; i++) { + w[i] = t[i]; + } + delete [] t; +} + +template void dense_matrix::apply_from_right(vector & w) { + vector t(m_m, numeric_traits::zero()); + for (unsigned i = 0; i < m_m; i ++) { + auto & v = t[i]; + for (unsigned j = 0; j < m_m; j++) + v += w[j]* get_elem(j, i); + } + + for (unsigned i = 0; i < m_m; i++) + w[i] = t[i]; +} + +template T* dense_matrix:: +apply_from_left_with_different_dims(vector & w) { + T * t = new T[m_m]; + for (int i = 0; i < m_m; i ++) { + T v = numeric_traits::zero(); + for (int j = 0; j < m_n; j++) { + v += w[j]* get_elem(i, j); + } + t[i] = v; + } + + return t; +} + +template void dense_matrix::apply_from_left(vector & w) { + T * t = new T[m_m]; + for (unsigned i = 0; i < m_m; i ++) { + T v = numeric_traits::zero(); + for (unsigned j = 0; j < m_m; j++) { + v += w[j]* get_elem(i, j); + } + t[i] = v; + } + + for (unsigned i = 0; i < m_m; i ++) { + w[i] = t[i]; + } + delete [] t; +} + +template void dense_matrix::apply_from_left(X * w, lp_settings & ) { + T * t = new T[m_m]; + for (int i = 0; i < m_m; i ++) { + T v = numeric_traits::zero(); + for (int j = 0; j < m_m; j++) { + v += w[j]* get_elem(i, j); + } + t[i] = v; + } + + for (int i = 0; i < m_m; i ++) { + w[i] = t[i]; + } + delete [] t; +} + +template void dense_matrix::apply_from_left_to_X(vector & w, lp_settings & ) { + vector t(m_m); + for (int i = 0; i < m_m; i ++) { + X v = zero_of_type(); + for (int j = 0; j < m_m; j++) { + v += w[j]* get_elem(i, j); + } + t[i] = v; + } + + for (int i = 0; i < m_m; i ++) { + w[i] = t[i]; + } +} + +// This method pivots row i to row i0 by muliplying row i by +// alpha and adding it to row i0. +template void dense_matrix::pivot_row_to_row(unsigned i, const T& alpha, unsigned i0, + const double & pivot_epsilon) { + for (unsigned j = 0; j < m_n; j++) { + m_values[i0 * m_n + j] += m_values[i * m_n + j] * alpha; + if (fabs(m_values[i0 + m_n + j]) < pivot_epsilon) { + m_values[i0 + m_n + j] = numeric_traits::zero();; + } + } +} + +template void dense_matrix::swap_columns(unsigned a, unsigned b) { + for (unsigned i = 0; i < m_m; i++) { + T t = get_elem(i, a); + set_elem(i, a, get_elem(i, b)); + set_elem(i, b, t); + } +} + +template void dense_matrix::swap_rows(unsigned a, unsigned b) { + for (unsigned i = 0; i < m_n; i++) { + T t = get_elem(a, i); + set_elem(a, i, get_elem(b, i)); + set_elem(b, i, t); + } +} + +template void dense_matrix::multiply_row_by_constant(unsigned row, T & t) { + for (unsigned i = 0; i < m_n; i++) { + set_elem(row, i, t * get_elem(row, i)); + } +} + +template +dense_matrix operator* (matrix & a, matrix & b){ + lean_assert(a.column_count() == b.row_count()); + dense_matrix ret(a.row_count(), b.column_count()); + for (unsigned i = 0; i < ret.m_m; i++) + for (unsigned j = 0; j< ret.m_n; j++) { + T v = numeric_traits::zero(); + for (unsigned k = 0; k < a.column_count(); k ++){ + v += (a.get_elem(i, k) * b.get_elem(k, j)); + } + ret.set_elem(i, j, v); + } + return ret; +} +} +#endif diff --git a/src/util/lp/dense_matrix_instances.cpp b/src/util/lp/dense_matrix_instances.cpp new file mode 100644 index 000000000..1e931211d --- /dev/null +++ b/src/util/lp/dense_matrix_instances.cpp @@ -0,0 +1,24 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include "util/lp/lp_settings.h" +#include "util/lp/dense_matrix.hpp" +#ifdef LEAN_DEBUG +#include "util/vector.h" +template lean::dense_matrix lean::operator*(lean::matrix&, lean::matrix&); +template void lean::dense_matrix::apply_from_left(vector &); +template lean::dense_matrix::dense_matrix(lean::matrix const*); +template lean::dense_matrix::dense_matrix(unsigned int, unsigned int); +template lean::dense_matrix& lean::dense_matrix::operator=(lean::dense_matrix const&); +template lean::dense_matrix >::dense_matrix(lean::matrix > const*); +template void lean::dense_matrix >::apply_from_left(vector&); +template lean::dense_matrix lean::operator*(lean::matrix&, lean::matrix&); +template lean::dense_matrix & lean::dense_matrix::operator=(lean::dense_matrix const&); +template lean::dense_matrix >::dense_matrix(unsigned int, unsigned int); +template lean::dense_matrix >& lean::dense_matrix >::operator=(lean::dense_matrix > const&); +template lean::dense_matrix > lean::operator* >(lean::matrix >&, lean::matrix >&); +template void lean::dense_matrix >::apply_from_right( vector< lean::mpq> &); +template void lean::dense_matrix::apply_from_right(class vector &); +template void lean::dense_matrix::apply_from_left(vector&); +#endif diff --git a/src/util/lp/eta_matrix.h b/src/util/lp/eta_matrix.h new file mode 100644 index 000000000..51b015066 --- /dev/null +++ b/src/util/lp/eta_matrix.h @@ -0,0 +1,83 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ + +#pragma once +#include "util/vector.h" +#include "util/lp/tail_matrix.h" +#include "util/lp/permutation_matrix.h" +namespace lean { + +// This is the sum of a unit matrix and a one-column matrix +template +class eta_matrix + : public tail_matrix { +#ifdef LEAN_DEBUG + unsigned m_length; +#endif + unsigned m_column_index; +public: + sparse_vector m_column_vector; + T m_diagonal_element; +#ifdef LEAN_DEBUG + eta_matrix(unsigned column_index, unsigned length): +#else + eta_matrix(unsigned column_index): +#endif + +#ifdef LEAN_DEBUG + m_length(length), +#endif + m_column_index(column_index) {} + + bool is_dense() const { return false; } + + void print(std::ostream & out) { + print_matrix(*this, out); + } + + bool is_unit() { + return m_column_vector.size() == 0 && m_diagonal_element == 1; + } + + bool set_diagonal_element(T const & diagonal_element) { + m_diagonal_element = diagonal_element; + return !lp_settings::is_eps_small_general(diagonal_element, 1e-12); + } + + const T & get_diagonal_element() const { + return m_diagonal_element; + } + + void apply_from_left(vector & w, lp_settings & ); + + template + void apply_from_left_local(indexed_vector & w, lp_settings & settings); + + void apply_from_left_to_T(indexed_vector & w, lp_settings & settings) { + apply_from_left_local(w, settings); + } + + + void push_back(unsigned row_index, T val ) { + lean_assert(row_index != m_column_index); + m_column_vector.push_back(row_index, val); + } + + void apply_from_right(vector & w); + void apply_from_right(indexed_vector & w); + + T get_elem(unsigned i, unsigned j) const; +#ifdef LEAN_DEBUG + unsigned row_count() const { return m_length; } + unsigned column_count() const { return m_length; } + void set_number_of_rows(unsigned m) { m_length = m; } + void set_number_of_columns(unsigned n) { m_length = n; } +#endif + void divide_by_diagonal_element() { + m_column_vector.divide(m_diagonal_element); + } + void conjugate_by_permutation(permutation_matrix & p); +}; +} diff --git a/src/util/lp/eta_matrix.hpp b/src/util/lp/eta_matrix.hpp new file mode 100644 index 000000000..142a408d1 --- /dev/null +++ b/src/util/lp/eta_matrix.hpp @@ -0,0 +1,136 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ + +#pragma once +#include "util/vector.h" +#include "util/lp/eta_matrix.h" +namespace lean { + +// This is the sum of a unit matrix and a one-column matrix +template +void eta_matrix::apply_from_left(vector & w, lp_settings & ) { + auto & w_at_column_index = w[m_column_index]; + for (auto & it : m_column_vector.m_data) { + w[it.first] += w_at_column_index * it.second; + } + w_at_column_index /= m_diagonal_element; +} +template +template +void eta_matrix:: +apply_from_left_local(indexed_vector & w, lp_settings & settings) { + const L w_at_column_index = w[m_column_index]; + if (is_zero(w_at_column_index)) return; + + if (settings.abs_val_is_smaller_than_drop_tolerance(w[m_column_index] /= m_diagonal_element)) { + w[m_column_index] = zero_of_type(); + w.erase_from_index(m_column_index); + } + + for (auto & it : m_column_vector.m_data) { + unsigned i = it.first; + if (is_zero(w[i])) { + L v = w[i] = w_at_column_index * it.second; + if (settings.abs_val_is_smaller_than_drop_tolerance(v)) { + w[i] = zero_of_type(); + continue; + } + w.m_index.push_back(i); + } else { + L v = w[i] += w_at_column_index * it.second; + if (settings.abs_val_is_smaller_than_drop_tolerance(v)) { + w[i] = zero_of_type(); + w.erase_from_index(i); + } + } + } +} +template +void eta_matrix::apply_from_right(vector & w) { +#ifdef LEAN_DEBUG + // dense_matrix deb(*this); + // auto clone_w = clone_vector(w, get_number_of_rows()); + // deb.apply_from_right(clone_w); +#endif + T t = w[m_column_index] / m_diagonal_element; + for (auto & it : m_column_vector.m_data) { + t += w[it.first] * it.second; + } + w[m_column_index] = t; +#ifdef LEAN_DEBUG + // lean_assert(vectors_are_equal(clone_w, w, get_number_of_rows())); + // delete clone_w; +#endif +} +template +void eta_matrix::apply_from_right(indexed_vector & w) { + if (w.m_index.size() == 0) + return; +#ifdef LEAN_DEBUG + // vector wcopy(w.m_data); + // apply_from_right(wcopy); +#endif + T & t = w[m_column_index]; + t /= m_diagonal_element; + bool was_in_index = (!numeric_traits::is_zero(t)); + + for (auto & it : m_column_vector.m_data) { + t += w[it.first] * it.second; + } + + if (numeric_traits::precise() ) { + if (!numeric_traits::is_zero(t)) { + if (!was_in_index) + w.m_index.push_back(m_column_index); + } else { + if (was_in_index) + w.erase_from_index(m_column_index); + } + } else { + if (!lp_settings::is_eps_small_general(t, 1e-14)) { + if (!was_in_index) + w.m_index.push_back(m_column_index); + } else { + if (was_in_index) + w.erase_from_index(m_column_index); + t = zero_of_type(); + } + } + +#ifdef LEAN_DEBUG + // lean_assert(w.is_OK()); + // lean_assert(vectors_are_equal(wcopy, w.m_data)); +#endif +} +#ifdef LEAN_DEBUG +template +T eta_matrix::get_elem(unsigned i, unsigned j) const { + if (j == m_column_index){ + if (i == j) { + return 1 / m_diagonal_element; + } + return m_column_vector[i]; + } + + return i == j ? numeric_traits::one() : numeric_traits::zero(); +} +#endif +template +void eta_matrix::conjugate_by_permutation(permutation_matrix & p) { + // this = p * this * p(-1) +#ifdef LEAN_DEBUG + // auto rev = p.get_reverse(); + // auto deb = ((*this) * rev); + // deb = p * deb; +#endif + m_column_index = p.get_rev(m_column_index); + for (auto & pair : m_column_vector.m_data) { + pair.first = p.get_rev(pair.first); + } +#ifdef LEAN_DEBUG + // lean_assert(deb == *this); +#endif +} +} diff --git a/src/util/lp/eta_matrix_instances.cpp b/src/util/lp/eta_matrix_instances.cpp new file mode 100644 index 000000000..d57d43fed --- /dev/null +++ b/src/util/lp/eta_matrix_instances.cpp @@ -0,0 +1,28 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include +#include "util/vector.h" +#include "util/lp/numeric_pair.h" +#include "util/lp/eta_matrix.hpp" +#ifdef LEAN_DEBUG +template double lean::eta_matrix::get_elem(unsigned int, unsigned int) const; +template lean::mpq lean::eta_matrix::get_elem(unsigned int, unsigned int) const; +template lean::mpq lean::eta_matrix >::get_elem(unsigned int, unsigned int) const; +#endif +template void lean::eta_matrix::apply_from_left(vector&, lean::lp_settings&); +template void lean::eta_matrix::apply_from_right(vector&); +template void lean::eta_matrix::conjugate_by_permutation(lean::permutation_matrix&); +template void lean::eta_matrix::apply_from_left(vector&, lean::lp_settings&); +template void lean::eta_matrix::apply_from_right(vector&); +template void lean::eta_matrix::conjugate_by_permutation(lean::permutation_matrix&); +template void lean::eta_matrix >::apply_from_left(vector >&, lean::lp_settings&); +template void lean::eta_matrix >::apply_from_right(vector&); +template void lean::eta_matrix >::conjugate_by_permutation(lean::permutation_matrix >&); +template void lean::eta_matrix::apply_from_left_local(lean::indexed_vector&, lean::lp_settings&); +template void lean::eta_matrix::apply_from_left_local(lean::indexed_vector&, lean::lp_settings&); +template void lean::eta_matrix >::apply_from_left_local(lean::indexed_vector&, lean::lp_settings&); +template void lean::eta_matrix >::apply_from_right(lean::indexed_vector&); +template void lean::eta_matrix::apply_from_right(lean::indexed_vector&); +template void lean::eta_matrix::apply_from_right(lean::indexed_vector&); diff --git a/src/util/lp/hash_helper.h b/src/util/lp/hash_helper.h new file mode 100644 index 000000000..6fe31d5cd --- /dev/null +++ b/src/util/lp/hash_helper.h @@ -0,0 +1,39 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#pragma once +#include +#include +#include "util/numerics/mpq.h" +#ifdef __CLANG__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wmismatched-tags" +#endif +namespace std { +template<> +struct hash { + inline size_t operator()(const lean::mpq & v) const { + return v.hash(); + } +}; +} + +template +inline void hash_combine(std::size_t & seed, const T & v) { + seed ^= std::hash()(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); +} + +namespace std { +template struct hash> { + inline size_t operator()(const pair & v) const { + size_t seed = 0; + hash_combine(seed, v.first); + hash_combine(seed, v.second); + return seed; + } +}; +} +#ifdef __CLANG__ +#pragma clang diagnostic pop +#endif diff --git a/src/util/lp/implied_bound.h b/src/util/lp/implied_bound.h new file mode 100644 index 000000000..9583e3cd8 --- /dev/null +++ b/src/util/lp/implied_bound.h @@ -0,0 +1,42 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#pragma once +#include "util/lp/lp_settings.h" +#include "util/lp/lar_constraints.h" +namespace lean { +struct implied_bound { + mpq m_bound; + unsigned m_j; // the column for which the bound has been found + bool m_is_low_bound; + bool m_coeff_before_j_is_pos; + unsigned m_row_or_term_index; + bool m_strict; + + lconstraint_kind kind() const { + lconstraint_kind k = m_is_low_bound? GE : LE; + if (m_strict) + k = static_cast(k / 2); + return k; + } + bool operator==(const implied_bound & o) const { + return m_j == o.m_j && m_is_low_bound == o.m_is_low_bound && m_bound == o.m_bound && + m_coeff_before_j_is_pos == o.m_coeff_before_j_is_pos && + m_row_or_term_index == o.m_row_or_term_index && m_strict == o.m_strict; + } + implied_bound(){} + implied_bound(const mpq & a, + unsigned j, + bool low_bound, + bool coeff_before_j_is_pos, + unsigned row_or_term_index, + bool strict): + m_bound(a), + m_j(j), + m_is_low_bound(low_bound), + m_coeff_before_j_is_pos(coeff_before_j_is_pos), + m_row_or_term_index(row_or_term_index), + m_strict(strict) {} +}; +} diff --git a/src/util/lp/indexed_value.h b/src/util/lp/indexed_value.h new file mode 100644 index 000000000..7963dfdf9 --- /dev/null +++ b/src/util/lp/indexed_value.h @@ -0,0 +1,55 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#pragma once + +namespace lean { +template +class indexed_value { +public: + T m_value; + // the idea is that m_index for a row element gives its column, and for a column element its row + unsigned m_index; + // m_other point is the offset of the corresponding element in its vector : for a row element it point to the column element offset, + // for a column element it points to the row element offset + unsigned m_other; + indexed_value() {} + indexed_value(T v, unsigned i) : m_value(v), m_index(i) {} + indexed_value(T v, unsigned i, unsigned other) : + m_value(v), m_index(i), m_other(other) { + } + + indexed_value(const indexed_value & iv) { + m_value = iv.m_value; + m_index = iv.m_index; + m_other = iv.m_other; + } + + indexed_value & operator=(const indexed_value & right_side) { + m_value = right_side.m_value; + m_index = right_side.m_index; + m_other = right_side.m_other; + return *this; + } + + const T & value() const { + return m_value; + } + + void set_value(T val) { + m_value = val; + } +}; +#ifdef LEAN_DEBUG +template +bool check_vector_for_small_values(indexed_vector & w, lp_settings & settings) { + for (unsigned i : w.m_index) { + const X & v = w[i]; + if ((!is_zero(v)) && settings.abs_val_is_smaller_than_drop_tolerance(v)) + return false; + } + return true; +} +#endif +} diff --git a/src/util/lp/indexed_vector.h b/src/util/lp/indexed_vector.h new file mode 100644 index 000000000..6e6a6009b --- /dev/null +++ b/src/util/lp/indexed_vector.h @@ -0,0 +1,169 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ + +#pragma once +#include "util/vector.h" +#include "util/debug.h" +#include +#include +#include "util/lp/lp_utils.h" +#include "util/lp/lp_settings.h" +#include +namespace lean { + +template void print_vector(const vector & t, std::ostream & out); +template void print_vector(const buffer & t, std::ostream & out); +template void print_sparse_vector(const vector & t, std::ostream & out); + +void print_vector(const vector & t, std::ostream & out); +template +class indexed_vector { +public: + // m_index points to non-zero elements of m_data + vector m_data; + vector m_index; + indexed_vector(unsigned data_size) { + m_data.resize(data_size, numeric_traits::zero()); + } + + indexed_vector& operator=(const indexed_vector& y) { + for (unsigned i: m_index) + m_data[i] = zero_of_type(); + + m_index = y.m_index; + + m_data.resize(y.data_size()); + for (unsigned i : m_index) + m_data[i] = y[i]; + return *this; + } + + bool operator==(const indexed_vector& y) const { + std::unordered_set y_index; + for (unsigned i : y.m_index) + y_index.insert(i); + + std::unordered_set this_index; + for (unsigned i : m_index) + this_index.insert(i); + + for (unsigned i : y.m_index) { + if (this_index.find(i) == this_index.end()) + return false; + } + + for (unsigned i : m_index) { + if (y_index.find(i) == y_index.end()) + return false; + } + + return vectors_are_equal(m_data, m_data); + + } + + indexed_vector() {} + + void resize(unsigned data_size); + unsigned data_size() const { + return m_data.size(); + } + + unsigned size() { + return m_index.size(); + } + + void set_value(const T& value, unsigned index); + void set_value_as_in_dictionary(unsigned index) { + lean_assert(index < m_data.size()); + T & loc = m_data[index]; + if (is_zero(loc)) { + m_index.push_back(index); + loc = one_of_type(); // use as a characteristic function + } + } + + + void clear(); + void clear_all(); + const T& operator[] (unsigned i) const { + return m_data[i]; + } + + T& operator[] (unsigned i) { + return m_data[i]; + } + + void clean_up() { +#if 0==1 + for (unsigned k = 0; k < m_index.size(); k++) { + unsigned i = m_index[k]; + T & v = m_data[i]; + if (lp_settings::is_eps_small_general(v, 1e-14)) { + v = zero_of_type(); + m_index.erase(m_index.begin() + k--); + } + } +#endif + vector index_copy; + for (unsigned i : m_index) { + T & v = m_data[i]; + if (!lp_settings::is_eps_small_general(v, 1e-14)) { + index_copy.push_back(i); + } else if (!numeric_traits::is_zero(v)) { + v = zero_of_type(); + } + } + m_index = index_copy; + } + + + void erase_from_index(unsigned j); + + void add_value_at_index_with_drop_tolerance(unsigned j, const T& val_to_add) { + T & v = m_data[j]; + bool was_zero = is_zero(v); + v += val_to_add; + if (lp_settings::is_eps_small_general(v, 1e-14)) { + v = zero_of_type(); + if (!was_zero) { + erase_from_index(j); + } + } else { + if (was_zero) + m_index.push_back(j); + } + } + + void add_value_at_index(unsigned j, const T& val_to_add) { + T & v = m_data[j]; + bool was_zero = is_zero(v); + v += val_to_add; + if (is_zero(v)) { + if (!was_zero) + erase_from_index(j); + } else { + if (was_zero) + m_index.push_back(j); + } + } + + void restore_index_and_clean_from_data() { + m_index.resize(0); + for (unsigned i = 0; i < m_data.size(); i++) { + T & v = m_data[i]; + if (lp_settings::is_eps_small_general(v, 1e-14)) { + v = zero_of_type(); + } else { + m_index.push_back(i); + } + } + } + +#ifdef LEAN_DEBUG + bool is_OK() const; + void print(std::ostream & out); +#endif +}; +} diff --git a/src/util/lp/indexed_vector.hpp b/src/util/lp/indexed_vector.hpp new file mode 100644 index 000000000..64e329adc --- /dev/null +++ b/src/util/lp/indexed_vector.hpp @@ -0,0 +1,110 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include "util/vector.h" +#include "util/lp/indexed_vector.h" +#include "util/lp/lp_settings.h" +namespace lean { + +template +void print_vector(const vector & t, std::ostream & out) { + for (unsigned i = 0; i < t.size(); i++) + out << t[i] << " "; + out << std::endl; +} + + +template +void print_vector(const buffer & t, std::ostream & out) { + for (unsigned i = 0; i < t.size(); i++) + out << t[i] << " "; + out << std::endl; +} + +template +void print_sparse_vector(const vector & t, std::ostream & out) { + for (unsigned i = 0; i < t.size(); i++) { + if (is_zero(t[i]))continue; + out << "[" << i << "] = " << t[i] << ", "; + } + out << std::endl; +} + +void print_vector(const vector & t, std::ostream & out) { + for (unsigned i = 0; i < t.size(); i++) + out << t[i].get_double() << std::setprecision(3) << " "; + out << std::endl; +} + +template +void indexed_vector::resize(unsigned data_size) { + clear(); + m_data.resize(data_size, numeric_traits::zero()); + lean_assert(is_OK()); +} + +template +void indexed_vector::set_value(const T& value, unsigned index) { + m_data[index] = value; + lean_assert(std::find(m_index.begin(), m_index.end(), index) == m_index.end()); + m_index.push_back(index); +} + +template +void indexed_vector::clear() { + for (unsigned i : m_index) + m_data[i] = numeric_traits::zero(); + m_index.resize(0); +} +template +void indexed_vector::clear_all() { + unsigned i = m_data.size(); + while (i--) m_data[i] = numeric_traits::zero(); + m_index.resize(0); +} +template +void indexed_vector::erase_from_index(unsigned j) { + auto it = std::find(m_index.begin(), m_index.end(), j); + if (it != m_index.end()) + m_index.erase(it); +} + +#ifdef LEAN_DEBUG +template +bool indexed_vector::is_OK() const { + return true; + const double drop_eps = 1e-14; + for (unsigned i = 0; i < m_data.size(); i++) { + if (!is_zero(m_data[i]) && lp_settings::is_eps_small_general(m_data[i], drop_eps)) { + return false; + } + if (lp_settings::is_eps_small_general(m_data[i], drop_eps) != (std::find(m_index.begin(), m_index.end(), i) == m_index.end())) { + return false; + } + } + + std::unordered_set s; + for (unsigned i : m_index) { + //no duplicates!!! + if (s.find(i) != s.end()) + return false; + s.insert(i); + if (i >= m_data.size()) + return false; + } + + return true; +} +template +void indexed_vector::print(std::ostream & out) { + out << "m_index " << std::endl; + for (unsigned i = 0; i < m_index.size(); i++) { + out << m_index[i] << " "; + } + out << std::endl; + print_vector(m_data, out); +} +#endif + +} diff --git a/src/util/lp/indexed_vector_instances.cpp b/src/util/lp/indexed_vector_instances.cpp new file mode 100644 index 000000000..6f17a894f --- /dev/null +++ b/src/util/lp/indexed_vector_instances.cpp @@ -0,0 +1,36 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include "util/vector.h" +#include "util/lp/indexed_vector.hpp" +namespace lean { +template void indexed_vector::clear(); +template void indexed_vector::clear_all(); +template void indexed_vector::erase_from_index(unsigned int); +template void indexed_vector::set_value(const double&, unsigned int); +template void indexed_vector::clear(); +template void indexed_vector::clear(); +template void indexed_vector::clear_all(); +template void indexed_vector::erase_from_index(unsigned int); +template void indexed_vector::resize(unsigned int); +template void indexed_vector::resize(unsigned int); +template void indexed_vector::set_value(const mpq&, unsigned int); +template void indexed_vector::set_value(const unsigned&, unsigned int); +#ifdef LEAN_DEBUG +template bool indexed_vector::is_OK() const; +template bool indexed_vector::is_OK() const; +template bool indexed_vector >::is_OK() const; +template void lean::indexed_vector< lean::mpq>::print(std::basic_ostream > &); +template void lean::indexed_vector::print(std::basic_ostream > &); +template void lean::indexed_vector >::print(std::ostream&); +#endif +} +template void lean::print_vector(vector const&, std::ostream&); +template void lean::print_vector(vector const&, std::ostream&); +template void lean::print_vector(vector const&, std::ostream&); +template void lean::print_vector >(vector> const&, std::ostream&); +template void lean::indexed_vector::resize(unsigned int); +template void lean::print_vector< lean::mpq>(vector< lean::mpq> const &, std::basic_ostream > &); +template void lean::print_vector >(vector> const&, std::ostream&); +template void lean::indexed_vector >::erase_from_index(unsigned int); diff --git a/src/util/lp/int_set.h b/src/util/lp/int_set.h new file mode 100644 index 000000000..0619facd8 --- /dev/null +++ b/src/util/lp/int_set.h @@ -0,0 +1,66 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#pragma once +#include "util/vector.h" +#include "util/lp/indexed_vector.h" +#include +namespace lean { +// serves at a set of non-negative integers smaller than the set size +class int_set { + vector m_data; +public: + vector m_index; + int_set(unsigned size): m_data(size, -1) {} + int_set() {} + bool contains(unsigned j) const { + if (j >= m_data.size()) + return false; + return m_data[j] >= 0; + } + void insert(unsigned j) { + lean_assert(j < m_data.size()); + if (contains(j)) return; + m_data[j] = m_index.size(); + m_index.push_back(j); + } + void erase(unsigned j) { + if (!contains(j)) return; + unsigned pos_j = m_data[j]; + unsigned last_pos = m_index.size() - 1; + int last_j = m_index[last_pos]; + if (last_pos != pos_j) { + // move last to j spot + m_data[last_j] = pos_j; + m_index[pos_j] = last_j; + } + m_index.pop_back(); + m_data[j] = -1; + } + + void resize(unsigned size) { + m_data.resize(size, -1); + } + + void increase_size_by_one() { + resize(m_data.size() + 1); + } + + unsigned data_size() const { return m_data.size(); } + unsigned size() const { return m_index.size();} + bool is_empty() const { return size() == 0; } + void clear() { + for (unsigned j : m_index) + m_data[j] = -1; + m_index.resize(0); + } + void print(std::ostream & out ) const { + for (unsigned j : m_index) { + out << j << " "; + } + out << std::endl; + } + +}; +} diff --git a/src/util/lp/iterator_on_column.h b/src/util/lp/iterator_on_column.h new file mode 100644 index 000000000..5ad925d3e --- /dev/null +++ b/src/util/lp/iterator_on_column.h @@ -0,0 +1,50 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#pragma once +#include "util/lp/linear_combination_iterator.h" +#include "util/lp/static_matrix.h" +#include "util/lp/lar_term.h" +namespace lean { +template +struct iterator_on_column:linear_combination_iterator { + const vector& m_column; // the offset in term coeffs + const static_matrix & m_A; + int m_i = -1; // the initial offset in the column + unsigned size() const { return m_column.size(); } + iterator_on_column(const vector& column, const static_matrix & A) // the offset in term coeffs + : + m_column(column), + m_A(A), + m_i(-1) {} + + bool next(mpq & a, unsigned & i) { + if (++m_i >= static_cast(m_column.size())) + return false; + + const column_cell& c = m_column[m_i]; + a = m_A.get_val(c); + i = c.m_i; + return true; + } + + bool next(unsigned & i) { + if (++m_i >= static_cast(m_column.size())) + return false; + + const column_cell& c = m_column[m_i]; + i = c.m_i; + return true; + } + + void reset() { + m_i = -1; + } + + linear_combination_iterator * clone() { + iterator_on_column * r = new iterator_on_column(m_column, m_A); + return r; + } +}; +} diff --git a/src/util/lp/iterator_on_indexed_vector.h b/src/util/lp/iterator_on_indexed_vector.h new file mode 100644 index 000000000..c19c08e64 --- /dev/null +++ b/src/util/lp/iterator_on_indexed_vector.h @@ -0,0 +1,35 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#pragma once +#include "util/lp/linear_combination_iterator.h" +namespace lean { +template +struct iterator_on_indexed_vector:linear_combination_iterator { + const indexed_vector & m_v; + unsigned m_offset = 0; + iterator_on_indexed_vector(const indexed_vector & v) : m_v(v){} + unsigned size() const { return m_v.m_index.size(); } + bool next(T & a, unsigned & i) { + if (m_offset >= m_v.m_index.size()) + return false; + i = m_v.m_index[m_offset++]; + a = m_v.m_data[i]; + return true; + } + + bool next(unsigned & i) { + if (m_offset >= m_v.m_index.size()) + return false; + i = m_v.m_index[m_offset++]; + return true; + } + void reset() { + m_offset = 0; + } + linear_combination_iterator* clone() { + return new iterator_on_indexed_vector(m_v); + } +}; +} diff --git a/src/util/lp/iterator_on_pivot_row.h b/src/util/lp/iterator_on_pivot_row.h new file mode 100644 index 000000000..19c0c7df1 --- /dev/null +++ b/src/util/lp/iterator_on_pivot_row.h @@ -0,0 +1,42 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#pragma once +#include "util/lp/iterator_on_indexed_vector.h" +namespace lean { +template +struct iterator_on_pivot_row:linear_combination_iterator { + bool m_basis_returned = false; + const indexed_vector & m_v; + unsigned m_basis_j; + iterator_on_indexed_vector m_it; + unsigned size() const { return m_it.size(); } + iterator_on_pivot_row(const indexed_vector & v, unsigned basis_j) : m_v(v), m_basis_j(basis_j), m_it(v) {} + bool next(T & a, unsigned & i) { + if (m_basis_returned == false) { + m_basis_returned = true; + a = one_of_type(); + i = m_basis_j; + return true; + } + return m_it.next(a, i); + } + bool next(unsigned & i) { + if (m_basis_returned == false) { + m_basis_returned = true; + i = m_basis_j; + return true; + } + return m_it.next(i); + } + void reset() { + m_basis_returned = false; + m_it.reset(); + } + linear_combination_iterator * clone() { + iterator_on_pivot_row * r = new iterator_on_pivot_row(m_v, m_basis_j); + return r; + } +}; +} diff --git a/src/util/lp/iterator_on_row.h b/src/util/lp/iterator_on_row.h new file mode 100644 index 000000000..212768db9 --- /dev/null +++ b/src/util/lp/iterator_on_row.h @@ -0,0 +1,37 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#pragma once +#include "util/lp/linear_combination_iterator.h" +namespace lean { +template +struct iterator_on_row:linear_combination_iterator { + const vector> & m_row; + unsigned m_i= 0; // offset + iterator_on_row(const vector> & row) : m_row(row) + {} + unsigned size() const { return m_row.size(); } + bool next(T & a, unsigned & i) { + if (m_i == m_row.size()) + return false; + auto &c = m_row[m_i++]; + i = c.m_j; + a = c.get_val(); + return true; + } + bool next(unsigned & i) { + if (m_i == m_row.size()) + return false; + auto &c = m_row[m_i++]; + i = c.m_j; + return true; + } + void reset() { + m_i = 0; + } + linear_combination_iterator* clone() { + return new iterator_on_row(m_row); + } +}; +} diff --git a/src/util/lp/iterator_on_term_with_basis_var.h b/src/util/lp/iterator_on_term_with_basis_var.h new file mode 100644 index 000000000..9279dd637 --- /dev/null +++ b/src/util/lp/iterator_on_term_with_basis_var.h @@ -0,0 +1,56 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#pragma once +#include "util/lp/linear_combination_iterator.h" +#include "util/lp/numeric_pair.h" +#include "util/lp/lar_term.h" +namespace lean { +struct iterator_on_term_with_basis_var:linear_combination_iterator { + std::unordered_map::const_iterator m_i; // the offset in term coeffs + bool m_term_j_returned = false; + const lar_term & m_term; + unsigned m_term_j; + unsigned size() const {return static_cast(m_term.m_coeffs.size() + 1);} + iterator_on_term_with_basis_var(const lar_term & t, unsigned term_j) : + m_i(t.m_coeffs.begin()), + m_term(t), + m_term_j(term_j) {} + + bool next(mpq & a, unsigned & i) { + if (m_term_j_returned == false) { + m_term_j_returned = true; + a = - one_of_type(); + i = m_term_j; + return true; + } + if (m_i == m_term.m_coeffs.end()) + return false; + i = m_i->first; + a = m_i->second; + m_i++; + return true; + } + bool next(unsigned & i) { + if (m_term_j_returned == false) { + m_term_j_returned = true; + i = m_term_j; + return true; + } + if (m_i == m_term.m_coeffs.end()) + return false; + i = m_i->first; + m_i++; + return true; + } + void reset() { + m_term_j_returned = false; + m_i = m_term.m_coeffs.begin(); + } + linear_combination_iterator * clone() { + iterator_on_term_with_basis_var * r = new iterator_on_term_with_basis_var(m_term, m_term_j); + return r; + } +}; +} diff --git a/src/util/lp/lar_constraints.h b/src/util/lp/lar_constraints.h new file mode 100644 index 000000000..ee0864a4e --- /dev/null +++ b/src/util/lp/lar_constraints.h @@ -0,0 +1,86 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ + +#pragma once +#include "util/vector.h" +#include +#include +#include +#include +#include "util/lp/lp_utils.h" +#include "util/lp/ul_pair.h" +#include "util/lp/lar_term.h" +namespace lean { +inline lconstraint_kind flip_kind(lconstraint_kind t) { + return static_cast( - static_cast(t)); +} + +inline std::string lconstraint_kind_string(lconstraint_kind t) { + switch (t) { + case LE: return std::string("<="); + case LT: return std::string("<"); + case GE: return std::string(">="); + case GT: return std::string(">"); + case EQ: return std::string("="); + } + lean_unreachable(); + return std::string(); // it is unreachable +} + +class lar_base_constraint { +public: + lconstraint_kind m_kind; + mpq m_right_side; + virtual vector> get_left_side_coefficients() const = 0; + lar_base_constraint() {} + lar_base_constraint(lconstraint_kind kind, const mpq& right_side) :m_kind(kind), m_right_side(right_side) {} + + virtual unsigned size() const = 0; + virtual ~lar_base_constraint(){} + virtual mpq get_free_coeff_of_left_side() const { return zero_of_type();} +}; + +struct lar_var_constraint: public lar_base_constraint { + unsigned m_j; + vector> get_left_side_coefficients() const { + vector> ret; + ret.push_back(std::make_pair(one_of_type(), m_j)); + return ret; + } + unsigned size() const { return 1;} + lar_var_constraint(unsigned j, lconstraint_kind kind, const mpq& right_side) : lar_base_constraint(kind, right_side), m_j(j) { } +}; + + +struct lar_term_constraint: public lar_base_constraint { + const lar_term * m_term; + vector> get_left_side_coefficients() const { + return m_term->coeffs_as_vector(); + } + unsigned size() const { return m_term->size();} + lar_term_constraint(const lar_term *t, lconstraint_kind kind, const mpq& right_side) : lar_base_constraint(kind, right_side), m_term(t) { } + virtual mpq get_free_coeff_of_left_side() const { return m_term->m_v;} + +}; + + +class lar_constraint : public lar_base_constraint { +public: + vector> m_coeffs; + lar_constraint() {} + lar_constraint(const vector> & left_side, lconstraint_kind kind, const mpq & right_side) + : lar_base_constraint(kind, right_side), m_coeffs(left_side) {} + + lar_constraint(const lar_base_constraint & c) { + lean_assert(false); // should not be called : todo! + } + + unsigned size() const { + return static_cast(m_coeffs.size()); + } + + vector> get_left_side_coefficients() const { return m_coeffs; } +}; +} diff --git a/src/util/lp/lar_core_solver.h b/src/util/lp/lar_core_solver.h new file mode 100644 index 000000000..b3117a2d7 --- /dev/null +++ b/src/util/lp/lar_core_solver.h @@ -0,0 +1,802 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#pragma once +#include "util/vector.h" +#include +#include +#include "util/lp/lp_core_solver_base.h" +#include +#include "util/lp/indexed_vector.h" +#include "util/lp/binary_heap_priority_queue.h" +#include "util/lp/breakpoint.h" +#include "util/lp/stacked_unordered_set.h" +#include "util/lp/lp_primal_core_solver.h" +#include "util/lp/stacked_vector.h" +#include "util/lp/lar_solution_signature.h" +#include "util/lp/iterator_on_column.h" +#include "util/lp/iterator_on_indexed_vector.h" +#include "util/lp/stacked_value.h" +namespace lean { + +class lar_core_solver { + // m_sign_of_entering is set to 1 if the entering variable needs + // to grow and is set to -1 otherwise + int m_sign_of_entering_delta; + vector> m_infeasible_linear_combination; + int m_infeasible_sum_sign = 0; // todo: get rid of this field + vector> m_right_sides_dummy; + vector m_costs_dummy; + vector m_d_right_sides_dummy; + vector m_d_costs_dummy; +public: + stacked_value m_stacked_simplex_strategy; + stacked_vector m_column_types; + // r - solver fields, for rational numbers + vector> m_r_x; // the solution + stacked_vector> m_r_low_bounds; + stacked_vector> m_r_upper_bounds; + static_matrix> m_r_A; + stacked_vector m_r_pushed_basis; + vector m_r_basis; + vector m_r_nbasis; + vector m_r_heading; + stacked_vector m_r_columns_nz; + stacked_vector m_r_rows_nz; + + // d - solver fields, for doubles + vector m_d_x; // the solution in doubles + vector m_d_low_bounds; + vector m_d_upper_bounds; + static_matrix m_d_A; + stacked_vector m_d_pushed_basis; + vector m_d_basis; + vector m_d_nbasis; + vector m_d_heading; + + + lp_primal_core_solver> m_r_solver; // solver in rational numbers + + lp_primal_core_solver m_d_solver; // solver in doubles + + lar_core_solver( + lp_settings & settings, + const column_namer & column_names + ); + + lp_settings & settings() { return m_r_solver.m_settings;} + const lp_settings & settings() const { return m_r_solver.m_settings;} + + int get_infeasible_sum_sign() const { return m_infeasible_sum_sign; } + + const vector> & get_infeasibility_info(int & inf_sign) const { + inf_sign = m_infeasible_sum_sign; + return m_infeasible_linear_combination; + } + + void fill_not_improvable_zero_sum_from_inf_row(); + + column_type get_column_type(unsigned j) { return m_column_types[j];} + + void init_costs(bool first_time); + + void init_cost_for_column(unsigned j); + + // returns m_sign_of_alpha_r + int column_is_out_of_bounds(unsigned j); + + void calculate_pivot_row(unsigned i); + + void print_pivot_row(std::ostream & out, unsigned row_index) const { // remove later debug !!!! + for (unsigned j : m_r_solver.m_pivot_row.m_index) { + if (numeric_traits::is_pos(m_r_solver.m_pivot_row.m_data[j])) + out << "+"; + out << m_r_solver.m_pivot_row.m_data[j] << m_r_solver.column_name(j) << " "; + } + + out << " +" << m_r_solver.column_name(m_r_solver.m_basis[row_index]) << std::endl; + + for (unsigned j : m_r_solver.m_pivot_row.m_index) { + m_r_solver.print_column_bound_info(j, out); + } + m_r_solver.print_column_bound_info(m_r_solver.m_basis[row_index], out); + + } + + + void advance_on_sorted_breakpoints(unsigned entering); + + void change_slope_on_breakpoint(unsigned entering, breakpoint> * b, mpq & slope_at_entering); + + bool row_is_infeasible(unsigned row); + + bool row_is_evidence(unsigned row); + + bool find_evidence_row(); + + void prefix_r(); + + void prefix_d(); + + unsigned m_m() const { + return m_r_A.row_count(); + } + + unsigned m_n() const { + return m_r_A.column_count(); + } + + bool is_tiny() const { return this->m_m() < 10 && this->m_n() < 20; } + + bool is_empty() const { return this->m_m() == 0 && this->m_n() == 0; } + + template + int get_sign(const L & v) { + return v > zero_of_type() ? 1 : (v < zero_of_type() ? -1 : 0); + } + + + + void fill_evidence(unsigned row); + + + + void solve(); + + bool low_bounds_are_set() const { return true; } + + const indexed_vector & get_pivot_row() const { + return m_r_solver.m_pivot_row; + } + + void fill_not_improvable_zero_sum(); + + void pop_basis(unsigned k) { + if (!settings().use_tableau()) { + m_r_pushed_basis.pop(k); + m_r_basis = m_r_pushed_basis(); + m_r_solver.init_basis_heading_and_non_basic_columns_vector(); + m_d_pushed_basis.pop(k); + m_d_basis = m_d_pushed_basis(); + m_d_solver.init_basis_heading_and_non_basic_columns_vector(); + } else { + m_d_basis = m_r_basis; + m_d_nbasis = m_r_nbasis; + m_d_heading = m_r_heading; + } + } + + void push() { + lean_assert(m_r_solver.basis_heading_is_correct()); + lean_assert(!need_to_presolve_with_double_solver() || m_d_solver.basis_heading_is_correct()); + lean_assert(m_column_types.size() == m_r_A.column_count()); + m_stacked_simplex_strategy = settings().simplex_strategy(); + m_stacked_simplex_strategy.push(); + m_column_types.push(); + // rational + if (!settings().use_tableau()) + m_r_A.push(); + m_r_low_bounds.push(); + m_r_upper_bounds.push(); + if (!settings().use_tableau()) { + push_vector(m_r_pushed_basis, m_r_basis); + push_vector(m_r_columns_nz, m_r_solver.m_columns_nz); + push_vector(m_r_rows_nz, m_r_solver.m_rows_nz); + } + + m_d_A.push(); + if (!settings().use_tableau()) + push_vector(m_d_pushed_basis, m_d_basis); + } + + template + void push_vector(stacked_vector & pushed_vector, const vector & vector) { + lean_assert(pushed_vector.size() <= vector.size()); + for (unsigned i = 0; i < vector.size();i++) { + if (i == pushed_vector.size()) { + pushed_vector.push_back(vector[i]); + } else { + pushed_vector[i] = vector[i]; + } + } + pushed_vector.push(); + } + + void pop_markowitz_counts(unsigned k) { + m_r_columns_nz.pop(k); + m_r_rows_nz.pop(k); + m_r_solver.m_columns_nz.resize(m_r_columns_nz.size()); + m_r_solver.m_rows_nz.resize(m_r_rows_nz.size()); + for (unsigned i = 0; i < m_r_columns_nz.size(); i++) + m_r_solver.m_columns_nz[i] = m_r_columns_nz[i]; + for (unsigned i = 0; i < m_r_rows_nz.size(); i++) + m_r_solver.m_rows_nz[i] = m_r_rows_nz[i]; + } + + + void pop(unsigned k) { + m_stacked_simplex_strategy.pop(k); + bool use_tableau = m_stacked_simplex_strategy() != simplex_strategy_enum::no_tableau; + // rationals + if (!settings().use_tableau()) + m_r_A.pop(k); + m_r_low_bounds.pop(k); + m_r_upper_bounds.pop(k); + m_column_types.pop(k); + + if (m_r_solver.m_factorization != nullptr) { + delete m_r_solver.m_factorization; + m_r_solver.m_factorization = nullptr; + } + m_r_x.resize(m_r_A.column_count()); + m_r_solver.m_costs.resize(m_r_A.column_count()); + m_r_solver.m_d.resize(m_r_A.column_count()); + if(!use_tableau) + pop_markowitz_counts(k); + m_d_A.pop(k); + if (m_d_solver.m_factorization != nullptr) { + delete m_d_solver.m_factorization; + m_d_solver.m_factorization = nullptr; + } + + m_d_x.resize(m_d_A.column_count()); + pop_basis(k); + + lean_assert(m_r_solver.basis_heading_is_correct()); + lean_assert(!need_to_presolve_with_double_solver() || m_d_solver.basis_heading_is_correct()); + } + + bool need_to_presolve_with_double_solver() const { + return settings().presolve_with_double_solver_for_lar && !settings().use_tableau(); + } + + template + bool is_zero_vector(const vector & b) { + for (const L & m: b) + if (!is_zero(m)) return false; + return true; + } + + + bool update_xj_and_get_delta(unsigned j, non_basic_column_value_position pos_type, numeric_pair & delta) { + auto & x = m_r_x[j]; + switch (pos_type) { + case at_low_bound: + if (x == m_r_solver.m_low_bounds[j]) + return false; + delta = m_r_solver.m_low_bounds[j] - x; + m_r_solver.m_x[j] = m_r_solver.m_low_bounds[j]; + break; + case at_fixed: + case at_upper_bound: + if (x == m_r_solver.m_upper_bounds[j]) + return false; + delta = m_r_solver.m_upper_bounds[j] - x; + x = m_r_solver.m_upper_bounds[j]; + break; + case free_of_bounds: { + return false; + } + case not_at_bound: + switch (m_column_types[j]) { + case column_type::free_column: + return false; + case column_type::upper_bound: + delta = m_r_solver.m_upper_bounds[j] - x; + x = m_r_solver.m_upper_bounds[j]; + break; + case column_type::low_bound: + delta = m_r_solver.m_low_bounds[j] - x; + x = m_r_solver.m_low_bounds[j]; + break; + case column_type::boxed: + if (x > m_r_solver.m_upper_bounds[j]) { + delta = m_r_solver.m_upper_bounds[j] - x; + x += m_r_solver.m_upper_bounds[j]; + } else { + delta = m_r_solver.m_low_bounds[j] - x; + x = m_r_solver.m_low_bounds[j]; + } + break; + case column_type::fixed: + delta = m_r_solver.m_low_bounds[j] - x; + x = m_r_solver.m_low_bounds[j]; + break; + + default: + lean_assert(false); + } + break; + default: + lean_unreachable(); + } + m_r_solver.remove_column_from_inf_set(j); + return true; + } + + + + void prepare_solver_x_with_signature_tableau(const lar_solution_signature & signature) { + lean_assert(m_r_solver.inf_set_is_correct()); + for (auto &t : signature) { + unsigned j = t.first; + if (m_r_heading[j] >= 0) + continue; + auto pos_type = t.second; + numeric_pair delta; + if (!update_xj_and_get_delta(j, pos_type, delta)) + continue; + for (const auto & cc : m_r_solver.m_A.m_columns[j]){ + unsigned i = cc.m_i; + unsigned jb = m_r_solver.m_basis[i]; + m_r_solver.m_x[jb] -= delta * m_r_solver.m_A.get_val(cc); + m_r_solver.update_column_in_inf_set(jb); + } + lean_assert(m_r_solver.A_mult_x_is_off() == false); + } + lean_assert(m_r_solver.inf_set_is_correct()); + } + + + template + void prepare_solver_x_with_signature(const lar_solution_signature & signature, lp_primal_core_solver & s) { + for (auto &t : signature) { + unsigned j = t.first; + lean_assert(m_r_heading[j] < 0); + auto pos_type = t.second; + switch (pos_type) { + case at_low_bound: + s.m_x[j] = s.m_low_bounds[j]; + break; + case at_fixed: + case at_upper_bound: + s.m_x[j] = s.m_upper_bounds[j]; + break; + case free_of_bounds: { + s.m_x[j] = zero_of_type(); + continue; + } + case not_at_bound: + switch (m_column_types[j]) { + case column_type::free_column: + lean_assert(false); // unreachable + case column_type::upper_bound: + s.m_x[j] = s.m_upper_bounds[j]; + break; + case column_type::low_bound: + s.m_x[j] = s.m_low_bounds[j]; + break; + case column_type::boxed: + if (my_random() % 2) { + s.m_x[j] = s.m_low_bounds[j]; + } else { + s.m_x[j] = s.m_upper_bounds[j]; + } + break; + case column_type::fixed: + s.m_x[j] = s.m_low_bounds[j]; + break; + default: + lean_assert(false); + } + break; + default: + lean_unreachable(); + } + } + + lean_assert(is_zero_vector(s.m_b)); + s.solve_Ax_eq_b(); + } + + template + void catch_up_in_lu_in_reverse(const vector & trace_of_basis_change, lp_primal_core_solver & cs) { + // recover the previous working basis + for (unsigned i = trace_of_basis_change.size(); i > 0; i-= 2) { + unsigned entering = trace_of_basis_change[i-1]; + unsigned leaving = trace_of_basis_change[i-2]; + cs.change_basis_unconditionally(entering, leaving); + } + cs.init_lu(); + } + + //basis_heading is the basis heading of the solver owning trace_of_basis_change + // here we compact the trace as we go to avoid unnecessary column changes + template + void catch_up_in_lu(const vector & trace_of_basis_change, const vector & basis_heading, lp_primal_core_solver & cs) { + if (cs.m_factorization == nullptr || cs.m_factorization->m_refactor_counter + trace_of_basis_change.size()/2 >= 200) { + for (unsigned i = 0; i < trace_of_basis_change.size(); i+= 2) { + unsigned entering = trace_of_basis_change[i]; + unsigned leaving = trace_of_basis_change[i+1]; + cs.change_basis_unconditionally(entering, leaving); + } + if (cs.m_factorization != nullptr) + delete cs.m_factorization; + cs.m_factorization = nullptr; + } else { + indexed_vector w(cs.m_A.row_count()); + // the queues of delayed indices + std::queue entr_q, leav_q; + auto * l = cs.m_factorization; + lean_assert(l->get_status() == LU_status::OK); + for (unsigned i = 0; i < trace_of_basis_change.size(); i+= 2) { + unsigned entering = trace_of_basis_change[i]; + unsigned leaving = trace_of_basis_change[i+1]; + bool good_e = basis_heading[entering] >= 0 && cs.m_basis_heading[entering] < 0; + bool good_l = basis_heading[leaving] < 0 && cs.m_basis_heading[leaving] >= 0; + if (!good_e && !good_l) continue; + if (good_e && !good_l) { + while (!leav_q.empty() && cs.m_basis_heading[leav_q.front()] < 0) + leav_q.pop(); + if (!leav_q.empty()) { + leaving = leav_q.front(); + leav_q.pop(); + } else { + entr_q.push(entering); + continue; + } + } else if (!good_e && good_l) { + while (!entr_q.empty() && cs.m_basis_heading[entr_q.front()] >= 0) + entr_q.pop(); + if (!entr_q.empty()) { + entering = entr_q.front(); + entr_q.pop(); + } else { + leav_q.push(leaving); + continue; + } + } + lean_assert(cs.m_basis_heading[entering] < 0); + lean_assert(cs.m_basis_heading[leaving] >= 0); + if (l->get_status() == LU_status::OK) { + l->prepare_entering(entering, w); // to init vector w + l->replace_column(zero_of_type(), w, cs.m_basis_heading[leaving]); + } + cs.change_basis_unconditionally(entering, leaving); + } + if (l->get_status() != LU_status::OK) { + delete l; + cs.m_factorization = nullptr; + } + } + if (cs.m_factorization == nullptr) { + if (numeric_traits::precise()) + init_factorization(cs.m_factorization, cs.m_A, cs.m_basis, settings()); + } + } + + bool no_r_lu() const { + return m_r_solver.m_factorization == nullptr || m_r_solver.m_factorization->get_status() == LU_status::Degenerated; + } + + void solve_on_signature_tableau(const lar_solution_signature & signature, const vector & changes_of_basis) { + r_basis_is_OK(); + lean_assert(settings().use_tableau()); + bool r = catch_up_in_lu_tableau(changes_of_basis, m_d_solver.m_basis_heading); + + if (!r) { // it is the case where m_d_solver gives a degenerated basis + prepare_solver_x_with_signature_tableau(signature); // still are going to use the signature partially + m_r_solver.find_feasible_solution(); + m_d_basis = m_r_basis; + m_d_heading = m_r_heading; + m_d_nbasis = m_r_nbasis; + delete m_d_solver.m_factorization; + m_d_solver.m_factorization = nullptr; + } else { + prepare_solver_x_with_signature_tableau(signature); + m_r_solver.start_tracing_basis_changes(); + m_r_solver.find_feasible_solution(); + if (settings().get_cancel_flag()) + return; + m_r_solver.stop_tracing_basis_changes(); + // and now catch up in the double solver + lean_assert(m_r_solver.total_iterations() >= m_r_solver.m_trace_of_basis_change_vector.size() /2); + catch_up_in_lu(m_r_solver.m_trace_of_basis_change_vector, m_r_solver.m_basis_heading, m_d_solver); + } + lean_assert(r_basis_is_OK()); + } + + bool adjust_x_of_column(unsigned j) { + /* + if (m_r_solver.m_basis_heading[j] >= 0) { + return false; + } + + if (m_r_solver.column_is_feasible(j)) { + return false; + } + + m_r_solver.snap_column_to_bound_tableau(j); + lean_assert(m_r_solver.column_is_feasible(j)); + m_r_solver.m_inf_set.erase(j); + */ + lean_assert(false); + return true; + } + + + bool catch_up_in_lu_tableau(const vector & trace_of_basis_change, const vector & basis_heading) { + lean_assert(r_basis_is_OK()); + // the queues of delayed indices + std::queue entr_q, leav_q; + for (unsigned i = 0; i < trace_of_basis_change.size(); i+= 2) { + unsigned entering = trace_of_basis_change[i]; + unsigned leaving = trace_of_basis_change[i+1]; + bool good_e = basis_heading[entering] >= 0 && m_r_solver.m_basis_heading[entering] < 0; + bool good_l = basis_heading[leaving] < 0 && m_r_solver.m_basis_heading[leaving] >= 0; + if (!good_e && !good_l) continue; + if (good_e && !good_l) { + while (!leav_q.empty() && m_r_solver.m_basis_heading[leav_q.front()] < 0) + leav_q.pop(); + if (!leav_q.empty()) { + leaving = leav_q.front(); + leav_q.pop(); + } else { + entr_q.push(entering); + continue; + } + } else if (!good_e && good_l) { + while (!entr_q.empty() && m_r_solver.m_basis_heading[entr_q.front()] >= 0) + entr_q.pop(); + if (!entr_q.empty()) { + entering = entr_q.front(); + entr_q.pop(); + } else { + leav_q.push(leaving); + continue; + } + } + lean_assert(m_r_solver.m_basis_heading[entering] < 0); + lean_assert(m_r_solver.m_basis_heading[leaving] >= 0); + m_r_solver.change_basis_unconditionally(entering, leaving); + if(!m_r_solver.pivot_column_tableau(entering, m_r_solver.m_basis_heading[entering])) { + // unroll the last step + m_r_solver.change_basis_unconditionally(leaving, entering); +#ifdef LEAN_DEBUG + bool t = +#endif + m_r_solver.pivot_column_tableau(leaving, m_r_solver.m_basis_heading[leaving]); +#ifdef LEAN_DEBUG + lean_assert(t); +#endif + return false; + } + } + lean_assert(r_basis_is_OK()); + return true; + } + + + bool r_basis_is_OK() const { +#ifdef LEAN_DEBUG + if (!m_r_solver.m_settings.use_tableau()) + return true; + for (unsigned j : m_r_solver.m_basis) { + lean_assert(m_r_solver.m_A.m_columns[j].size() == 1); + lean_assert(m_r_solver.m_A.get_val(m_r_solver.m_A.m_columns[j][0]) == one_of_type()); + } + for (unsigned j =0; j < m_r_solver.m_basis_heading.size(); j++) { + if (m_r_solver.m_basis_heading[j] >= 0) continue; + if (m_r_solver.m_column_types[j] == column_type::fixed) continue; + lean_assert(static_cast(- m_r_solver.m_basis_heading[j] - 1) < m_r_solver.m_column_types.size()); + lean_assert( m_r_solver.m_basis_heading[j] <= -1); + } +#endif + return true; + } + + void solve_on_signature(const lar_solution_signature & signature, const vector & changes_of_basis) { + lean_assert(!settings().use_tableau()); + if (m_r_solver.m_factorization == nullptr) { + for (unsigned j = 0; j < changes_of_basis.size(); j+=2) { + unsigned entering = changes_of_basis[j]; + unsigned leaving = changes_of_basis[j + 1]; + m_r_solver.change_basis_unconditionally(entering, leaving); + } + init_factorization(m_r_solver.m_factorization, m_r_A, m_r_basis, settings()); + } else { + catch_up_in_lu(changes_of_basis, m_d_solver.m_basis_heading, m_r_solver); + } + + if (no_r_lu()) { // it is the case where m_d_solver gives a degenerated basis, we need to roll back + std::cout << "no_r_lu" << std::endl; + catch_up_in_lu_in_reverse(changes_of_basis, m_r_solver); + m_r_solver.find_feasible_solution(); + m_d_basis = m_r_basis; + m_d_heading = m_r_heading; + m_d_nbasis = m_r_nbasis; + delete m_d_solver.m_factorization; + m_d_solver.m_factorization = nullptr; + } else { + prepare_solver_x_with_signature(signature, m_r_solver); + m_r_solver.start_tracing_basis_changes(); + m_r_solver.find_feasible_solution(); + if (settings().get_cancel_flag()) + return; + m_r_solver.stop_tracing_basis_changes(); + // and now catch up in the double solver + lean_assert(m_r_solver.total_iterations() >= m_r_solver.m_trace_of_basis_change_vector.size() /2); + catch_up_in_lu(m_r_solver.m_trace_of_basis_change_vector, m_r_solver.m_basis_heading, m_d_solver); + } + } + + void create_double_matrix(static_matrix & A) { + for (unsigned i = 0; i < m_r_A.row_count(); i++) { + auto & row = m_r_A.m_rows[i]; + for (row_cell & c : row) { + A.set(i, c.m_j, c.get_val().get_double()); + } + } + } + + void fill_basis_d( + vector& basis_d, + vector& heading_d, + vector& nbasis_d){ + basis_d = m_r_basis; + heading_d = m_r_heading; + nbasis_d = m_r_nbasis; + } + + template + void extract_signature_from_lp_core_solver(const lp_primal_core_solver & solver, lar_solution_signature & signature) { + signature.clear(); + lean_assert(signature.size() == 0); + for (unsigned j = 0; j < solver.m_basis_heading.size(); j++) { + if (solver.m_basis_heading[j] < 0) { + signature[j] = solver.get_non_basic_column_value_position(j); + } + } + } + + void get_bounds_for_double_solver() { + unsigned n = m_n(); + m_d_low_bounds.resize(n); + m_d_upper_bounds.resize(n); + double delta = find_delta_for_strict_boxed_bounds().get_double(); + if (delta > 0.000001) + delta = 0.000001; + for (unsigned j = 0; j < n; j++) { + if (low_bound_is_set(j)) { + const auto & lb = m_r_solver.m_low_bounds[j]; + m_d_low_bounds[j] = lb.x.get_double() + delta * lb.y.get_double(); + } + if (upper_bound_is_set(j)) { + const auto & ub = m_r_solver.m_upper_bounds[j]; + m_d_upper_bounds[j] = ub.x.get_double() + delta * ub.y.get_double(); + lean_assert(!low_bound_is_set(j) || (m_d_upper_bounds[j] >= m_d_low_bounds[j])); + } + } + } + + void scale_problem_for_doubles( + static_matrix& A, + vector & low_bounds, + vector & upper_bounds) { + vector column_scale_vector; + vector right_side_vector(A.column_count()); + settings().reps_in_scaler = 5; + scaler scaler(right_side_vector, + A, + settings().scaling_minimum, + settings().scaling_maximum, + column_scale_vector, + settings()); + if (! scaler.scale()) { + // the scale did not succeed, unscaling + A.clear(); + create_double_matrix(A); + } else { + for (unsigned j = 0; j < A.column_count(); j++) { + if (m_r_solver.column_has_upper_bound(j)) { + upper_bounds[j] /= column_scale_vector[j]; + } + if (m_r_solver.column_has_low_bound(j)) { + low_bounds[j] /= column_scale_vector[j]; + } + } + } + + } + // returns the trace of basis changes + vector find_solution_signature_with_doubles(lar_solution_signature & signature) { + if (m_d_solver.m_factorization == nullptr || m_d_solver.m_factorization->get_status() != LU_status::OK) { + vector ret; + return ret; + } + get_bounds_for_double_solver(); + + extract_signature_from_lp_core_solver(m_r_solver, signature); + prepare_solver_x_with_signature(signature, m_d_solver); + m_d_solver.start_tracing_basis_changes(); + m_d_solver.find_feasible_solution(); + if (settings().get_cancel_flag()) + return vector(); + + m_d_solver.stop_tracing_basis_changes(); + extract_signature_from_lp_core_solver(m_d_solver, signature); + return m_d_solver.m_trace_of_basis_change_vector; + } + + + bool low_bound_is_set(unsigned j) const { + switch (m_column_types[j]) { + case column_type::free_column: + case column_type::upper_bound: + return false; + case column_type::low_bound: + case column_type::boxed: + case column_type::fixed: + return true; + default: + lean_assert(false); + } + return false; + } + + bool upper_bound_is_set(unsigned j) const { + switch (m_column_types[j]) { + case column_type::free_column: + case column_type::low_bound: + return false; + case column_type::upper_bound: + case column_type::boxed: + case column_type::fixed: + return true; + default: + lean_assert(false); + } + return false; + } + + void update_delta(mpq& delta, numeric_pair const& l, numeric_pair const& u) const { + lean_assert(l <= u); + if (l.x < u.x && l.y > u.y) { + mpq delta1 = (u.x - l.x) / (l.y - u.y); + if (delta1 < delta) { + delta = delta1; + } + } + lean_assert(l.x + delta * l.y <= u.x + delta * u.y); + } + + + mpq find_delta_for_strict_boxed_bounds() const{ + mpq delta = numeric_traits::one(); + for (unsigned j = 0; j < m_r_A.column_count(); j++ ) { + if (m_column_types()[j] != column_type::boxed) + continue; + update_delta(delta, m_r_low_bounds[j], m_r_upper_bounds[j]); + + } + return delta; + } + + + mpq find_delta_for_strict_bounds() const{ + mpq delta = numeric_traits::one(); + for (unsigned j = 0; j < m_r_A.column_count(); j++ ) { + if (low_bound_is_set(j)) + update_delta(delta, m_r_low_bounds[j], m_r_x[j]); + if (upper_bound_is_set(j)) + update_delta(delta, m_r_x[j], m_r_upper_bounds[j]); + } + return delta; + } + + void init_column_row_nz_for_r_solver() { + m_r_solver.init_column_row_non_zeroes(); + } + + linear_combination_iterator * get_column_iterator(unsigned j) { + if (settings().use_tableau()) { + return new iterator_on_column>(m_r_solver.m_A.m_columns[j], m_r_solver.m_A); + } else { + m_r_solver.solve_Bd(j); + return new iterator_on_indexed_vector(m_r_solver.m_ed); + } + } + +}; +} diff --git a/src/util/lp/lar_core_solver.hpp b/src/util/lp/lar_core_solver.hpp new file mode 100644 index 000000000..ded6762c5 --- /dev/null +++ b/src/util/lp/lar_core_solver.hpp @@ -0,0 +1,292 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include +#include "util/vector.h" +#include "util/lp/lar_core_solver.h" +#include "util/lp/lar_solution_signature.h" +namespace lean { +lar_core_solver::lar_core_solver( + lp_settings & settings, + const column_namer & column_names +): + m_r_solver(m_r_A, + m_right_sides_dummy, + m_r_x, + m_r_basis, + m_r_nbasis, + m_r_heading, + m_costs_dummy, + m_column_types(), + m_r_low_bounds(), + m_r_upper_bounds(), + settings, + column_names), + m_d_solver(m_d_A, + m_d_right_sides_dummy, + m_d_x, + m_d_basis, + m_d_nbasis, + m_d_heading, + m_d_costs_dummy, + m_column_types(), + m_d_low_bounds, + m_d_upper_bounds, + settings, + column_names){} + +void lar_core_solver::init_costs(bool first_time) { + lean_assert(false); // should not be called + // lean_assert(this->m_x.size() >= this->m_n()); + // lean_assert(this->m_column_types.size() >= this->m_n()); + // if (first_time) + // this->m_costs.resize(this->m_n()); + // X inf = this->m_infeasibility; + // this->m_infeasibility = zero_of_type(); + // for (unsigned j = this->m_n(); j--;) + // init_cost_for_column(j); + // if (!(first_time || inf >= this->m_infeasibility)) { + // LP_OUT(this->m_settings, "iter = " << this->total_iterations() << std::endl); + // LP_OUT(this->m_settings, "inf was " << T_to_string(inf) << " and now " << T_to_string(this->m_infeasibility) << std::endl); + // lean_assert(false); + // } + // if (inf == this->m_infeasibility) + // this->m_iters_with_no_cost_growing++; +} + + +void lar_core_solver::init_cost_for_column(unsigned j) { + /* + // If j is a breakpoint column, then we set the cost zero. + // When anylyzing an entering column candidate we update the cost of the breakpoints columns to get the left or the right derivative if the infeasibility function + const numeric_pair & x = this->m_x[j]; + // set zero cost for each non-basis column + if (this->m_basis_heading[j] < 0) { + this->m_costs[j] = numeric_traits::zero(); + return; + } + // j is a basis column + switch (this->m_column_types[j]) { + case fixed: + case column_type::boxed: + if (x > this->m_upper_bounds[j]) { + this->m_costs[j] = 1; + this->m_infeasibility += x - this->m_upper_bounds[j]; + } else if (x < this->m_low_bounds[j]) { + this->m_infeasibility += this->m_low_bounds[j] - x; + this->m_costs[j] = -1; + } else { + this->m_costs[j] = numeric_traits::zero(); + } + break; + case low_bound: + if (x < this->m_low_bounds[j]) { + this->m_costs[j] = -1; + this->m_infeasibility += this->m_low_bounds[j] - x; + } else { + this->m_costs[j] = numeric_traits::zero(); + } + break; + case upper_bound: + if (x > this->m_upper_bounds[j]) { + this->m_costs[j] = 1; + this->m_infeasibility += x - this->m_upper_bounds[j]; + } else { + this->m_costs[j] = numeric_traits::zero(); + } + break; + case free_column: + this->m_costs[j] = numeric_traits::zero(); + break; + default: + lean_assert(false); + break; + }*/ +} + + +// returns m_sign_of_alpha_r +int lar_core_solver::column_is_out_of_bounds(unsigned j) { + /* + switch (this->m_column_type[j]) { + case fixed: + case column_type::boxed: + if (this->x_below_low_bound(j)) { + return -1; + } + if (this->x_above_upper_bound(j)) { + return 1; + } + return 0; + case low_bound: + if (this->x_below_low_bound(j)) { + return -1; + } + return 0; + case upper_bound: + if (this->x_above_upper_bound(j)) { + return 1; + } + return 0; + default: + return 0; + break; + }*/ + lean_assert(false); + return true; +} + + + +void lar_core_solver::calculate_pivot_row(unsigned i) { + lean_assert(!m_r_solver.use_tableau()); + lean_assert(m_r_solver.m_pivot_row.is_OK()); + m_r_solver.m_pivot_row_of_B_1.clear(); + m_r_solver.m_pivot_row_of_B_1.resize(m_r_solver.m_m()); + m_r_solver.m_pivot_row.clear(); + m_r_solver.m_pivot_row.resize(m_r_solver.m_n()); + if (m_r_solver.m_settings.use_tableau()) { + unsigned basis_j = m_r_solver.m_basis[i]; + for (auto & c : m_r_solver.m_A.m_rows[i]) { + if (c.m_j != basis_j) + m_r_solver.m_pivot_row.set_value(c.get_val(), c.m_j); + } + return; + } + + m_r_solver.calculate_pivot_row_of_B_1(i); + m_r_solver.calculate_pivot_row_when_pivot_row_of_B1_is_ready(i); +} + + + + void lar_core_solver::prefix_r() { + if (!m_r_solver.m_settings.use_tableau()) { + m_r_solver.m_copy_of_xB.resize(m_r_solver.m_n()); + m_r_solver.m_ed.resize(m_r_solver.m_m()); + m_r_solver.m_pivot_row.resize(m_r_solver.m_n()); + m_r_solver.m_pivot_row_of_B_1.resize(m_r_solver.m_m()); + m_r_solver.m_w.resize(m_r_solver.m_m()); + m_r_solver.m_y.resize(m_r_solver.m_m()); + m_r_solver.m_rows_nz.resize(m_r_solver.m_m(), 0); + m_r_solver.m_columns_nz.resize(m_r_solver.m_n(), 0); + init_column_row_nz_for_r_solver(); + } + + m_r_solver.m_b.resize(m_r_solver.m_m()); + if (m_r_solver.m_settings.simplex_strategy() != simplex_strategy_enum::tableau_rows) { + if(m_r_solver.m_settings.use_breakpoints_in_feasibility_search) + m_r_solver.m_breakpoint_indices_queue.resize(m_r_solver.m_n()); + m_r_solver.m_costs.resize(m_r_solver.m_n()); + m_r_solver.m_d.resize(m_r_solver.m_n()); + m_r_solver.m_using_infeas_costs = true; + } +} + + void lar_core_solver::prefix_d() { + m_d_solver.m_b.resize(m_d_solver.m_m()); + m_d_solver.m_breakpoint_indices_queue.resize(m_d_solver.m_n()); + m_d_solver.m_copy_of_xB.resize(m_d_solver.m_n()); + m_d_solver.m_costs.resize(m_d_solver.m_n()); + m_d_solver.m_d.resize(m_d_solver.m_n()); + m_d_solver.m_ed.resize(m_d_solver.m_m()); + m_d_solver.m_pivot_row.resize(m_d_solver.m_n()); + m_d_solver.m_pivot_row_of_B_1.resize(m_d_solver.m_m()); + m_d_solver.m_w.resize(m_d_solver.m_m()); + m_d_solver.m_y.resize(m_d_solver.m_m()); + m_d_solver.m_steepest_edge_coefficients.resize(m_d_solver.m_n()); + m_d_solver.m_column_norms.clear(); + m_d_solver.m_column_norms.resize(m_d_solver.m_n(), 2); + m_d_solver.m_inf_set.clear(); + m_d_solver.m_inf_set.resize(m_d_solver.m_n()); +} + +void lar_core_solver::fill_not_improvable_zero_sum_from_inf_row() { + lean_assert(m_r_solver.A_mult_x_is_off() == false); + unsigned bj = m_r_basis[m_r_solver.m_inf_row_index_for_tableau]; + m_infeasible_sum_sign = m_r_solver.inf_sign_of_column(bj); + m_infeasible_linear_combination.clear(); + for (auto & rc : m_r_solver.m_A.m_rows[m_r_solver.m_inf_row_index_for_tableau]) { + m_infeasible_linear_combination.push_back(std::make_pair( rc.get_val(), rc.m_j)); + } +} + +void lar_core_solver::fill_not_improvable_zero_sum() { + if (m_r_solver.m_settings.simplex_strategy() == simplex_strategy_enum::tableau_rows) { + fill_not_improvable_zero_sum_from_inf_row(); + return; + } + // reusing the existing mechanism for row_feasibility_loop + m_infeasible_sum_sign = m_r_solver.m_settings.use_breakpoints_in_feasibility_search? -1 : 1; + m_infeasible_linear_combination.clear(); + for (auto j : m_r_solver.m_basis) { + const mpq & cost_j = m_r_solver.m_costs[j]; + if (!numeric_traits::is_zero(cost_j)) { + m_infeasible_linear_combination.push_back(std::make_pair(cost_j, j)); + } + } + // m_costs are expressed by m_d ( additional costs), substructing the latter gives 0 + for (unsigned j = 0; j < m_r_solver.m_n(); j++) { + if (m_r_solver.m_basis_heading[j] >= 0) continue; + const mpq & d_j = m_r_solver.m_d[j]; + if (!numeric_traits::is_zero(d_j)) { + m_infeasible_linear_combination.push_back(std::make_pair(-d_j, j)); + } + } +} + + +void lar_core_solver::solve() { + lean_assert(m_r_solver.non_basic_columns_are_set_correctly()); + lean_assert(m_r_solver.inf_set_is_correct()); + if (m_r_solver.current_x_is_feasible() && m_r_solver.m_look_for_feasible_solution_only) { + m_r_solver.set_status(OPTIMAL); + return; + } + ++settings().st().m_need_to_solve_inf; + lean_assert(!m_r_solver.A_mult_x_is_off()); + lean_assert((!settings().use_tableau()) || r_basis_is_OK()); + if (need_to_presolve_with_double_solver()) { + prefix_d(); + lar_solution_signature solution_signature; + vector changes_of_basis = find_solution_signature_with_doubles(solution_signature); + if (m_d_solver.get_status() == TIME_EXHAUSTED) { + m_r_solver.set_status(TIME_EXHAUSTED); + return; + } + if (settings().use_tableau()) + solve_on_signature_tableau(solution_signature, changes_of_basis); + else + solve_on_signature(solution_signature, changes_of_basis); + lean_assert(!settings().use_tableau() || r_basis_is_OK()); + } else { + if (!settings().use_tableau()) { + bool snapped = m_r_solver.snap_non_basic_x_to_bound(); + lean_assert(m_r_solver.non_basic_columns_are_set_correctly()); + if (snapped) + m_r_solver.solve_Ax_eq_b(); + } + if (m_r_solver.m_look_for_feasible_solution_only) + m_r_solver.find_feasible_solution(); + else + m_r_solver.solve(); + lean_assert(!settings().use_tableau() || r_basis_is_OK()); + } + if (m_r_solver.get_status() == INFEASIBLE) { + fill_not_improvable_zero_sum(); + } else if (m_r_solver.get_status() != UNBOUNDED) { + m_r_solver.set_status(OPTIMAL); + } + lean_assert(r_basis_is_OK()); + lean_assert(m_r_solver.non_basic_columns_are_set_correctly()); + lean_assert(m_r_solver.inf_set_is_correct()); +} + + +} + diff --git a/src/util/lp/lar_core_solver_instances.cpp b/src/util/lp/lar_core_solver_instances.cpp new file mode 100644 index 000000000..432d1a939 --- /dev/null +++ b/src/util/lp/lar_core_solver_instances.cpp @@ -0,0 +1,10 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include +#include +#include +#include "util/vector.h" +#include +#include "util/lp/lar_core_solver.hpp" diff --git a/src/util/lp/lar_solution_signature.h b/src/util/lp/lar_solution_signature.h new file mode 100644 index 000000000..2c4169c81 --- /dev/null +++ b/src/util/lp/lar_solution_signature.h @@ -0,0 +1,13 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ + +#pragma once +#include "util/vector.h" +#include "util/debug.h" +#include "util/lp/lp_settings.h" +#include +namespace lean { +typedef std::unordered_map lar_solution_signature; +} diff --git a/src/util/lp/lar_solver.h b/src/util/lp/lar_solver.h new file mode 100644 index 000000000..1fe5c6ca1 --- /dev/null +++ b/src/util/lp/lar_solver.h @@ -0,0 +1,2135 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#pragma once +#include "util/vector.h" +#include +#include "util/debug.h" +#include "util/buffer.h" +#include +#include +#include +#include "util/lp/lar_constraints.h" +#include +#include "util/lp/lar_core_solver.h" +#include +#include "util/lp/numeric_pair.h" +#include "util/lp/scaler.h" +#include "util/lp/lp_primal_core_solver.h" +#include "util/lp/random_updater.h" +#include +#include "util/lp/stacked_map.h" +#include "util/lp/stacked_value.h" +#include "util/lp/stacked_vector.h" +#include "util/lp/stacked_unordered_set.h" +#include "util/lp/iterator_on_pivot_row.h" +#include "util/lp/implied_bound.h" +#include "util/lp/bound_analyzer_on_row.h" +#include "util/lp/iterator_on_term_with_basis_var.h" +#include "util/lp/iterator_on_row.h" +#include "util/lp/quick_xplain.h" +namespace lean { +template +struct conversion_helper { + static V get_low_bound(const column_info & ci) { + return V(ci.get_low_bound(), ci.low_bound_is_strict()? 1 : 0); + } + + static V get_upper_bound(const column_info & ci) { + return V(ci.get_upper_bound(), ci.upper_bound_is_strict()? -1 : 0); + } +}; + +template<> +struct conversion_helper { + static double conversion_helper ::get_upper_bound(const column_info & ci) { + if (!ci.upper_bound_is_strict()) + return ci.get_upper_bound().get_double(); + double eps = 0.00001; + if (!ci.low_bound_is_set()) + return ci.get_upper_bound().get_double() - eps; + eps = std::min((ci.get_upper_bound() - ci.get_low_bound()).get_double() / 1000, eps); + return ci.get_upper_bound().get_double() - eps; + } + + static double get_low_bound(const column_info & ci) { + if (!ci.low_bound_is_strict()) + return ci.get_low_bound().get_double(); + double eps = 0.00001; + if (!ci.upper_bound_is_set()) + return ci.get_low_bound().get_double() + eps; + eps = std::min((ci.get_upper_bound() - ci.get_low_bound()).get_double() / 1000, eps); + return ci.get_low_bound().get_double() + eps; + } + +}; + +struct constraint_index_and_column_struct { + int m_ci = -1; + int m_j = -1; + constraint_index_and_column_struct() {} + constraint_index_and_column_struct(unsigned ci, unsigned j): + m_ci(static_cast(ci)), + m_j(static_cast(j)) + {} + bool operator==(const constraint_index_and_column_struct & a) const { return a.m_ci == m_ci && a.m_j == m_j; } + bool operator!=(const constraint_index_and_column_struct & a) const { return ! (*this == a);} +}; + +class lar_solver : public column_namer { + //////////////////// fields ////////////////////////// + lp_settings m_settings; + stacked_value m_status = OPTIMAL; + std::unordered_map m_ext_vars_to_columns; + vector m_columns_to_ext_vars_or_term_indices; + stacked_vector m_vars_to_ul_pairs; + vector m_constraints; + stacked_value m_constraint_count; + indexed_vector m_incoming_buffer; + // the set of column indices j such that bounds have changed for j + int_set m_columns_with_changed_bound; + int_set m_rows_with_changed_bounds; + int_set m_basic_columns_with_changed_cost; + stacked_value m_infeasible_column_index = -1; // such can be found at the initialization step + stacked_value m_term_count; +public: // debug remove later + vector m_terms; +private: + vector m_orig_terms; + const var_index m_terms_start_index = 1000000; + indexed_vector m_column_buffer; + std::function m_column_type_function = [this] (unsigned j) {return m_mpq_lar_core_solver.m_column_types()[j];}; + +public: + lar_core_solver m_mpq_lar_core_solver; + unsigned constraint_count() const { + return m_constraints.size(); + } + const lar_base_constraint& get_constraint(unsigned ci) const { + return *(m_constraints[ci]); + } + + ////////////////// methods //////////////////////////////// + static_matrix> & A_r() { return m_mpq_lar_core_solver.m_r_A;} + static_matrix> const & A_r() const { return m_mpq_lar_core_solver.m_r_A;} + static_matrix & A_d() { return m_mpq_lar_core_solver.m_d_A;} + static_matrix const & A_d() const { return m_mpq_lar_core_solver.m_d_A;} + + static bool valid_index(unsigned j){ return static_cast(j) >= 0;} + + +public: + lp_settings & settings() { return m_settings;} + + lp_settings const & settings() const { return m_settings;} + + void clear() {lean_assert(false); // not implemented + } + + + lar_solver() : m_mpq_lar_core_solver( + m_settings, + *this + ) {} + + void set_propagate_bounds_on_pivoted_rows_mode(bool v) { + m_mpq_lar_core_solver.m_r_solver.m_pivoted_rows = v? (& m_rows_with_changed_bounds) : nullptr; + } + + virtual ~lar_solver(){ + for (auto c : m_constraints) + delete c; + for (auto t : m_terms) + delete t; + for (auto t : m_orig_terms) + delete t; + } + + var_index add_var(unsigned ext_j) { + var_index i; + lean_assert (ext_j < m_terms_start_index); + + if (ext_j >= m_terms_start_index) + throw 0; // todo : what is the right was to exit? + + if (try_get_val(m_ext_vars_to_columns, ext_j, i)) { + return i; + } + lean_assert(m_vars_to_ul_pairs.size() == A_r().column_count()); + i = A_r().column_count(); + m_vars_to_ul_pairs.push_back (ul_pair(static_cast(-1))); + register_new_ext_var_index(ext_j); + add_non_basic_var_to_core_fields(); + lean_assert(sizes_are_correct()); + return i; + } + + numeric_pair const& get_value(var_index vi) const { return m_mpq_lar_core_solver.m_r_x[vi]; } + + bool is_term(var_index j) const { + return j >= m_terms_start_index && j - m_terms_start_index < m_terms.size(); + } + + unsigned adjust_term_index(unsigned j) const { + lean_assert(is_term(j)); + return j - m_terms_start_index; + } + + + bool need_to_presolve_with_doubles() const { return m_mpq_lar_core_solver.need_to_presolve_with_double_solver(); } + + void add_row_from_term_no_constraint(const lar_term * term) { + // j will be a new variable + unsigned j = A_r().column_count(); + ul_pair ul(j); + m_vars_to_ul_pairs.push_back(ul); + add_basic_var_to_core_fields(); + if (use_tableau()) { + auto it = iterator_on_term_with_basis_var(*term, j); + A_r().fill_last_row_with_pivoting(it, + m_mpq_lar_core_solver.m_r_solver.m_basis_heading); + m_mpq_lar_core_solver.m_r_solver.m_b.resize(A_r().column_count(), zero_of_type()); + } else { + fill_last_row_of_A_r(A_r(), term); + } + m_mpq_lar_core_solver.m_r_x[j] = get_basic_var_value_from_row_directly(A_r().row_count() - 1); + if (need_to_presolve_with_doubles()) + fill_last_row_of_A_d(A_d(), term); + } + + void add_constraint_from_term_and_create_new_column_row(unsigned term_j, const lar_term* term, + lconstraint_kind kind, const mpq & right_side) { + + lean_assert(A_r().column_count() == m_mpq_lar_core_solver.m_r_solver.m_costs.size()); + // j will be a new variable + unsigned j = A_r().column_count(); + ul_pair ul(j); + m_vars_to_ul_pairs.push_back(ul); + add_basic_var_to_core_fields(); + if (!m_settings.use_tableau()) { + fill_last_row_of_A_r(A_r(), term); + } + else { + auto it = iterator_on_term_with_basis_var(*term, j); + A_r().fill_last_row_with_pivoting(it, + m_mpq_lar_core_solver.m_r_solver.m_basis_heading); + m_mpq_lar_core_solver.m_r_solver.m_b.resize(A_r().column_count(), zero_of_type()); + } + m_mpq_lar_core_solver.m_r_x[A_r().column_count() - 1] = get_basic_var_value_from_row_directly(A_r().row_count() - 1); + fill_last_row_of_A_d(A_d(), term); + register_new_ext_var_index(term_j); + + // m_constraints.size() is the index of the constrained that is about to be added + update_column_type_and_bound(j, kind, right_side - term->m_v, m_constraints.size()); + m_constraints.push_back(new lar_term_constraint(term, kind, right_side)); + lean_assert(A_r().column_count() == m_mpq_lar_core_solver.m_r_solver.m_costs.size()); + } + + void add_var_bound_on_constraint_for_term(var_index j, lconstraint_kind kind, const mpq & right_side, constraint_index ci) { + lean_assert(is_term(j)); + unsigned adjusted_term_index = adjust_term_index(j); + unsigned term_j; + if (try_get_val(m_ext_vars_to_columns, j, term_j)) { + mpq rs = right_side - m_orig_terms[adjusted_term_index]->m_v; + m_constraints.push_back(new lar_term_constraint(m_orig_terms[adjusted_term_index], kind, right_side)); + update_column_type_and_bound(term_j, kind, rs, ci); + } + else { + add_constraint_from_term_and_create_new_column_row(j, m_orig_terms[adjusted_term_index], kind, right_side); + } + } + + + void add_row_for_term(const lar_term * term) { + lean_assert(sizes_are_correct()); + add_row_from_term_no_constraint(term); + lean_assert(sizes_are_correct()); + } + + bool sizes_are_correct() const { + lean_assert(!m_mpq_lar_core_solver.need_to_presolve_with_double_solver() || A_r().column_count() == A_d().column_count()); + lean_assert(A_r().column_count() == m_mpq_lar_core_solver.m_r_solver.m_column_types.size()); + lean_assert(A_r().column_count() == m_mpq_lar_core_solver.m_r_solver.m_costs.size()); + lean_assert(A_r().column_count() == m_mpq_lar_core_solver.m_r_x.size()); + return true; + } + + constraint_index add_var_bound(var_index j, lconstraint_kind kind, const mpq & right_side) { + lean_assert(sizes_are_correct()); + constraint_index ci = m_constraints.size(); + if (!is_term(j)) { // j is a var + auto vc = new lar_var_constraint(j, kind, right_side); + m_constraints.push_back(vc); + update_column_type_and_bound(j, kind, right_side, ci); + } else { + add_var_bound_on_constraint_for_term(j, kind, right_side, ci); + } + lean_assert(sizes_are_correct()); + return ci; + } + + + void print_implied_bound(const implied_bound& be, std::ostream & out) const { + out << "implied bound\n"; + unsigned v = be.m_j; + if (is_term(v)) { + out << "it is a term number " << be.m_j << std::endl; + print_term(*m_orig_terms[be.m_j - m_terms_start_index], out); + } + else { + out << get_column_name(v); + } + out << " " << lconstraint_kind_string(be.kind()) << " " << be.m_bound << std::endl; + // for (auto & p : be.m_explanation) { + // out << p.first << " : "; + // print_constraint(p.second, out); + // } + + // m_mpq_lar_core_solver.m_r_solver.print_column_info(be.m_j< m_terms_start_index? be.m_j : adjust_term_index(be.m_j), out); + out << "end of implied bound" << std::endl; + } + + bool implied_bound_is_correctly_explained(implied_bound const & be, const vector> & explanation) const { + std::unordered_map coeff_map; + auto rs_of_evidence = zero_of_type(); + unsigned n_of_G = 0, n_of_L = 0; + bool strict = false; + for (auto & it : explanation) { + mpq coeff = it.first; + constraint_index con_ind = it.second; + const auto & constr = *m_constraints[con_ind]; + lconstraint_kind kind = coeff.is_pos() ? constr.m_kind : flip_kind(constr.m_kind); + register_in_map(coeff_map, constr, coeff); + if (kind == GT || kind == LT) + strict = true; + if (kind == GE || kind == GT) n_of_G++; + else if (kind == LE || kind == LT) n_of_L++; + rs_of_evidence += coeff*constr.m_right_side; + } + lean_assert(n_of_G == 0 || n_of_L == 0); + lconstraint_kind kind = n_of_G ? GE : (n_of_L ? LE : EQ); + if (strict) + kind = static_cast((static_cast(kind) / 2)); + + if (!is_term(be.m_j)) { + if (coeff_map.size() != 1) + return false; + auto it = coeff_map.find(be.m_j); + if (it == coeff_map.end()) return false; + mpq ratio = it->second; + if (ratio < zero_of_type()) { + kind = static_cast(-kind); + } + rs_of_evidence /= ratio; + } else { + const lar_term * t = m_orig_terms[adjust_term_index(be.m_j)]; + const auto first_coeff = *t->m_coeffs.begin(); + unsigned j = first_coeff.first; + auto it = coeff_map.find(j); + if (it == coeff_map.end()) + return false; + mpq ratio = it->second / first_coeff.second; + for (auto & p : t->m_coeffs) { + it = coeff_map.find(p.first); + if (it == coeff_map.end()) + return false; + if (p.second * ratio != it->second) + return false; + } + if (ratio < zero_of_type()) { + kind = static_cast(-kind); + } + rs_of_evidence /= ratio; + rs_of_evidence += t->m_v * ratio; + } + + return kind == be.kind() && rs_of_evidence == be.m_bound; + } + + + void analyze_new_bounds_on_row( + unsigned row_index, + bound_propagator & bp) { + lean_assert(!use_tableau()); + iterator_on_pivot_row it(m_mpq_lar_core_solver.get_pivot_row(), m_mpq_lar_core_solver.m_r_basis[row_index]); + + bound_analyzer_on_row ra_pos(it, + zero_of_type>(), + row_index, + bp + ); + ra_pos.analyze(); + } + + void analyze_new_bounds_on_row_tableau( + unsigned row_index, + bound_propagator & bp + ) { + + if (A_r().m_rows[row_index].size() > settings().max_row_length_for_bound_propagation) + return; + iterator_on_row it(A_r().m_rows[row_index]); + lean_assert(use_tableau()); + bound_analyzer_on_row::analyze_row(it, + zero_of_type>(), + row_index, + bp + ); + } + + + void substitute_basis_var_in_terms_for_row(unsigned i) { + // todo : create a map from term basic vars to the rows where they are used + unsigned basis_j = m_mpq_lar_core_solver.m_r_solver.m_basis[i]; + for (unsigned k = 0; k < m_terms.size(); k++) { + if (term_is_used_as_row(k)) + continue; + if (!m_terms[k]->contains(basis_j)) + continue; + m_terms[k]->subst(basis_j, m_mpq_lar_core_solver.m_r_solver.m_pivot_row); + } + } + + void calculate_implied_bounds_for_row(unsigned i, bound_propagator & bp) { + if(use_tableau()) { + analyze_new_bounds_on_row_tableau(i, bp); + } else { + m_mpq_lar_core_solver.calculate_pivot_row(i); + substitute_basis_var_in_terms_for_row(i); + analyze_new_bounds_on_row(i, bp); + } + } + + /* + void process_new_implied_evidence_for_low_bound( + implied_bound_explanation& implied_evidence, // not pushed yet + vector & bound_evidences, + std::unordered_map & improved_low_bounds) { + + unsigned existing_index; + if (try_get_val(improved_low_bounds, implied_evidence.m_j, existing_index)) { + // we are improving the existent bound + bound_evidences[existing_index] = fill_bound_evidence(implied_evidence); + } else { + improved_low_bounds[implied_evidence.m_j] = bound_evidences.size(); + bound_evidences.push_back(fill_bound_evidence(implied_evidence)); + } + } + + void fill_bound_evidence_on_term(implied_bound & ie, implied_bound& be) { + lean_assert(false); + } + void fill_implied_bound_on_row(implied_bound & ie, implied_bound& be) { + iterator_on_row it(A_r().m_rows[ie.m_row_or_term_index]); + mpq a; unsigned j; + bool toggle = ie.m_coeff_before_j_is_pos; + if (!ie.m_is_low_bound) + toggle = !toggle; + while (it.next(a, j)) { + if (j == ie.m_j) continue; + const ul_pair & ul = m_vars_to_ul_pairs[j]; + + if (is_neg(a)) { // so the monoid has a positive coeff on the right side + constraint_index witness = toggle ? ul.m_low_bound_witness : ul.m_upper_bound_witness; + lean_assert(is_valid(witness)); + be.m_explanation.emplace_back(a, witness); + } + } + } + */ + /* + implied_bound fill_implied_bound_for_low_bound(implied_bound& ie) { + implied_bound be(ie.m_j, ie.m_bound.y.is_zero() ? GE : GT, ie.m_bound.x); + if (is_term(ie.m_row_or_term_index)) { + fill_implied_bound_for_low_bound_on_term(ie, be); + } + else { + fill_implied_bound_for_low_bound_on_row(ie, be); + } + return be; + } + + implied_bound fill_implied_bound_for_upper_bound(implied_bound& implied_evidence) { + lean_assert(false); + + be.m_j = implied_evidence.m_j; + be.m_bound = implied_evidence.m_bound.x; + be.m_kind = implied_evidence.m_bound.y.is_zero() ? LE : LT; + for (auto t : implied_evidence.m_vector_of_bound_signatures) { + const ul_pair & ul = m_vars_to_ul_pairs[t.m_column_index]; + constraint_index witness = t.m_low_bound ? ul.m_low_bound_witness : ul.m_upper_bound_witness; + lean_assert(is_valid(witness)); + be.m_explanation.emplace_back(t.m_coeff, witness); + } + + } + */ + /* + void process_new_implied_evidence_for_upper_bound( + implied_bound& implied_evidence, + vector & implied_bounds, + std::unordered_map & improved_upper_bounds) { + unsigned existing_index; + if (try_get_val(improved_upper_bounds, implied_evidence.m_j, existing_index)) { + implied_bound & be = implied_bounds[existing_index]; + be.m_explanation.clear(); + // we are improving the existent bound improve the existing bound + be = fill_implied_bound_for_upper_bound(implied_evidence); + } else { + improved_upper_bounds[implied_evidence.m_j] = implied_bounds.size(); + implied_bounds.push_back(fill_implied_bound_for_upper_bound(implied_evidence)); + } + } + */ + // implied_bound * get_existing_ + + linear_combination_iterator * create_new_iter_from_term(unsigned term_index) const { + lean_assert(false); // not implemented + return nullptr; + // new linear_combination_iterator_on_vector(m_terms[adjust_term_index(term_index)]->coeffs_as_vector()); + } + + unsigned adjust_column_index_to_term_index(unsigned j) const { + unsigned ext_var_or_term = m_columns_to_ext_vars_or_term_indices[j]; + return ext_var_or_term < m_terms_start_index ? j : ext_var_or_term; + } + + void propagate_bounds_on_a_term(const lar_term& t, bound_propagator & bp, unsigned term_offset) { + lean_assert(false); // not implemented + } + + + void explain_implied_bound(implied_bound & ib, bound_propagator & bp) { + unsigned i = ib.m_row_or_term_index; + int bound_sign = ib.m_is_low_bound? 1: -1; + int j_sign = (ib.m_coeff_before_j_is_pos ? 1 :-1) * bound_sign; + unsigned m_j = ib.m_j; + if (is_term(m_j)) { + m_j = m_ext_vars_to_columns[m_j]; + } + for (auto const& r : A_r().m_rows[i]) { + unsigned j = r.m_j; + mpq const& a = r.get_val(); + if (j == m_j) continue; + if (is_term(j)) { + j = m_ext_vars_to_columns[j]; + } + int a_sign = is_pos(a)? 1: -1; + int sign = j_sign * a_sign; + const ul_pair & ul = m_vars_to_ul_pairs[j]; + auto witness = sign > 0? ul.upper_bound_witness(): ul.low_bound_witness(); + lean_assert(is_valid(witness)); + bp.consume(a, witness); + } + // lean_assert(implied_bound_is_correctly_explained(ib, explanation)); + } + + + bool term_is_used_as_row(unsigned term) const { + lean_assert(is_term(term)); + return contains(m_ext_vars_to_columns, term); + } + + void propagate_bounds_on_terms(bound_propagator & bp) { + for (unsigned i = 0; i < m_terms.size(); i++) { + if (term_is_used_as_row(i + m_terms_start_index)) + continue; // this term is used a left side of a constraint, + // it was processed as a touched row if needed + propagate_bounds_on_a_term(*m_terms[i], bp, i); + } + } + + + // goes over touched rows and tries to induce bounds + void propagate_bounds_for_touched_rows(bound_propagator & bp) { + if (!use_tableau()) + return; // ! todo : enable bound propagaion here. The current bug is that after the pop + // the changed terms become incorrect! + + for (unsigned i : m_rows_with_changed_bounds.m_index) { + calculate_implied_bounds_for_row(i, bp); + } + m_rows_with_changed_bounds.clear(); + if (!use_tableau()) { + propagate_bounds_on_terms(bp); + } + } + + lp_status get_status() const { return m_status;} + + void set_status(lp_status s) {m_status = s;} + + lp_status find_feasible_solution() { + m_mpq_lar_core_solver.m_r_solver.m_look_for_feasible_solution_only = true; + return solve(); + } + + lp_status solve() { + if (m_status == INFEASIBLE) { + return m_status; + } + solve_with_core_solver(); + if (m_status != INFEASIBLE) { + if (m_settings.bound_propagation()) + detect_rows_with_changed_bounds(); + } + + m_columns_with_changed_bound.clear(); + return m_status; + } + + void fill_explanation_from_infeasible_column(vector> & evidence) const{ + + // this is the case when the lower bound is in conflict with the upper one + const ul_pair & ul = m_vars_to_ul_pairs[m_infeasible_column_index]; + evidence.push_back(std::make_pair(numeric_traits::one(), ul.upper_bound_witness())); + evidence.push_back(std::make_pair(-numeric_traits::one(), ul.low_bound_witness())); + } + + + unsigned get_total_iterations() const { return m_mpq_lar_core_solver.m_r_solver.total_iterations(); } + // see http://research.microsoft.com/projects/z3/smt07.pdf + // This method searches for a feasible solution with as many different values of variables, reverenced in vars, as it can find + // Attention, after a call to this method the non-basic variables don't necesserarly stick to their bounds anymore + vector get_list_of_all_var_indices() const { + vector ret; + for (unsigned j = 0; j < m_mpq_lar_core_solver.m_r_heading.size(); j++) + ret.push_back(j); + return ret; + } + void push() { + lean_assert(sizes_are_correct()); + m_status.push(); + m_vars_to_ul_pairs.push(); + m_infeasible_column_index.push(); + m_mpq_lar_core_solver.push(); + m_term_count = m_terms.size(); + m_term_count.push(); + m_constraint_count = m_constraints.size(); + m_constraint_count.push(); + lean_assert(sizes_are_correct()); + } + + static void clean_large_elements_after_pop(unsigned n, int_set& set) { + vector to_remove; + for (unsigned j: set.m_index) + if (j >= n) + to_remove.push_back(j); + for (unsigned j : to_remove) + set.erase(j); + } + + static void shrink_inf_set_after_pop(unsigned n, int_set & set) { + clean_large_elements_after_pop(n, set); + set.resize(n); + } + + + void pop(unsigned k) { + lean_assert(sizes_are_correct()); + int n_was = static_cast(m_ext_vars_to_columns.size()); + m_status.pop(k); + m_infeasible_column_index.pop(k); + unsigned n = m_vars_to_ul_pairs.peek_size(k); + for (unsigned j = n_was; j-- > n;) + m_ext_vars_to_columns.erase(m_columns_to_ext_vars_or_term_indices[j]); + m_columns_to_ext_vars_or_term_indices.resize(n); + if (m_settings.use_tableau()) { + pop_tableau(); + } + m_vars_to_ul_pairs.pop(k); + + m_mpq_lar_core_solver.pop(k); + clean_large_elements_after_pop(n, m_columns_with_changed_bound); + unsigned m = A_r().row_count(); + clean_large_elements_after_pop(m, m_rows_with_changed_bounds); + clean_inf_set_of_r_solver_after_pop(); + lean_assert(!use_tableau() || m_mpq_lar_core_solver.m_r_solver.reduced_costs_are_correct_tableau()); + + + lean_assert(ax_is_correct()); + lean_assert(m_mpq_lar_core_solver.m_r_solver.inf_set_is_correct()); + m_constraint_count.pop(k); + for (unsigned i = m_constraint_count; i < m_constraints.size(); i++) + delete m_constraints[i]; + + m_constraints.resize(m_constraint_count); + m_term_count.pop(k); + for (unsigned i = m_term_count; i < m_terms.size(); i++) { + delete m_terms[i]; + delete m_orig_terms[i]; + } + m_terms.resize(m_term_count); + m_orig_terms.resize(m_term_count); + lean_assert(sizes_are_correct()); + lean_assert((!m_settings.use_tableau()) || m_mpq_lar_core_solver.m_r_solver.reduced_costs_are_correct_tableau()); + } + + vector get_all_constraint_indices() const { + vector ret; + constraint_index i = 0; + while ( i < m_constraints.size()) + ret.push_back(i++); + return ret; + } + + bool maximize_term_on_tableau(const vector> & term, + impq &term_max) { + m_mpq_lar_core_solver.solve(); + if (m_mpq_lar_core_solver.m_r_solver.get_status() == UNBOUNDED) + return false; + + term_max = 0; + for (auto & p : term) + term_max += p.first * m_mpq_lar_core_solver.m_r_x[p.second]; + + return true; + } + + bool costs_are_zeros_for_r_solver() const { + for (unsigned j = 0; j < m_mpq_lar_core_solver.m_r_solver.m_costs.size(); j++) { + lean_assert(is_zero(m_mpq_lar_core_solver.m_r_solver.m_costs[j])); + } + return true; + } + bool reduced_costs_are_zeroes_for_r_solver() const { + for (unsigned j = 0; j < m_mpq_lar_core_solver.m_r_solver.m_d.size(); j++) { + lean_assert(is_zero(m_mpq_lar_core_solver.m_r_solver.m_d[j])); + } + return true; + } + + void set_costs_to_zero(const vector> & term) { + auto & rslv = m_mpq_lar_core_solver.m_r_solver; + auto & jset = m_mpq_lar_core_solver.m_r_solver.m_inf_set; // hijack this set that should be empty right now + lean_assert(jset.m_index.size()==0); + + for (auto & p : term) { + unsigned j = p.second; + rslv.m_costs[j] = zero_of_type(); + int i = rslv.m_basis_heading[j]; + if (i < 0) + jset.insert(j); + else { + for (auto & rc : A_r().m_rows[i]) + jset.insert(rc.m_j); + } + } + + for (unsigned j : jset.m_index) + rslv.m_d[j] = zero_of_type(); + + jset.clear(); + + lean_assert(reduced_costs_are_zeroes_for_r_solver()); + lean_assert(costs_are_zeros_for_r_solver()); + } + + void prepare_costs_for_r_solver(const vector> & term) { + + auto & rslv = m_mpq_lar_core_solver.m_r_solver; + rslv.m_using_infeas_costs = false; + lean_assert(costs_are_zeros_for_r_solver()); + lean_assert(reduced_costs_are_zeroes_for_r_solver()); + rslv.m_costs.resize(A_r().column_count(), zero_of_type()); + for (auto & p : term) { + unsigned j = p.second; + rslv.m_costs[j] = p.first; + if (rslv.m_basis_heading[j] < 0) + rslv.m_d[j] += p.first; + else + rslv.update_reduced_cost_for_basic_column_cost_change(- p.first, j); + } + lean_assert(rslv.reduced_costs_are_correct_tableau()); + } + + bool maximize_term_on_corrected_r_solver(const vector> & term, + impq &term_max) { + settings().backup_costs = false; + switch (settings().m_simplex_strategy) { + case simplex_strategy_enum::tableau_rows: + prepare_costs_for_r_solver(term); + settings().m_simplex_strategy = simplex_strategy_enum::tableau_costs; + { + bool ret = maximize_term_on_tableau(term, term_max); + settings().m_simplex_strategy = simplex_strategy_enum::tableau_rows; + set_costs_to_zero(term); + m_mpq_lar_core_solver.m_r_solver.set_status(OPTIMAL); + return ret; + } + case simplex_strategy_enum::tableau_costs: + prepare_costs_for_r_solver(term); + { + bool ret = maximize_term_on_tableau(term, term_max); + set_costs_to_zero(term); + m_mpq_lar_core_solver.m_r_solver.set_status(OPTIMAL); + return ret; + } + + case simplex_strategy_enum::no_tableau: + lean_assert(false); // not implemented + return false; + default: + lean_unreachable(); // wrong mode + } + return false; + } + // starting from a given feasible state look for the maximum of the term + // return true if found and false if unbounded + bool maximize_term(const vector> & term, + impq &term_max) { + lean_assert(m_mpq_lar_core_solver.m_r_solver.current_x_is_feasible()); + m_mpq_lar_core_solver.m_r_solver.m_look_for_feasible_solution_only = false; + return maximize_term_on_corrected_r_solver(term, term_max); + } + + + + var_index add_term(const vector> & coeffs, + const mpq &m_v) { + + lean_assert(A_r().column_count() == m_mpq_lar_core_solver.m_r_solver.m_costs.size()); + m_terms.push_back(new lar_term(coeffs, m_v)); + m_orig_terms.push_back(new lar_term(coeffs, m_v)); + unsigned adjusted_term_index = m_terms.size() - 1; + if (use_tableau() && !coeffs.empty()) { + register_new_ext_var_index(m_terms_start_index + adjusted_term_index); + add_row_for_term(m_orig_terms.back()); + if (m_settings.bound_propagation()) + m_rows_with_changed_bounds.insert(A_r().row_count() - 1); + } + lean_assert(m_ext_vars_to_columns.size() == A_r().column_count()); + lean_assert(A_r().column_count() == m_mpq_lar_core_solver.m_r_solver.m_costs.size()); + return m_terms_start_index + adjusted_term_index; + } + + const lar_term & get_term(unsigned j) const { + lean_assert(j >= m_terms_start_index); + return *m_terms[j - m_terms_start_index]; + } + + void pop_core_solver_params() { + pop_core_solver_params(1); + } + + void pop_core_solver_params(unsigned k) { + A_r().pop(k); + A_d().pop(k); + } + + void add_new_var_to_core_fields_for_mpq(bool register_in_basis) { + unsigned j = A_r().column_count(); + A_r().add_column(); + lean_assert(m_mpq_lar_core_solver.m_r_x.size() == j); + // lean_assert(m_mpq_lar_core_solver.m_r_low_bounds.size() == j && m_mpq_lar_core_solver.m_r_upper_bounds.size() == j); // restore later + m_mpq_lar_core_solver.m_r_x.resize(j + 1); + m_mpq_lar_core_solver.m_r_low_bounds.increase_size_by_one(); + m_mpq_lar_core_solver.m_r_upper_bounds.increase_size_by_one(); + m_mpq_lar_core_solver.m_r_solver.m_inf_set.increase_size_by_one(); + m_mpq_lar_core_solver.m_r_solver.m_costs.resize(j + 1); + m_mpq_lar_core_solver.m_r_solver.m_d.resize(j + 1); + lean_assert(m_mpq_lar_core_solver.m_r_heading.size() == j); // as A().column_count() on the entry to the method + if (register_in_basis) { + A_r().add_row(); + m_mpq_lar_core_solver.m_r_heading.push_back(m_mpq_lar_core_solver.m_r_basis.size()); + m_mpq_lar_core_solver.m_r_basis.push_back(j); + if (m_settings.bound_propagation()) + m_rows_with_changed_bounds.insert(A_r().row_count() - 1); + } else { + m_mpq_lar_core_solver.m_r_heading.push_back(- static_cast(m_mpq_lar_core_solver.m_r_nbasis.size()) - 1); + m_mpq_lar_core_solver.m_r_nbasis.push_back(j); + } + } + + void add_new_var_to_core_fields_for_doubles(bool register_in_basis) { + unsigned j = A_d().column_count(); + A_d().add_column(); + lean_assert(m_mpq_lar_core_solver.m_d_x.size() == j); + // lean_assert(m_mpq_lar_core_solver.m_d_low_bounds.size() == j && m_mpq_lar_core_solver.m_d_upper_bounds.size() == j); // restore later + m_mpq_lar_core_solver.m_d_x.resize(j + 1 ); + m_mpq_lar_core_solver.m_d_low_bounds.resize(j + 1); + m_mpq_lar_core_solver.m_d_upper_bounds.resize(j + 1); + lean_assert(m_mpq_lar_core_solver.m_d_heading.size() == j); // as A().column_count() on the entry to the method + if (register_in_basis) { + A_d().add_row(); + m_mpq_lar_core_solver.m_d_heading.push_back(m_mpq_lar_core_solver.m_d_basis.size()); + m_mpq_lar_core_solver.m_d_basis.push_back(j); + }else { + m_mpq_lar_core_solver.m_d_heading.push_back(- static_cast(m_mpq_lar_core_solver.m_d_nbasis.size()) - 1); + m_mpq_lar_core_solver.m_d_nbasis.push_back(j); + } + } + + void add_basic_var_to_core_fields() { + bool need_to_presolve_with_doubles = m_mpq_lar_core_solver.need_to_presolve_with_double_solver(); + lean_assert(!need_to_presolve_with_doubles || A_r().column_count() == A_d().column_count()); + m_mpq_lar_core_solver.m_column_types.push_back(column_type::free_column); + m_columns_with_changed_bound.increase_size_by_one(); + m_rows_with_changed_bounds.increase_size_by_one(); + add_new_var_to_core_fields_for_mpq(true); + if (need_to_presolve_with_doubles) + add_new_var_to_core_fields_for_doubles(true); + } + + void add_non_basic_var_to_core_fields() { + lean_assert(!m_mpq_lar_core_solver.need_to_presolve_with_double_solver() || A_r().column_count() == A_d().column_count()); + m_mpq_lar_core_solver.m_column_types.push_back(column_type::free_column); + m_columns_with_changed_bound.increase_size_by_one(); + add_new_var_to_core_fields_for_mpq(false); + if (m_mpq_lar_core_solver.need_to_presolve_with_double_solver()) + add_new_var_to_core_fields_for_doubles(false); + } + + void register_new_ext_var_index(unsigned s) { + lean_assert(!contains(m_ext_vars_to_columns, s)); + unsigned j = static_cast(m_ext_vars_to_columns.size()); + m_ext_vars_to_columns[s] = j; + lean_assert(m_columns_to_ext_vars_or_term_indices.size() == j); + m_columns_to_ext_vars_or_term_indices.push_back(s); + } + + + + void set_upper_bound_witness(var_index j, constraint_index ci) { + ul_pair ul = m_vars_to_ul_pairs[j]; + ul.upper_bound_witness() = ci; + m_vars_to_ul_pairs[j] = ul; + } + + void set_low_bound_witness(var_index j, constraint_index ci) { + ul_pair ul = m_vars_to_ul_pairs[j]; + ul.low_bound_witness() = ci; + m_vars_to_ul_pairs[j] = ul; + } + + void update_free_column_type_and_bound(var_index j, lconstraint_kind kind, const mpq & right_side, constraint_index constr_ind) { + mpq y_of_bound(0); + switch (kind) { + case LT: + y_of_bound = -1; + case LE: + m_mpq_lar_core_solver.m_column_types[j] = column_type::upper_bound; + lean_assert(m_mpq_lar_core_solver.m_column_types()[j] == column_type::upper_bound); + lean_assert(m_mpq_lar_core_solver.m_r_upper_bounds.size() > j); + { + auto up = numeric_pair(right_side, y_of_bound); + m_mpq_lar_core_solver.m_r_upper_bounds[j] = up; + } + set_upper_bound_witness(j, constr_ind); + break; + case GT: + y_of_bound = 1; + case GE: + m_mpq_lar_core_solver.m_column_types[j] = column_type::low_bound; + lean_assert(m_mpq_lar_core_solver.m_r_upper_bounds.size() > j); + { + auto low = numeric_pair(right_side, y_of_bound); + m_mpq_lar_core_solver.m_r_low_bounds[j] = low; + } + set_low_bound_witness(j, constr_ind); + break; + case EQ: + m_mpq_lar_core_solver.m_column_types[j] = column_type::fixed; + m_mpq_lar_core_solver.m_r_low_bounds[j] = m_mpq_lar_core_solver.m_r_upper_bounds[j] = numeric_pair(right_side, zero_of_type()); + set_upper_bound_witness(j, constr_ind); + set_low_bound_witness(j, constr_ind); + break; + + default: + lean_unreachable(); + + } + m_columns_with_changed_bound.insert(j); + } + + void update_upper_bound_column_type_and_bound(var_index j, lconstraint_kind kind, const mpq & right_side, constraint_index ci) { + lean_assert(m_mpq_lar_core_solver.m_column_types()[j] == column_type::upper_bound); + mpq y_of_bound(0); + switch (kind) { + case LT: + y_of_bound = -1; + case LE: + { + auto up = numeric_pair(right_side, y_of_bound); + if (up < m_mpq_lar_core_solver.m_r_upper_bounds()[j]) { + m_mpq_lar_core_solver.m_r_upper_bounds[j] = up; + set_upper_bound_witness(j, ci); + m_columns_with_changed_bound.insert(j); + } + } + break; + case GT: + y_of_bound = 1; + case GE: + m_mpq_lar_core_solver.m_column_types[j] = column_type::boxed; + { + auto low = numeric_pair(right_side, y_of_bound); + m_mpq_lar_core_solver.m_r_low_bounds[j] = low; + set_low_bound_witness(j, ci); + m_columns_with_changed_bound.insert(j); + if (low > m_mpq_lar_core_solver.m_r_upper_bounds[j]) { + m_status = INFEASIBLE; + m_infeasible_column_index = j; + } else { + m_mpq_lar_core_solver.m_column_types[j] = m_mpq_lar_core_solver.m_r_low_bounds()[j] < m_mpq_lar_core_solver.m_r_upper_bounds()[j]? column_type::boxed : column_type::fixed; + } + } + break; + case EQ: + { + auto v = numeric_pair(right_side, zero_of_type()); + if (v > m_mpq_lar_core_solver.m_r_upper_bounds[j]) { + m_status = INFEASIBLE; + set_low_bound_witness(j, ci); + m_infeasible_column_index = j; + } else { + m_mpq_lar_core_solver.m_r_low_bounds[j] = m_mpq_lar_core_solver.m_r_upper_bounds[j] = v; + m_columns_with_changed_bound.insert(j); + set_low_bound_witness(j, ci); + set_upper_bound_witness(j, ci); + m_mpq_lar_core_solver.m_column_types[j] = column_type::fixed; + } + break; + } + break; + + default: + lean_unreachable(); + + } + } + + void update_boxed_column_type_and_bound(var_index j, lconstraint_kind kind, const mpq & right_side, constraint_index ci) { + lean_assert(m_status == INFEASIBLE || (m_mpq_lar_core_solver.m_column_types()[j] == column_type::boxed && m_mpq_lar_core_solver.m_r_low_bounds()[j] < m_mpq_lar_core_solver.m_r_upper_bounds()[j])); + mpq y_of_bound(0); + switch (kind) { + case LT: + y_of_bound = -1; + case LE: + { + auto up = numeric_pair(right_side, y_of_bound); + if (up < m_mpq_lar_core_solver.m_r_upper_bounds[j]) { + m_mpq_lar_core_solver.m_r_upper_bounds[j] = up; + set_upper_bound_witness(j, ci); + m_columns_with_changed_bound.insert(j); + } + + if (up < m_mpq_lar_core_solver.m_r_low_bounds[j]) { + m_status = INFEASIBLE; + lean_assert(false); + m_infeasible_column_index = j; + } else { + if (m_mpq_lar_core_solver.m_r_low_bounds()[j] == m_mpq_lar_core_solver.m_r_upper_bounds()[j]) + m_mpq_lar_core_solver.m_column_types[j] = column_type::fixed; + } + } + break; + case GT: + y_of_bound = 1; + case GE: + { + auto low = numeric_pair(right_side, y_of_bound); + if (low > m_mpq_lar_core_solver.m_r_low_bounds[j]) { + m_mpq_lar_core_solver.m_r_low_bounds[j] = low; + m_columns_with_changed_bound.insert(j); + set_low_bound_witness(j, ci); + } + if (low > m_mpq_lar_core_solver.m_r_upper_bounds[j]) { + m_status = INFEASIBLE; + m_infeasible_column_index = j; + } else if ( low == m_mpq_lar_core_solver.m_r_upper_bounds[j]) { + m_mpq_lar_core_solver.m_column_types[j] = column_type::fixed; + } + } + break; + case EQ: + { + auto v = numeric_pair(right_side, zero_of_type()); + if (v < m_mpq_lar_core_solver.m_r_low_bounds[j]) { + m_status = INFEASIBLE; + m_infeasible_column_index = j; + set_upper_bound_witness(j, ci); + } else if (v > m_mpq_lar_core_solver.m_r_upper_bounds[j]) { + m_status = INFEASIBLE; + m_infeasible_column_index = j; + set_low_bound_witness(j, ci); + } else { + m_mpq_lar_core_solver.m_r_low_bounds[j] = m_mpq_lar_core_solver.m_r_upper_bounds[j] = v; + set_low_bound_witness(j, ci); + set_upper_bound_witness(j, ci); + m_mpq_lar_core_solver.m_column_types[j] = column_type::fixed; + m_columns_with_changed_bound.insert(j); + } + + break; + } + + default: + lean_unreachable(); + + } + } + void update_low_bound_column_type_and_bound(var_index j, lconstraint_kind kind, const mpq & right_side, constraint_index ci) { + lean_assert(m_mpq_lar_core_solver.m_column_types()[j] == column_type::low_bound); + mpq y_of_bound(0); + switch (kind) { + case LT: + y_of_bound = -1; + case LE: + { + auto up = numeric_pair(right_side, y_of_bound); + m_mpq_lar_core_solver.m_r_upper_bounds[j] = up; + set_upper_bound_witness(j, ci); + m_columns_with_changed_bound.insert(j); + + if (up < m_mpq_lar_core_solver.m_r_low_bounds[j]) { + m_status = INFEASIBLE; + m_infeasible_column_index = j; + } else { + m_mpq_lar_core_solver.m_column_types[j] = m_mpq_lar_core_solver.m_r_low_bounds()[j] < m_mpq_lar_core_solver.m_r_upper_bounds()[j]? column_type::boxed : column_type::fixed; + } + } + break; + case GT: + y_of_bound = 1; + case GE: + { + auto low = numeric_pair(right_side, y_of_bound); + if (low > m_mpq_lar_core_solver.m_r_low_bounds[j]) { + m_mpq_lar_core_solver.m_r_low_bounds[j] = low; + m_columns_with_changed_bound.insert(j); + set_low_bound_witness(j, ci); + } + } + break; + case EQ: + { + auto v = numeric_pair(right_side, zero_of_type()); + if (v < m_mpq_lar_core_solver.m_r_low_bounds[j]) { + m_status = INFEASIBLE; + m_infeasible_column_index = j; + set_upper_bound_witness(j, ci); + } else { + m_mpq_lar_core_solver.m_r_low_bounds[j] = m_mpq_lar_core_solver.m_r_upper_bounds[j] = v; + set_low_bound_witness(j, ci); + set_upper_bound_witness(j, ci); + m_mpq_lar_core_solver.m_column_types[j] = column_type::fixed; + } + m_columns_with_changed_bound.insert(j); + break; + } + + default: + lean_unreachable(); + + } + } + + void update_fixed_column_type_and_bound(var_index j, lconstraint_kind kind, const mpq & right_side, constraint_index ci) { + lean_assert(m_status == INFEASIBLE || (m_mpq_lar_core_solver.m_column_types()[j] == column_type::fixed && m_mpq_lar_core_solver.m_r_low_bounds()[j] == m_mpq_lar_core_solver.m_r_upper_bounds()[j])); + lean_assert(m_status == INFEASIBLE || (m_mpq_lar_core_solver.m_r_low_bounds()[j].y.is_zero() && m_mpq_lar_core_solver.m_r_upper_bounds()[j].y.is_zero())); + auto v = numeric_pair(right_side, mpq(0)); + + mpq y_of_bound(0); + switch (kind) { + case LT: + if (v <= m_mpq_lar_core_solver.m_r_low_bounds[j]) { + m_status = INFEASIBLE; + m_infeasible_column_index = j; + set_upper_bound_witness(j, ci); + } + break; + case LE: + { + if (v < m_mpq_lar_core_solver.m_r_low_bounds[j]) { + m_status = INFEASIBLE; + m_infeasible_column_index = j; + set_upper_bound_witness(j, ci); + } + } + break; + case GT: + { + if (v >= m_mpq_lar_core_solver.m_r_upper_bounds[j]) { + m_status = INFEASIBLE; + m_infeasible_column_index =j; + set_low_bound_witness(j, ci); + } + } + break; + case GE: + { + if (v > m_mpq_lar_core_solver.m_r_upper_bounds[j]) { + m_status = INFEASIBLE; + m_infeasible_column_index = j; + set_low_bound_witness(j, ci); + } + } + break; + case EQ: + { + if (v < m_mpq_lar_core_solver.m_r_low_bounds[j]) { + m_status = INFEASIBLE; + m_infeasible_column_index = j; + set_upper_bound_witness(j, ci); + } else if (v > m_mpq_lar_core_solver.m_r_upper_bounds[j]) { + m_status = INFEASIBLE; + m_infeasible_column_index = j; + set_low_bound_witness(j, ci); + } + break; + } + + default: + lean_unreachable(); + + } + } + + void update_column_type_and_bound(var_index j, lconstraint_kind kind, const mpq & right_side, constraint_index constr_index) { + switch(m_mpq_lar_core_solver.m_column_types[j]) { + case column_type::free_column: + update_free_column_type_and_bound(j, kind, right_side, constr_index); + break; + case column_type::boxed: + update_boxed_column_type_and_bound(j, kind, right_side, constr_index); + break; + case column_type::low_bound: + update_low_bound_column_type_and_bound(j, kind, right_side, constr_index); + break; + case column_type::upper_bound: + update_upper_bound_column_type_and_bound(j, kind, right_side, constr_index); + break; + case column_type::fixed: + update_fixed_column_type_and_bound(j, kind, right_side, constr_index); + break; + default: + lean_assert(false); // cannot be here + } + } + + + void substitute_terms(const mpq & mult, + const vector>& left_side_with_terms, + vector> &left_side, mpq & right_side) const { + for (auto & t : left_side_with_terms) { + if (t.second < m_terms_start_index) { + lean_assert(t.second < A_r().column_count()); + left_side.push_back(std::pair(mult * t.first, t.second)); + } else { + const lar_term & term = * m_terms[adjust_term_index(t.second)]; + substitute_terms(mult * t.first, left_side_with_terms, left_side, right_side); + right_side -= mult * term.m_v; + } + } + } + + + void detect_rows_of_bound_change_column_for_nbasic_column(unsigned j) { + if (A_r().row_count() != m_column_buffer.data_size()) + m_column_buffer.resize(A_r().row_count()); + else + m_column_buffer.clear(); + lean_assert(m_column_buffer.size() == 0 && m_column_buffer.is_OK()); + + m_mpq_lar_core_solver.m_r_solver.solve_Bd(j, m_column_buffer); + for (unsigned i : m_column_buffer.m_index) + m_rows_with_changed_bounds.insert(i); + } + + + + void detect_rows_of_bound_change_column_for_nbasic_column_tableau(unsigned j) { + for (auto & rc : m_mpq_lar_core_solver.m_r_A.m_columns[j]) + m_rows_with_changed_bounds.insert(rc.m_i); + } + + bool use_tableau() const { return m_settings.use_tableau(); } + + bool use_tableau_costs() const { return m_settings.simplex_strategy() == simplex_strategy_enum::tableau_costs; } + + + void detect_rows_of_column_with_bound_change(unsigned j) { + if (m_mpq_lar_core_solver.m_r_heading[j] >= 0) { // it is a basic column + // just mark the row at touched and exit + m_rows_with_changed_bounds.insert(m_mpq_lar_core_solver.m_r_heading[j]); + return; + } + + if (use_tableau()) + detect_rows_of_bound_change_column_for_nbasic_column_tableau(j); + else + detect_rows_of_bound_change_column_for_nbasic_column(j); + } + + void adjust_x_of_column(unsigned j) { + lean_assert(false); + } + + bool row_is_correct(unsigned i) const { + numeric_pair r = zero_of_type>(); + for (const auto & c : A_r().m_rows[i]) + r += c.m_value * m_mpq_lar_core_solver.m_r_x[c.m_j]; + return is_zero(r); + } + + bool ax_is_correct() const { + for (unsigned i = 0; i < A_r().row_count(); i++) { + if (!row_is_correct(i)) + return false; + } + return true; + } + + bool tableau_with_costs() const { + return m_settings.simplex_strategy() == simplex_strategy_enum::tableau_costs; + } + + bool costs_are_used() const { + return m_settings.simplex_strategy() != simplex_strategy_enum::tableau_rows; + } + + void change_basic_x_by_delta_on_column(unsigned j, const numeric_pair & delta) { + if (use_tableau()) { + for (const auto & c : A_r().m_columns[j]) { + unsigned bj = m_mpq_lar_core_solver.m_r_basis[c.m_i]; + m_mpq_lar_core_solver.m_r_x[bj] -= A_r().get_val(c) * delta; + if (tableau_with_costs()) { + m_basic_columns_with_changed_cost.insert(bj); + } + m_mpq_lar_core_solver.m_r_solver.update_column_in_inf_set(bj); + } + } else { + m_column_buffer.clear(); + m_column_buffer.resize(A_r().row_count()); + m_mpq_lar_core_solver.m_r_solver.solve_Bd(j, m_column_buffer); + for (unsigned i : m_column_buffer.m_index) { + unsigned bj = m_mpq_lar_core_solver.m_r_basis[i]; + m_mpq_lar_core_solver.m_r_x[bj] -= m_column_buffer[i] * delta; + m_mpq_lar_core_solver.m_r_solver.update_column_in_inf_set(bj); + } + } + } + + void update_x_and_inf_costs_for_column_with_changed_bounds(unsigned j) { + if (m_mpq_lar_core_solver.m_r_heading[j] >= 0) { + if (costs_are_used()) { + bool was_infeas = m_mpq_lar_core_solver.m_r_solver.m_inf_set.contains(j); + m_mpq_lar_core_solver.m_r_solver.update_column_in_inf_set(j); + if (was_infeas != m_mpq_lar_core_solver.m_r_solver.m_inf_set.contains(j)) + m_basic_columns_with_changed_cost.insert(j); + } else { + m_mpq_lar_core_solver.m_r_solver.update_column_in_inf_set(j); + } + } else { + numeric_pair delta; + if (m_mpq_lar_core_solver.m_r_solver.make_column_feasible(j, delta)) + change_basic_x_by_delta_on_column(j, delta); + } + } + + + void detect_rows_with_changed_bounds_for_column(unsigned j) { + if (m_mpq_lar_core_solver.m_r_heading[j] >= 0) { + m_rows_with_changed_bounds.insert(m_mpq_lar_core_solver.m_r_heading[j]); + return; + } + + if (use_tableau()) + detect_rows_of_bound_change_column_for_nbasic_column_tableau(j); + else + detect_rows_of_bound_change_column_for_nbasic_column(j); + } + + void detect_rows_with_changed_bounds() { + for (auto j : m_columns_with_changed_bound.m_index) + detect_rows_with_changed_bounds_for_column(j); + } + + void update_x_and_inf_costs_for_columns_with_changed_bounds() { + for (auto j : m_columns_with_changed_bound.m_index) + update_x_and_inf_costs_for_column_with_changed_bounds(j); + } + + void update_x_and_inf_costs_for_columns_with_changed_bounds_tableau() { + lean_assert(ax_is_correct()); + for (auto j : m_columns_with_changed_bound.m_index) + update_x_and_inf_costs_for_column_with_changed_bounds(j); + + if (tableau_with_costs()) { + for (unsigned j : m_basic_columns_with_changed_cost.m_index) + m_mpq_lar_core_solver.m_r_solver.update_inf_cost_for_column_tableau(j); + lean_assert(m_mpq_lar_core_solver.m_r_solver.reduced_costs_are_correct_tableau()); + } + } + + + void solve_with_core_solver() { + if (!use_tableau()) + add_last_rows_to_lu(m_mpq_lar_core_solver.m_r_solver); + if (m_mpq_lar_core_solver.need_to_presolve_with_double_solver()) { + add_last_rows_to_lu(m_mpq_lar_core_solver.m_d_solver); + } + m_mpq_lar_core_solver.prefix_r(); + if (costs_are_used()) { + m_basic_columns_with_changed_cost.clear(); + m_basic_columns_with_changed_cost.resize(m_mpq_lar_core_solver.m_r_x.size()); + } + if (use_tableau()) + update_x_and_inf_costs_for_columns_with_changed_bounds_tableau(); + else + update_x_and_inf_costs_for_columns_with_changed_bounds(); + m_mpq_lar_core_solver.solve(); + set_status(m_mpq_lar_core_solver.m_r_solver.get_status()); + lean_assert(m_status != OPTIMAL || all_constraints_hold()); + } + + + numeric_pair get_basic_var_value_from_row_directly(unsigned i) { + numeric_pair r = zero_of_type>(); + + unsigned bj = m_mpq_lar_core_solver.m_r_solver.m_basis[i]; + for (const auto & c: A_r().m_rows[i]) { + if (c.m_j == bj) continue; + const auto & x = m_mpq_lar_core_solver.m_r_x[c.m_j]; + if (!is_zero(x)) + r -= c.m_value * x; + } + return r; + } + + + + numeric_pair get_basic_var_value_from_row(unsigned i) { + if (settings().use_tableau()) { + return get_basic_var_value_from_row_directly(i); + } + + numeric_pair r = zero_of_type>(); + m_mpq_lar_core_solver.calculate_pivot_row(i); + for (unsigned j : m_mpq_lar_core_solver.m_r_solver.m_pivot_row.m_index) { + lean_assert(m_mpq_lar_core_solver.m_r_solver.m_basis_heading[j] < 0); + r -= m_mpq_lar_core_solver.m_r_solver.m_pivot_row.m_data[j] * m_mpq_lar_core_solver.m_r_x[j]; + } + return r; + } + + template + void add_last_rows_to_lu(lp_primal_core_solver & s) { + auto & f = s.m_factorization; + if (f != nullptr) { + auto columns_to_replace = f->get_set_of_columns_to_replace_for_add_last_rows(s.m_basis_heading); + if (f->m_refactor_counter + columns_to_replace.size() >= 200 || f->has_dense_submatrix()) { + delete f; + f = nullptr; + } else { + f->add_last_rows_to_B(s.m_basis_heading, columns_to_replace); + } + } + if (f == nullptr) { + init_factorization(f, s.m_A, s.m_basis, m_settings); + if (f->get_status() != LU_status::OK) { + delete f; + f = nullptr; + } + } + + } + + bool x_is_correct() const { + if (m_mpq_lar_core_solver.m_r_x.size() != A_r().column_count()) { + // std::cout << "the size is off " << m_r_solver.m_x.size() << ", " << A().column_count() << std::endl; + return false; + } + for (unsigned i = 0; i < A_r().row_count(); i++) { + numeric_pair delta = A_r().dot_product_with_row(i, m_mpq_lar_core_solver.m_r_x); + if (!delta.is_zero()) { + // std::cout << "x is off ("; + // std::cout << "m_b[" << i << "] = " << m_b[i] << " "; + // std::cout << "left side = " << A().dot_product_with_row(i, m_r_solver.m_x) << ' '; + // std::cout << "delta = " << delta << ' '; + // std::cout << "iters = " << total_iterations() << ")" << std::endl; + // std::cout << "row " << i << " is off" << std::endl; + return false; + } + } + return true;; + + } + + bool var_is_registered(var_index vj) const { + if (vj >= m_terms_start_index) { + if (vj - m_terms_start_index >= m_terms.size()) + return false; + } else if ( vj >= A_r().column_count()) { + return false; + } + return true; + } + + unsigned constraint_stack_size() const { + return m_constraint_count.stack_size(); + } + + void fill_last_row_of_A_r(static_matrix> & A, const lar_term * ls) { + lean_assert(A.row_count() > 0); + lean_assert(A.column_count() > 0); + unsigned last_row = A.row_count() - 1; + lean_assert(A.m_rows[last_row].size() == 0); + for (auto & t : ls->m_coeffs) { + lean_assert(!is_zero(t.second)); + var_index j = t.first; + A.set(last_row, j, - t.second); + } + unsigned basis_j = A.column_count() - 1; + A.set(last_row, basis_j, mpq(1)); + } + // this fills the last row of A_d and sets the basis column: -1 in the last column of the row + void fill_last_row_of_A_d(static_matrix & A, const lar_term* ls) { + lean_assert(A.row_count() > 0); + lean_assert(A.column_count() > 0); + unsigned last_row = A.row_count() - 1; + lean_assert(A.m_rows[last_row].empty()); + + for (auto & t : ls->m_coeffs) { + lean_assert(!is_zero(t.second)); + var_index j = t.first; + A.set(last_row, j, - t.second.get_double()); + } + + unsigned basis_j = A.column_count() - 1; + A.set(last_row, basis_j, - 1 ); + } + + + template + void create_matrix_A(static_matrix & matr) { + lean_assert(false); // not implemented + /* + unsigned m = number_or_nontrivial_left_sides(); + unsigned n = m_vec_of_canonic_left_sides.size(); + if (matr.row_count() == m && matr.column_count() == n) + return; + matr.init_empty_matrix(m, n); + copy_from_mpq_matrix(matr); + */ + } + + template + void copy_from_mpq_matrix(static_matrix & matr) { + lean_assert(matr.row_count() == A_r().row_count()); + lean_assert(matr.column_count() == A_r().column_count()); + for (unsigned i = 0; i < matr.row_count(); i++) { + for (auto & it : A_r().m_rows[i]) { + matr.set(i, it.m_j, convert_struct::convert(it.get_val())); + } + } + } + + + bool try_to_set_fixed(column_info & ci) { + if (ci.upper_bound_is_set() && ci.low_bound_is_set() && ci.get_upper_bound() == ci.get_low_bound() && !ci.is_fixed()) { + ci.set_fixed_value(ci.get_upper_bound()); + return true; + } + return false; + } + + column_type get_column_type(const column_info & ci) { + auto ret = ci.get_column_type_no_flipping(); + if (ret == column_type::boxed) { // changing boxed to fixed because of the no span + if (ci.get_low_bound() == ci.get_upper_bound()) + ret = column_type::fixed; + } + return ret; + } + + std::string get_column_name(unsigned j) const { + if (j >= m_terms_start_index) + return std::string("_t") + T_to_string(j); + if (j >= m_columns_to_ext_vars_or_term_indices.size()) + return std::string("_s") + T_to_string(j); + + return std::string("v") + T_to_string(m_columns_to_ext_vars_or_term_indices[j]); + } + + bool all_constrained_variables_are_registered(const vector>& left_side) { + for (auto it : left_side) { + if (! var_is_registered(it.second)) + return false; + } + return true; + } + + constraint_index add_constraint(const vector>& left_side_with_terms, lconstraint_kind kind_par, const mpq& right_side_parm) { + /* + mpq rs = right_side_parm; + vector> left_side; + substitute_terms(one_of_type(), left_side_with_terms, left_side, rs); + lean_assert(left_side.size() > 0); + lean_assert(all_constrained_variables_are_registered(left_side)); + lar_constraint original_constr(left_side, kind_par, rs); + unsigned j; // j is the index of the basic variables corresponding to the left side + canonic_left_side ls = create_or_fetch_canonic_left_side(left_side, j); + mpq ratio = find_ratio_of_original_constraint_to_normalized(ls, original_constr); + auto kind = ratio.is_neg() ? flip_kind(kind_par) : kind_par; + rs/= ratio; + lar_normalized_constraint normalized_constraint(ls, ratio, kind, rs, original_constr); + m_constraints.push_back(normalized_constraint); + constraint_index constr_ind = m_constraints.size() - 1; + update_column_type_and_bound(j, kind, rs, constr_ind); + return constr_ind; + */ + lean_assert(false); // not implemented + return 0; + } + + bool all_constraints_hold() const { + if (m_settings.get_cancel_flag()) + return true; + std::unordered_map var_map; + get_model(var_map); + + for (unsigned i = 0; i < m_constraints.size(); i++) { + if (!constraint_holds(*m_constraints[i], var_map)) { + print_constraint(i, std::cout); + return false; + } + } + return true; + } + + bool constraint_holds(const lar_base_constraint & constr, std::unordered_map & var_map) const { + mpq left_side_val = get_left_side_val(constr, var_map); + switch (constr.m_kind) { + case LE: return left_side_val <= constr.m_right_side; + case LT: return left_side_val < constr.m_right_side; + case GE: return left_side_val >= constr.m_right_side; + case GT: return left_side_val > constr.m_right_side; + case EQ: return left_side_val == constr.m_right_side; + default: + lean_unreachable(); + } + return false; // it is unreachable + } + + + + + + + + bool the_relations_are_of_same_type(const vector> & evidence, lconstraint_kind & the_kind_of_sum) const { + unsigned n_of_G = 0, n_of_L = 0; + bool strict = false; + for (auto & it : evidence) { + mpq coeff = it.first; + constraint_index con_ind = it.second; + lconstraint_kind kind = coeff.is_pos() ? + m_constraints[con_ind]->m_kind : + flip_kind(m_constraints[con_ind]->m_kind); + if (kind == GT || kind == LT) + strict = true; + if (kind == GE || kind == GT) n_of_G++; + else if (kind == LE || kind == LT) n_of_L++; + } + the_kind_of_sum = n_of_G ? GE : (n_of_L ? LE : EQ); + if (strict) + the_kind_of_sum = static_cast((static_cast(the_kind_of_sum) / 2)); + + return n_of_G == 0 || n_of_L == 0; + } + + static void register_in_map(std::unordered_map & coeffs, const lar_base_constraint & cn, const mpq & a) { + for (auto & it : cn.get_left_side_coefficients()) { + unsigned j = it.second; + auto p = coeffs.find(j); + if (p == coeffs.end()) + coeffs[j] = it.first * a; + else { + p->second += it.first * a; + if (p->second.is_zero()) + coeffs.erase(p); + } + } + } + bool the_left_sides_sum_to_zero(const vector> & evidence) const { + std::unordered_map coeff_map; + for (auto & it : evidence) { + mpq coeff = it.first; + constraint_index con_ind = it.second; + lean_assert(con_ind < m_constraints.size()); + register_in_map(coeff_map, *m_constraints[con_ind], coeff); + } + + if (!coeff_map.empty()) { + std::cout << "left side = "; + vector> t; + for (auto & it : coeff_map) { + t.push_back(std::make_pair(it.second, it.first)); + } + print_linear_combination_of_column_indices(t, std::cout); + std::cout << std::endl; + return false; + } + + return true; + } + + bool the_right_sides_do_not_sum_to_zero(const vector> & evidence) { + mpq ret = numeric_traits::zero(); + for (auto & it : evidence) { + mpq coeff = it.first; + constraint_index con_ind = it.second; + lean_assert(con_ind < m_constraints.size()); + const lar_constraint & constr = *m_constraints[con_ind]; + ret += constr.m_right_side * coeff; + } + return !numeric_traits::is_zero(ret); + } + + bool explanation_is_correct(const vector>& explanation) const { +#ifdef LEAN_DEBUG + lconstraint_kind kind; + lean_assert(the_relations_are_of_same_type(explanation, kind)); + lean_assert(the_left_sides_sum_to_zero(explanation)); + mpq rs = sum_of_right_sides_of_explanation(explanation); + switch (kind) { + case LE: lean_assert(rs < zero_of_type()); + break; + case LT: lean_assert(rs <= zero_of_type()); + break; + case GE: lean_assert(rs > zero_of_type()); + break; + case GT: lean_assert(rs >= zero_of_type()); + break; + case EQ: lean_assert(rs != zero_of_type()); + break; + default: + lean_assert(false); + return false; + } +#endif + return true; + } + + bool inf_explanation_is_correct() const { +#ifdef LEAN_DEBUG + vector> explanation; + get_infeasibility_explanation(explanation); + return explanation_is_correct(explanation); +#endif + return true; + } + + mpq sum_of_right_sides_of_explanation(const vector> & explanation) const { + mpq ret = numeric_traits::zero(); + for (auto & it : explanation) { + mpq coeff = it.first; + constraint_index con_ind = it.second; + lean_assert(con_ind < m_constraints.size()); + ret += (m_constraints[con_ind]->m_right_side - m_constraints[con_ind]->get_free_coeff_of_left_side()) * coeff; + } + return ret; + } + + // template + // void prepare_core_solver_fields_with_signature(static_matrix & A, vector & x, + // vector & low_bound, + // vector & upper_bound, const lar_solution_signature & signature) { + // create_matrix_A_r(A); + // fill_bounds_for_core_solver(low_bound, upper_bound); + // if (m_status == INFEASIBLE) { + // lean_assert(false); // not implemented + // } + + // resize_and_init_x_with_signature(x, low_bound, upper_bound, signature); + // lean_assert(A.column_count() == x.size()); + // } + + // void find_solution_signature_with_doubles(lar_solution_signature & signature) { + // static_matrix A; + // vector x, low_bounds, upper_bounds; + // lean_assert(false); // not implemented + // // prepare_core_solver_fields(A, x, low_bounds, upper_bounds); + // vector column_scale_vector; + // vector right_side_vector(A.row_count(), 0); + + // scaler scaler(right_side_vector, + // A, + // m_settings.scaling_minimum, + // m_settings.scaling_maximum, + // column_scale_vector, + // m_settings); + // if (!scaler.scale()) { + // // the scale did not succeed, unscaling + // A.clear(); + // create_matrix_A_r(A); + // for (auto & s : column_scale_vector) + // s = 1.0; + // } + // vector costs(A.column_count()); + // auto core_solver = lp_primal_core_solver(A, + // right_side_vector, + // x, + // m_mpq_lar_core_solver.m_basis, + // m_mpq_lar_core_solver.m_nbasis, + // m_mpq_lar_core_solver.m_heading, + // costs, + // m_mpq_lar_core_solver.m_column_types(), + // low_bounds, + // upper_bounds, + // m_settings, + // *this); + // core_solver.find_feasible_solution(); + // extract_signature_from_lp_core_solver(core_solver, signature); + // } + + bool has_lower_bound(var_index var, constraint_index& ci, mpq& value, bool& is_strict) { + + if (var >= m_vars_to_ul_pairs.size()) { + // TBD: bounds on terms could also be used, caller may have to track these. + return false; + } + const ul_pair & ul = m_vars_to_ul_pairs[var]; + ci = ul.low_bound_witness(); + if (ci != static_cast(-1)) { + auto& p = m_mpq_lar_core_solver.m_r_low_bounds()[var]; + value = p.x; + is_strict = p.y.is_pos(); + return true; + } + else { + return false; + } + } + + bool has_upper_bound(var_index var, constraint_index& ci, mpq& value, bool& is_strict) { + + if (var >= m_vars_to_ul_pairs.size()) { + // TBD: bounds on terms could also be used, caller may have to track these. + return false; + } + const ul_pair & ul = m_vars_to_ul_pairs[var]; + ci = ul.upper_bound_witness(); + if (ci != static_cast(-1)) { + auto& p = m_mpq_lar_core_solver.m_r_upper_bounds()[var]; + value = p.x; + is_strict = p.y.is_neg(); + return true; + } + else { + return false; + } + } + + + void get_infeasibility_explanation(vector> & explanation) const { + explanation.clear(); + if (m_infeasible_column_index != -1) { + fill_explanation_from_infeasible_column(explanation); + return; + } + if (m_mpq_lar_core_solver.get_infeasible_sum_sign() == 0) { + return; + } + // the infeasibility sign + int inf_sign; + auto inf_row = m_mpq_lar_core_solver.get_infeasibility_info(inf_sign); + get_infeasibility_explanation_for_inf_sign(explanation, inf_row, inf_sign); + lean_assert(explanation_is_correct(explanation)); + } + + void get_infeasibility_explanation_for_inf_sign( + vector> & explanation, + const vector> & inf_row, + int inf_sign) const { + + for (auto & it : inf_row) { + mpq coeff = it.first; + unsigned j = it.second; + + int adj_sign = coeff.is_pos() ? inf_sign : -inf_sign; + const ul_pair & ul = m_vars_to_ul_pairs[j]; + + constraint_index bound_constr_i = adj_sign < 0 ? ul.upper_bound_witness() : ul.low_bound_witness(); + lean_assert(bound_constr_i < m_constraints.size()); + explanation.push_back(std::make_pair(coeff, bound_constr_i)); + } + } + + + + void get_model(std::unordered_map & variable_values) const { + lean_assert(m_status == OPTIMAL); + mpq delta = m_mpq_lar_core_solver.find_delta_for_strict_bounds(); + for (unsigned i = 0; i < m_mpq_lar_core_solver.m_r_x.size(); i++ ) { + const numeric_pair & rp = m_mpq_lar_core_solver.m_r_x[i]; + variable_values[i] = rp.x + delta * rp.y; + } + } + + + std::string get_variable_name(var_index vi) const { + return get_column_name(vi); + } + + // ********** print region start + void print_constraint(constraint_index ci, std::ostream & out) const { + if (ci >= m_constraints.size()) { + out << "constraint " << T_to_string(ci) << " is not found"; + out << std::endl; + return; + } + + print_constraint(m_constraints[ci], out); + } + + void print_constraints(std::ostream& out) const { + for (auto c : m_constraints) { + print_constraint(c, out); + } + } + + void print_terms(std::ostream& out) const { + for (auto it : m_terms) { + print_term(*it, out); + out << "\n"; + } + } + + void print_left_side_of_constraint(const lar_base_constraint * c, std::ostream & out) const { + print_linear_combination_of_column_indices(c->get_left_side_coefficients(), out); + mpq free_coeff = c->get_free_coeff_of_left_side(); + if (!is_zero(free_coeff)) + out << " + " << free_coeff; + + } + + void print_term(lar_term const& term, std::ostream & out) const { + if (!numeric_traits::is_zero(term.m_v)) { + out << term.m_v << " + "; + } + print_linear_combination_of_column_indices(term.coeffs_as_vector(), out); + } + + mpq get_left_side_val(const lar_base_constraint & cns, const std::unordered_map & var_map) const { + mpq ret = cns.get_free_coeff_of_left_side(); + for (auto & it : cns.get_left_side_coefficients()) { + var_index j = it.second; + auto vi = var_map.find(j); + lean_assert(vi != var_map.end()); + ret += it.first * vi->second; + } + return ret; + } + + void print_constraint(const lar_base_constraint * c, std::ostream & out) const { + print_left_side_of_constraint(c, out); + out << " " << lconstraint_kind_string(c->m_kind) << " " << c->m_right_side << std::endl; + } + + void fill_var_set_for_random_update(unsigned sz, var_index const * vars, vector& column_list) { + for (unsigned i = 0; i < sz; i++) { + var_index var = vars[i]; + if (var >= m_terms_start_index) { // handle the term + for (auto & it : m_terms[var - m_terms_start_index]->m_coeffs) { + column_list.push_back(it.first); + } + } else { + column_list.push_back(var); + } + } + } + + void random_update(unsigned sz, var_index const * vars) { + vector column_list; + fill_var_set_for_random_update(sz, vars, column_list); + random_updater ru(m_mpq_lar_core_solver, column_list); + ru.update(); + } + + + void try_pivot_fixed_vars_from_basis() { + m_mpq_lar_core_solver.m_r_solver.pivot_fixed_vars_from_basis(); + } + + void pop() { + pop(1); + } + + + bool column_represents_row_in_tableau(unsigned j) { + return m_vars_to_ul_pairs()[j].m_i != static_cast(-1); + } + + void make_sure_that_the_bottom_right_elem_not_zero_in_tableau(unsigned i, unsigned j) { + // i, j - is the indices of the bottom-right element of the tableau + lean_assert(A_r().row_count() == i + 1 && A_r().column_count() == j + 1); + auto & last_column = A_r().m_columns[j]; + int non_zero_column_cell_index = -1; + for (unsigned k = last_column.size(); k-- > 0;){ + auto & cc = last_column[k]; + if (cc.m_i == i) + return; + non_zero_column_cell_index = k; + } + + lean_assert(non_zero_column_cell_index != -1); + lean_assert(static_cast(non_zero_column_cell_index) != i); + m_mpq_lar_core_solver.m_r_solver.transpose_rows_tableau(last_column[non_zero_column_cell_index].m_i, i); + } + + void remove_last_row_and_column_from_tableau(unsigned j) { + lean_assert(A_r().column_count() == m_mpq_lar_core_solver.m_r_solver.m_costs.size()); + auto & slv = m_mpq_lar_core_solver.m_r_solver; + unsigned i = A_r().row_count() - 1; //last row index + make_sure_that_the_bottom_right_elem_not_zero_in_tableau(i, j); + if (slv.m_basis_heading[j] < 0) { + slv.pivot_column_tableau(j, i); + } + + auto & last_row = A_r().m_rows[i]; + mpq &cost_j = m_mpq_lar_core_solver.m_r_solver.m_costs[j]; + bool cost_is_nz = !is_zero(cost_j); + for (unsigned k = last_row.size(); k-- > 0;) { + auto &rc = last_row[k]; + if (cost_is_nz) { + m_mpq_lar_core_solver.m_r_solver.m_d[rc.m_j] += cost_j*rc.get_val(); + } + + A_r().remove_element(last_row, rc); + } + lean_assert(last_row.size() == 0); + lean_assert(A_r().m_columns[j].size() == 0); + A_r().m_rows.pop_back(); + A_r().m_columns.pop_back(); + slv.m_b.pop_back(); + } + + void remove_last_column_from_tableau(unsigned j) { + lean_assert(j == A_r().column_count() - 1); + // the last column has to be empty + lean_assert(A_r().m_columns[j].size() == 0); + A_r().m_columns.pop_back(); + } + + void remove_last_column_from_basis_tableau(unsigned j) { + auto& rslv = m_mpq_lar_core_solver.m_r_solver; + int i = rslv.m_basis_heading[j]; + if (i >= 0) { // j is a basic var + int last_pos = static_cast(rslv.m_basis.size()) - 1; + lean_assert(last_pos >= 0); + if (i != last_pos) { + unsigned j_at_last_pos = rslv.m_basis[last_pos]; + rslv.m_basis[i] = j_at_last_pos; + rslv.m_basis_heading[j_at_last_pos] = i; + } + rslv.m_basis.pop_back(); // remove j from the basis + } else { + int last_pos = static_cast(rslv.m_nbasis.size()) - 1; + lean_assert(last_pos >= 0); + i = - 1 - i; + if (i != last_pos) { + unsigned j_at_last_pos = rslv.m_nbasis[last_pos]; + rslv.m_nbasis[i] = j_at_last_pos; + rslv.m_basis_heading[j_at_last_pos] = - i - 1; + } + rslv.m_nbasis.pop_back(); // remove j from the basis + } + rslv.m_basis_heading.pop_back(); + lean_assert(rslv.m_basis.size() == A_r().row_count()); + lean_assert(rslv.basis_heading_is_correct()); + } + + void remove_column_from_tableau(unsigned j) { + auto& rslv = m_mpq_lar_core_solver.m_r_solver; + lean_assert(j == A_r().column_count() - 1); + lean_assert(A_r().column_count() == m_mpq_lar_core_solver.m_r_solver.m_costs.size()); + if (column_represents_row_in_tableau(j)) { + remove_last_row_and_column_from_tableau(j); + if (rslv.m_basis_heading[j] < 0) + rslv.change_basis_unconditionally(j, rslv.m_basis[A_r().row_count()]); // A_r().row_count() is the index of the last row in the basis still + } + else { + remove_last_column_from_tableau(j); + } + rslv.m_x.pop_back(); + rslv.m_d.pop_back(); + rslv.m_costs.pop_back(); + + remove_last_column_from_basis_tableau(j); + lean_assert(m_mpq_lar_core_solver.r_basis_is_OK()); + lean_assert(A_r().column_count() == m_mpq_lar_core_solver.m_r_solver.m_costs.size()); + } + + + void pop_tableau() { + lean_assert(m_mpq_lar_core_solver.m_r_solver.m_costs.size() == A_r().column_count()); + + lean_assert(m_mpq_lar_core_solver.m_r_solver.m_basis.size() == A_r().row_count()); + lean_assert(m_mpq_lar_core_solver.m_r_solver.basis_heading_is_correct()); + // We remove last variables starting from m_column_names.size() to m_vec_of_canonic_left_sides.size(). + // At this moment m_column_names is already popped + for (unsigned j = A_r().column_count(); j-- > m_columns_to_ext_vars_or_term_indices.size();) + remove_column_from_tableau(j); + lean_assert(m_mpq_lar_core_solver.m_r_solver.m_costs.size() == A_r().column_count()); + lean_assert(m_mpq_lar_core_solver.m_r_solver.m_basis.size() == A_r().row_count()); + lean_assert(m_mpq_lar_core_solver.m_r_solver.basis_heading_is_correct()); + } + + + + + void clean_inf_set_of_r_solver_after_pop() { + vector became_feas; + clean_large_elements_after_pop(A_r().column_count(), m_mpq_lar_core_solver.m_r_solver.m_inf_set); + std::unordered_set basic_columns_with_changed_cost; + auto inf_index_copy = m_mpq_lar_core_solver.m_r_solver.m_inf_set.m_index; + for (auto j: inf_index_copy) { + if (m_mpq_lar_core_solver.m_r_heading[j] >= 0) { + continue; + } + // some basic columns might become non-basic - these columns need to be made feasible + numeric_pair delta; + if (m_mpq_lar_core_solver.m_r_solver.make_column_feasible(j, delta)) + change_basic_x_by_delta_on_column(j, delta); + became_feas.push_back(j); + } + + for (unsigned j : became_feas) { + lean_assert(m_mpq_lar_core_solver.m_r_solver.m_basis_heading[j] < 0); + m_mpq_lar_core_solver.m_r_solver.m_d[j] -= m_mpq_lar_core_solver.m_r_solver.m_costs[j]; + m_mpq_lar_core_solver.m_r_solver.m_costs[j] = zero_of_type(); + m_mpq_lar_core_solver.m_r_solver.m_inf_set.erase(j); + } + became_feas.clear(); + for (unsigned j : m_mpq_lar_core_solver.m_r_solver.m_inf_set.m_index) { + lean_assert(m_mpq_lar_core_solver.m_r_heading[j] >= 0); + if (m_mpq_lar_core_solver.m_r_solver.column_is_feasible(j)) + became_feas.push_back(j); + } + for (unsigned j : became_feas) + m_mpq_lar_core_solver.m_r_solver.m_inf_set.erase(j); + + + if (use_tableau_costs()) { + for (unsigned j : became_feas) + m_mpq_lar_core_solver.m_r_solver.update_inf_cost_for_column_tableau(j); + for (unsigned j : basic_columns_with_changed_cost) + m_mpq_lar_core_solver.m_r_solver.update_inf_cost_for_column_tableau(j); + lean_assert(m_mpq_lar_core_solver.m_r_solver.reduced_costs_are_correct_tableau()); + } + } + + + void shrink_explanation_to_minimum(vector> & explanation) const { + // implementing quickXplain + quick_xplain::run(explanation, *this); + lean_assert(this->explanation_is_correct(explanation)); + } + + +}; +} diff --git a/src/util/lp/lar_term.h b/src/util/lp/lar_term.h new file mode 100644 index 000000000..0e715ad0b --- /dev/null +++ b/src/util/lp/lar_term.h @@ -0,0 +1,64 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#pragma once +#include "util/lp/indexed_vector.h" +namespace lean { +struct lar_term { + // the term evaluates to sum of m_coeffs + m_v + std::unordered_map m_coeffs; + mpq m_v; + lar_term() {} + void add_to_map(unsigned j, const mpq& c) { + auto it = m_coeffs.find(j); + if (it == m_coeffs.end()) { + m_coeffs.emplace(j, c); + } else { + it->second += c; + if (is_zero(it->second)) + m_coeffs.erase(it); + } + } + + unsigned size() const { return static_cast(m_coeffs.size()); } + + const std::unordered_map & coeffs() const { + return m_coeffs; + } + + lar_term(const vector>& coeffs, + const mpq & v) : m_v(v) { + for (const auto & p : coeffs) { + add_to_map(p.second, p.first); + } + } + bool operator==(const lar_term & a) const { return false; } // take care not to create identical terms + bool operator!=(const lar_term & a) const { return ! (*this == a);} + // some terms get used in add constraint + // it is the same as the offset in the m_constraints + + vector> coeffs_as_vector() const { + vector> ret; + for (const auto & p : m_coeffs) { + ret.push_back(std::make_pair(p.second, p.first)); + } + return ret; + } + + // j is the basic variable to substitute + void subst(unsigned j, indexed_vector & li) { + auto it = m_coeffs.find(j); + if (it == m_coeffs.end()) return; + const mpq & b = it->second; + for (unsigned it_j :li.m_index) { + add_to_map(it_j, - b * li.m_data[it_j]); + } + m_coeffs.erase(it); + } + + bool contains(unsigned j) const { + return m_coeffs.find(j) != m_coeffs.end(); + } +}; +} diff --git a/src/util/lp/linear_combination_iterator.h b/src/util/lp/linear_combination_iterator.h new file mode 100644 index 000000000..9a1a2de77 --- /dev/null +++ b/src/util/lp/linear_combination_iterator.h @@ -0,0 +1,47 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#pragma once +namespace lean { +template +struct linear_combination_iterator { + virtual bool next(T & a, unsigned & i) = 0; + virtual bool next(unsigned & i) = 0; + virtual void reset() = 0; + virtual linear_combination_iterator * clone() = 0; + virtual ~linear_combination_iterator(){} + virtual unsigned size() const = 0; +}; +template +struct linear_combination_iterator_on_vector : linear_combination_iterator { + vector> & m_vector; + int m_offset = 0; + bool next(T & a, unsigned & i) { + if(m_offset >= m_vector.size()) + return false; + auto & p = m_vector[m_offset]; + a = p.first; + i = p.second; + m_offset++; + return true; + } + + bool next(unsigned & i) { + if(m_offset >= m_vector.size()) + return false; + auto & p = m_vector[m_offset]; + i = p.second; + m_offset++; + return true; + } + + void reset() {m_offset = 0;} + linear_combination_iterator * clone() { + return new linear_combination_iterator_on_vector(m_vector); + } + linear_combination_iterator_on_vector(vector> & vec): m_vector(vec) {} + unsigned size() const { return m_vector.size(); } +}; + +} diff --git a/src/util/lp/lp_core_solver_base.h b/src/util/lp/lp_core_solver_base.h new file mode 100644 index 000000000..d8618f32a --- /dev/null +++ b/src/util/lp/lp_core_solver_base.h @@ -0,0 +1,683 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#pragma once +#include +#include "util/vector.h" +#include +#include "util/lp/lp_utils.h" +#include "util/lp/core_solver_pretty_printer.h" +#include "util/lp/numeric_pair.h" +#include "util/lp/static_matrix.h" +#include "util/lp/lu.h" +#include "util/lp/permutation_matrix.h" +#include "util/lp/column_namer.h" +namespace lean { + +template // X represents the type of the x variable and the bounds +class lp_core_solver_base { + unsigned m_total_iterations = 0; + unsigned inc_total_iterations() { ++m_settings.st().m_total_iterations; return m_total_iterations++; } +private: + lp_status m_status; +public: + bool current_x_is_feasible() const { return m_inf_set.size() == 0; } + bool current_x_is_infeasible() const { return m_inf_set.size() != 0; } + int_set m_inf_set; + bool m_using_infeas_costs = false; + + + vector m_columns_nz; // m_columns_nz[i] keeps an approximate value of non zeroes the i-th column + vector m_rows_nz; // m_rows_nz[i] keeps an approximate value of non zeroes in the i-th row + indexed_vector m_pivot_row_of_B_1; // the pivot row of the reverse of B + indexed_vector m_pivot_row; // this is the real pivot row of the simplex tableu + static_matrix & m_A; // the matrix A + vector & m_b; // the right side + vector & m_basis; + vector& m_nbasis; + vector& m_basis_heading; + vector & m_x; // a feasible solution, the fist time set in the constructor + vector & m_costs; + lp_settings & m_settings; + vector m_y; // the buffer for yB = cb + // a device that is able to solve Bx=c, xB=d, and change the basis + lu * m_factorization = nullptr; + const column_namer & m_column_names; + indexed_vector m_w; // the vector featuring in 24.3 of the Chvatal book + vector m_d; // the vector of reduced costs + indexed_vector m_ed; // the solution of B*m_ed = a + unsigned m_iters_with_no_cost_growing = 0; + const vector & m_column_types; + const vector & m_low_bounds; + const vector & m_upper_bounds; + vector m_column_norms; // the approximate squares of column norms that help choosing a profitable column + vector m_copy_of_xB; + unsigned m_basis_sort_counter = 0; + vector m_steepest_edge_coefficients; + vector m_trace_of_basis_change_vector; // the even positions are entering, the odd positions are leaving + bool m_tracing_basis_changes = false; + int_set* m_pivoted_rows = nullptr; + bool m_look_for_feasible_solution_only = false; + void start_tracing_basis_changes() { + m_trace_of_basis_change_vector.resize(0); + m_tracing_basis_changes = true; + } + + void stop_tracing_basis_changes() { + m_tracing_basis_changes = false; + } + + void trace_basis_change(unsigned entering, unsigned leaving) { + unsigned size = m_trace_of_basis_change_vector.size(); + if (size >= 2 && m_trace_of_basis_change_vector[size-2] == leaving + && m_trace_of_basis_change_vector[size -1] == entering) { + m_trace_of_basis_change_vector.pop_back(); + m_trace_of_basis_change_vector.pop_back(); + } else { + m_trace_of_basis_change_vector.push_back(entering); + m_trace_of_basis_change_vector.push_back(leaving); + } + } + + unsigned m_m() const { return m_A.row_count(); } // it is the length of basis. The matrix m_A has m_m rows and the dimension of the matrix A is m_m + unsigned m_n() const { return m_A.column_count(); } // the number of columns in the matrix m_A + + lp_core_solver_base(static_matrix & A, + vector & b, // the right side vector + vector & basis, + vector & nbasis, + vector & heading, + vector & x, + vector & costs, + lp_settings & settings, + const column_namer& column_names, + const vector & column_types, + const vector & low_bound_values, + const vector & upper_bound_values); + + void allocate_basis_heading(); + void init(); + + virtual ~lp_core_solver_base() { + if (m_factorization != nullptr) + delete m_factorization; + } + + vector & non_basis() { + return m_nbasis; + } + + const vector & non_basis() const { return m_nbasis; } + + + + void set_status(lp_status status) { + m_status = status; + } + lp_status get_status() const{ + return m_status; + } + + void fill_cb(T * y); + + void fill_cb(vector & y); + + void solve_yB(vector & y); + + void solve_Bd(unsigned entering); + + void solve_Bd(unsigned entering, indexed_vector & column); + + void pretty_print(std::ostream & out); + + void save_state(T * w_buffer, T * d_buffer); + + void restore_state(T * w_buffer, T * d_buffer); + + X get_cost() { + return dot_product(m_costs, m_x); + } + + void copy_m_w(T * buffer); + + void restore_m_w(T * buffer); + + // needed for debugging + void copy_m_ed(T * buffer); + + void restore_m_ed(T * buffer); + + bool A_mult_x_is_off() const; + + bool A_mult_x_is_off_on_index(const vector & index) const; + // from page 182 of Istvan Maros's book + void calculate_pivot_row_of_B_1(unsigned pivot_row); + + void calculate_pivot_row_when_pivot_row_of_B1_is_ready(unsigned pivot_row); + + void update_x(unsigned entering, const X & delta); + + const T & get_var_value(unsigned j) const { + return m_x[j]; + } + + void print_statistics(char const* str, X cost, std::ostream & message_stream); + + bool print_statistics_with_iterations_and_check_that_the_time_is_over(std::ostream & message_stream); + + bool print_statistics_with_iterations_and_nonzeroes_and_cost_and_check_that_the_time_is_over(char const* str, std::ostream & message_stream); + + bool print_statistics_with_cost_and_check_that_the_time_is_over(X cost, std::ostream & message_stream); + + unsigned total_iterations() const { return m_total_iterations; } + + void set_total_iterations(unsigned s) { m_total_iterations = s; } + + void set_non_basic_x_to_correct_bounds(); + + bool at_bound(const X &x, const X & bound) const { + return !below_bound(x, bound) && !above_bound(x, bound); + } + + + bool need_to_pivot_to_basis_tableau() const { + lean_assert(m_A.is_correct()); + unsigned m = m_A.row_count(); + for (unsigned i = 0; i < m; i++) { + unsigned bj = m_basis[i]; + lean_assert(m_A.m_columns[bj].size() > 0); + if (m_A.m_columns[bj].size() > 1 || m_A.get_val(m_A.m_columns[bj][0]) != one_of_type()) return true; + } + return false; + } + + bool reduced_costs_are_correct_tableau() const { + if (m_settings.simplex_strategy() == simplex_strategy_enum::tableau_rows) + return true; + lean_assert(m_A.is_correct()); + if (m_using_infeas_costs) { + if (infeasibility_costs_are_correct() == false) { + std::cout << "infeasibility_costs_are_correct() does not hold" << std::endl; + return false; + } + } + + unsigned n = m_A.column_count(); + for (unsigned j = 0; j < n; j++) { + if (m_basis_heading[j] >= 0) { + if (!is_zero(m_d[j])) { + + std::cout << "case a\n"; + print_column_info(j, std::cout); + return false; + } + } else { + auto d = m_costs[j]; + for (auto & cc : this->m_A.m_columns[j]) { + d -= this->m_costs[this->m_basis[cc.m_i]] * this->m_A.get_val(cc); + } + if (m_d[j] != d) { + std::cout << "case b\n"; + print_column_info(j, std::cout); + return false; + } + } + } + return true; + } + + bool below_bound(const X & x, const X & bound) const { + if (precise()) return x < bound; + return below_bound_numeric(x, bound, m_settings.primal_feasibility_tolerance); + } + + bool above_bound(const X & x, const X & bound) const { + if (precise()) return x > bound; + return above_bound_numeric(x, bound, m_settings.primal_feasibility_tolerance); + } + + bool x_below_low_bound(unsigned p) const { + return below_bound(m_x[p], m_low_bounds[p]); + } + + bool infeasibility_costs_are_correct() const; + bool infeasibility_cost_is_correct_for_column(unsigned j) const; + + bool x_above_low_bound(unsigned p) const { + return above_bound(m_x[p], m_low_bounds[p]); + } + + bool x_below_upper_bound(unsigned p) const { + return below_bound(m_x[p], m_upper_bounds[p]); + } + + + bool x_above_upper_bound(unsigned p) const { + return above_bound(m_x[p], m_upper_bounds[p]); + } + bool x_is_at_low_bound(unsigned j) const { + return at_bound(m_x[j], m_low_bounds[j]); + } + bool x_is_at_upper_bound(unsigned j) const { + return at_bound(m_x[j], m_upper_bounds[j]); + } + + bool x_is_at_bound(unsigned j) const { + return x_is_at_low_bound(j) || x_is_at_upper_bound(j); + } + bool column_is_feasible(unsigned j) const; + + bool calc_current_x_is_feasible_include_non_basis() const; + + bool inf_set_is_correct() const; + + bool column_is_dual_feasible(unsigned j) const; + + bool d_is_not_negative(unsigned j) const; + + bool d_is_not_positive(unsigned j) const; + + + bool time_is_over(); + + void rs_minus_Anx(vector & rs); + + bool find_x_by_solving(); + + bool update_basis_and_x(int entering, int leaving, X const & tt); + + bool basis_has_no_doubles() const; + + bool non_basis_has_no_doubles() const; + + bool basis_is_correctly_represented_in_heading() const ; + bool non_basis_is_correctly_represented_in_heading() const ; + + bool basis_heading_is_correct() const; + + void restore_x_and_refactor(int entering, int leaving, X const & t); + + void restore_x(unsigned entering, X const & t); + + void fill_reduced_costs_from_m_y_by_rows(); + + void copy_rs_to_xB(vector & rs); + virtual bool low_bounds_are_set() const { return false; } + X low_bound_value(unsigned j) const { return m_low_bounds[j]; } + X upper_bound_value(unsigned j) const { return m_upper_bounds[j]; } + + column_type get_column_type(unsigned j) const {return m_column_types[j]; } + + bool pivot_row_element_is_too_small_for_ratio_test(unsigned j) { + return m_settings.abs_val_is_smaller_than_pivot_tolerance(m_pivot_row[j]); + } + + X bound_span(unsigned j) const { + return m_upper_bounds[j] - m_low_bounds[j]; + } + + std::string column_name(unsigned column) const; + + void copy_right_side(vector & rs); + + void add_delta_to_xB(vector & del); + + void find_error_in_BxB(vector& rs); + + // recalculates the projection of x to B, such that Ax = b, whereab is the right side + void solve_Ax_eq_b(); + + bool snap_non_basic_x_to_bound() { + bool ret = false; + for (unsigned j : non_basis()) + ret = snap_column_to_bound(j) || ret; + return ret; + } + + + + bool snap_column_to_bound(unsigned j) { + switch (m_column_types[j]) { + case column_type::fixed: + if (x_is_at_bound(j)) + break; + m_x[j] = m_low_bounds[j]; + return true; + case column_type::boxed: + if (x_is_at_bound(j)) + break; // we should preserve x if possible + // snap randomly + if (my_random() % 2 == 1) + m_x[j] = m_low_bounds[j]; + else + m_x[j] = m_upper_bounds[j]; + return true; + case column_type::low_bound: + if (x_is_at_low_bound(j)) + break; + m_x[j] = m_low_bounds[j]; + return true; + case column_type::upper_bound: + if (x_is_at_upper_bound(j)) + break; + m_x[j] = m_upper_bounds[j]; + return true; + default: + break; + } + return false; + } + + bool make_column_feasible(unsigned j, numeric_pair & delta) { + lean_assert(m_basis_heading[j] < 0); + auto & x = m_x[j]; + switch (m_column_types[j]) { + case column_type::fixed: + lean_assert(m_low_bounds[j] == m_upper_bounds[j]); + if (x != m_low_bounds[j]) { + delta = m_low_bounds[j] - x; + x = m_low_bounds[j]; + return true; + } + break; + case column_type::boxed: + if (x < m_low_bounds[j]) { + delta = m_low_bounds[j] - x; + x = m_low_bounds[j]; + return true; + } + if (x > m_upper_bounds[j]) { + delta = m_upper_bounds[j] - x; + x = m_upper_bounds[j]; + return true; + } + break; + case column_type::low_bound: + if (x < m_low_bounds[j]) { + delta = m_low_bounds[j] - x; + x = m_low_bounds[j]; + return true; + } + break; + case column_type::upper_bound: + if (x > m_upper_bounds[j]) { + delta = m_upper_bounds[j] - x; + x = m_upper_bounds[j]; + return true; + } + break; + case column_type::free_column: + break; + default: + lean_assert(false); + break; + } + return false; + } + + + void snap_non_basic_x_to_bound_and_free_to_zeroes(); + void snap_xN_to_bounds_and_fill_xB(); + + void snap_xN_to_bounds_and_free_columns_to_zeroes(); + + void init_reduced_costs_for_one_iteration(); + + non_basic_column_value_position get_non_basic_column_value_position(unsigned j) const; + + void init_lu(); + int pivots_in_column_and_row_are_different(int entering, int leaving) const; + void pivot_fixed_vars_from_basis(); + bool pivot_for_tableau_on_basis(); + bool pivot_row_for_tableau_on_basis(unsigned row); + void init_basic_part_of_basis_heading() { + unsigned m = m_basis.size(); + for (unsigned i = 0; i < m; i++) { + unsigned column = m_basis[i]; + m_basis_heading[column] = i; + } + } + + void init_non_basic_part_of_basis_heading() { + this->m_nbasis.clear(); + for (int j = m_basis_heading.size(); j--;){ + if (m_basis_heading[j] < 0) { + m_nbasis.push_back(j); + // the index of column j in m_nbasis is (- basis_heading[j] - 1) + m_basis_heading[j] = - static_cast(m_nbasis.size()); + } + } + } + + void init_basis_heading_and_non_basic_columns_vector() { + m_basis_heading.resize(0); + m_basis_heading.resize(m_n(), -1); + init_basic_part_of_basis_heading(); + init_non_basic_part_of_basis_heading(); + } + + void change_basis_unconditionally(unsigned entering, unsigned leaving) { + lean_assert(m_basis_heading[entering] < 0); + int place_in_non_basis = -1 - m_basis_heading[entering]; + if (static_cast(place_in_non_basis) >= m_nbasis.size()) { + // entering variable in not in m_nbasis, we need to put it back; + m_basis_heading[entering] = place_in_non_basis = m_nbasis.size(); + m_nbasis.push_back(entering); + } + + int place_in_basis = m_basis_heading[leaving]; + m_basis_heading[entering] = place_in_basis; + m_basis[place_in_basis] = entering; + m_basis_heading[leaving] = -place_in_non_basis - 1; + m_nbasis[place_in_non_basis] = leaving; + if (m_tracing_basis_changes) + trace_basis_change(entering, leaving); + + } + + void change_basis(unsigned entering, unsigned leaving) { + lean_assert(m_basis_heading[entering] < 0); + + int place_in_basis = m_basis_heading[leaving]; + int place_in_non_basis = - m_basis_heading[entering] - 1; + m_basis_heading[entering] = place_in_basis; + m_basis[place_in_basis] = entering; + + m_basis_heading[leaving] = -place_in_non_basis - 1; + m_nbasis[place_in_non_basis] = leaving; + + if (m_tracing_basis_changes) + trace_basis_change(entering, leaving); + } + + void restore_basis_change(unsigned entering, unsigned leaving) { + if (m_basis_heading[entering] < 0) { + return; // the basis has not been changed + } + change_basis_unconditionally(leaving, entering); + } + + bool non_basic_column_is_set_correctly(unsigned j) const { + if (j >= this->m_n()) + return false; + switch (this->m_column_types[j]) { + case column_type::fixed: + case column_type::boxed: + if (!this->x_is_at_bound(j)) + return false; + break; + case column_type::low_bound: + if (!this->x_is_at_low_bound(j)) + return false; + break; + case column_type::upper_bound: + if (!this->x_is_at_upper_bound(j)) + return false; + break; + case column_type::free_column: + break; + default: + lean_assert(false); + break; + } + return true; + } + bool non_basic_columns_are_set_correctly() const { + for (unsigned j : this->m_nbasis) + if (!column_is_feasible(j)) { + print_column_info(j, std::cout); + return false; + } + return true; + } + + void print_column_bound_info(unsigned j, std::ostream & out) const { + out << column_name(j) << " type = " << column_type_to_string(m_column_types[j]) << std::endl; + switch (m_column_types[j]) { + case column_type::fixed: + case column_type::boxed: + out << "(" << m_low_bounds[j] << ", " << m_upper_bounds[j] << ")" << std::endl; + break; + case column_type::low_bound: + out << m_low_bounds[j] << std::endl; + break; + case column_type::upper_bound: + out << m_upper_bounds[j] << std::endl; + break; + default: + break; + } + } + + void print_column_info(unsigned j, std::ostream & out) const { + out << "column_index = " << j << ", name = "<< column_name(j) << " type = " << column_type_to_string(m_column_types[j]) << std::endl; + switch (m_column_types[j]) { + case column_type::fixed: + case column_type::boxed: + out << "(" << m_low_bounds[j] << ", " << m_upper_bounds[j] << ")" << std::endl; + break; + case column_type::low_bound: + out << m_low_bounds[j] << std::endl; + break; + case column_type::upper_bound: + out << m_upper_bounds[j] << std::endl; + break; + case column_type::free_column: + break; + default: + lean_assert(false); + } + std::cout << "basis heading = " << m_basis_heading[j] << std::endl; + std::cout << "x = " << m_x[j] << std::endl; + /* + std::cout << "cost = " << m_costs[j] << std::endl; + std:: cout << "m_d = " << m_d[j] << std::endl;*/ + } + + bool column_is_free(unsigned j) { return this->m_column_type[j] == free; } + + bool column_has_upper_bound(unsigned j) { + switch(m_column_types[j]) { + case column_type::free_column: + case column_type::low_bound: + return false; + default: + return true; + } + } + + bool bounds_for_boxed_are_set_correctly() const { + for (unsigned j = 0; j < m_column_types.size(); j++) { + if (m_column_types[j] != column_type::boxed) continue; + if (m_low_bounds[j] > m_upper_bounds[j]) + return false; + } + return true; + } + + bool column_has_low_bound(unsigned j) { + switch(m_column_types[j]) { + case column_type::free_column: + case column_type::upper_bound: + return false; + default: + return true; + } + } + + // only check for basic columns + bool calc_current_x_is_feasible() const { + unsigned i = this->m_m(); + while (i--) { + if (!column_is_feasible(m_basis[i])) + return false; + } + return true; + } + + int find_pivot_index_in_row(unsigned i, const vector & col) const { + for (const auto & c: col) { + if (c.m_i == i) + return c.m_offset; + } + return -1; + } + + void transpose_rows_tableau(unsigned i, unsigned ii); + + void pivot_to_reduced_costs_tableau(unsigned i, unsigned j); + + bool pivot_column_tableau(unsigned j, unsigned row_index); + bool divide_row_by_pivot(unsigned pivot_row, unsigned pivot_col); + + bool precise() const { return numeric_traits::precise(); } + + simplex_strategy_enum simplex_strategy() const { return + m_settings.simplex_strategy(); + } + + bool use_tableau() const { return m_settings.use_tableau(); } + + template + static void swap(vector &v, unsigned i, unsigned j) { + auto t = v[i]; + v[i] = v[j]; + v[j] = t; + } + + // called when transposing row i and ii + void transpose_basis(unsigned i, unsigned ii) { + swap(m_basis, i, ii); + swap(m_basis_heading, m_basis[i], m_basis[ii]); + } + + bool column_is_in_inf_set(unsigned j) const { + return m_inf_set.contains(j); + } + + void update_column_in_inf_set(unsigned j) { + if (column_is_feasible(j)) { + m_inf_set.erase(j); + } else { + m_inf_set.insert(j); + } + } + void insert_column_into_inf_set(unsigned j) { + m_inf_set.insert(j); + lean_assert(!column_is_feasible(j)); + } + void remove_column_from_inf_set(unsigned j) { + m_inf_set.erase(j); + lean_assert(column_is_feasible(j)); + } + bool costs_on_nbasis_are_zeros() const { + lean_assert(this->basis_heading_is_correct()); + for (unsigned j = 0; j < this->m_n(); j++) { + if (this->m_basis_heading[j] < 0) + lean_assert(is_zero(this->m_costs[j])); + } + return true; +} +}; +} diff --git a/src/util/lp/lp_core_solver_base.hpp b/src/util/lp/lp_core_solver_base.hpp new file mode 100644 index 000000000..34b2d0b68 --- /dev/null +++ b/src/util/lp/lp_core_solver_base.hpp @@ -0,0 +1,1007 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include +#include +#include "util/vector.h" +#include "util/lp/lp_utils.h" +#include "util/lp/lp_core_solver_base.h" +namespace lean { + +template lp_core_solver_base:: +lp_core_solver_base(static_matrix & A, + vector & b, // the right side vector + vector & basis, + vector & nbasis, + vector & heading, + vector & x, + vector & costs, + lp_settings & settings, + const column_namer& column_names, + const vector & column_types, + const vector & low_bound_values, + const vector & upper_bound_values): + m_status(FEASIBLE), + m_inf_set(A.column_count()), + m_pivot_row_of_B_1(A.row_count()), + m_pivot_row(A.column_count()), + m_A(A), + m_b(b), + m_basis(basis), + m_nbasis(nbasis), + m_basis_heading(heading), + m_x(x), + m_costs(costs), + m_settings(settings), + m_y(m_m()), + m_factorization(nullptr), + m_column_names(column_names), + m_w(m_m()), + m_d(m_n()), + m_ed(m_m()), + m_column_types(column_types), + m_low_bounds(low_bound_values), + m_upper_bounds(upper_bound_values), + m_column_norms(m_n()), + m_copy_of_xB(m_m()), + m_steepest_edge_coefficients(A.column_count()) { + lean_assert(bounds_for_boxed_are_set_correctly()); + init(); + init_basis_heading_and_non_basic_columns_vector(); +} + +template void lp_core_solver_base:: +allocate_basis_heading() { // the rest of initilization will be handled by the factorization class + init_basis_heading_and_non_basic_columns_vector(); + lean_assert(basis_heading_is_correct()); +} +template void lp_core_solver_base:: +init() { + my_random_init(m_settings.random_seed); + allocate_basis_heading(); + if (!use_tableau()) + init_factorization(m_factorization, m_A, m_basis, m_settings); +} + +template bool lp_core_solver_base:: +pivot_for_tableau_on_basis() { + m_d = m_costs; // we will be pivoting to m_d as well + unsigned m = m_A.row_count(); + for (unsigned i = 0; i < m; i++) + if (!pivot_column_tableau(m_basis[i], i)) + return false; + return true; +} + +// i is the pivot row, and j is the pivot column +template void lp_core_solver_base:: +pivot_to_reduced_costs_tableau(unsigned i, unsigned j) { + if (j >= m_d.size()) + return; + T &a = m_d[j]; + if (is_zero(a)) + return; + for (const row_cell & r: m_A.m_rows[i]) + if (r.m_j != j) + m_d[r.m_j] -= a * r.get_val(); + + a = zero_of_type(); // zero the pivot column's m_d finally +} + + +template void lp_core_solver_base:: +fill_cb(T * y){ + for (unsigned i = 0; i < m_m(); i++) { + y[i] = m_costs[m_basis[i]]; + } +} + + +template void lp_core_solver_base:: +fill_cb(vector & y){ + for (unsigned i = 0; i < m_m(); i++) { + y[i] = m_costs[m_basis[i]]; + } +} + +template void lp_core_solver_base:: +solve_yB(vector & y) { + fill_cb(y); // now y = cB, that is the projection of costs to basis + m_factorization->solve_yB_with_error_check(y, m_basis); +} + +// template void lp_core_solver_base:: +// update_index_of_ed() { +// m_index_of_ed.clear(); +// unsigned i = static_cast(m_ed.size()); +// while (i--) { +// if (!is_zero(m_ed[i])) +// m_index_of_ed.push_back(i); +// } +// } +template void lp_core_solver_base::solve_Bd(unsigned entering, indexed_vector & column) { + lean_assert(!m_settings.use_tableau()); + if (m_factorization == nullptr) { + init_factorization(m_factorization, m_A, m_basis, m_settings); + } + m_factorization->solve_Bd_faster(entering, column); +} + + +template void lp_core_solver_base:: +solve_Bd(unsigned entering) { + lean_assert(m_ed.is_OK()); + m_factorization->solve_Bd(entering, m_ed, m_w); + if (this->precise()) + m_columns_nz[entering] = m_ed.m_index.size(); + lean_assert(m_ed.is_OK()); + lean_assert(m_w.is_OK()); +#ifdef LEAN_DEBUG + // auto B = get_B(*m_factorization, m_basis); + // vector a(m_m()); + // m_A.copy_column_to_vector(entering, a); + // vector cd(m_ed.m_data); + // B.apply_from_left(cd, m_settings); + // lean_assert(vectors_are_equal(cd , a)); +#endif +} + +template void lp_core_solver_base:: +pretty_print(std::ostream & out) { + core_solver_pretty_printer pp(*this, out); + pp.print(); +} + +template void lp_core_solver_base:: +save_state(T * w_buffer, T * d_buffer) { + copy_m_w(w_buffer); + copy_m_ed(d_buffer); +} + +template void lp_core_solver_base:: +restore_state(T * w_buffer, T * d_buffer) { + restore_m_w(w_buffer); + restore_m_ed(d_buffer); +} + +template void lp_core_solver_base:: +copy_m_w(T * buffer) { + unsigned i = m_m(); + while (i --) { + buffer[i] = m_w[i]; + } +} + +template void lp_core_solver_base:: +restore_m_w(T * buffer) { + m_w.m_index.clear(); + unsigned i = m_m(); + while (i--) { + if (!is_zero(m_w[i] = buffer[i])) + m_w.m_index.push_back(i); + } +} + +// needed for debugging +template void lp_core_solver_base:: +copy_m_ed(T * buffer) { + unsigned i = m_m(); + while (i --) { + buffer[i] = m_ed[i]; + } +} + +template void lp_core_solver_base:: +restore_m_ed(T * buffer) { + unsigned i = m_m(); + while (i --) { + m_ed[i] = buffer[i]; + } +} + +template bool lp_core_solver_base:: +A_mult_x_is_off() const { + lean_assert(m_x.size() == m_A.column_count()); + if (numeric_traits::precise()) { + for (unsigned i = 0; i < m_m(); i++) { + X delta = m_b[i] - m_A.dot_product_with_row(i, m_x); + if (delta != numeric_traits::zero()) { + std::cout << "precise x is off ("; + std::cout << "m_b[" << i << "] = " << m_b[i] << " "; + std::cout << "left side = " << m_A.dot_product_with_row(i, m_x) << ' '; + std::cout << "delta = " << delta << ' '; + std::cout << "iters = " << total_iterations() << ")" << std::endl; + return true; + } + } + return false; + } + T feps = convert_struct::convert(m_settings.refactor_tolerance); + X one = convert_struct::convert(1.0); + for (unsigned i = 0; i < m_m(); i++) { + X delta = abs(m_b[i] - m_A.dot_product_with_row(i, m_x)); + X eps = feps * (one + T(0.1) * abs(m_b[i])); + + if (delta > eps) { +#if 0 + LP_OUT(m_settings, "x is off (" + << "m_b[" << i << "] = " << m_b[i] << " " + << "left side = " << m_A.dot_product_with_row(i, m_x) << ' ' + << "delta = " << delta << ' ' + << "iters = " << total_iterations() << ")" << std::endl); +#endif + return true; + } + } + return false; +} +template bool lp_core_solver_base:: +A_mult_x_is_off_on_index(const vector & index) const { + lean_assert(m_x.size() == m_A.column_count()); + if (numeric_traits::precise()) return false; +#if RUN_A_MULT_X_IS_OFF_FOR_PRECESE + for (unsigned i : index) { + X delta = m_b[i] - m_A.dot_product_with_row(i, m_x); + if (delta != numeric_traits::zero()) { + // std::cout << "x is off ("; + // std::cout << "m_b[" << i << "] = " << m_b[i] << " "; + // std::cout << "left side = " << m_A.dot_product_with_row(i, m_x) << ' '; + // std::cout << "delta = " << delta << ' '; + // std::cout << "iters = " << total_iterations() << ")" << std::endl; + return true; + } + } + return false; +#endif + // todo(levnach) run on m_ed.m_index only !!!!! + T feps = convert_struct::convert(m_settings.refactor_tolerance); + X one = convert_struct::convert(1.0); + for (unsigned i : index) { + X delta = abs(m_b[i] - m_A.dot_product_with_row(i, m_x)); + X eps = feps * (one + T(0.1) * abs(m_b[i])); + + if (delta > eps) { +#if 0 + LP_OUT(m_settings, "x is off (" + << "m_b[" << i << "] = " << m_b[i] << " " + << "left side = " << m_A.dot_product_with_row(i, m_x) << ' ' + << "delta = " << delta << ' ' + << "iters = " << total_iterations() << ")" << std::endl); +#endif + return true; + } + } + return false; +} + +// from page 182 of Istvan Maros's book +template void lp_core_solver_base:: +calculate_pivot_row_of_B_1(unsigned pivot_row) { + lean_assert(! use_tableau()); + lean_assert(m_pivot_row_of_B_1.is_OK()); + m_pivot_row_of_B_1.clear(); + m_pivot_row_of_B_1.set_value(numeric_traits::one(), pivot_row); + lean_assert(m_pivot_row_of_B_1.is_OK()); + m_factorization->solve_yB_with_error_check_indexed(m_pivot_row_of_B_1, m_basis_heading, m_basis, m_settings); + lean_assert(m_pivot_row_of_B_1.is_OK()); +} + + +template void lp_core_solver_base:: +calculate_pivot_row_when_pivot_row_of_B1_is_ready(unsigned pivot_row) { + m_pivot_row.clear(); + + for (unsigned i : m_pivot_row_of_B_1.m_index) { + const T & pi_1 = m_pivot_row_of_B_1[i]; + if (numeric_traits::is_zero(pi_1)) { + continue; + } + for (auto & c : m_A.m_rows[i]) { + unsigned j = c.m_j; + if (m_basis_heading[j] < 0) { + m_pivot_row.add_value_at_index_with_drop_tolerance(j, c.get_val() * pi_1); + } + } + } + if (precise()) { + m_rows_nz[pivot_row] = m_pivot_row.m_index.size(); + } +} + +template void lp_core_solver_base:: +update_x(unsigned entering, const X& delta) { + m_x[entering] += delta; + if (!use_tableau()) + for (unsigned i : m_ed.m_index) { + if (!numeric_traits::precise()) + m_copy_of_xB[i] = m_x[m_basis[i]]; + m_x[m_basis[i]] -= delta * m_ed[i]; + } + else + for (const auto & c : m_A.m_columns[entering]) { + unsigned i = c.m_i; + m_x[m_basis[i]] -= delta * m_A.get_val(c); + } +} + + +template void lp_core_solver_base:: +print_statistics(char const* str, X cost, std::ostream & out) { + if (str!= nullptr) + out << str << " "; + out << "iterations = " << (total_iterations() - 1) << ", cost = " << T_to_string(cost) + << ", nonzeros = " << (m_factorization != nullptr? m_factorization->get_number_of_nonzeroes() : m_A.number_of_non_zeroes()) << std::endl; +} + +template bool lp_core_solver_base:: +print_statistics_with_iterations_and_check_that_the_time_is_over(std::ostream & str) { + unsigned total_iterations = inc_total_iterations(); + if (m_settings.report_frequency != 0) { + if (m_settings.print_statistics && (total_iterations % m_settings.report_frequency == 0)) { + print_statistics("", X(), str); + } + } + return time_is_over(); +} + +template bool lp_core_solver_base:: +print_statistics_with_iterations_and_nonzeroes_and_cost_and_check_that_the_time_is_over(char const* str, std::ostream & out) { + unsigned total_iterations = inc_total_iterations(); + if (m_settings.report_frequency != 0) + if (m_settings.print_statistics && (total_iterations % m_settings.report_frequency == 0)) { + print_statistics(str, get_cost(), out); + } + return time_is_over(); +} + +template bool lp_core_solver_base:: +print_statistics_with_cost_and_check_that_the_time_is_over(X cost, std::ostream & out) { + unsigned total_iterations = inc_total_iterations(); + if (m_settings.report_frequency != 0) + if (m_settings.print_statistics && (total_iterations % m_settings.report_frequency == 0)) { + print_statistics("", cost, out); + } + return time_is_over(); +} + +template void lp_core_solver_base:: +set_non_basic_x_to_correct_bounds() { + for (unsigned j : non_basis()) { + switch (m_column_types[j]) { + case column_type::boxed: + m_x[j] = m_d[j] < 0? m_upper_bounds[j]: m_low_bounds[j]; + break; + case column_type::low_bound: + m_x[j] = m_low_bounds[j]; + lean_assert(column_is_dual_feasible(j)); + break; + case column_type::upper_bound: + m_x[j] = m_upper_bounds[j]; + lean_assert(column_is_dual_feasible(j)); + break; + default: + break; + } + } +} +template bool lp_core_solver_base:: +column_is_dual_feasible(unsigned j) const { + switch (m_column_types[j]) { + case column_type::fixed: + case column_type::boxed: + return (x_is_at_low_bound(j) && d_is_not_negative(j)) || + (x_is_at_upper_bound(j) && d_is_not_positive(j)); + case column_type::low_bound: + return x_is_at_low_bound(j) && d_is_not_negative(j); + case column_type::upper_bound: + LP_OUT(m_settings, "upper_bound type should be switched to low_bound" << std::endl); + lean_assert(false); // impossible case + case column_type::free_column: + return numeric_traits::is_zero(m_d[j]); + default: + LP_OUT(m_settings, "column = " << j << std::endl); + LP_OUT(m_settings, "unexpected column type = " << column_type_to_string(m_column_types[j]) << std::endl); + lean_unreachable(); + } + lean_unreachable(); + return false; +} +template bool lp_core_solver_base:: +d_is_not_negative(unsigned j) const { + if (numeric_traits::precise()) { + return m_d[j] >= numeric_traits::zero(); + } + return m_d[j] > -T(0.00001); +} + +template bool lp_core_solver_base:: +d_is_not_positive(unsigned j) const { + if (numeric_traits::precise()) { + return m_d[j] <= numeric_traits::zero(); + } + return m_d[j] < T(0.00001); +} + + +template bool lp_core_solver_base:: +time_is_over() { + if (m_settings.get_cancel_flag()) { + m_status = lp_status::TIME_EXHAUSTED; + return true; + } + else { + return false; + } +} + +template void lp_core_solver_base:: +rs_minus_Anx(vector & rs) { + unsigned row = m_m(); + while (row--) { + auto &rsv = rs[row] = m_b[row]; + for (auto & it : m_A.m_rows[row]) { + unsigned j = it.m_j; + if (m_basis_heading[j] < 0) { + rsv -= m_x[j] * it.get_val(); + } + } + } +} + +template bool lp_core_solver_base:: +find_x_by_solving() { + solve_Ax_eq_b(); + bool ret= !A_mult_x_is_off(); + return ret; +} + +template bool lp_core_solver_base::column_is_feasible(unsigned j) const { + const X& x = this->m_x[j]; + switch (this->m_column_types[j]) { + case column_type::fixed: + case column_type::boxed: + if (this->above_bound(x, this->m_upper_bounds[j])) { + return false; + } else if (this->below_bound(x, this->m_low_bounds[j])) { + return false; + } else { + return true; + } + break; + case column_type::low_bound: + if (this->below_bound(x, this->m_low_bounds[j])) { + return false; + } else { + return true; + } + break; + case column_type::upper_bound: + if (this->above_bound(x, this->m_upper_bounds[j])) { + return false; + } else { + return true; + } + break; + case column_type::free_column: + return true; + break; + default: + lean_unreachable(); + } + return false; // it is unreachable +} + +template bool lp_core_solver_base::calc_current_x_is_feasible_include_non_basis() const { + unsigned j = this->m_n(); + while (j--) { + if (!column_is_feasible(j)) { + return false; + } + } + return true; +} + +template bool lp_core_solver_base::inf_set_is_correct() const { + unsigned j = this->m_n(); + while (j--) { + bool belongs_to_set = m_inf_set.contains(j); + bool is_feas = column_is_feasible(j); + + if (is_feas == belongs_to_set) { + print_column_info(j, std::cout); + std::cout << "belongs_to_set = " << belongs_to_set << std::endl; + std::cout <<( is_feas? "feas":"inf") << std::endl; + return false; + } + } + return true; +} + +template bool lp_core_solver_base:: +update_basis_and_x(int entering, int leaving, X const & tt) { + + if (!is_zero(tt)) { + update_x(entering, tt); + if ((!numeric_traits::precise()) && A_mult_x_is_off_on_index(m_ed.m_index) && !find_x_by_solving()) { + init_factorization(m_factorization, m_A, m_basis, m_settings); + if (!find_x_by_solving()) { + restore_x(entering, tt); + lean_assert(!A_mult_x_is_off()); + init_factorization(m_factorization, m_A, m_basis, m_settings); + m_iters_with_no_cost_growing++; + if (m_factorization->get_status() != LU_status::OK) { + std::stringstream s; + s << "failing refactor on off_result for entering = " << entering << ", leaving = " << leaving << " total_iterations = " << total_iterations(); + throw_exception(s.str()); + } + return false; + } + } + } + + bool refactor = m_factorization->need_to_refactor(); + if (!refactor) { + const T & pivot = this->m_pivot_row[entering]; // m_ed[m_factorization->basis_heading(leaving)] is the same but the one that we are using is more precise + m_factorization->replace_column(pivot, m_w, m_basis_heading[leaving]); + if (m_factorization->get_status() == LU_status::OK) { + change_basis(entering, leaving); + return true; + } + } + // need to refactor == true + change_basis(entering, leaving); + init_lu(); + if (m_factorization->get_status() != LU_status::OK) { + if (m_look_for_feasible_solution_only && !precise()) { + m_status = UNSTABLE; + delete m_factorization; + m_factorization = nullptr; + return false; + } + // LP_OUT(m_settings, "failing refactor for entering = " << entering << ", leaving = " << leaving << " total_iterations = " << total_iterations() << std::endl); + restore_x_and_refactor(entering, leaving, tt); + if (m_status == FLOATING_POINT_ERROR) + return false; + lean_assert(!A_mult_x_is_off()); + m_iters_with_no_cost_growing++; + // LP_OUT(m_settings, "rolled back after failing of init_factorization()" << std::endl); + m_status = UNSTABLE; + return false; + } + return true; +} + + +template bool lp_core_solver_base:: +divide_row_by_pivot(unsigned pivot_row, unsigned pivot_col) { + lean_assert(numeric_traits::precise()); + int pivot_index = -1; + auto & row = m_A.m_rows[pivot_row]; + unsigned size = row.size(); + for (unsigned j = 0; j < size; j++) { + if (row[j].m_j == pivot_col) { + pivot_index = static_cast(j); + break; + } + } + if (pivot_index == -1) + return false; + auto & pivot_cell = row[pivot_index]; + if (is_zero(pivot_cell.m_value)) + return false; + + this->m_b[pivot_row] /= pivot_cell.m_value; + for (unsigned j = 0; j < size; j++) { + if (row[j].m_j != pivot_col) { + row[j].m_value /= pivot_cell.m_value; + } + } + pivot_cell.m_value = one_of_type(); + return true; +} +template bool lp_core_solver_base:: +pivot_column_tableau(unsigned j, unsigned piv_row_index) { + if (!divide_row_by_pivot(piv_row_index, j)) + return false; + auto &column = m_A.m_columns[j]; + int pivot_col_cell_index = -1; + for (unsigned k = 0; k < column.size(); k++) { + if (column[k].m_i == piv_row_index) { + pivot_col_cell_index = k; + break; + } + } + if (pivot_col_cell_index < 0) + return false; + + if (pivot_col_cell_index != 0) { + lean_assert(column.size() > 1); + // swap the pivot column cell with the head cell + auto c = column[0]; + column[0] = column[pivot_col_cell_index]; + column[pivot_col_cell_index] = c; + + m_A.m_rows[piv_row_index][column[0].m_offset].m_offset = 0; + m_A.m_rows[c.m_i][c.m_offset].m_offset = pivot_col_cell_index; + } + while (column.size() > 1) { + auto & c = column.back(); + lean_assert(c.m_i != piv_row_index); + if(! m_A.pivot_row_to_row_given_cell(piv_row_index, c, j)) { + return false; + } + if (m_pivoted_rows!= nullptr) + m_pivoted_rows->insert(c.m_i); + } + + if (m_settings.simplex_strategy() == simplex_strategy_enum::tableau_costs) + pivot_to_reduced_costs_tableau(piv_row_index, j); + return true; +} + + +template bool lp_core_solver_base:: +basis_has_no_doubles() const { + std::set bm; + for (unsigned i = 0; i < m_m(); i++) { + bm.insert(m_basis[i]); + } + return bm.size() == m_m(); +} + +template bool lp_core_solver_base:: +non_basis_has_no_doubles() const { + std::set bm; + for (auto j : m_nbasis) { + bm.insert(j); + } + return bm.size() == m_nbasis.size(); +} + +template bool lp_core_solver_base:: +basis_is_correctly_represented_in_heading() const { + for (unsigned i = 0; i < m_m(); i++) { + if (m_basis_heading[m_basis[i]] != static_cast(i)) + return false; + } + return true; +} +template bool lp_core_solver_base:: +non_basis_is_correctly_represented_in_heading() const { + for (unsigned i = 0; i < m_nbasis.size(); i++) { + if (m_basis_heading[m_nbasis[i]] != - static_cast(i) - 1) + return false; + } + for (unsigned j = 0; j < m_A.column_count(); j++) { + if (m_basis_heading[j] >= 0) { + lean_assert(static_cast(m_basis_heading[j]) < m_A.row_count() && m_basis[m_basis_heading[j]] == j); + } + } + return true; +} + +template bool lp_core_solver_base:: + basis_heading_is_correct() const { + lean_assert(m_basis_heading.size() == m_A.column_count()); + lean_assert(m_basis.size() == m_A.row_count()); + lean_assert(m_nbasis.size() <= m_A.column_count() - m_A.row_count()); // for the dual the size of non basis can be smaller + if (!basis_has_no_doubles()) { + // std::cout << "basis_has_no_doubles" << std::endl; + return false; + } + + if (!non_basis_has_no_doubles()) { + // std::cout << "non_basis_has_no_doubles" << std::endl; + return false; + } + + if (!basis_is_correctly_represented_in_heading()) { + // std::cout << "basis_is_correctly_represented_in_heading" << std::endl; + return false; + } + + if (!non_basis_is_correctly_represented_in_heading()) { + // std::cout << "non_basis_is_correctly_represented_in_heading" << std::endl; + return false; + } + + + return true; +} + +template void lp_core_solver_base:: +restore_x_and_refactor(int entering, int leaving, X const & t) { + this->restore_basis_change(entering, leaving); + restore_x(entering, t); + init_factorization(m_factorization, m_A, m_basis, m_settings); + if (m_factorization->get_status() == LU_status::Degenerated) { + LP_OUT(m_settings, "cannot refactor" << std::endl); + m_status = lp_status::FLOATING_POINT_ERROR; + return; + } + // solve_Ax_eq_b(); + if (A_mult_x_is_off()) { + LP_OUT(m_settings, "cannot restore solution" << std::endl); + m_status = lp_status::FLOATING_POINT_ERROR; + return; + } +} + +template void lp_core_solver_base:: +restore_x(unsigned entering, X const & t) { + if (is_zero(t)) return; + m_x[entering] -= t; + for (unsigned i : m_ed.m_index) { + m_x[m_basis[i]] = m_copy_of_xB[i]; + } +} + +template void lp_core_solver_base:: +fill_reduced_costs_from_m_y_by_rows() { + unsigned j = m_n(); + while (j--) { + if (m_basis_heading[j] < 0) + m_d[j] = m_costs[j]; + else + m_d[j] = numeric_traits::zero(); + } + + unsigned i = m_m(); + while (i--) { + const T & y = m_y[i]; + if (is_zero(y)) continue; + for (row_cell & it : m_A.m_rows[i]) { + j = it.m_j; + if (m_basis_heading[j] < 0) { + m_d[j] -= y * it.get_val(); + } + } + } +} + +template void lp_core_solver_base:: +copy_rs_to_xB(vector & rs) { + unsigned j = m_m(); + while (j--) { + m_x[m_basis[j]] = rs[j]; + } +} + +template std::string lp_core_solver_base:: +column_name(unsigned column) const { + return m_column_names.get_column_name(column); +} + +template void lp_core_solver_base:: +copy_right_side(vector & rs) { + unsigned i = m_m(); + while (i --) { + rs[i] = m_b[i]; + } +} + +template void lp_core_solver_base:: +add_delta_to_xB(vector & del) { + unsigned i = m_m(); + while (i--) { + this->m_x[this->m_basis[i]] -= del[i]; + } +} + +template void lp_core_solver_base:: +find_error_in_BxB(vector& rs){ + unsigned row = m_m(); + while (row--) { + auto &rsv = rs[row]; + for (auto & it : m_A.m_rows[row]) { + unsigned j = it.m_j; + if (m_basis_heading[j] >= 0) { + rsv -= m_x[j] * it.get_val(); + } + } + } +} + +// recalculates the projection of x to B, such that Ax = b +template void lp_core_solver_base:: +solve_Ax_eq_b() { + if (numeric_traits::precise()) { + vector rs(m_m()); + rs_minus_Anx(rs); + m_factorization->solve_By(rs); + copy_rs_to_xB(rs); + } else { + vector rs(m_m()); + rs_minus_Anx(rs); + vector rrs = rs; // another copy of rs + m_factorization->solve_By(rs); + copy_rs_to_xB(rs); + find_error_in_BxB(rrs); + m_factorization->solve_By(rrs); + add_delta_to_xB(rrs); + } +} + + + + +template void lp_core_solver_base:: +snap_non_basic_x_to_bound_and_free_to_zeroes() { + for (unsigned j : non_basis()) { + lean_assert(j < m_x.size()); + switch (m_column_types[j]) { + case column_type::fixed: + case column_type::boxed: + case column_type::low_bound: + m_x[j] = m_low_bounds[j]; + break; + case column_type::upper_bound: + m_x[j] = m_upper_bounds[j]; + break; + default: + m_x[j] = zero_of_type(); + break; + } + } +} +template void lp_core_solver_base:: +snap_xN_to_bounds_and_fill_xB() { + snap_non_basic_x_to_bound(); + solve_Ax_eq_b(); +} + +template void lp_core_solver_base:: +snap_xN_to_bounds_and_free_columns_to_zeroes() { + snap_non_basic_x_to_bound_and_free_to_zeroes(); + solve_Ax_eq_b(); +} + +template void lp_core_solver_base:: +init_reduced_costs_for_one_iteration() { + solve_yB(m_y); + fill_reduced_costs_from_m_y_by_rows(); +} + +template non_basic_column_value_position lp_core_solver_base:: +get_non_basic_column_value_position(unsigned j) const { + switch (m_column_types[j]) { + case column_type::fixed: + return x_is_at_low_bound(j)? at_fixed : not_at_bound; + case column_type::free_column: + return free_of_bounds; + case column_type::boxed: + return x_is_at_low_bound(j)? at_low_bound :( + x_is_at_upper_bound(j)? at_upper_bound: + not_at_bound + ); + case column_type::low_bound: + return x_is_at_low_bound(j)? at_low_bound : not_at_bound; + case column_type::upper_bound: + return x_is_at_upper_bound(j)? at_upper_bound : not_at_bound; + default: + lean_unreachable(); + } + lean_unreachable(); + return at_low_bound; +} + +template void lp_core_solver_base::init_lu() { + init_factorization(this->m_factorization, this->m_A, this->m_basis, this->m_settings); +} + +template int lp_core_solver_base::pivots_in_column_and_row_are_different(int entering, int leaving) const { + const T & column_p = this->m_ed[this->m_basis_heading[leaving]]; + const T & row_p = this->m_pivot_row[entering]; + if (is_zero(column_p) || is_zero(row_p)) return true; // pivots cannot be zero + // the pivots have to have the same sign + if (column_p < 0) { + if (row_p > 0) + return 2; + } else { // column_p > 0 + if (row_p < 0) + return 2; + } + T diff_normalized = abs((column_p - row_p) / (numeric_traits::one() + abs(row_p))); + if ( !this->m_settings.abs_val_is_smaller_than_harris_tolerance(diff_normalized / T(10))) + return 1; + return 0; +} +template void lp_core_solver_base::transpose_rows_tableau(unsigned i, unsigned j) { + transpose_basis(i, j); + m_A.transpose_rows(i, j); +} + +template void lp_core_solver_base::pivot_fixed_vars_from_basis() { + // run over basis and non-basis at the same time + indexed_vector w(m_basis.size()); // the buffer + unsigned i = 0; // points to basis + unsigned j = 0; // points to nonbasis + for (; i < m_basis.size() && j < m_nbasis.size(); i++) { + unsigned ii = m_basis[i]; + unsigned jj; + + if (get_column_type(ii) != column_type::fixed) continue; + while (j < m_nbasis.size()) { + for (; j < m_nbasis.size(); j++) { + jj = m_nbasis[j]; + if (get_column_type(jj) != column_type::fixed) + break; + } + if (j >= m_nbasis.size()) + break; + j++; + if (m_factorization->need_to_refactor()) { + change_basis(jj, ii); + init_lu(); + } else { + m_factorization->prepare_entering(jj, w); // to init vector w + m_factorization->replace_column(zero_of_type(), w, m_basis_heading[ii]); + change_basis(jj, ii); + } + if (m_factorization->get_status() != LU_status::OK) { + change_basis(ii, jj); + init_lu(); + } else { + break; + } + } + lean_assert(m_factorization->get_status()== LU_status::OK); + } +} + +template bool +lp_core_solver_base::infeasibility_costs_are_correct() const { + if (! this->m_using_infeas_costs) + return true; + lean_assert(costs_on_nbasis_are_zeros()); + for (unsigned j :this->m_basis) { + if (!infeasibility_cost_is_correct_for_column(j)) { + std::cout << "infeasibility_cost_is_correct_for_column does not hold\n"; + print_column_info(j, std::cout); + return false; + } + if (!is_zero(m_d[j])) { + std::cout << "m_d is not zero\n"; + print_column_info(j, std::cout); + return false; + } + } + return true; +} + +template bool +lp_core_solver_base::infeasibility_cost_is_correct_for_column(unsigned j) const { + T r = (!this->m_settings.use_breakpoints_in_feasibility_search)? -one_of_type(): one_of_type(); + + switch (this->m_column_types[j]) { + case column_type::fixed: + case column_type::boxed: + if (this->x_above_upper_bound(j)) { + return (this->m_costs[j] == r); + } + if (this->x_below_low_bound(j)) { + return (this->m_costs[j] == -r); + } + return is_zero(this->m_costs[j]); + + case column_type::low_bound: + if (this->x_below_low_bound(j)) { + return this->m_costs[j] == -r; + } + return is_zero(this->m_costs[j]); + + case column_type::upper_bound: + if (this->x_above_upper_bound(j)) { + return this->m_costs[j] == r; + } + return is_zero(this->m_costs[j]); + case column_type::free_column: + return is_zero(this->m_costs[j]); + default: + lean_assert(false); + return true; + } +} + +} diff --git a/src/util/lp/lp_core_solver_base_instances.cpp b/src/util/lp/lp_core_solver_base_instances.cpp new file mode 100644 index 000000000..17dcb87db --- /dev/null +++ b/src/util/lp/lp_core_solver_base_instances.cpp @@ -0,0 +1,131 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include +#include +#include +#include "util/vector.h" +#include +#include "util/lp/lp_core_solver_base.hpp" +template bool lean::lp_core_solver_base::A_mult_x_is_off() const; +template bool lean::lp_core_solver_base::A_mult_x_is_off_on_index(const vector &) const; +template bool lean::lp_core_solver_base::basis_heading_is_correct() const; +template void lean::lp_core_solver_base::calculate_pivot_row_of_B_1(unsigned int); +template void lean::lp_core_solver_base::calculate_pivot_row_when_pivot_row_of_B1_is_ready(unsigned); +template bool lean::lp_core_solver_base::column_is_dual_feasible(unsigned int) const; +template void lean::lp_core_solver_base::fill_reduced_costs_from_m_y_by_rows(); +template bool lean::lp_core_solver_base::find_x_by_solving(); +template lean::non_basic_column_value_position lean::lp_core_solver_base::get_non_basic_column_value_position(unsigned int) const; +template lean::non_basic_column_value_position lean::lp_core_solver_base >::get_non_basic_column_value_position(unsigned int) const; +template lean::non_basic_column_value_position lean::lp_core_solver_base::get_non_basic_column_value_position(unsigned int) const; +template void lean::lp_core_solver_base::init_reduced_costs_for_one_iteration(); +template lean::lp_core_solver_base::lp_core_solver_base( + lean::static_matrix&, vector&, + vector&, + vector &, vector &, + vector&, + vector&, + lean::lp_settings&, const column_namer&, const vector&, + const vector&, + const vector&); + +template bool lean::lp_core_solver_base::print_statistics_with_iterations_and_nonzeroes_and_cost_and_check_that_the_time_is_over(char const*, std::ostream &); +template bool lean::lp_core_solver_base >::print_statistics_with_iterations_and_nonzeroes_and_cost_and_check_that_the_time_is_over(char const*, std::ostream &); +template void lean::lp_core_solver_base::restore_x(unsigned int, double const&); +template void lean::lp_core_solver_base::set_non_basic_x_to_correct_bounds(); +template void lean::lp_core_solver_base::snap_xN_to_bounds_and_free_columns_to_zeroes(); +template void lean::lp_core_solver_base >::snap_xN_to_bounds_and_free_columns_to_zeroes(); +template void lean::lp_core_solver_base::solve_Ax_eq_b(); +template void lean::lp_core_solver_base::solve_Bd(unsigned int); +template void lean::lp_core_solver_base>::solve_Bd(unsigned int, indexed_vector&); +template void lean::lp_core_solver_base::solve_yB(vector&); +template bool lean::lp_core_solver_base::update_basis_and_x(int, int, double const&); +template void lean::lp_core_solver_base::update_x(unsigned int, const double&); +template bool lean::lp_core_solver_base::A_mult_x_is_off() const; +template bool lean::lp_core_solver_base::A_mult_x_is_off_on_index(const vector &) const; +template bool lean::lp_core_solver_base::basis_heading_is_correct() const ; +template void lean::lp_core_solver_base::calculate_pivot_row_of_B_1(unsigned int); +template void lean::lp_core_solver_base::calculate_pivot_row_when_pivot_row_of_B1_is_ready(unsigned); +template bool lean::lp_core_solver_base::column_is_dual_feasible(unsigned int) const; +template void lean::lp_core_solver_base::fill_reduced_costs_from_m_y_by_rows(); +template bool lean::lp_core_solver_base::find_x_by_solving(); +template void lean::lp_core_solver_base::init_reduced_costs_for_one_iteration(); +template bool lean::lp_core_solver_base::print_statistics_with_iterations_and_nonzeroes_and_cost_and_check_that_the_time_is_over(char const*, std::ostream &); +template void lean::lp_core_solver_base::restore_x(unsigned int, lean::mpq const&); +template void lean::lp_core_solver_base::set_non_basic_x_to_correct_bounds(); +template void lean::lp_core_solver_base::solve_Ax_eq_b(); +template void lean::lp_core_solver_base::solve_Bd(unsigned int); +template void lean::lp_core_solver_base::solve_yB(vector&); +template bool lean::lp_core_solver_base::update_basis_and_x(int, int, lean::mpq const&); +template void lean::lp_core_solver_base::update_x(unsigned int, const lean::mpq&); +template void lean::lp_core_solver_base >::calculate_pivot_row_of_B_1(unsigned int); +template void lean::lp_core_solver_base >::calculate_pivot_row_when_pivot_row_of_B1_is_ready(unsigned); +template void lean::lp_core_solver_base >::init(); +template void lean::lp_core_solver_base >::init_basis_heading_and_non_basic_columns_vector(); +template void lean::lp_core_solver_base >::init_reduced_costs_for_one_iteration(); +template lean::lp_core_solver_base >::lp_core_solver_base(lean::static_matrix >&, vector >&, vector&, vector &, vector &, vector >&, vector&, lean::lp_settings&, const column_namer&, const vector&, + const vector >&, + const vector >&); +template bool lean::lp_core_solver_base >::print_statistics_with_cost_and_check_that_the_time_is_over(lean::numeric_pair, std::ostream&); +template void lean::lp_core_solver_base >::snap_xN_to_bounds_and_fill_xB(); +template void lean::lp_core_solver_base >::solve_Bd(unsigned int); +template bool lean::lp_core_solver_base >::update_basis_and_x(int, int, lean::numeric_pair const&); +template void lean::lp_core_solver_base >::update_x(unsigned int, const lean::numeric_pair&); +template lean::lp_core_solver_base::lp_core_solver_base( + lean::static_matrix&, + vector&, + vector&, + vector &, vector &, + vector&, + vector&, + lean::lp_settings&, + const column_namer&, + const vector&, + const vector&, + const vector&); +template bool lean::lp_core_solver_base >::print_statistics_with_iterations_and_check_that_the_time_is_over(std::ostream &); +template std::string lean::lp_core_solver_base::column_name(unsigned int) const; +template void lean::lp_core_solver_base::pretty_print(std::ostream & out); +template void lean::lp_core_solver_base::restore_state(double*, double*); +template void lean::lp_core_solver_base::save_state(double*, double*); +template std::string lean::lp_core_solver_base::column_name(unsigned int) const; +template void lean::lp_core_solver_base::pretty_print(std::ostream & out); +template void lean::lp_core_solver_base::restore_state(lean::mpq*, lean::mpq*); +template void lean::lp_core_solver_base::save_state(lean::mpq*, lean::mpq*); +template std::string lean::lp_core_solver_base >::column_name(unsigned int) const; +template void lean::lp_core_solver_base >::pretty_print(std::ostream & out); +template void lean::lp_core_solver_base >::restore_state(lean::mpq*, lean::mpq*); +template void lean::lp_core_solver_base >::save_state(lean::mpq*, lean::mpq*); +template void lean::lp_core_solver_base >::solve_yB(vector&); +template void lean::lp_core_solver_base::init_lu(); +template void lean::lp_core_solver_base::init_lu(); +template int lean::lp_core_solver_base::pivots_in_column_and_row_are_different(int, int) const; +template int lean::lp_core_solver_base >::pivots_in_column_and_row_are_different(int, int) const; +template int lean::lp_core_solver_base::pivots_in_column_and_row_are_different(int, int) const; +template bool lean::lp_core_solver_base::calc_current_x_is_feasible_include_non_basis(void)const; +template bool lean::lp_core_solver_base::calc_current_x_is_feasible_include_non_basis(void)const; +template bool lean::lp_core_solver_base >::calc_current_x_is_feasible_include_non_basis() const; +template void lean::lp_core_solver_base >::pivot_fixed_vars_from_basis(); +template bool lean::lp_core_solver_base::column_is_feasible(unsigned int) const; +template bool lean::lp_core_solver_base::column_is_feasible(unsigned int) const; +// template void lean::lp_core_solver_base >::print_linear_combination_of_column_indices(vector, std::allocator > > const&, std::ostream&) const; +template bool lean::lp_core_solver_base >::column_is_feasible(unsigned int) const; +template bool lean::lp_core_solver_base >::snap_non_basic_x_to_bound(); +template void lean::lp_core_solver_base >::init_lu(); +template bool lean::lp_core_solver_base >::A_mult_x_is_off_on_index(vector const&) const; +template bool lean::lp_core_solver_base >::find_x_by_solving(); +template void lean::lp_core_solver_base >::restore_x(unsigned int, lean::numeric_pair const&); +template bool lean::lp_core_solver_base::pivot_for_tableau_on_basis(); +template bool lean::lp_core_solver_base::pivot_for_tableau_on_basis(); +template bool lean::lp_core_solver_base>::pivot_for_tableau_on_basis(); +template bool lean::lp_core_solver_base>::pivot_column_tableau(unsigned int, unsigned int); +template bool lean::lp_core_solver_base::pivot_column_tableau(unsigned int, unsigned int); +template bool lean::lp_core_solver_base::pivot_column_tableau(unsigned int, unsigned int); +template void lean::lp_core_solver_base >::transpose_rows_tableau(unsigned int, unsigned int); +template bool lean::lp_core_solver_base >::inf_set_is_correct() const; +template bool lean::lp_core_solver_base::inf_set_is_correct() const; +template bool lean::lp_core_solver_base::inf_set_is_correct() const; +template bool lean::lp_core_solver_base >::infeasibility_costs_are_correct() const; +template bool lean::lp_core_solver_base::infeasibility_costs_are_correct() const; +template bool lean::lp_core_solver_base::infeasibility_costs_are_correct() const; diff --git a/src/util/lp/lp_dual_core_solver.h b/src/util/lp/lp_dual_core_solver.h new file mode 100644 index 000000000..b873cb711 --- /dev/null +++ b/src/util/lp/lp_dual_core_solver.h @@ -0,0 +1,197 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#pragma once +#include "util/lp/static_matrix.h" +#include "util/lp/lp_core_solver_base.h" +#include +#include +#include +#include +#include "util/vector.h" + +namespace lean { +template +class lp_dual_core_solver:public lp_core_solver_base { +public: + vector & m_can_enter_basis; + int m_r; // the row of the leaving column + int m_p; // leaving column; that is m_p = m_basis[m_r] + T m_delta; // the offset of the leaving basis variable + int m_sign_of_alpha_r; // see page 27 + T m_theta_D; + T m_theta_P; + int m_q; + // todo : replace by a vector later + std::set m_breakpoint_set; // it is F in "Progress in the dual simplex method ..." + std::set m_flipped_boxed; + std::set m_tight_set; // it is the set of all breakpoints that become tight when m_q becomes tight + vector m_a_wave; + vector m_betas; // m_betas[i] is approximately a square of the norm of the i-th row of the reverse of B + T m_harris_tolerance; + std::set m_forbidden_rows; + + lp_dual_core_solver(static_matrix & A, + vector & can_enter_basis, + vector & b, // the right side vector + vector & x, // the number of elements in x needs to be at least as large as the number of columns in A + vector & basis, + vector & nbasis, + vector & heading, + vector & costs, + vector & column_type_array, + vector & low_bound_values, + vector & upper_bound_values, + lp_settings & settings, + const column_namer & column_names): + lp_core_solver_base(A, + b, + basis, + nbasis, + heading, + x, + costs, + settings, + column_names, + column_type_array, + low_bound_values, + upper_bound_values), + m_can_enter_basis(can_enter_basis), + m_a_wave(this->m_m()), + m_betas(this->m_m()) { + m_harris_tolerance = numeric_traits::precise()? numeric_traits::zero() : T(this->m_settings.harris_feasibility_tolerance); + this->solve_yB(this->m_y); + this->init_basic_part_of_basis_heading(); + fill_non_basis_with_only_able_to_enter_columns(); + } + + void init_a_wave_by_zeros(); + + void fill_non_basis_with_only_able_to_enter_columns() { + auto & nb = this->m_nbasis; + nb.reset(); + unsigned j = this->m_n(); + while (j--) { + if (this->m_basis_heading[j] >= 0 || !m_can_enter_basis[j]) continue; + nb.push_back(j); + this->m_basis_heading[j] = - static_cast(nb.size()); + } + } + + void restore_non_basis(); + + bool update_basis(int entering, int leaving); + + void recalculate_xB_and_d(); + + void recalculate_d(); + + void init_betas(); + + void adjust_xb_for_changed_xn_and_init_betas(); + + void start_with_initial_basis_and_make_it_dual_feasible(); + + bool done(); + + T get_edge_steepness_for_low_bound(unsigned p); + + T get_edge_steepness_for_upper_bound(unsigned p); + + T pricing_for_row(unsigned i); + + void pricing_loop(unsigned number_of_rows_to_try, unsigned offset_in_rows); + + bool advance_on_known_p(); + + int define_sign_of_alpha_r(); + + bool can_be_breakpoint(unsigned j); + + void fill_breakpoint_set(); + + void DSE_FTran(); + T get_delta(); + + void restore_d(); + + bool d_is_correct(); + + void xb_minus_delta_p_pivot_column(); + + void update_betas(); + + void apply_flips(); + + void snap_xN_column_to_bounds(unsigned j); + + void snap_xN_to_bounds(); + + void init_beta_precisely(unsigned i); + + void init_betas_precisely(); + + // step 7 of the algorithm from Progress + bool basis_change_and_update(); + + void revert_to_previous_basis(); + + non_basic_column_value_position m_entering_boundary_position; + bool update_basis_and_x_local(int entering, int leaving, X const & tt); + void recover_leaving(); + + bool problem_is_dual_feasible() const; + + bool snap_runaway_nonbasic_column(unsigned); + + bool snap_runaway_nonbasic_columns(); + + unsigned get_number_of_rows_to_try_for_leaving(); + + void update_a_wave(const T & del, unsigned j) { + this->m_A.add_column_to_vector(del, j, & m_a_wave[0]); + } + + bool delta_keeps_the_sign(int initial_delta_sign, const T & delta); + + void set_status_to_tentative_dual_unbounded_or_dual_unbounded(); + + // it is positive if going from low bound to upper bound and negative if going from upper bound to low bound + T signed_span_of_boxed(unsigned j) { + return this->x_is_at_low_bound(j)? this->bound_span(j): - this->bound_span(j); + } + + void add_tight_breakpoints_and_q_to_flipped_set(); + + T delta_lost_on_flips_of_tight_breakpoints(); + + bool tight_breakpoinst_are_all_boxed(); + + T calculate_harris_delta_on_breakpoint_set(); + + void fill_tight_set_on_harris_delta(const T & harris_delta ); + + void find_q_on_tight_set(); + + void find_q_and_tight_set(); + + void erase_tight_breakpoints_and_q_from_breakpoint_set(); + + bool ratio_test(); + + void process_flipped(); + void update_d_and_xB(); + + void calculate_beta_r_precisely(); + // see "Progress in the dual simplex method for large scale LP problems: practical dual phase 1 algorithms" + + void update_xb_after_bound_flips(); + + void one_iteration(); + + void solve(); + + bool low_bounds_are_set() const { return true; } +}; +} diff --git a/src/util/lp/lp_dual_core_solver.hpp b/src/util/lp/lp_dual_core_solver.hpp new file mode 100644 index 000000000..92a84e238 --- /dev/null +++ b/src/util/lp/lp_dual_core_solver.hpp @@ -0,0 +1,743 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include +#include +#include "util/vector.h" +#include "util/lp/lp_dual_core_solver.h" + +namespace lean { + +template void lp_dual_core_solver::init_a_wave_by_zeros() { + unsigned j = this->m_m(); + while (j--) { + m_a_wave[j] = numeric_traits::zero(); + } +} + +template void lp_dual_core_solver::restore_non_basis() { + auto & nb = this->m_nbasis; + nb.reset(); + unsigned j = this->m_n(); + while (j--) { + if (this->m_basis_heading[j] >= 0 ) continue; + if (m_can_enter_basis[j]) { + lean_assert(std::find(nb.begin(), nb.end(), j) == nb.end()); + nb.push_back(j); + this->m_basis_heading[j] = - static_cast(nb.size()); + } + } +} + +template bool lp_dual_core_solver::update_basis(int entering, int leaving) { + // the second argument is the element of the entering column from the pivot row - its value should be equal to the low diagonal element of the bump after all pivoting is done + if (this->m_refactor_counter++ < 200) { + this->m_factorization->replace_column(this->m_ed[this->m_factorization->basis_heading(leaving)], this->m_w); + if (this->m_factorization->get_status() == LU_status::OK) { + this->m_factorization->change_basis(entering, leaving); + return true; + } + } + // need to refactor + this->m_factorization->change_basis(entering, leaving); + init_factorization(this->m_factorization, this->m_A, this->m_basis, this->m_basis_heading, this->m_settings); + this->m_refactor_counter = 0; + if (this->m_factorization->get_status() != LU_status::OK) { + LP_OUT(this->m_settings, "failing refactor for entering = " << entering << ", leaving = " << leaving << " total_iterations = " << this->total_iterations() << std::endl); + this->m_iters_with_no_cost_growing++; + return false; + } + return true; +} + +template void lp_dual_core_solver::recalculate_xB_and_d() { + this->solve_Ax_eq_b(); + recalculate_d(); +} + +template void lp_dual_core_solver::recalculate_d() { + this->solve_yB(this->m_y); + this->fill_reduced_costs_from_m_y_by_rows(); +} + +template void lp_dual_core_solver::init_betas() { + // todo : look at page 194 of Progress in the dual simplex algorithm for solving large scale LP problems : techniques for a fast and stable implementation + // the current implementation is not good enough: todo + unsigned i = this->m_m(); + while (i--) { + m_betas[i] = 1; + } +} + +template void lp_dual_core_solver::adjust_xb_for_changed_xn_and_init_betas() { + this->solve_Ax_eq_b(); + init_betas(); +} + +template void lp_dual_core_solver::start_with_initial_basis_and_make_it_dual_feasible() { + this->set_non_basic_x_to_correct_bounds(); // It is not an efficient version, see 3.29, + // however this version does not require that m_x is the solution of Ax = 0 beforehand + adjust_xb_for_changed_xn_and_init_betas(); +} + +template bool lp_dual_core_solver::done() { + if (this->get_status() == OPTIMAL) { + return true; + } + if (this->total_iterations() > this->m_settings.max_total_number_of_iterations) { // debug !!!! + this->set_status(ITERATIONS_EXHAUSTED); + return true; + } + return false; // todo, need to be more cases +} + +template T lp_dual_core_solver::get_edge_steepness_for_low_bound(unsigned p) { + lean_assert(this->m_basis_heading[p] >= 0 && static_cast(this->m_basis_heading[p]) < this->m_m()); + T del = this->m_x[p] - this->m_low_bounds[p]; + del *= del; + return del / this->m_betas[this->m_basis_heading[p]]; +} + +template T lp_dual_core_solver::get_edge_steepness_for_upper_bound(unsigned p) { + lean_assert(this->m_basis_heading[p] >= 0 && static_cast(this->m_basis_heading[p]) < this->m_m()); + T del = this->m_x[p] - this->m_upper_bounds[p]; + del *= del; + return del / this->m_betas[this->m_basis_heading[p]]; +} + +template T lp_dual_core_solver::pricing_for_row(unsigned i) { + unsigned p = this->m_basis[i]; + switch (this->m_column_types[p]) { + case column_type::fixed: + case column_type::boxed: + if (this->x_below_low_bound(p)) { + T del = get_edge_steepness_for_low_bound(p); + return del; + } + if (this->x_above_upper_bound(p)) { + T del = get_edge_steepness_for_upper_bound(p); + return del; + } + return numeric_traits::zero(); + case column_type::low_bound: + if (this->x_below_low_bound(p)) { + T del = get_edge_steepness_for_low_bound(p); + return del; + } + return numeric_traits::zero(); + break; + case column_type::upper_bound: + if (this->x_above_upper_bound(p)) { + T del = get_edge_steepness_for_upper_bound(p); + return del; + } + return numeric_traits::zero(); + break; + case column_type::free_column: + lean_assert(numeric_traits::is_zero(this->m_d[p])); + return numeric_traits::zero(); + default: + lean_unreachable(); + } + lean_unreachable(); + return numeric_traits::zero(); +} + +template void lp_dual_core_solver::pricing_loop(unsigned number_of_rows_to_try, unsigned offset_in_rows) { + m_r = -1; + T steepest_edge_max = numeric_traits::zero(); + unsigned initial_offset_in_rows = offset_in_rows; + unsigned i = offset_in_rows; + unsigned rows_left = number_of_rows_to_try; + do { + if (m_forbidden_rows.find(i) != m_forbidden_rows.end()) { + if (++i == this->m_m()) { + i = 0; + } + continue; + } + T se = pricing_for_row(i); + if (se > steepest_edge_max) { + steepest_edge_max = se; + m_r = i; + if (rows_left > 0) { + rows_left--; + } + } + if (++i == this->m_m()) { + i = 0; + } + } while (i != initial_offset_in_rows && rows_left); + if (m_r == -1) { + if (this->get_status() != UNSTABLE) { + this->set_status(OPTIMAL); + } + } else { + m_p = this->m_basis[m_r]; + m_delta = get_delta(); + if (advance_on_known_p()){ + m_forbidden_rows.clear(); + return; + } + // failure in advance_on_known_p + if (this->get_status() == FLOATING_POINT_ERROR) { + return; + } + this->set_status(UNSTABLE); + m_forbidden_rows.insert(m_r); + } +} + + // this calculation is needed for the steepest edge update, + // it hijackes m_pivot_row_of_B_1 for this purpose since we will need it anymore to the end of the cycle +template void lp_dual_core_solver::DSE_FTran() { // todo, see algorithm 7 from page 35 + this->m_factorization->solve_By_for_T_indexed_only(this->m_pivot_row_of_B_1, this->m_settings); +} + +template bool lp_dual_core_solver::advance_on_known_p() { + if (done()) { + return true; + } + this->calculate_pivot_row_of_B_1(m_r); + this->calculate_pivot_row_when_pivot_row_of_B1_is_ready(m_r); + if (!ratio_test()) { + return true; + } + calculate_beta_r_precisely(); + this->solve_Bd(m_q); // FTRAN + int pivot_compare_result = this->pivots_in_column_and_row_are_different(m_q, m_p); + if (!pivot_compare_result){;} + else if (pivot_compare_result == 2) { // the sign is changed, cannot continue + lean_unreachable(); // not implemented yet + } else { + lean_assert(pivot_compare_result == 1); + this->init_lu(); + } + DSE_FTran(); + return basis_change_and_update(); +} + +template int lp_dual_core_solver::define_sign_of_alpha_r() { + switch (this->m_column_types[m_p]) { + case column_type::boxed: + case column_type::fixed: + if (this->x_below_low_bound(m_p)) { + return -1; + } + if (this->x_above_upper_bound(m_p)) { + return 1; + } + lean_unreachable(); + case column_type::low_bound: + if (this->x_below_low_bound(m_p)) { + return -1; + } + lean_unreachable(); + case column_type::upper_bound: + if (this->x_above_upper_bound(m_p)) { + return 1; + } + lean_unreachable(); + default: + lean_unreachable(); + } + lean_unreachable(); + return 0; +} + +template bool lp_dual_core_solver::can_be_breakpoint(unsigned j) { + if (this->pivot_row_element_is_too_small_for_ratio_test(j)) return false; + switch (this->m_column_types[j]) { + case column_type::low_bound: + lean_assert(this->m_settings.abs_val_is_smaller_than_harris_tolerance(this->m_x[j] - this->m_low_bounds[j])); + return m_sign_of_alpha_r * this->m_pivot_row[j] > 0; + case column_type::upper_bound: + lean_assert(this->m_settings.abs_val_is_smaller_than_harris_tolerance(this->m_x[j] - this->m_upper_bounds[j])); + return m_sign_of_alpha_r * this->m_pivot_row[j] < 0; + case column_type::boxed: + { + bool low_bound = this->x_is_at_low_bound(j); + bool grawing = m_sign_of_alpha_r * this->m_pivot_row[j] > 0; + return low_bound == grawing; + } + case column_type::fixed: // is always dual feasible so we ingore it + return false; + case column_type::free_column: + return true; + default: + return false; + } +} + +template void lp_dual_core_solver::fill_breakpoint_set() { + m_breakpoint_set.clear(); + for (unsigned j : this->non_basis()) { + if (can_be_breakpoint(j)) { + m_breakpoint_set.insert(j); + } + } +} + +// template void lp_dual_core_solver::FTran() { +// this->solve_Bd(m_q); +// } + +template T lp_dual_core_solver::get_delta() { + switch (this->m_column_types[m_p]) { + case column_type::boxed: + if (this->x_below_low_bound(m_p)) { + return this->m_x[m_p] - this->m_low_bounds[m_p]; + } + if (this->x_above_upper_bound(m_p)) { + return this->m_x[m_p] - this->m_upper_bounds[m_p]; + } + lean_unreachable(); + case column_type::low_bound: + if (this->x_below_low_bound(m_p)) { + return this->m_x[m_p] - this->m_low_bounds[m_p]; + } + lean_unreachable(); + case column_type::upper_bound: + if (this->x_above_upper_bound(m_p)) { + return get_edge_steepness_for_upper_bound(m_p); + } + lean_unreachable(); + case column_type::fixed: + return this->m_x[m_p] - this->m_upper_bounds[m_p]; + default: + lean_unreachable(); + } + lean_unreachable(); + return zero_of_type(); +} + +template void lp_dual_core_solver::restore_d() { + this->m_d[m_p] = numeric_traits::zero(); + for (auto j : this->non_basis()) { + this->m_d[j] += m_theta_D * this->m_pivot_row[j]; + } +} + +template bool lp_dual_core_solver::d_is_correct() { + this->solve_yB(this->m_y); + for (auto j : this->non_basis()) { + T d = this->m_costs[j] - this->m_A.dot_product_with_column(this->m_y, j); + if (numeric_traits::get_double(abs(d - this->m_d[j])) >= 0.001) { + LP_OUT(this->m_settings, "total_iterations = " << this->total_iterations() << std::endl + << "d[" << j << "] = " << this->m_d[j] << " but should be " << d << std::endl); + return false; + } + } + return true; +} + +template void lp_dual_core_solver::xb_minus_delta_p_pivot_column() { + unsigned i = this->m_m(); + while (i--) { + this->m_x[this->m_basis[i]] -= m_theta_P * this->m_ed[i]; + } +} + +template void lp_dual_core_solver::update_betas() { // page 194 of Progress ... todo - once in a while betas have to be reinitialized + T one_over_arq = numeric_traits::one() / this->m_pivot_row[m_q]; + T beta_r = this->m_betas[m_r] = std::max(T(0.0001), (m_betas[m_r] * one_over_arq) * one_over_arq); + T k = -2 * one_over_arq; + unsigned i = this->m_m(); + while (i--) { + if (static_cast(i) == m_r) continue; + T a = this->m_ed[i]; + m_betas[i] += a * (a * beta_r + k * this->m_pivot_row_of_B_1[i]); + if (m_betas[i] < T(0.0001)) + m_betas[i] = T(0.0001); + } +} + +template void lp_dual_core_solver::apply_flips() { + for (unsigned j : m_flipped_boxed) { + lean_assert(this->x_is_at_bound(j)); + if (this->x_is_at_low_bound(j)) { + this->m_x[j] = this->m_upper_bounds[j]; + } else { + this->m_x[j] = this->m_low_bounds[j]; + } + } +} + +template void lp_dual_core_solver::snap_xN_column_to_bounds(unsigned j) { + switch (this->m_column_type[j]) { + case column_type::fixed: + this->m_x[j] = this->m_low_bounds[j]; + break; + case column_type::boxed: + if (this->x_is_at_low_bound(j)) { + this->m_x[j] = this->m_low_bounds[j]; + } else { + this->m_x[j] = this->m_upper_bounds[j]; + } + break; + case column_type::low_bound: + this->m_x[j] = this->m_low_bounds[j]; + break; + case column_type::upper_bound: + this->m_x[j] = this->m_upper_bounds[j]; + break; + case column_type::free_column: + break; + default: + lean_unreachable(); + } +} + +template void lp_dual_core_solver::snap_xN_to_bounds() { + for (auto j : this->non_basis()) { + snap_xN_column_to_bounds(j); + } +} + +template void lp_dual_core_solver::init_beta_precisely(unsigned i) { + vector vec(this->m_m(), numeric_traits::zero()); + vec[i] = numeric_traits::one(); + this->m_factorization->solve_yB_with_error_check(vec, this->m_basis); + T beta = numeric_traits::zero(); + for (T & v : vec) { + beta += v * v; + } + this->m_betas[i] =beta; +} + +template void lp_dual_core_solver::init_betas_precisely() { + unsigned i = this->m_m(); + while (i--) { + init_beta_precisely(i); + } +} + +// step 7 of the algorithm from Progress +template bool lp_dual_core_solver::basis_change_and_update() { + update_betas(); + update_d_and_xB(); + // m_theta_P = m_delta / this->m_ed[m_r]; + m_theta_P = m_delta / this->m_pivot_row[m_q]; + // xb_minus_delta_p_pivot_column(); + apply_flips(); + if (!this->update_basis_and_x(m_q, m_p, m_theta_P)) { + init_betas_precisely(); + return false; + } + + if (snap_runaway_nonbasic_column(m_p)) { + if (!this->find_x_by_solving()) { + revert_to_previous_basis(); + this->m_iters_with_no_cost_growing++; + return false; + } + } + + if (!problem_is_dual_feasible()) { + // todo : shift the costs!!!! + revert_to_previous_basis(); + this->m_iters_with_no_cost_growing++; + return false; + } + + lean_assert(d_is_correct()); + return true; +} + +template void lp_dual_core_solver::recover_leaving() { + switch (m_entering_boundary_position) { + case at_low_bound: + case at_fixed: + this->m_x[m_q] = this->m_low_bounds[m_q]; + break; + case at_upper_bound: + this->m_x[m_q] = this->m_upper_bounds[m_q]; + break; + case free_of_bounds: + this->m_x[m_q] = zero_of_type(); + default: + lean_unreachable(); + } +} + +template void lp_dual_core_solver::revert_to_previous_basis() { + LP_OUT(this->m_settings, "revert to previous basis on ( " << m_p << ", " << m_q << ")" << std::endl); + this->change_basis_unconditionally(m_p, m_q); + init_factorization(this->m_factorization, this->m_A, this->m_basis, this->m_settings); + if (this->m_factorization->get_status() != LU_status::OK) { + this->set_status(FLOATING_POINT_ERROR); // complete failure + return; + } + recover_leaving(); + if (!this->find_x_by_solving()) { + this->set_status(FLOATING_POINT_ERROR); + return; + } + recalculate_xB_and_d(); + init_betas_precisely(); +} + +// returns true if the column has been snapped +template bool lp_dual_core_solver::snap_runaway_nonbasic_column(unsigned j) { + switch (this->m_column_types[j]) { + case column_type::fixed: + case column_type::low_bound: + if (!this->x_is_at_low_bound(j)) { + this->m_x[j] = this->m_low_bounds[j]; + return true; + } + break; + case column_type::boxed: + { + bool closer_to_low_bound = abs(this->m_low_bounds[j] - this->m_x[j]) < abs(this->m_upper_bounds[j] - this->m_x[j]); + if (closer_to_low_bound) { + if (!this->x_is_at_low_bound(j)) { + this->m_x[j] = this->m_low_bounds[j]; + return true; + } + } else { + if (!this->x_is_at_upper_bound(j)) { + this->m_x[j] = this->m_low_bounds[j]; + return true; + } + } + } + break; + case column_type::upper_bound: + if (!this->x_is_at_upper_bound(j)) { + this->m_x[j] = this->m_upper_bounds[j]; + return true; + } + break; + default: + break; + } + return false; +} + + +template bool lp_dual_core_solver::problem_is_dual_feasible() const { + for (unsigned j : this->non_basis()){ + if (!this->column_is_dual_feasible(j)) { + // std::cout << "column " << j << " is not dual feasible" << std::endl; + // std::cout << "m_d[" << j << "] = " << this->m_d[j] << std::endl; + // std::cout << "x[" << j << "] = " << this->m_x[j] << std::endl; + // std::cout << "type = " << column_type_to_string(this->m_column_type[j]) << std::endl; + // std::cout << "bounds = " << this->m_low_bounds[j] << "," << this->m_upper_bounds[j] << std::endl; + // std::cout << "total_iterations = " << this->total_iterations() << std::endl; + return false; + } + } + return true; +} + +template unsigned lp_dual_core_solver::get_number_of_rows_to_try_for_leaving() { + unsigned s = this->m_m(); + if (this->m_m() > 300) { + s = (unsigned)((s / 100.0) * this->m_settings.percent_of_entering_to_check); + } + return my_random() % s + 1; +} + +template bool lp_dual_core_solver::delta_keeps_the_sign(int initial_delta_sign, const T & delta) { + if (numeric_traits::precise()) + return ((delta > numeric_traits::zero()) && (initial_delta_sign == 1)) || + ((delta < numeric_traits::zero()) && (initial_delta_sign == -1)); + + double del = numeric_traits::get_double(delta); + return ( (del > this->m_settings.zero_tolerance) && (initial_delta_sign == 1)) || + ((del < - this->m_settings.zero_tolerance) && (initial_delta_sign == -1)); +} + +template void lp_dual_core_solver::set_status_to_tentative_dual_unbounded_or_dual_unbounded() { + if (this->get_status() == TENTATIVE_DUAL_UNBOUNDED) { + this->set_status(DUAL_UNBOUNDED); + } else { + this->set_status(TENTATIVE_DUAL_UNBOUNDED); + } +} + +template void lp_dual_core_solver::add_tight_breakpoints_and_q_to_flipped_set() { + m_flipped_boxed.insert(m_q); + for (auto j : m_tight_set) { + m_flipped_boxed.insert(j); + } +} + +template T lp_dual_core_solver::delta_lost_on_flips_of_tight_breakpoints() { + T ret = abs(this->bound_span(m_q) * this->m_pivot_row[m_q]); + for (auto j : m_tight_set) { + ret += abs(this->bound_span(j) * this->m_pivot_row[j]); + } + return ret; +} + +template bool lp_dual_core_solver::tight_breakpoinst_are_all_boxed() { + if (this->m_column_types[m_q] != column_type::boxed) return false; + for (auto j : m_tight_set) { + if (this->m_column_types[j] != column_type::boxed) return false; + } + return true; +} + +template T lp_dual_core_solver::calculate_harris_delta_on_breakpoint_set() { + bool first_time = true; + T ret = zero_of_type(); + lean_assert(m_breakpoint_set.size() > 0); + for (auto j : m_breakpoint_set) { + T t; + if (this->x_is_at_low_bound(j)) { + t = abs((std::max(this->m_d[j], numeric_traits::zero()) + m_harris_tolerance) / this->m_pivot_row[j]); + } else { + t = abs((std::min(this->m_d[j], numeric_traits::zero()) - m_harris_tolerance) / this->m_pivot_row[j]); + } + if (first_time) { + ret = t; + first_time = false; + } else if (t < ret) { + ret = t; + } + } + return ret; +} + +template void lp_dual_core_solver::fill_tight_set_on_harris_delta(const T & harris_delta ){ + m_tight_set.clear(); + for (auto j : m_breakpoint_set) { + if (this->x_is_at_low_bound(j)) { + if (abs(std::max(this->m_d[j], numeric_traits::zero()) / this->m_pivot_row[j]) <= harris_delta){ + m_tight_set.insert(j); + } + } else { + if (abs(std::min(this->m_d[j], numeric_traits::zero() ) / this->m_pivot_row[j]) <= harris_delta){ + m_tight_set.insert(j); + } + } + } +} + +template void lp_dual_core_solver::find_q_on_tight_set() { + m_q = -1; + T max_pivot; + for (auto j : m_tight_set) { + T r = abs(this->m_pivot_row[j]); + if (m_q != -1) { + if (r > max_pivot) { + max_pivot = r; + m_q = j; + } + } else { + max_pivot = r; + m_q = j; + } + } + m_tight_set.erase(m_q); + lean_assert(m_q != -1); +} + +template void lp_dual_core_solver::find_q_and_tight_set() { + T harris_del = calculate_harris_delta_on_breakpoint_set(); + fill_tight_set_on_harris_delta(harris_del); + find_q_on_tight_set(); + m_entering_boundary_position = this->get_non_basic_column_value_position(m_q); +} + +template void lp_dual_core_solver::erase_tight_breakpoints_and_q_from_breakpoint_set() { + m_breakpoint_set.erase(m_q); + for (auto j : m_tight_set) { + m_breakpoint_set.erase(j); + } +} + +template bool lp_dual_core_solver::ratio_test() { + m_sign_of_alpha_r = define_sign_of_alpha_r(); + fill_breakpoint_set(); + m_flipped_boxed.clear(); + int initial_delta_sign = m_delta >= numeric_traits::zero()? 1: -1; + do { + if (m_breakpoint_set.size() == 0) { + set_status_to_tentative_dual_unbounded_or_dual_unbounded(); + return false; + } + this->set_status(FEASIBLE); + find_q_and_tight_set(); + if (!tight_breakpoinst_are_all_boxed()) break; + T del = m_delta - delta_lost_on_flips_of_tight_breakpoints() * initial_delta_sign; + if (!delta_keeps_the_sign(initial_delta_sign, del)) break; + if (m_tight_set.size() + 1 == m_breakpoint_set.size()) { + break; // deciding not to flip since we might get stuck without finding m_q, the column entering the basis + } + // we can flip m_q together with the tight set and look for another breakpoint candidate for m_q and another tight set + add_tight_breakpoints_and_q_to_flipped_set(); + m_delta = del; + erase_tight_breakpoints_and_q_from_breakpoint_set(); + } while (true); + m_theta_D = this->m_d[m_q] / this->m_pivot_row[m_q]; + return true; +} + +template void lp_dual_core_solver::process_flipped() { + init_a_wave_by_zeros(); + for (auto j : m_flipped_boxed) { + update_a_wave(signed_span_of_boxed(j), j); + } +} +template void lp_dual_core_solver::update_d_and_xB() { + for (auto j : this->non_basis()) { + this->m_d[j] -= m_theta_D * this->m_pivot_row[j]; + } + this->m_d[m_p] = - m_theta_D; + if (m_flipped_boxed.size() > 0) { + process_flipped(); + update_xb_after_bound_flips(); + } +} + +template void lp_dual_core_solver::calculate_beta_r_precisely() { + T t = numeric_traits::zero(); + unsigned i = this->m_m(); + while (i--) { + T b = this->m_pivot_row_of_B_1[i]; + t += b * b; + } + m_betas[m_r] = t; +} +// see "Progress in the dual simplex method for large scale LP problems: practical dual phase 1 algorithms" + +template void lp_dual_core_solver::update_xb_after_bound_flips() { + this->m_factorization->solve_By(m_a_wave); + unsigned i = this->m_m(); + while (i--) { + this->m_x[this->m_basis[i]] -= m_a_wave[i]; + } +} + +template void lp_dual_core_solver::one_iteration() { + unsigned number_of_rows_to_try = get_number_of_rows_to_try_for_leaving(); + unsigned offset_in_rows = my_random() % this->m_m(); + if (this->get_status() == TENTATIVE_DUAL_UNBOUNDED) { + number_of_rows_to_try = this->m_m(); + } else { + this->set_status(FEASIBLE); + } + pricing_loop(number_of_rows_to_try, offset_in_rows); + lean_assert(problem_is_dual_feasible()); +} + +template void lp_dual_core_solver::solve() { // see the page 35 + lean_assert(d_is_correct()); + lean_assert(problem_is_dual_feasible()); + lean_assert(this->basis_heading_is_correct()); + this->set_total_iterations(0); + this->m_iters_with_no_cost_growing = 0; + do { + if (this->print_statistics_with_iterations_and_nonzeroes_and_cost_and_check_that_the_time_is_over("", *this->m_settings.get_message_ostream())){ + return; + } + one_iteration(); + } while (this->get_status() != FLOATING_POINT_ERROR && this->get_status() != DUAL_UNBOUNDED && this->get_status() != OPTIMAL && + this->m_iters_with_no_cost_growing <= this->m_settings.max_number_of_iterations_with_no_improvements + && this->total_iterations() <= this->m_settings.max_total_number_of_iterations); +} +} diff --git a/src/util/lp/lp_dual_core_solver_instances.cpp b/src/util/lp/lp_dual_core_solver_instances.cpp new file mode 100644 index 000000000..8016088f8 --- /dev/null +++ b/src/util/lp/lp_dual_core_solver_instances.cpp @@ -0,0 +1,29 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include +#include +#include +#include "util/vector.h" +#include +#include "util/lp/lp_dual_core_solver.hpp" +template void lean::lp_dual_core_solver::start_with_initial_basis_and_make_it_dual_feasible(); +template void lean::lp_dual_core_solver::solve(); +template lean::lp_dual_core_solver::lp_dual_core_solver(lean::static_matrix&, vector&, + vector&, + vector&, + vector&, + vector &, + vector &, + vector&, + vector&, + vector&, + vector&, + lean::lp_settings&, const lean::column_namer&); +template void lean::lp_dual_core_solver::start_with_initial_basis_and_make_it_dual_feasible(); +template void lean::lp_dual_core_solver::solve(); +template void lean::lp_dual_core_solver::restore_non_basis(); +template void lean::lp_dual_core_solver::restore_non_basis(); +template void lean::lp_dual_core_solver::revert_to_previous_basis(); +template void lean::lp_dual_core_solver::revert_to_previous_basis(); diff --git a/src/util/lp/lp_dual_simplex.h b/src/util/lp/lp_dual_simplex.h new file mode 100644 index 000000000..78ff08b0a --- /dev/null +++ b/src/util/lp/lp_dual_simplex.h @@ -0,0 +1,79 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#pragma once +#include "util/vector.h" +#include "util/lp/lp_utils.h" +#include "util/lp/lp_solver.h" +#include "util/lp/lp_dual_core_solver.h" +namespace lean { + +template +class lp_dual_simplex: public lp_solver { + lp_dual_core_solver * m_core_solver = nullptr; + vector m_b_copy; + vector m_low_bounds; // We don't have a convention here that all low bounds are zeros. At least it does not hold for the first stage solver + vector m_column_types_of_core_solver; + vector m_column_types_of_logicals; + vector m_can_enter_basis; +public: + ~lp_dual_simplex() { + if (m_core_solver != nullptr) { + delete m_core_solver; + } + } + + + + void decide_on_status_after_stage1(); + + void fix_logical_for_stage2(unsigned j); + + void fix_structural_for_stage2(unsigned j); + + void unmark_boxed_and_fixed_columns_and_fix_structural_costs(); + + void restore_right_sides(); + + void solve_for_stage2(); + + void fill_x_with_zeros(); + + void stage1(); + + void stage2(); + + void fill_first_stage_solver_fields(); + + column_type get_column_type(unsigned j); + + void fill_costs_bounds_types_and_can_enter_basis_for_the_first_stage_solver_structural_column(unsigned j); + + void fill_costs_bounds_types_and_can_enter_basis_for_the_first_stage_solver_logical_column(unsigned j); + + void fill_costs_and_bounds_and_column_types_for_the_first_stage_solver(); + + void set_type_for_logical(unsigned j, column_type col_type) { + this->m_column_types_of_logicals[j - this->number_of_core_structurals()] = col_type; + } + + void fill_first_stage_solver_fields_for_row_slack_and_artificial(unsigned row, + unsigned & slack_var, + unsigned & artificial); + + void augment_matrix_A_and_fill_x_and_allocate_some_fields(); + + + + void copy_m_b_aside_and_set_it_to_zeros(); + + void find_maximal_solution(); + + virtual T get_column_value(unsigned column) const { + return this->get_column_value_with_core_solver(column, m_core_solver); + } + + T get_current_cost() const; +}; +} diff --git a/src/util/lp/lp_dual_simplex.hpp b/src/util/lp/lp_dual_simplex.hpp new file mode 100644 index 000000000..5047e117f --- /dev/null +++ b/src/util/lp/lp_dual_simplex.hpp @@ -0,0 +1,362 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include "util/lp/lp_dual_simplex.h" +namespace lean{ + +template void lp_dual_simplex::decide_on_status_after_stage1() { + switch (m_core_solver->get_status()) { + case OPTIMAL: + if (this->m_settings.abs_val_is_smaller_than_artificial_tolerance(m_core_solver->get_cost())) { + this->m_status = FEASIBLE; + } else { + this->m_status = UNBOUNDED; + } + break; + case DUAL_UNBOUNDED: + lean_unreachable(); + case ITERATIONS_EXHAUSTED: + this->m_status = ITERATIONS_EXHAUSTED; + break; + case TIME_EXHAUSTED: + this->m_status = TIME_EXHAUSTED; + break; + case FLOATING_POINT_ERROR: + this->m_status = FLOATING_POINT_ERROR; + break; + default: + lean_unreachable(); + } +} + +template void lp_dual_simplex::fix_logical_for_stage2(unsigned j) { + lean_assert(j >= this->number_of_core_structurals()); + switch (m_column_types_of_logicals[j - this->number_of_core_structurals()]) { + case column_type::low_bound: + m_low_bounds[j] = numeric_traits::zero(); + m_column_types_of_core_solver[j] = column_type::low_bound; + m_can_enter_basis[j] = true; + break; + case column_type::fixed: + this->m_upper_bounds[j] = m_low_bounds[j] = numeric_traits::zero(); + m_column_types_of_core_solver[j] = column_type::fixed; + m_can_enter_basis[j] = false; + break; + default: + lean_unreachable(); + } +} + +template void lp_dual_simplex::fix_structural_for_stage2(unsigned j) { + column_info * ci = this->m_map_from_var_index_to_column_info[this->m_core_solver_columns_to_external_columns[j]]; + switch (ci->get_column_type()) { + case column_type::low_bound: + m_low_bounds[j] = numeric_traits::zero(); + m_column_types_of_core_solver[j] = column_type::low_bound; + m_can_enter_basis[j] = true; + break; + case column_type::fixed: + case column_type::upper_bound: + lean_unreachable(); + case column_type::boxed: + this->m_upper_bounds[j] = ci->get_adjusted_upper_bound() / this->m_column_scale[j]; + m_low_bounds[j] = numeric_traits::zero(); + m_column_types_of_core_solver[j] = column_type::boxed; + m_can_enter_basis[j] = true; + break; + case column_type::free_column: + m_can_enter_basis[j] = true; + m_column_types_of_core_solver[j] = column_type::free_column; + break; + default: + lean_unreachable(); + } + // T cost_was = this->m_costs[j]; + this->set_scaled_cost(j); +} + +template void lp_dual_simplex::unmark_boxed_and_fixed_columns_and_fix_structural_costs() { + unsigned j = this->m_A->column_count(); + while (j-- > this->number_of_core_structurals()) { + fix_logical_for_stage2(j); + } + j = this->number_of_core_structurals(); + while (j--) { + fix_structural_for_stage2(j); + } +} + +template void lp_dual_simplex::restore_right_sides() { + unsigned i = this->m_A->row_count(); + while (i--) { + this->m_b[i] = m_b_copy[i]; + } +} + +template void lp_dual_simplex::solve_for_stage2() { + m_core_solver->restore_non_basis(); + m_core_solver->solve_yB(m_core_solver->m_y); + m_core_solver->fill_reduced_costs_from_m_y_by_rows(); + m_core_solver->start_with_initial_basis_and_make_it_dual_feasible(); + m_core_solver->set_status(FEASIBLE); + m_core_solver->solve(); + switch (m_core_solver->get_status()) { + case OPTIMAL: + this->m_status = OPTIMAL; + break; + case DUAL_UNBOUNDED: + this->m_status = INFEASIBLE; + break; + case TIME_EXHAUSTED: + this->m_status = TIME_EXHAUSTED; + break; + case FLOATING_POINT_ERROR: + this->m_status = FLOATING_POINT_ERROR; + break; + default: + lean_unreachable(); + } + this->m_second_stage_iterations = m_core_solver->total_iterations(); + this->m_total_iterations = (this->m_first_stage_iterations + this->m_second_stage_iterations); +} + +template void lp_dual_simplex::fill_x_with_zeros() { + unsigned j = this->m_A->column_count(); + while (j--) { + this->m_x[j] = numeric_traits::zero(); + } +} + +template void lp_dual_simplex::stage1() { + lean_assert(m_core_solver == nullptr); + this->m_x.resize(this->m_A->column_count(), numeric_traits::zero()); + if (this->m_settings.get_message_ostream() != nullptr) + this->print_statistics_on_A(*this->m_settings.get_message_ostream()); + m_core_solver = new lp_dual_core_solver( + *this->m_A, + m_can_enter_basis, + this->m_b, // the right side vector + this->m_x, + this->m_basis, + this->m_nbasis, + this->m_heading, + this->m_costs, + this->m_column_types_of_core_solver, + this->m_low_bounds, + this->m_upper_bounds, + this->m_settings, + *this); + m_core_solver->fill_reduced_costs_from_m_y_by_rows(); + m_core_solver->start_with_initial_basis_and_make_it_dual_feasible(); + if (this->m_settings.abs_val_is_smaller_than_artificial_tolerance(m_core_solver->get_cost())) { + // skipping stage 1 + m_core_solver->set_status(OPTIMAL); + m_core_solver->set_total_iterations(0); + } else { + m_core_solver->solve(); + } + decide_on_status_after_stage1(); + this->m_first_stage_iterations = m_core_solver->total_iterations(); +} + +template void lp_dual_simplex::stage2() { + unmark_boxed_and_fixed_columns_and_fix_structural_costs(); + restore_right_sides(); + solve_for_stage2(); +} + +template void lp_dual_simplex::fill_first_stage_solver_fields() { + unsigned slack_var = this->number_of_core_structurals(); + unsigned artificial = this->number_of_core_structurals() + this->m_slacks; + + for (unsigned row = 0; row < this->row_count(); row++) { + fill_first_stage_solver_fields_for_row_slack_and_artificial(row, slack_var, artificial); + } + fill_costs_and_bounds_and_column_types_for_the_first_stage_solver(); +} + +template column_type lp_dual_simplex::get_column_type(unsigned j) { + lean_assert(j < this->m_A->column_count()); + if (j >= this->number_of_core_structurals()) { + return m_column_types_of_logicals[j - this->number_of_core_structurals()]; + } + return this->m_map_from_var_index_to_column_info[this->m_core_solver_columns_to_external_columns[j]]->get_column_type(); +} + +template void lp_dual_simplex::fill_costs_bounds_types_and_can_enter_basis_for_the_first_stage_solver_structural_column(unsigned j) { + // see 4.7 in the dissertation of Achim Koberstein + lean_assert(this->m_core_solver_columns_to_external_columns.find(j) != + this->m_core_solver_columns_to_external_columns.end()); + + T free_bound = T(1e4); // see 4.8 + unsigned jj = this->m_core_solver_columns_to_external_columns[j]; + lean_assert(this->m_map_from_var_index_to_column_info.find(jj) != this->m_map_from_var_index_to_column_info.end()); + column_info * ci = this->m_map_from_var_index_to_column_info[jj]; + switch (ci->get_column_type()) { + case column_type::upper_bound: { + std::stringstream s; + s << "unexpected bound type " << j << " " + << column_type_to_string(get_column_type(j)); + throw_exception(s.str()); + break; + } + case column_type::low_bound: { + m_can_enter_basis[j] = true; + this->set_scaled_cost(j); + this->m_low_bounds[j] = numeric_traits::zero(); + this->m_upper_bounds[j] =numeric_traits::one(); + break; + } + case column_type::free_column: { + m_can_enter_basis[j] = true; + this->set_scaled_cost(j); + this->m_upper_bounds[j] = free_bound; + this->m_low_bounds[j] = -free_bound; + break; + } + case column_type::boxed: + m_can_enter_basis[j] = false; + this->m_costs[j] = numeric_traits::zero(); + this->m_upper_bounds[j] = this->m_low_bounds[j] = numeric_traits::zero(); // is it needed? + break; + default: + lean_unreachable(); + } + m_column_types_of_core_solver[j] = column_type::boxed; +} + +template void lp_dual_simplex::fill_costs_bounds_types_and_can_enter_basis_for_the_first_stage_solver_logical_column(unsigned j) { + this->m_costs[j] = 0; + lean_assert(get_column_type(j) != column_type::upper_bound); + if ((m_can_enter_basis[j] = (get_column_type(j) == column_type::low_bound))) { + m_column_types_of_core_solver[j] = column_type::boxed; + this->m_low_bounds[j] = numeric_traits::zero(); + this->m_upper_bounds[j] = numeric_traits::one(); + } else { + m_column_types_of_core_solver[j] = column_type::fixed; + this->m_low_bounds[j] = numeric_traits::zero(); + this->m_upper_bounds[j] = numeric_traits::zero(); + } +} + +template void lp_dual_simplex::fill_costs_and_bounds_and_column_types_for_the_first_stage_solver() { + unsigned j = this->m_A->column_count(); + while (j-- > this->number_of_core_structurals()) { // go over logicals here + fill_costs_bounds_types_and_can_enter_basis_for_the_first_stage_solver_logical_column(j); + } + j = this->number_of_core_structurals(); + while (j--) { + fill_costs_bounds_types_and_can_enter_basis_for_the_first_stage_solver_structural_column(j); + } +} + +template void lp_dual_simplex::fill_first_stage_solver_fields_for_row_slack_and_artificial(unsigned row, + unsigned & slack_var, + unsigned & artificial) { + lean_assert(row < this->row_count()); + auto & constraint = this->m_constraints[this->m_core_solver_rows_to_external_rows[row]]; + // we need to bring the program to the form Ax = b + T rs = this->m_b[row]; + switch (constraint.m_relation) { + case Equal: // no slack variable here + set_type_for_logical(artificial, column_type::fixed); + this->m_basis[row] = artificial; + this->m_costs[artificial] = numeric_traits::zero(); + (*this->m_A)(row, artificial) = numeric_traits::one(); + artificial++; + break; + + case Greater_or_equal: + set_type_for_logical(slack_var, column_type::low_bound); + (*this->m_A)(row, slack_var) = - numeric_traits::one(); + if (rs > 0) { + // adding one artificial + set_type_for_logical(artificial, column_type::fixed); + (*this->m_A)(row, artificial) = numeric_traits::one(); + this->m_basis[row] = artificial; + this->m_costs[artificial] = numeric_traits::zero(); + artificial++; + } else { + // we can put a slack_var into the basis, and avoid adding an artificial variable + this->m_basis[row] = slack_var; + this->m_costs[slack_var] = numeric_traits::zero(); + } + slack_var++; + break; + case Less_or_equal: + // introduce a non-negative slack variable + set_type_for_logical(slack_var, column_type::low_bound); + (*this->m_A)(row, slack_var) = numeric_traits::one(); + if (rs < 0) { + // adding one artificial + set_type_for_logical(artificial, column_type::fixed); + (*this->m_A)(row, artificial) = - numeric_traits::one(); + this->m_basis[row] = artificial; + this->m_costs[artificial] = numeric_traits::zero(); + artificial++; + } else { + // we can put slack_var into the basis, and avoid adding an artificial variable + this->m_basis[row] = slack_var; + this->m_costs[slack_var] = numeric_traits::zero(); + } + slack_var++; + break; + } +} + +template void lp_dual_simplex::augment_matrix_A_and_fill_x_and_allocate_some_fields() { + this->count_slacks_and_artificials(); + this->m_A->add_columns_at_the_end(this->m_slacks + this->m_artificials); + unsigned n = this->m_A->column_count(); + this->m_column_types_of_core_solver.resize(n); + m_column_types_of_logicals.resize(this->m_slacks + this->m_artificials); + this->m_costs.resize(n); + this->m_upper_bounds.resize(n); + this->m_low_bounds.resize(n); + m_can_enter_basis.resize(n); + this->m_basis.resize(this->m_A->row_count()); +} + + + +template void lp_dual_simplex::copy_m_b_aside_and_set_it_to_zeros() { + for (unsigned i = 0; i < this->m_b.size(); i++) { + m_b_copy.push_back(this->m_b[i]); + this->m_b[i] = numeric_traits::zero(); // preparing for the first stage + } +} + +template void lp_dual_simplex::find_maximal_solution(){ + if (this->problem_is_empty()) { + this->m_status = lp_status::EMPTY; + return; + } + + this->flip_costs(); // do it for now, todo ( remove the flipping) + + this->cleanup(); + if (this->m_status == INFEASIBLE) { + return; + } + this->fill_matrix_A_and_init_right_side(); + this->fill_m_b(); + this->scale(); + augment_matrix_A_and_fill_x_and_allocate_some_fields(); + fill_first_stage_solver_fields(); + copy_m_b_aside_and_set_it_to_zeros(); + stage1(); + if (this->m_status == FEASIBLE) { + stage2(); + } +} + + +template T lp_dual_simplex::get_current_cost() const { + T ret = numeric_traits::zero(); + for (auto it : this->m_map_from_var_index_to_column_info) { + ret += this->get_column_cost_value(it.first, it.second); + } + return -ret; // we flip costs for now +} +} diff --git a/src/util/lp/lp_dual_simplex_instances.cpp b/src/util/lp/lp_dual_simplex_instances.cpp new file mode 100644 index 000000000..6610814d8 --- /dev/null +++ b/src/util/lp/lp_dual_simplex_instances.cpp @@ -0,0 +1,9 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include "util/lp/lp_dual_simplex.hpp" +template lean::mpq lean::lp_dual_simplex::get_current_cost() const; +template void lean::lp_dual_simplex::find_maximal_solution(); +template double lean::lp_dual_simplex::get_current_cost() const; +template void lean::lp_dual_simplex::find_maximal_solution(); diff --git a/src/util/lp/lp_primal_core_solver.h b/src/util/lp/lp_primal_core_solver.h new file mode 100644 index 000000000..f05f88db6 --- /dev/null +++ b/src/util/lp/lp_primal_core_solver.h @@ -0,0 +1,986 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ + +#pragma once +#include +#include +#include +#include +#include +#include "util/vector.h" +#include +#include +#include +#include +#include "util/lp/lu.h" +#include "util/lp/lp_solver.h" +#include "util/lp/static_matrix.h" +#include "util/lp/core_solver_pretty_printer.h" +#include "util/lp/lp_core_solver_base.h" +#include "util/lp/breakpoint.h" +#include "util/lp/binary_heap_priority_queue.h" +#include "util/lp/int_set.h" +#include "util/lp/iterator_on_row.h" +namespace lean { + +// This core solver solves (Ax=b, low_bound_values \leq x \leq upper_bound_values, maximize costs*x ) +// The right side b is given implicitly by x and the basis +template +class lp_primal_core_solver:public lp_core_solver_base { +public: + // m_sign_of_entering is set to 1 if the entering variable needs + // to grow and is set to -1 otherwise + unsigned m_column_norm_update_counter; + T m_enter_price_eps; + int m_sign_of_entering_delta; + vector> m_breakpoints; + binary_heap_priority_queue m_breakpoint_indices_queue; + indexed_vector m_beta; // see Swietanowski working vector beta for column norms + T m_epsilon_of_reduced_cost = T(1)/T(10000000); + vector m_costs_backup; + T m_converted_harris_eps; + unsigned m_inf_row_index_for_tableau; + bool m_bland_mode_tableau; + int_set m_left_basis_tableau; + unsigned m_bland_mode_threshold = 1000; + unsigned m_left_basis_repeated; + vector m_leaving_candidates; + // T m_converted_harris_eps = convert_struct::convert(this->m_settings.harris_feasibility_tolerance); + std::list m_non_basis_list; + void sort_non_basis(); + void sort_non_basis_rational(); + int choose_entering_column(unsigned number_of_benefitial_columns_to_go_over); + int choose_entering_column_tableau(); + int choose_entering_column_presize(unsigned number_of_benefitial_columns_to_go_over); + int find_leaving_and_t_with_breakpoints(unsigned entering, X & t); + // int find_inf_row() { + // // mimicing CLP : todo : use a heap + // int j = -1; + // for (unsigned k : this->m_inf_set.m_index) { + // if (k < static_cast(j)) + // j = static_cast(k); + // } + // if (j == -1) + // return -1; + // return this->m_basis_heading[j]; + // #if 0 + // vector choices; + // unsigned len = 100000000; + // for (unsigned j : this->m_inf_set.m_index) { + // int i = this->m_basis_heading[j]; + // lean_assert(i >= 0); + // unsigned row_len = this->m_A.m_rows[i].size(); + // if (row_len < len) { + // choices.clear(); + // choices.push_back(i); + // len = row_len; + // if (my_random() % 10) break; + // } else if (row_len == len) { + // choices.push_back(i); + // if (my_random() % 10) break; + // } + // } + + // if (choices.size() == 0) + // return -1; + + // if (choices.size() == 1) + // return choices[0]; + + // unsigned k = my_random() % choices.size(); + // return choices[k]; + // #endif + // } + + + bool column_is_benefitial_for_entering_basis_on_sign_row_strategy(unsigned j, int sign) const { + // sign = 1 means the x of the basis column of the row has to grow to become feasible, when the coeff before j is neg, or x - has to diminish when the coeff is pos + // we have xbj = -aj * xj + lean_assert(this->m_basis_heading[j] < 0); + lean_assert(this->column_is_feasible(j)); + switch (this->m_column_types[j]) { + case column_type::free_column: return true; + case column_type::fixed: return false; + case column_type::low_bound: + if (sign < 0) + return true; + return !this->x_is_at_low_bound(j); + case column_type::upper_bound: + if (sign > 0) + return true; + return !this->x_is_at_upper_bound(j); + case column_type::boxed: + if (sign < 0) + return !this->x_is_at_low_bound(j); + return !this->x_is_at_upper_bound(j); + } + + lean_assert(false); // cannot be here + return false; + } + + + bool needs_to_grow(unsigned bj) const { + lean_assert(!this->column_is_feasible(bj)); + switch(this->m_column_types[bj]) { + case column_type::free_column: + return false; + case column_type::fixed: + case column_type::low_bound: + case column_type::boxed: + return this-> x_below_low_bound(bj); + default: + return false; + } + lean_assert(false); // unreachable + return false; + } + + int inf_sign_of_column(unsigned bj) const { + lean_assert(!this->column_is_feasible(bj)); + switch(this->m_column_types[bj]) { + case column_type::free_column: + return 0; + case column_type::low_bound: + return 1; + case column_type::fixed: + case column_type::boxed: + return this->x_above_upper_bound(bj)? -1: 1; + default: + return -1; + } + lean_assert(false); // unreachable + return 0; + + } + + + bool monoid_can_decrease(const row_cell & rc) const { + unsigned j = rc.m_j; + lean_assert(this->column_is_feasible(j)); + switch (this->m_column_types[j]) { + case column_type::free_column: + return true; + case column_type::fixed: + return false; + case column_type::low_bound: + if (is_pos(rc.get_val())) { + return this->x_above_low_bound(j); + } + + return true; + case column_type::upper_bound: + if (is_pos(rc.get_val())) { + return true; + } + + return this->x_below_upper_bound(j); + case column_type::boxed: + if (is_pos(rc.get_val())) { + return this->x_above_low_bound(j); + } + + return this->x_below_upper_bound(j); + default: + return false; + } + lean_assert(false); // unreachable + return false; + } + + bool monoid_can_increase(const row_cell & rc) const { + unsigned j = rc.m_j; + lean_assert(this->column_is_feasible(j)); + switch (this->m_column_types[j]) { + case column_type::free_column: + return true; + case column_type::fixed: + return false; + case column_type::low_bound: + if (is_neg(rc.get_val())) { + return this->x_above_low_bound(j); + } + + return true; + case column_type::upper_bound: + if (is_neg(rc.get_val())) { + return true; + } + + return this->x_below_upper_bound(j); + case column_type::boxed: + if (is_neg(rc.get_val())) { + return this->x_above_low_bound(j); + } + + return this->x_below_upper_bound(j); + default: + return false; + } + lean_assert(false); // unreachable + return false; + } + + unsigned get_number_of_basic_vars_that_might_become_inf(unsigned j) const { // consider looking at the signs here: todo + unsigned r = 0; + for (auto & cc : this->m_A.m_columns[j]) { + unsigned k = this->m_basis[cc.m_i]; + if (this->m_column_types[k] != column_type::free_column) + r++; + } + return r; + } + + + int find_beneficial_column_in_row_tableau_rows_bland_mode(int i, T & a_ent) { + int j = -1; + unsigned bj = this->m_basis[i]; + bool bj_needs_to_grow = needs_to_grow(bj); + for (const row_cell& rc : this->m_A.m_rows[i]) { + if (rc.m_j == bj) + continue; + if (bj_needs_to_grow) { + if (!monoid_can_decrease(rc)) + continue; + } else { + if (!monoid_can_increase(rc)) + continue; + } + if (rc.m_j < static_cast(j) ) { + j = rc.m_j; + a_ent = rc.m_value; + } + } + if (j == -1) { + m_inf_row_index_for_tableau = i; + } + + return j; + } + + int find_beneficial_column_in_row_tableau_rows(int i, T & a_ent) { + if (m_bland_mode_tableau) + return find_beneficial_column_in_row_tableau_rows_bland_mode(i, a_ent); + // a short row produces short infeasibility explanation and benefits at least one pivot operation + vector*> choices; + unsigned num_of_non_free_basics = 1000000; + unsigned len = 100000000; + unsigned bj = this->m_basis[i]; + bool bj_needs_to_grow = needs_to_grow(bj); + for (const row_cell& rc : this->m_A.m_rows[i]) { + unsigned j = rc.m_j; + if (j == bj) + continue; + if (bj_needs_to_grow) { + if (!monoid_can_decrease(rc)) + continue; + } else { + if (!monoid_can_increase(rc)) + continue; + } + unsigned damage = get_number_of_basic_vars_that_might_become_inf(j); + if (damage < num_of_non_free_basics) { + num_of_non_free_basics = damage; + len = this->m_A.m_columns[j].size(); + choices.clear(); + choices.push_back(&rc); + } else if (damage == num_of_non_free_basics && + this->m_A.m_columns[j].size() <= len && (my_random() % 2)) { + choices.push_back(&rc); + len = this->m_A.m_columns[j].size(); + } + } + + + if (choices.size() == 0) { + m_inf_row_index_for_tableau = i; + return -1; + } + const row_cell* rc = choices.size() == 1? choices[0] : + choices[my_random() % choices.size()]; + + a_ent = rc->m_value; + return rc->m_j; + } + static X positive_infinity() { + return convert_struct::convert(std::numeric_limits::max()); + } + + bool get_harris_theta(X & theta); + + void restore_harris_eps() { m_converted_harris_eps = convert_struct::convert(this->m_settings.harris_feasibility_tolerance); } + void zero_harris_eps() { m_converted_harris_eps = zero_of_type(); } + int find_leaving_on_harris_theta(X const & harris_theta, X & t); + bool try_jump_to_another_bound_on_entering(unsigned entering, const X & theta, X & t, bool & unlimited); + bool try_jump_to_another_bound_on_entering_unlimited(unsigned entering, X & t); + int find_leaving_and_t(unsigned entering, X & t); + int find_leaving_and_t_precise(unsigned entering, X & t); + int find_leaving_and_t_tableau(unsigned entering, X & t); + + void limit_theta(const X & lim, X & theta, bool & unlimited) { + if (unlimited) { + theta = lim; + unlimited = false; + } else { + theta = std::min(lim, theta); + } + } + + void limit_theta_on_basis_column_for_inf_case_m_neg_upper_bound(unsigned j, const T & m, X & theta, bool & unlimited) { + lean_assert(m < 0 && this->m_column_types[j] == column_type::upper_bound); + limit_inf_on_upper_bound_m_neg(m, this->m_x[j], this->m_upper_bounds[j], theta, unlimited); + } + + + void limit_theta_on_basis_column_for_inf_case_m_neg_low_bound(unsigned j, const T & m, X & theta, bool & unlimited) { + lean_assert(m < 0 && this->m_column_types[j] == column_type::low_bound); + limit_inf_on_bound_m_neg(m, this->m_x[j], this->m_low_bounds[j], theta, unlimited); + } + + + void limit_theta_on_basis_column_for_inf_case_m_pos_low_bound(unsigned j, const T & m, X & theta, bool & unlimited) { + lean_assert(m > 0 && this->m_column_types[j] == column_type::low_bound); + limit_inf_on_low_bound_m_pos(m, this->m_x[j], this->m_low_bounds[j], theta, unlimited); + } + + void limit_theta_on_basis_column_for_inf_case_m_pos_upper_bound(unsigned j, const T & m, X & theta, bool & unlimited) { + lean_assert(m > 0 && this->m_column_types[j] == column_type::upper_bound); + limit_inf_on_bound_m_pos(m, this->m_x[j], this->m_upper_bounds[j], theta, unlimited); + }; + + X harris_eps_for_bound(const X & bound) const { return ( convert_struct::convert(1) + abs(bound)/10) * m_converted_harris_eps/3; + } + + void get_bound_on_variable_and_update_leaving_precisely(unsigned j, vector & leavings, T m, X & t, T & abs_of_d_of_leaving); + + vector m_low_bounds_dummy; // needed for the base class only + + X get_max_bound(vector & b); + +#ifdef LEAN_DEBUG + void check_Ax_equal_b(); + void check_the_bounds(); + void check_bound(unsigned i); + void check_correctness(); +#endif + + // from page 183 of Istvan Maros's book + // the basis structures have not changed yet + void update_reduced_costs_from_pivot_row(unsigned entering, unsigned leaving); + + // return 0 if the reduced cost at entering is close enough to the refreshed + // 1 if it is way off, and 2 if it is unprofitable + int refresh_reduced_cost_at_entering_and_check_that_it_is_off(unsigned entering); + + void backup_and_normalize_costs(); + + void init_run(); + + void calc_working_vector_beta_for_column_norms(); + + void advance_on_entering_and_leaving(int entering, int leaving, X & t); + void advance_on_entering_and_leaving_tableau(int entering, int leaving, X & t); + void advance_on_entering_equal_leaving(int entering, X & t); + void advance_on_entering_equal_leaving_tableau(int entering, X & t); + + bool need_to_switch_costs() const { + if (this->m_settings.simplex_strategy() == simplex_strategy_enum::tableau_rows) + return false; + // lean_assert(calc_current_x_is_feasible() == current_x_is_feasible()); + return this->current_x_is_feasible() == this->m_using_infeas_costs; + } + + + void advance_on_entering(int entering); + void advance_on_entering_tableau(int entering); + void advance_on_entering_precise(int entering); + void push_forward_offset_in_non_basis(unsigned & offset_in_nb); + + unsigned get_number_of_non_basic_column_to_try_for_enter(); + + void print_column_norms(std::ostream & out); + + // returns the number of iterations + unsigned solve(); + + lu * factorization() {return this->m_factorization;} + + void delete_factorization(); + + // according to Swietanowski, " A new steepest edge approximation for the simplex method for linear programming" + void init_column_norms(); + + T calculate_column_norm_exactly(unsigned j); + + void update_or_init_column_norms(unsigned entering, unsigned leaving); + + // following Swietanowski - A new steepest ... + void update_column_norms(unsigned entering, unsigned leaving); + + T calculate_norm_of_entering_exactly(); + + void find_feasible_solution(); + + bool is_tiny() const {return this->m_m < 10 && this->m_n < 20;} + + void one_iteration(); + void one_iteration_tableau(); + + void advance_on_entering_and_leaving_tableau_rows(int entering, int leaving, const X &theta ) { + this->update_basis_and_x_tableau(entering, leaving, theta); + this->update_column_in_inf_set(entering); + } + + + int find_leaving_tableau_rows(X & new_val_for_leaving) { + int j = -1; + for (unsigned k : this->m_inf_set.m_index) { + if (k < static_cast(j)) + j = static_cast(k); + } + if (j == -1) + return -1; + + lean_assert(!this->column_is_feasible(j)); + switch (this->m_column_types[j]) { + case column_type::fixed: + case column_type::upper_bound: + new_val_for_leaving = this->m_upper_bounds[j]; + break; + case column_type::low_bound: + new_val_for_leaving = this->m_low_bounds[j]; + break; + case column_type::boxed: + if (this->x_above_upper_bound(j)) + new_val_for_leaving = this->m_upper_bounds[j]; + else + new_val_for_leaving = this->m_low_bounds[j]; + break; + default: + lean_assert(false); + } + return j; + } + + void one_iteration_tableau_rows() { + X new_val_for_leaving; + int leaving = find_leaving_tableau_rows(new_val_for_leaving); + if (leaving == -1) { + this->set_status(OPTIMAL); + return; + } + + if (!m_bland_mode_tableau) { + if (m_left_basis_tableau.contains(leaving)) { + if (++m_left_basis_repeated > m_bland_mode_threshold) { + m_bland_mode_tableau = true; + } + } else { + m_left_basis_tableau.insert(leaving); + } + } + T a_ent; + int entering = find_beneficial_column_in_row_tableau_rows(this->m_basis_heading[leaving], a_ent); + if (entering == -1) { + this->set_status(INFEASIBLE); + return; + } + X theta = (this->m_x[leaving] - new_val_for_leaving) / a_ent; + advance_on_entering_and_leaving_tableau_rows(entering, leaving, theta ); + lean_assert(this->m_x[leaving] == new_val_for_leaving); + if (this->current_x_is_feasible()) + this->set_status(OPTIMAL); + } + + void fill_breakpoints_array(unsigned entering); + + void try_add_breakpoint_in_row(unsigned i); + + void clear_breakpoints(); + + void change_slope_on_breakpoint(unsigned entering, breakpoint * b, T & slope_at_entering); + void advance_on_sorted_breakpoints(unsigned entering); + + void update_basis_and_x_with_comparison(unsigned entering, unsigned leaving, X delta); + + void decide_on_status_when_cannot_find_entering() { + lean_assert(!need_to_switch_costs()); + this->set_status(this->current_x_is_feasible()? OPTIMAL: INFEASIBLE); + } + + // void limit_theta_on_basis_column_for_feas_case_m_neg(unsigned j, const T & m, X & theta) { + // lean_assert(m < 0); + // lean_assert(this->m_column_type[j] == low_bound || this->m_column_type[j] == boxed); + // const X & eps = harris_eps_for_bound(this->m_low_bounds[j]); + // if (this->above_bound(this->m_x[j], this->m_low_bounds[j])) { + // theta = std::min((this->m_low_bounds[j] -this->m_x[j] - eps) / m, theta); + // if (theta < zero_of_type()) theta = zero_of_type(); + // } + // } + + void limit_theta_on_basis_column_for_feas_case_m_neg_no_check(unsigned j, const T & m, X & theta, bool & unlimited) { + lean_assert(m < 0); + const X& eps = harris_eps_for_bound(this->m_low_bounds[j]); + limit_theta((this->m_low_bounds[j] - this->m_x[j] - eps) / m, theta, unlimited); + if (theta < zero_of_type()) theta = zero_of_type(); + } + + bool limit_inf_on_bound_m_neg(const T & m, const X & x, const X & bound, X & theta, bool & unlimited) { + // x gets smaller + lean_assert(m < 0); + if (numeric_traits::precise()) { + if (this->below_bound(x, bound)) return false; + if (this->above_bound(x, bound)) { + limit_theta((bound - x) / m, theta, unlimited); + } else { + theta = zero_of_type(); + unlimited = false; + } + } else { + const X& eps = harris_eps_for_bound(bound); + if (this->below_bound(x, bound)) return false; + if (this->above_bound(x, bound)) { + limit_theta((bound - x - eps) / m, theta, unlimited); + } else { + theta = zero_of_type(); + unlimited = false; + } + } + return true; + } + + bool limit_inf_on_bound_m_pos(const T & m, const X & x, const X & bound, X & theta, bool & unlimited) { + // x gets larger + lean_assert(m > 0); + if (numeric_traits::precise()) { + if (this->above_bound(x, bound)) return false; + if (this->below_bound(x, bound)) { + limit_theta((bound - x) / m, theta, unlimited); + } else { + theta = zero_of_type(); + unlimited = false; + } + } else { + const X& eps = harris_eps_for_bound(bound); + if (this->above_bound(x, bound)) return false; + if (this->below_bound(x, bound)) { + limit_theta((bound - x + eps) / m, theta, unlimited); + } else { + theta = zero_of_type(); + unlimited = false; + } + } + return true; + } + + void limit_inf_on_low_bound_m_pos(const T & m, const X & x, const X & bound, X & theta, bool & unlimited) { + if (numeric_traits::precise()) { + // x gets larger + lean_assert(m > 0); + if (this->below_bound(x, bound)) { + limit_theta((bound - x) / m, theta, unlimited); + } + } + else { + // x gets larger + lean_assert(m > 0); + const X& eps = harris_eps_for_bound(bound); + if (this->below_bound(x, bound)) { + limit_theta((bound - x + eps) / m, theta, unlimited); + } + } + } + + void limit_inf_on_upper_bound_m_neg(const T & m, const X & x, const X & bound, X & theta, bool & unlimited) { + // x gets smaller + lean_assert(m < 0); + const X& eps = harris_eps_for_bound(bound); + if (this->above_bound(x, bound)) { + limit_theta((bound - x - eps) / m, theta, unlimited); + } + } + + void limit_theta_on_basis_column_for_inf_case_m_pos_boxed(unsigned j, const T & m, X & theta, bool & unlimited) { + // lean_assert(m > 0 && this->m_column_type[j] == column_type::boxed); + const X & x = this->m_x[j]; + const X & lbound = this->m_low_bounds[j]; + + if (this->below_bound(x, lbound)) { + const X& eps = harris_eps_for_bound(this->m_upper_bounds[j]); + limit_theta((lbound - x + eps) / m, theta, unlimited); + } else { + const X & ubound = this->m_upper_bounds[j]; + if (this->below_bound(x, ubound)){ + const X& eps = harris_eps_for_bound(ubound); + limit_theta((ubound - x + eps) / m, theta, unlimited); + } else if (!this->above_bound(x, ubound)) { + theta = zero_of_type(); + unlimited = false; + } + } + } + + void limit_theta_on_basis_column_for_inf_case_m_neg_boxed(unsigned j, const T & m, X & theta, bool & unlimited) { + // lean_assert(m < 0 && this->m_column_type[j] == column_type::boxed); + const X & x = this->m_x[j]; + const X & ubound = this->m_upper_bounds[j]; + if (this->above_bound(x, ubound)) { + const X& eps = harris_eps_for_bound(ubound); + limit_theta((ubound - x - eps) / m, theta, unlimited); + } else { + const X & lbound = this->m_low_bounds[j]; + if (this->above_bound(x, lbound)){ + const X& eps = harris_eps_for_bound(lbound); + limit_theta((lbound - x - eps) / m, theta, unlimited); + } else if (!this->below_bound(x, lbound)) { + theta = zero_of_type(); + unlimited = false; + } + } + } + void limit_theta_on_basis_column_for_feas_case_m_pos(unsigned j, const T & m, X & theta, bool & unlimited) { + lean_assert(m > 0); + const T& eps = harris_eps_for_bound(this->m_upper_bounds[j]); + if (this->below_bound(this->m_x[j], this->m_upper_bounds[j])) { + limit_theta((this->m_upper_bounds[j] - this->m_x[j] + eps) / m, theta, unlimited); + if (theta < zero_of_type()) { + theta = zero_of_type(); + unlimited = false; + } + } + } + + void limit_theta_on_basis_column_for_feas_case_m_pos_no_check(unsigned j, const T & m, X & theta, bool & unlimited ) { + lean_assert(m > 0); + const X& eps = harris_eps_for_bound(this->m_upper_bounds[j]); + limit_theta( (this->m_upper_bounds[j] - this->m_x[j] + eps) / m, theta, unlimited); + if (theta < zero_of_type()) { + theta = zero_of_type(); + } + } + + // j is a basic column or the entering, in any case x[j] has to stay feasible. + // m is the multiplier. updating t in a way that holds the following + // x[j] + t * m >= this->m_low_bounds[j]- harris_feasibility_tolerance ( if m < 0 ) + // or + // x[j] + t * m <= this->m_upper_bounds[j] + harris_feasibility_tolerance ( if m > 0) + void limit_theta_on_basis_column(unsigned j, T m, X & theta, bool & unlimited) { + switch (this->m_column_types[j]) { + case column_type::free_column: break; + case column_type::upper_bound: + if (this->current_x_is_feasible()) { + if (m > 0) + limit_theta_on_basis_column_for_feas_case_m_pos_no_check(j, m, theta, unlimited); + } else { // inside of feasibility_loop + if (m > 0) + limit_theta_on_basis_column_for_inf_case_m_pos_upper_bound(j, m, theta, unlimited); + else + limit_theta_on_basis_column_for_inf_case_m_neg_upper_bound(j, m, theta, unlimited); + } + break; + case column_type::low_bound: + if (this->current_x_is_feasible()) { + if (m < 0) + limit_theta_on_basis_column_for_feas_case_m_neg_no_check(j, m, theta, unlimited); + } else { + if (m < 0) + limit_theta_on_basis_column_for_inf_case_m_neg_low_bound(j, m, theta, unlimited); + else + limit_theta_on_basis_column_for_inf_case_m_pos_low_bound(j, m, theta, unlimited); + } + break; + // case fixed: + // if (get_this->current_x_is_feasible()) { + // theta = zero_of_type(); + // break; + // } + // if (m < 0) + // limit_theta_on_basis_column_for_inf_case_m_neg_fixed(j, m, theta); + // else + // limit_theta_on_basis_column_for_inf_case_m_pos_fixed(j, m, theta); + // break; + case column_type::fixed: + case column_type::boxed: + if (this->current_x_is_feasible()) { + if (m > 0) { + limit_theta_on_basis_column_for_feas_case_m_pos_no_check(j, m, theta, unlimited); + } else { + limit_theta_on_basis_column_for_feas_case_m_neg_no_check(j, m, theta, unlimited); + } + } else { + if (m > 0) { + limit_theta_on_basis_column_for_inf_case_m_pos_boxed(j, m, theta, unlimited); + } else { + limit_theta_on_basis_column_for_inf_case_m_neg_boxed(j, m, theta, unlimited); + } + } + + break; + default: + lean_unreachable(); + } + if (!unlimited && theta < zero_of_type()) { + theta = zero_of_type(); + } + } + + + bool column_is_benefitial_for_entering_basis(unsigned j) const; + bool column_is_benefitial_for_entering_basis_precise(unsigned j) const; + + bool column_is_benefitial_for_entering_on_breakpoints(unsigned j) const; + + + bool can_enter_basis(unsigned j); + bool done(); + void init_infeasibility_costs(); + + void init_infeasibility_cost_for_column(unsigned j); + T get_infeasibility_cost_for_column(unsigned j) const; + void init_infeasibility_costs_for_changed_basis_only(); + + void print_column(unsigned j, std::ostream & out); + void add_breakpoint(unsigned j, X delta, breakpoint_type type); + + // j is the basic column, x is the value at x[j] + // d is the coefficient before m_entering in the row with j as the basis column + void try_add_breakpoint(unsigned j, const X & x, const T & d, breakpoint_type break_type, const X & break_value); + template + bool same_sign_with_entering_delta(const L & a) { + return (a > zero_of_type() && m_sign_of_entering_delta > 0) || (a < zero_of_type() && m_sign_of_entering_delta < 0); + } + + void init_reduced_costs(); + + bool low_bounds_are_set() const { return true; } + + int advance_on_sorted_breakpoints(unsigned entering, X & t); + + std::string break_type_to_string(breakpoint_type type); + + void print_breakpoint(const breakpoint * b, std::ostream & out); + + void print_bound_info_and_x(unsigned j, std::ostream & out); + + void init_infeasibility_after_update_x_if_inf(unsigned leaving) { + if (this->m_using_infeas_costs) { + init_infeasibility_costs_for_changed_basis_only(); + this->m_costs[leaving] = zero_of_type(); + this->m_inf_set.erase(leaving); + } + } + + void init_inf_set() { + this->m_inf_set.clear(); + for (unsigned j = 0; j < this->m_n(); j++) { + if (this->m_basis_heading[j] < 0) + continue; + if (!this->column_is_feasible(j)) + this->m_inf_set.insert(j); + } + } + + int get_column_out_of_bounds_delta_sign(unsigned j) { + switch (this->m_column_types[j]) { + case column_type::fixed: + case column_type::boxed: + if (this->x_below_low_bound(j)) + return -1; + if (this->x_above_upper_bound(j)) + return 1; + break; + case column_type::low_bound: + if (this->x_below_low_bound(j)) + return -1; + break; + case column_type::upper_bound: + if (this->x_above_upper_bound(j)) + return 1; + break; + case column_type::free_column: + return 0; + default: + lean_assert(false); + } + return 0; + } + + void init_column_row_non_zeroes() { + this->m_columns_nz.resize(this->m_A.column_count()); + this->m_rows_nz.resize(this->m_A.row_count()); + for (unsigned i = 0; i < this->m_A.column_count(); i++) { + if (this->m_columns_nz[i] == 0) + this->m_columns_nz[i] = this->m_A.m_columns[i].size(); + } + for (unsigned i = 0; i < this->m_A.row_count(); i++) { + if (this->m_rows_nz[i] == 0) + this->m_rows_nz[i] = this->m_A.m_rows[i].size(); + } + } + + + int x_at_bound_sign(unsigned j) { + switch (this->m_column_types[j]) { + case column_type::fixed: + return 0; + case column_type::boxed: + if (this->x_is_at_low_bound(j)) + return 1; + return -1; + break; + case column_type::low_bound: + return 1; + break; + case column_type::upper_bound: + return -1; + break; + default: + lean_assert(false); + } + return 0; + + } + + unsigned solve_with_tableau(); + + bool basis_column_is_set_correctly(unsigned j) const { + return this->m_A.m_columns[j].size() == 1; + + } + + bool basis_columns_are_set_correctly() const { + for (unsigned j : this->m_basis) + if(!basis_column_is_set_correctly(j)) + return false; + return true; + } + + void init_run_tableau(); + void update_x_tableau(unsigned entering, const X & delta); + void update_inf_cost_for_column_tableau(unsigned j); + +// the delta is between the old and the new cost (old - new) + void update_reduced_cost_for_basic_column_cost_change(const T & delta, unsigned j) { + lean_assert(this->m_basis_heading[j] >= 0); + unsigned i = static_cast(this->m_basis_heading[j]); + for (const row_cell & rc : this->m_A.m_rows[i]) { + unsigned k = rc.m_j; + if (k == j) + continue; + this->m_d[k] += delta * rc.get_val(); + } + } + + bool update_basis_and_x_tableau(int entering, int leaving, X const & tt); + void init_reduced_costs_tableau(); + void init_tableau_rows() { + m_bland_mode_tableau = false; + m_left_basis_tableau.clear(); + m_left_basis_tableau.resize(this->m_A.column_count()); + m_left_basis_repeated = 0; + } +// stage1 constructor + lp_primal_core_solver(static_matrix & A, + vector & b, // the right side vector + vector & x, // the number of elements in x needs to be at least as large as the number of columns in A + vector & basis, + vector & nbasis, + vector & heading, + vector & costs, + const vector & column_type_array, + const vector & low_bound_values, + const vector & upper_bound_values, + lp_settings & settings, + const column_namer& column_names): + lp_core_solver_base(A, b, + basis, + nbasis, + heading, + x, + costs, + settings, + column_names, + column_type_array, + low_bound_values, + upper_bound_values), + m_beta(A.row_count()) { + + if (!(numeric_traits::precise())) { + m_converted_harris_eps = convert_struct::convert(this->m_settings.harris_feasibility_tolerance); + } else { + m_converted_harris_eps = zero_of_type(); + } + this->set_status(UNKNOWN); + } + + // constructor + lp_primal_core_solver(static_matrix & A, + vector & b, // the right side vector + vector & x, // the number of elements in x needs to be at least as large as the number of columns in A + vector & basis, + vector & nbasis, + vector & heading, + vector & costs, + const vector & column_type_array, + const vector & upper_bound_values, + lp_settings & settings, + const column_namer& column_names): + lp_core_solver_base(A, b, + basis, + nbasis, + heading, + x, + costs, + settings, + column_names, + column_type_array, + m_low_bounds_dummy, + upper_bound_values), + m_beta(A.row_count()), + m_converted_harris_eps(convert_struct::convert(this->m_settings.harris_feasibility_tolerance)) { + lean_assert(initial_x_is_correct()); + m_low_bounds_dummy.resize(A.column_count(), zero_of_type()); + m_enter_price_eps = numeric_traits::precise() ? numeric_traits::zero() : T(1e-5); +#ifdef LEAN_DEBUG + // check_correctness(); +#endif + } + + bool initial_x_is_correct() { + std::set basis_set; + for (unsigned i = 0; i < this->m_A.row_count(); i++) { + basis_set.insert(this->m_basis[i]); + } + for (unsigned j = 0; j < this->m_n(); j++) { + if (this->column_has_low_bound(j) && this->m_x[j] < numeric_traits::zero()) { + LP_OUT(this->m_settings, "low bound for variable " << j << " does not hold: this->m_x[" << j << "] = " << this->m_x[j] << " is negative " << std::endl); + return false; + } + + if (this->column_has_upper_bound(j) && this->m_x[j] > this->m_upper_bounds[j]) { + LP_OUT(this->m_settings, "upper bound for " << j << " does not hold: " << this->m_upper_bounds[j] << ">" << this->m_x[j] << std::endl); + return false; + } + + if (basis_set.find(j) != basis_set.end()) continue; + if (this->m_column_types[j] == column_type::low_bound) { + if (numeric_traits::zero() != this->m_x[j]) { + LP_OUT(this->m_settings, "only low bound is set for " << j << " but low bound value " << numeric_traits::zero() << " is not equal to " << this->m_x[j] << std::endl); + return false; + } + } + if (this->m_column_types[j] == column_type::boxed) { + if (this->m_upper_bounds[j] != this->m_x[j] && !numeric_traits::is_zero(this->m_x[j])) { + return false; + } + } + } + return true; + } + + + friend core_solver_pretty_printer; +}; +} diff --git a/src/util/lp/lp_primal_core_solver.hpp b/src/util/lp/lp_primal_core_solver.hpp new file mode 100644 index 000000000..5bcbe317b --- /dev/null +++ b/src/util/lp/lp_primal_core_solver.hpp @@ -0,0 +1,1374 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include +#include "util/vector.h" +#include +#include +#include +#include +#include "util/lp/lp_primal_core_solver.h" +namespace lean { +// This core solver solves (Ax=b, low_bound_values \leq x \leq upper_bound_values, maximize costs*x ) +// The right side b is given implicitly by x and the basis + +template +void lp_primal_core_solver::sort_non_basis_rational() { + lean_assert(numeric_traits::precise()); + if (this->m_settings.use_tableau()) { + std::sort(this->m_nbasis.begin(), this->m_nbasis.end(), [this](unsigned a, unsigned b) { + unsigned ca = this->m_A.number_of_non_zeroes_in_column(a); + unsigned cb = this->m_A.number_of_non_zeroes_in_column(b); + if (ca == 0 && cb != 0) return false; + return ca < cb; + }); + } else { + std::sort(this->m_nbasis.begin(), this->m_nbasis.end(), [this](unsigned a, unsigned b) { + unsigned ca = this->m_columns_nz[a]; + unsigned cb = this->m_columns_nz[b]; + if (ca == 0 && cb != 0) return false; + return ca < cb; + });} + + m_non_basis_list.clear(); + // reinit m_basis_heading + for (unsigned j = 0; j < this->m_nbasis.size(); j++) { + unsigned col = this->m_nbasis[j]; + this->m_basis_heading[col] = - static_cast(j) - 1; + m_non_basis_list.push_back(col); + } +} + + +template +void lp_primal_core_solver::sort_non_basis() { + if (numeric_traits::precise()) { + sort_non_basis_rational(); + return; + } + for (unsigned j : this->m_nbasis) { + T const & da = this->m_d[j]; + this->m_steepest_edge_coefficients[j] = da * da / this->m_column_norms[j]; + } + std::sort(this->m_nbasis.begin(), this->m_nbasis.end(), [this](unsigned a, unsigned b) { + return this->m_steepest_edge_coefficients[a] > this->m_steepest_edge_coefficients[b]; + }); + + m_non_basis_list.clear(); + // reinit m_basis_heading + for (unsigned j = 0; j < this->m_nbasis.size(); j++) { + unsigned col = this->m_nbasis[j]; + this->m_basis_heading[col] = - static_cast(j) - 1; + m_non_basis_list.push_back(col); + } +} + +template +bool lp_primal_core_solver::column_is_benefitial_for_entering_on_breakpoints(unsigned j) const { + bool ret; + const T & d = this->m_d[j]; + switch (this->m_column_types[j]) { + case column_type::low_bound: + lean_assert(this->x_is_at_low_bound(j)); + ret = d < -m_epsilon_of_reduced_cost; + break; + case column_type::upper_bound: + lean_assert(this->x_is_at_upper_bound(j)); + ret = d > m_epsilon_of_reduced_cost; + break; + case column_type::fixed: + ret = false; + break; + case column_type::boxed: + { + bool low_bound = this->x_is_at_low_bound(j); + lean_assert(low_bound || this->x_is_at_upper_bound(j)); + ret = (low_bound && d < -m_epsilon_of_reduced_cost) || ((!low_bound) && d > m_epsilon_of_reduced_cost); + } + break; + case column_type::free_column: + ret = d > m_epsilon_of_reduced_cost || d < - m_epsilon_of_reduced_cost; + break; + default: + lean_unreachable(); + ret = false; + break; + } + return ret; +} +template +bool lp_primal_core_solver::column_is_benefitial_for_entering_basis(unsigned j) const { + if (numeric_traits::precise()) + return column_is_benefitial_for_entering_basis_precise(j); + if (this->m_using_infeas_costs && this->m_settings.use_breakpoints_in_feasibility_search) + return column_is_benefitial_for_entering_on_breakpoints(j); + const T& dj = this->m_d[j]; + switch (this->m_column_types[j]) { + case column_type::fixed: break; + case column_type::free_column: + if (dj > m_epsilon_of_reduced_cost || dj < -m_epsilon_of_reduced_cost) + return true; + break; + case column_type::low_bound: + if (dj > m_epsilon_of_reduced_cost) return true;; + break; + case column_type::upper_bound: + if (dj < -m_epsilon_of_reduced_cost) return true; + break; + case column_type::boxed: + if (dj > m_epsilon_of_reduced_cost) { + if (this->m_x[j] < this->m_upper_bounds[j] - this->bound_span(j)/2) + return true; + break; + } else if (dj < - m_epsilon_of_reduced_cost) { + if (this->m_x[j] > this->m_low_bounds[j] + this->bound_span(j)/2) + return true; + } + break; + default: + lean_unreachable(); + break; + } + return false; +} +template +bool lp_primal_core_solver::column_is_benefitial_for_entering_basis_precise(unsigned j) const { + lean_assert (numeric_traits::precise()); + if (this->m_using_infeas_costs && this->m_settings.use_breakpoints_in_feasibility_search) + return column_is_benefitial_for_entering_on_breakpoints(j); + const T& dj = this->m_d[j]; + switch (this->m_column_types[j]) { + case column_type::fixed: break; + case column_type::free_column: + if (!is_zero(dj)) + return true; + break; + case column_type::low_bound: + if (dj > zero_of_type()) return true; + if (dj < 0 && this->m_x[j] > this->m_low_bounds[j]){ + return true; + } + break; + case column_type::upper_bound: + if (dj < zero_of_type()) return true; + if (dj > 0 && this->m_x[j] < this->m_upper_bounds[j]) { + return true; + } + break; + case column_type::boxed: + if (dj > zero_of_type()) { + if (this->m_x[j] < this->m_upper_bounds[j]) + return true; + break; + } else if (dj < zero_of_type()) { + if (this->m_x[j] > this->m_low_bounds[j]) + return true; + } + break; + default: + lean_unreachable(); + break; + } + return false; +} + +template +int lp_primal_core_solver::choose_entering_column_presize(unsigned number_of_benefitial_columns_to_go_over) { // at this moment m_y = cB * B(-1) + lean_assert(numeric_traits::precise()); + if (number_of_benefitial_columns_to_go_over == 0) + return -1; + if (this->m_basis_sort_counter == 0) { + sort_non_basis(); + this->m_basis_sort_counter = 20; + } + else { + this->m_basis_sort_counter--; + } + unsigned j_nz = this->m_m() + 1; // this number is greater than the max column size + std::list::iterator entering_iter = m_non_basis_list.end(); + for (auto non_basis_iter = m_non_basis_list.begin(); number_of_benefitial_columns_to_go_over && non_basis_iter != m_non_basis_list.end(); ++non_basis_iter) { + unsigned j = *non_basis_iter; + if (!column_is_benefitial_for_entering_basis(j)) + continue; + + // if we are here then j is a candidate to enter the basis + unsigned t = this->m_columns_nz[j]; + if (t < j_nz) { + j_nz = t; + entering_iter = non_basis_iter; + if (number_of_benefitial_columns_to_go_over) + number_of_benefitial_columns_to_go_over--; + } else if (t == j_nz && my_random() % 2 == 0) { + entering_iter = non_basis_iter; + } + }// while (number_of_benefitial_columns_to_go_over && initial_offset_in_non_basis != offset_in_nb); + if (entering_iter == m_non_basis_list.end()) + return -1; + unsigned entering = *entering_iter; + m_sign_of_entering_delta = this->m_d[entering] > 0 ? 1 : -1; + if (this->m_using_infeas_costs && this->m_settings.use_breakpoints_in_feasibility_search) + m_sign_of_entering_delta = -m_sign_of_entering_delta; + m_non_basis_list.erase(entering_iter); + m_non_basis_list.push_back(entering); + return entering; +} + + +template +int lp_primal_core_solver::choose_entering_column(unsigned number_of_benefitial_columns_to_go_over) { // at this moment m_y = cB * B(-1) + if (numeric_traits::precise()) + return choose_entering_column_presize(number_of_benefitial_columns_to_go_over); + if (number_of_benefitial_columns_to_go_over == 0) + return -1; + if (this->m_basis_sort_counter == 0) { + sort_non_basis(); + this->m_basis_sort_counter = 20; + } else { + this->m_basis_sort_counter--; + } + T steepest_edge = zero_of_type(); + std::list::iterator entering_iter = m_non_basis_list.end(); + for (auto non_basis_iter= m_non_basis_list.begin(); number_of_benefitial_columns_to_go_over && non_basis_iter != m_non_basis_list.end(); ++non_basis_iter) { + unsigned j = *non_basis_iter; + if (!column_is_benefitial_for_entering_basis(j)) + continue; + + // if we are here then j is a candidate to enter the basis + T dj = this->m_d[j]; + T t = dj * dj / this->m_column_norms[j]; + if (t > steepest_edge) { + steepest_edge = t; + entering_iter = non_basis_iter; + if (number_of_benefitial_columns_to_go_over) + number_of_benefitial_columns_to_go_over--; + } + }// while (number_of_benefitial_columns_to_go_over && initial_offset_in_non_basis != offset_in_nb); + if (entering_iter != m_non_basis_list.end()) { + unsigned entering = *entering_iter; + m_sign_of_entering_delta = this->m_d[entering] > 0? 1 : -1; + if (this->m_using_infeas_costs && this->m_settings.use_breakpoints_in_feasibility_search) + m_sign_of_entering_delta = - m_sign_of_entering_delta; + m_non_basis_list.erase(entering_iter); + m_non_basis_list.push_back(entering); + return entering; + } + return -1; +} + +template int lp_primal_core_solver::advance_on_sorted_breakpoints(unsigned entering, X &t) { + T slope_at_entering = this->m_d[entering]; + breakpoint * last_bp = nullptr; + lean_assert(m_breakpoint_indices_queue.is_empty()==false); + while (m_breakpoint_indices_queue.is_empty() == false) { + unsigned bi = m_breakpoint_indices_queue.dequeue(); + breakpoint *b = &m_breakpoints[bi]; + change_slope_on_breakpoint(entering, b, slope_at_entering); + last_bp = b; + if (slope_at_entering * m_sign_of_entering_delta > - m_epsilon_of_reduced_cost) { // the slope started to increase infeasibility + break; + } else { + if ((numeric_traits::precise() == false) || ( numeric_traits::is_zero(slope_at_entering) && my_random() % 2 == 0)) { + // it is not cost benefitial to advance the delta more, so just break to increas the randomness + break; + } + } + } + lean_assert (last_bp != nullptr); + t = last_bp->m_delta; + return last_bp->m_j; +} + + +template int +lp_primal_core_solver::find_leaving_and_t_with_breakpoints(unsigned entering, X & t){ + lean_assert(this->precise() == false); + fill_breakpoints_array(entering); + return advance_on_sorted_breakpoints(entering, t); +} + +template bool lp_primal_core_solver::get_harris_theta(X & theta) { + lean_assert(this->m_ed.is_OK()); + bool unlimited = true; + for (unsigned i : this->m_ed.m_index) { + if (this->m_settings.abs_val_is_smaller_than_pivot_tolerance(this->m_ed[i])) continue; + limit_theta_on_basis_column(this->m_basis[i], - this->m_ed[i] * m_sign_of_entering_delta, theta, unlimited); + if (!unlimited && is_zero(theta)) break; + } + return unlimited; +} + + +template int lp_primal_core_solver:: +find_leaving_on_harris_theta(X const & harris_theta, X & t) { + int leaving = -1; + T pivot_abs_max = zero_of_type(); + // we know already that there is no bound flip on entering + // we also know that harris_theta is limited, so we will find a leaving + zero_harris_eps(); + unsigned steps = this->m_ed.m_index.size(); + unsigned k = my_random() % steps; + unsigned initial_k = k; + do { + unsigned i = this->m_ed.m_index[k]; + const T & ed = this->m_ed[i]; + if (this->m_settings.abs_val_is_smaller_than_pivot_tolerance(ed)) { + if (++k == steps) + k = 0; + continue; + } + X ratio; + unsigned j = this->m_basis[i]; + bool unlimited = true; + limit_theta_on_basis_column(j, - ed * m_sign_of_entering_delta, ratio, unlimited); + if ((!unlimited) && ratio <= harris_theta) { + if (leaving == -1 || abs(ed) > pivot_abs_max) { + t = ratio; + leaving = j; + pivot_abs_max = abs(ed); + } + } + if (++k == steps) k = 0; + } while (k != initial_k); + if (!this->precise()) + restore_harris_eps(); + return leaving; +} + + +template bool lp_primal_core_solver::try_jump_to_another_bound_on_entering(unsigned entering, + const X & theta, + X & t, + bool & unlimited) { + switch(this->m_column_types[entering]){ + case column_type::boxed: + if (m_sign_of_entering_delta > 0) { + t = this->m_upper_bounds[entering] - this->m_x[entering]; + if (unlimited || t <= theta){ + lean_assert(t >= zero_of_type()); + return true; + } + } else { // m_sign_of_entering_delta == -1 + t = this->m_x[entering] - this->m_low_bounds[entering]; + if (unlimited || t <= theta) { + lean_assert(t >= zero_of_type()); + return true; + } + } + return false; + case column_type::upper_bound: + if (m_sign_of_entering_delta > 0) { + t = this->m_upper_bounds[entering] - this->m_x[entering]; + if (unlimited || t <= theta){ + lean_assert(t >= zero_of_type()); + return true; + } + } + return false; + case column_type::low_bound: + if (m_sign_of_entering_delta < 0) { + t = this->m_x[entering] - this->m_low_bounds[entering]; + if (unlimited || t <= theta) { + lean_assert(t >= zero_of_type()); + return true; + } + } + return false; + default:return false; + } + return false; +} + +template bool lp_primal_core_solver:: +try_jump_to_another_bound_on_entering_unlimited(unsigned entering, X & t ) { + if (this->m_column_types[entering] != column_type::boxed) + return false; + + if (m_sign_of_entering_delta > 0) { + t = this->m_upper_bounds[entering] - this->m_x[entering]; + return true; + } + // m_sign_of_entering_delta == -1 + t = this->m_x[entering] - this->m_low_bounds[entering]; + return true; +} + +template int lp_primal_core_solver::find_leaving_and_t_precise(unsigned entering, X & t) { + if (this->m_settings.use_breakpoints_in_feasibility_search && !this->current_x_is_feasible()) + return find_leaving_and_t_with_breakpoints(entering, t); + bool unlimited = true; + unsigned steps = this->m_ed.m_index.size(); + unsigned k = my_random() % steps; + unsigned initial_k = k; + unsigned row_min_nz = this->m_n() + 1; + m_leaving_candidates.clear(); + do { + unsigned i = this->m_ed.m_index[k]; + const T & ed = this->m_ed[i]; + lean_assert(!numeric_traits::is_zero(ed)); + unsigned j = this->m_basis[i]; + limit_theta_on_basis_column(j, - ed * m_sign_of_entering_delta, t, unlimited); + if (!unlimited) { + m_leaving_candidates.push_back(j); + row_min_nz = this->m_rows_nz[i]; + } + if (++k == steps) k = 0; + } while (unlimited && k != initial_k); + if (unlimited) { + if (try_jump_to_another_bound_on_entering_unlimited(entering, t)) + return entering; + return -1; + } + + X ratio; + while (k != initial_k) { + unsigned i = this->m_ed.m_index[k]; + const T & ed = this->m_ed[i]; + lean_assert(!numeric_traits::is_zero(ed)); + unsigned j = this->m_basis[i]; + unlimited = true; + limit_theta_on_basis_column(j, -ed * m_sign_of_entering_delta, ratio, unlimited); + if (unlimited) { + if (++k == steps) k = 0; + continue; + } + unsigned i_nz = this->m_rows_nz[i]; + if (ratio < t) { + t = ratio; + m_leaving_candidates.clear(); + m_leaving_candidates.push_back(j); + row_min_nz = this->m_rows_nz[i]; + } else if (ratio == t && i_nz < row_min_nz) { + m_leaving_candidates.clear(); + m_leaving_candidates.push_back(j); + row_min_nz = this->m_rows_nz[i]; + } else if (ratio == t && i_nz == row_min_nz) { + m_leaving_candidates.push_back(j); + } + if (++k == steps) k = 0; + } + + ratio = t; + unlimited = false; + if (try_jump_to_another_bound_on_entering(entering, t, ratio, unlimited)) { + t = ratio; + return entering; + } + k = my_random() % m_leaving_candidates.size(); + return m_leaving_candidates[k]; +} + + +template int lp_primal_core_solver::find_leaving_and_t(unsigned entering, X & t) { + if (this->m_settings.use_breakpoints_in_feasibility_search && !this->current_x_is_feasible()) + return find_leaving_and_t_with_breakpoints(entering, t); + X theta; + bool unlimited = get_harris_theta(theta); + lean_assert(unlimited || theta >= zero_of_type()); + if (try_jump_to_another_bound_on_entering(entering, theta, t, unlimited)) return entering; + if (unlimited) + return -1; + return find_leaving_on_harris_theta(theta, t); +} + + + +// m is the multiplier. updating t in a way that holds the following +// x[j] + t * m >= m_low_bounds[j] ( if m < 0 ) +// or +// x[j] + t * m <= this->m_upper_bounds[j] ( if m > 0) +template void +lp_primal_core_solver::get_bound_on_variable_and_update_leaving_precisely(unsigned j, vector & leavings, T m, X & t, T & abs_of_d_of_leaving) { + if (m > 0) { + switch(this->m_column_types[j]) { // check that j has a low bound + case column_type::free_column: + case column_type::upper_bound: + return; + default:break; + } + X tt = - (this->m_low_bounds[j] - this->m_x[j]) / m; + if (numeric_traits::is_neg(tt)) + tt = zero_of_type(); + if (leavings.size() == 0 || tt < t || (tt == t && m > abs_of_d_of_leaving)) { + t = tt; + abs_of_d_of_leaving = m; + leavings.clear(); + leavings.push_back(j); + } + else if (tt == t || m == abs_of_d_of_leaving) { + leavings.push_back(j); + } + } else if (m < 0){ + switch (this->m_column_types[j]) { // check that j has an upper bound + case column_type::free_column: + case column_type::low_bound: + return; + default:break; + } + + X tt = (this->m_upper_bounds[j] - this->m_x[j]) / m; + if (numeric_traits::is_neg(tt)) + tt = zero_of_type(); + if (leavings.size() == 0 || tt < t || (tt == t && - m > abs_of_d_of_leaving)) { + t = tt; + abs_of_d_of_leaving = - m; + leavings.clear(); + leavings.push_back(j); + } else if (tt == t || m == abs_of_d_of_leaving) { + leavings.push_back(j); + } + } +} + +template X lp_primal_core_solver::get_max_bound(vector & b) { + X ret = zero_of_type(); + for (auto & v : b) { + X a = abs(v); + if (a > ret) ret = a; + } + return ret; +} + +#ifdef LEAN_DEBUG +template void lp_primal_core_solver::check_Ax_equal_b() { + dense_matrix d(this->m_A); + T * ls = d.apply_from_left_with_different_dims(this->m_x); + lean_assert(vectors_are_equal(ls, this->m_b, this->m_m())); + delete [] ls; +} +template void lp_primal_core_solver::check_the_bounds() { + for (unsigned i = 0; i < this->m_n(); i++) { + check_bound(i); + } +} + +template void lp_primal_core_solver::check_bound(unsigned i) { + lean_assert (!(this->column_has_low_bound(i) && (numeric_traits::zero() > this->m_x[i]))); + lean_assert (!(this->column_has_upper_bound(i) && (this->m_upper_bounds[i] < this->m_x[i]))); +} + +template void lp_primal_core_solver::check_correctness() { + check_the_bounds(); + check_Ax_equal_b(); +} +#endif + +// from page 183 of Istvan Maros's book +// the basis structures have not changed yet +template +void lp_primal_core_solver::update_reduced_costs_from_pivot_row(unsigned entering, unsigned leaving) { + // the basis heading has changed already +#ifdef LEAN_DEBUG + auto & basis_heading = this->m_basis_heading; + lean_assert(basis_heading[entering] >= 0 && static_cast(basis_heading[entering]) < this->m_m()); + lean_assert(basis_heading[leaving] < 0); +#endif + T pivot = this->m_pivot_row[entering]; + T dq = this->m_d[entering]/pivot; + for (auto j : this->m_pivot_row.m_index) { + // for (auto j : this->m_nbasis) + if (this->m_basis_heading[j] >= 0) continue; + if (j != leaving) + this->m_d[j] -= dq * this->m_pivot_row[j]; + } + this->m_d[leaving] = -dq; + if (this->current_x_is_infeasible() && !this->m_settings.use_breakpoints_in_feasibility_search) { + this->m_d[leaving] -= this->m_costs[leaving]; + this->m_costs[leaving] = zero_of_type(); + } + this->m_d[entering] = numeric_traits::zero(); +} + +// return 0 if the reduced cost at entering is close enough to the refreshed +// 1 if it is way off, and 2 if it is unprofitable +template int lp_primal_core_solver::refresh_reduced_cost_at_entering_and_check_that_it_is_off(unsigned entering) { + if (numeric_traits::precise()) return 0; + T reduced_at_entering_was = this->m_d[entering]; // can benefit from going over non-zeros of m_ed + lean_assert(abs(reduced_at_entering_was) > m_epsilon_of_reduced_cost); + T refreshed_cost = this->m_costs[entering]; + unsigned i = this->m_m(); + while (i--) refreshed_cost -= this->m_costs[this->m_basis[i]] * this->m_ed[i]; + this->m_d[entering] = refreshed_cost; + T delta = abs(reduced_at_entering_was - refreshed_cost); + if (delta * 2 > abs(reduced_at_entering_was)) { + // this->m_status = UNSTABLE; + if (reduced_at_entering_was > m_epsilon_of_reduced_cost) { + if (refreshed_cost <= zero_of_type()) + return 2; // abort entering + } else { + if (refreshed_cost > -m_epsilon_of_reduced_cost) + return 2; // abort entiring + } + return 1; // go on with this entering + } else { + if (reduced_at_entering_was > m_epsilon_of_reduced_cost) { + if (refreshed_cost <= zero_of_type()) + return 2; // abort entering + } else { + if (refreshed_cost > -m_epsilon_of_reduced_cost) + return 2; // abort entiring + } + } + return 0; +} + +template void lp_primal_core_solver::backup_and_normalize_costs() { + if (this->m_look_for_feasible_solution_only) + return; // no need to backup cost, since we are going to use only feasibility costs + if (numeric_traits::precise() ) { + m_costs_backup = this->m_costs; + } else { + T cost_max = std::max(max_abs_in_vector(this->m_costs), T(1)); + lean_assert(m_costs_backup.size() == 0); + for (unsigned j = 0; j < this->m_costs.size(); j++) + m_costs_backup.push_back(this->m_costs[j] /= cost_max); + } +} + +template void lp_primal_core_solver::init_run() { + this->m_basis_sort_counter = 0; // to initiate the sort of the basis + this->set_total_iterations(0); + this->m_iters_with_no_cost_growing = 0; + init_inf_set(); + if (this->current_x_is_feasible() && this->m_look_for_feasible_solution_only) + return; + this->m_using_infeas_costs = false; + if (this->m_settings.backup_costs) + backup_and_normalize_costs(); + m_epsilon_of_reduced_cost = numeric_traits::precise()? zero_of_type(): T(1)/T(10000000); + m_breakpoint_indices_queue.resize(this->m_n()); + init_reduced_costs(); + if (!numeric_traits::precise()) { + this->m_column_norm_update_counter = 0; + init_column_norms(); + } else { + if (this->m_columns_nz.size() != this->m_n()) + init_column_row_non_zeroes(); + } +} + + +template void lp_primal_core_solver::calc_working_vector_beta_for_column_norms(){ + lean_assert(numeric_traits::precise() == false); + lean_assert(this->m_ed.is_OK()); + lean_assert(m_beta.is_OK()); + m_beta = this->m_ed; + this->m_factorization->solve_yB_with_error_check_indexed(m_beta, this->m_basis_heading, this->m_basis, this->m_settings); +} + +template +void lp_primal_core_solver::advance_on_entering_equal_leaving(int entering, X & t) { + lean_assert(!this->A_mult_x_is_off() ); + this->update_x(entering, t * m_sign_of_entering_delta); + if (this->A_mult_x_is_off_on_index(this->m_ed.m_index) && !this->find_x_by_solving()) { + this->init_lu(); + if (!this->find_x_by_solving()) { + this->restore_x(entering, t * m_sign_of_entering_delta); + this->m_iters_with_no_cost_growing++; + LP_OUT(this->m_settings, "failing in advance_on_entering_equal_leaving for entering = " << entering << std::endl); + return; + } + } + if (this->m_using_infeas_costs) { + lean_assert(is_zero(this->m_costs[entering])); + init_infeasibility_costs_for_changed_basis_only(); + } + if (this->m_look_for_feasible_solution_only && this->current_x_is_feasible()) + return; + + if (need_to_switch_costs() ||!this->current_x_is_feasible()) { + init_reduced_costs(); + } + this->m_iters_with_no_cost_growing = 0; +} + +template void lp_primal_core_solver::advance_on_entering_and_leaving(int entering, int leaving, X & t) { + lean_assert(entering >= 0 && m_non_basis_list.back() == static_cast(entering)); + lean_assert(this->m_using_infeas_costs || t >= zero_of_type()); + lean_assert(leaving >= 0 && entering >= 0); + lean_assert(entering != leaving || !is_zero(t)); // otherwise nothing changes + if (entering == leaving) { + advance_on_entering_equal_leaving(entering, t); + return; + } + unsigned pivot_row = this->m_basis_heading[leaving]; + this->calculate_pivot_row_of_B_1(pivot_row); + this->calculate_pivot_row_when_pivot_row_of_B1_is_ready(pivot_row); + + int pivot_compare_result = this->pivots_in_column_and_row_are_different(entering, leaving); + if (!pivot_compare_result){;} + else if (pivot_compare_result == 2) { // the sign is changed, cannot continue + this->set_status(UNSTABLE); + this->m_iters_with_no_cost_growing++; + return; + } else { + lean_assert(pivot_compare_result == 1); + this->init_lu(); + if (this->m_factorization == nullptr || this->m_factorization->get_status() != LU_status::OK) { + this->set_status(UNSTABLE); + this->m_iters_with_no_cost_growing++; + return; + } + } + if (!numeric_traits::precise()) + calc_working_vector_beta_for_column_norms(); + if (this->current_x_is_feasible() || !this->m_settings.use_breakpoints_in_feasibility_search) { + if (m_sign_of_entering_delta == -1) + t = -t; + } + if (!this->update_basis_and_x(entering, leaving, t)) { + if (this->get_status() == FLOATING_POINT_ERROR) + return; + if (this->m_look_for_feasible_solution_only) { + this->set_status(FLOATING_POINT_ERROR); + return; + } + init_reduced_costs(); + return; + } + + if (!is_zero(t)) { + this->m_iters_with_no_cost_growing = 0; + init_infeasibility_after_update_x_if_inf(leaving); + } + + if (this->current_x_is_feasible()) { + this->set_status(FEASIBLE); + if (this->m_look_for_feasible_solution_only) + return; + } + if (numeric_traits::precise() == false) + update_or_init_column_norms(entering, leaving); + + + if (need_to_switch_costs()) { + init_reduced_costs(); + } else { + update_reduced_costs_from_pivot_row(entering, leaving); + } + lean_assert(!need_to_switch_costs()); + std::list::iterator it = m_non_basis_list.end(); + it--; + * it = static_cast(leaving); +} + + +template void lp_primal_core_solver::advance_on_entering_precise(int entering) { + lean_assert(numeric_traits::precise()); + lean_assert(entering > -1); + this->solve_Bd(entering); + X t; + int leaving = find_leaving_and_t_precise(entering, t); + if (leaving == -1) { + this->set_status(UNBOUNDED); + return; + } + advance_on_entering_and_leaving(entering, leaving, t); +} + +template void lp_primal_core_solver::advance_on_entering(int entering) { + if (numeric_traits::precise()) { + advance_on_entering_precise(entering); + return; + } + lean_assert(entering > -1); + this->solve_Bd(entering); + int refresh_result = refresh_reduced_cost_at_entering_and_check_that_it_is_off(entering); + if (refresh_result) { + if (this->m_look_for_feasible_solution_only) { + this->set_status(FLOATING_POINT_ERROR); + return; + } + + this->init_lu(); + init_reduced_costs(); + if (refresh_result == 2) { + this->m_iters_with_no_cost_growing++; + return; + } + } + X t; + int leaving = find_leaving_and_t(entering, t); + if (leaving == -1){ + if (!this->current_x_is_feasible()) { + lean_assert(!numeric_traits::precise()); // we cannot have unbounded with inf costs + + // if (m_look_for_feasible_solution_only) { + // this->m_status = INFEASIBLE; + // return; + // } + + + if (this->get_status() == UNSTABLE) { + this->set_status(FLOATING_POINT_ERROR); + return; + } + init_infeasibility_costs(); + this->set_status(UNSTABLE); + + return; + } + if (this->get_status() == TENTATIVE_UNBOUNDED) { + this->set_status(UNBOUNDED); + } else { + this->set_status(TENTATIVE_UNBOUNDED); + } + return; + } + advance_on_entering_and_leaving(entering, leaving, t); +} + +template void lp_primal_core_solver::push_forward_offset_in_non_basis(unsigned & offset_in_nb) { + if (++offset_in_nb == this->m_nbasis.size()) + offset_in_nb = 0; +} + +template unsigned lp_primal_core_solver::get_number_of_non_basic_column_to_try_for_enter() { + unsigned ret = static_cast(this->m_nbasis.size()); + if (this->get_status() == TENTATIVE_UNBOUNDED) + return ret; // we really need to find entering with a large reduced cost + if (ret > 300) { + ret = (unsigned)(ret * this->m_settings.percent_of_entering_to_check / 100); + } + if (ret == 0) { + return 0; + } + return std::max(static_cast(my_random() % ret), 1u); +} + +template void lp_primal_core_solver::print_column_norms(std::ostream & out) { + out << " column norms " << std::endl; + for (unsigned j = 0; j < this->m_n(); j++) { + out << this->m_column_norms[j] << " "; + } + out << std::endl; + out << std::endl; +} + +// returns the number of iterations +template unsigned lp_primal_core_solver::solve() { + if (numeric_traits::precise() && this->m_settings.use_tableau()) + return solve_with_tableau(); + + init_run(); + if (this->current_x_is_feasible() && this->m_look_for_feasible_solution_only) { + this->set_status(FEASIBLE); + return 0; + } + + if ((!numeric_traits::precise()) && this->A_mult_x_is_off()) { + this->set_status(FLOATING_POINT_ERROR); + return 0; + } + do { + if (this->print_statistics_with_iterations_and_nonzeroes_and_cost_and_check_that_the_time_is_over((this->m_using_infeas_costs? "inf" : "feas"), * this->m_settings.get_message_ostream())) { + return this->total_iterations(); + } + one_iteration(); + lean_assert(!this->m_using_infeas_costs || this->costs_on_nbasis_are_zeros()); + switch (this->get_status()) { + case OPTIMAL: // double check that we are at optimum + case INFEASIBLE: + if (this->m_look_for_feasible_solution_only && this->current_x_is_feasible()) + break; + if (!numeric_traits::precise()) { + if(this->m_look_for_feasible_solution_only) + break; + this->init_lu(); + + if (this->m_factorization->get_status() != LU_status::OK) { + this->set_status (FLOATING_POINT_ERROR); + break; + } + init_reduced_costs(); + if (choose_entering_column(1) == -1) { + decide_on_status_when_cannot_find_entering(); + break; + } + this->set_status(UNKNOWN); + } else { // precise case + if (this->m_look_for_feasible_solution_only) { // todo: keep the reduced costs correct all the time! + init_reduced_costs(); + if (choose_entering_column(1) == -1) { + decide_on_status_when_cannot_find_entering(); + break; + } + this->set_status(UNKNOWN); + } + } + break; + case TENTATIVE_UNBOUNDED: + this->init_lu(); + if (this->m_factorization->get_status() != LU_status::OK) { + this->set_status(FLOATING_POINT_ERROR); + break; + } + + init_reduced_costs(); + break; + case UNBOUNDED: + if (this->current_x_is_infeasible()) { + init_reduced_costs(); + this->set_status(UNKNOWN); + } + break; + + case UNSTABLE: + lean_assert(! (numeric_traits::precise())); + this->init_lu(); + if (this->m_factorization->get_status() != LU_status::OK) { + this->set_status(FLOATING_POINT_ERROR); + break; + } + init_reduced_costs(); + break; + + default: + break; // do nothing + } + } while (this->get_status() != FLOATING_POINT_ERROR + && + this->get_status() != UNBOUNDED + && + this->get_status() != OPTIMAL + && + this->get_status() != INFEASIBLE + && + this->m_iters_with_no_cost_growing <= this->m_settings.max_number_of_iterations_with_no_improvements + && + this->total_iterations() <= this->m_settings.max_total_number_of_iterations + && + !(this->current_x_is_feasible() && this->m_look_for_feasible_solution_only)); + + lean_assert(this->get_status() == FLOATING_POINT_ERROR + || + this->current_x_is_feasible() == false + || + this->calc_current_x_is_feasible_include_non_basis()); + return this->total_iterations(); +} + +template void lp_primal_core_solver::delete_factorization() { + if (this->m_factorization != nullptr) { + delete this->m_factorization; + this->m_factorization = nullptr; + } +} + +// according to Swietanowski, " A new steepest edge approximation for the simplex method for linear programming" +template void lp_primal_core_solver::init_column_norms() { + lean_assert(numeric_traits::precise() == false); + for (unsigned j = 0; j < this->m_n(); j++) { + this->m_column_norms[j] = T(static_cast(this->m_A.m_columns[j].size() + 1)) + + + T(static_cast(my_random() % 10000)) / T(100000); + } +} + +// debug only +template T lp_primal_core_solver::calculate_column_norm_exactly(unsigned j) { + lean_assert(numeric_traits::precise() == false); + indexed_vector w(this->m_m()); + this->m_A.copy_column_to_vector(j, w); + vector d(this->m_m()); + this->m_factorization->solve_Bd_when_w_is_ready(d, w); + T ret = zero_of_type(); + for (auto v : d) + ret += v*v; + return ret+1; +} + +template void lp_primal_core_solver::update_or_init_column_norms(unsigned entering, unsigned leaving) { + lean_assert(numeric_traits::precise() == false); + lean_assert(m_column_norm_update_counter <= this->m_settings.column_norms_update_frequency); + if (m_column_norm_update_counter == this->m_settings.column_norms_update_frequency) { + m_column_norm_update_counter = 0; + init_column_norms(); + } else { + m_column_norm_update_counter++; + update_column_norms(entering, leaving); + } +} + +// following Swietanowski - A new steepest ... +template void lp_primal_core_solver::update_column_norms(unsigned entering, unsigned leaving) { + lean_assert(numeric_traits::precise() == false); + T pivot = this->m_pivot_row[entering]; + T g_ent = calculate_norm_of_entering_exactly() / pivot / pivot; + if (!numeric_traits::precise()) { + if (g_ent < T(0.000001)) + g_ent = T(0.000001); + } + this->m_column_norms[leaving] = g_ent; + + for (unsigned j : this->m_pivot_row.m_index) { + if (j == leaving) + continue; + const T & t = this->m_pivot_row[j]; + T s = this->m_A.dot_product_with_column(m_beta.m_data, j); + T k = -2 / pivot; + T tp = t/pivot; + if (this->m_column_types[j] != column_type::fixed) { // a fixed columns do not enter the basis, we don't use the norm of a fixed column + this->m_column_norms[j] = std::max(this->m_column_norms[j] + t * (t * g_ent + k * s), // see Istvan Maros, page 196 + 1 + tp * tp); + } + } +} + +template T lp_primal_core_solver::calculate_norm_of_entering_exactly() { + T r = numeric_traits::one(); + for (auto i : this->m_ed.m_index) { + T t = this->m_ed[i]; + r += t * t; + } + return r; +} + +// calling it stage1 is too cryptic +template void lp_primal_core_solver::find_feasible_solution() { + this->m_look_for_feasible_solution_only = true; + lean_assert(this->non_basic_columns_are_set_correctly()); + this->set_status(UNKNOWN); + solve(); +} + +template void lp_primal_core_solver::one_iteration() { + unsigned number_of_benefitial_columns_to_go_over = get_number_of_non_basic_column_to_try_for_enter(); + int entering = choose_entering_column(number_of_benefitial_columns_to_go_over); + if (entering == -1) { + decide_on_status_when_cannot_find_entering(); + } + else { + advance_on_entering(entering); + } +} + +template void lp_primal_core_solver::update_basis_and_x_with_comparison(unsigned entering, unsigned leaving, X delta) { + if (entering != leaving) + this->update_basis_and_x(entering, leaving, delta); + else + this->update_x(entering, delta); +} + + +template void lp_primal_core_solver::clear_breakpoints() { + m_breakpoints.clear(); + m_breakpoint_indices_queue.clear(); +} + +template void lp_primal_core_solver::fill_breakpoints_array(unsigned entering) { + clear_breakpoints(); + for (unsigned i : this->m_ed.m_index) + try_add_breakpoint_in_row(i); + + if (this->m_column_types[entering] == column_type::boxed) { + if (m_sign_of_entering_delta < 0) + add_breakpoint(entering, - this->bound_span(entering), low_break); + else + add_breakpoint(entering, this->bound_span(entering), upper_break); + } +} + + + +template bool lp_primal_core_solver::done() { + if (this->get_status() == OPTIMAL || this->get_status() == FLOATING_POINT_ERROR) return true; + if (this->get_status() == INFEASIBLE) { + return true; + } + if (this->m_iters_with_no_cost_growing >= this->m_settings.max_number_of_iterations_with_no_improvements) { + this->get_status() = ITERATIONS_EXHAUSTED; return true; + } + if (this->total_iterations() >= this->m_settings.max_total_number_of_iterations) { + this->get_status() = ITERATIONS_EXHAUSTED; return true; + } + return false; +} + +template +void lp_primal_core_solver::init_infeasibility_costs_for_changed_basis_only() { + for (unsigned i : this->m_ed.m_index) + init_infeasibility_cost_for_column(this->m_basis[i]); + this->m_using_infeas_costs = true; +} + + +template +void lp_primal_core_solver::init_infeasibility_costs() { + lean_assert(this->m_x.size() >= this->m_n()); + lean_assert(this->m_column_types.size() >= this->m_n()); + for (unsigned j = this->m_n(); j--;) + init_infeasibility_cost_for_column(j); + this->m_using_infeas_costs = true; +} + +template T +lp_primal_core_solver::get_infeasibility_cost_for_column(unsigned j) const { + if (this->m_basis_heading[j] < 0) { + return zero_of_type(); + } + T ret; + // j is a basis column + switch (this->m_column_types[j]) { + case column_type::fixed: + case column_type::boxed: + if (this->x_above_upper_bound(j)) { + ret = 1; + } else if (this->x_below_low_bound(j)) { + ret = -1; + } else { + ret = numeric_traits::zero(); + } + break; + case column_type::low_bound: + if (this->x_below_low_bound(j)) { + ret = -1; + } else { + ret = numeric_traits::zero(); + } + break; + case column_type::upper_bound: + if (this->x_above_upper_bound(j)) { + ret = 1; + } else { + ret = numeric_traits::zero(); + } + break; + case column_type::free_column: + ret = numeric_traits::zero(); + break; + default: + lean_assert(false); + break; + } + + if (!this->m_settings.use_breakpoints_in_feasibility_search) { + ret = - ret; + } + return ret; +} + + +// changed m_inf_set too! +template void +lp_primal_core_solver::init_infeasibility_cost_for_column(unsigned j) { + + // If j is a breakpoint column, then we set the cost zero. + // When anylyzing an entering column candidate we update the cost of the breakpoints columns to get the left or the right derivative if the infeasibility function + // set zero cost for each non-basis column + if (this->m_basis_heading[j] < 0) { + this->m_costs[j] = numeric_traits::zero(); + this->m_inf_set.erase(j); + return; + } + // j is a basis column + switch (this->m_column_types[j]) { + case column_type::fixed: + case column_type::boxed: + if (this->x_above_upper_bound(j)) { + this->m_costs[j] = 1; + } else if (this->x_below_low_bound(j)) { + this->m_costs[j] = -1; + } else { + this->m_costs[j] = numeric_traits::zero(); + } + break; + case column_type::low_bound: + if (this->x_below_low_bound(j)) { + this->m_costs[j] = -1; + } else { + this->m_costs[j] = numeric_traits::zero(); + } + break; + case column_type::upper_bound: + if (this->x_above_upper_bound(j)) { + this->m_costs[j] = 1; + } else { + this->m_costs[j] = numeric_traits::zero(); + } + break; + case column_type::free_column: + this->m_costs[j] = numeric_traits::zero(); + break; + default: + lean_assert(false); + break; + } + + if (numeric_traits::is_zero(this->m_costs[j])) { + this->m_inf_set.erase(j); + } else { + this->m_inf_set.insert(j); + } + if (!this->m_settings.use_breakpoints_in_feasibility_search) { + this->m_costs[j] = - this->m_costs[j]; + } +} + + +template void lp_primal_core_solver::print_column(unsigned j, std::ostream & out) { + out << this->column_name(j) << " " << j << " " << column_type_to_string(this->m_column_type[j]) << " x = " << this->m_x[j] << " " << "c = " << this->m_costs[j];; + switch (this->m_column_type[j]) { + case column_type::fixed: + case column_type::boxed: + out << "( " << this->m_low_bounds[j] << " " << this->m_x[j] << " " << this->m_upper_bounds[j] << ")" << std::endl; + break; + case column_type::upper_bound: + out << "( _" << this->m_x[j] << " " << this->m_upper_bounds[j] << ")" << std::endl; + break; + case column_type::low_bound: + out << "( " << this->m_low_bounds[j] << " " << this->m_x[j] << " " << "_ )" << std::endl; + break; + case column_type::free_column: + out << "( _" << this->m_x[j] << "_)" << std::endl; + default: + lean_unreachable(); + } +} + +template void lp_primal_core_solver::add_breakpoint(unsigned j, X delta, breakpoint_type type) { + m_breakpoints.push_back(breakpoint(j, delta, type)); + m_breakpoint_indices_queue.enqueue(m_breakpoint_indices_queue.size(), abs(delta)); +} + +// j is the basic column, x is the value at x[j] +// d is the coefficient before m_entering in the row with j as the basis column +template void lp_primal_core_solver::try_add_breakpoint(unsigned j, const X & x, const T & d, breakpoint_type break_type, const X & break_value) { + X diff = x - break_value; + if (is_zero(diff)) { + switch (break_type) { + case low_break: + if (!same_sign_with_entering_delta(d)) + return; // no breakpoint + break; + case upper_break: + if (same_sign_with_entering_delta(d)) + return; // no breakpoint + break; + default: break; + } + add_breakpoint(j, zero_of_type(), break_type); + return; + } + auto delta_j = diff / d; + if (same_sign_with_entering_delta(delta_j)) + add_breakpoint(j, delta_j, break_type); +} + +template std::string lp_primal_core_solver::break_type_to_string(breakpoint_type type) { + switch (type){ + case low_break: return "low_break"; + case upper_break: return "upper_break"; + case fixed_break: return "fixed_break"; + default: + lean_assert(false); + break; + } + return "type is not found"; +} + +template void lp_primal_core_solver::print_breakpoint(const breakpoint * b, std::ostream & out) { + out << "(" << this->column_name(b->m_j) << "," << break_type_to_string(b->m_type) << "," << T_to_string(b->m_delta) << ")" << std::endl; + print_bound_info_and_x(b->m_j, out); +} + +template +void lp_primal_core_solver::init_reduced_costs() { + lean_assert(!this->use_tableau()); + if (this->current_x_is_infeasible() && !this->m_using_infeas_costs) { + init_infeasibility_costs(); + } else if (this->current_x_is_feasible() && this->m_using_infeas_costs) { + if (this->m_look_for_feasible_solution_only) + return; + this->m_costs = m_costs_backup; + this->m_using_infeas_costs = false; + } + + this->init_reduced_costs_for_one_iteration(); +} + +template void lp_primal_core_solver::change_slope_on_breakpoint(unsigned entering, breakpoint * b, T & slope_at_entering) { + if (b->m_j == entering) { + lean_assert(b->m_type != fixed_break && (!is_zero(b->m_delta))); + slope_at_entering += m_sign_of_entering_delta; + return; + } + + lean_assert(this->m_basis_heading[b->m_j] >= 0); + unsigned i_row = this->m_basis_heading[b->m_j]; + const T & d = - this->m_ed[i_row]; + if (numeric_traits::is_zero(d)) return; + + T delta = m_sign_of_entering_delta * abs(d); + switch (b->m_type) { + case fixed_break: + if (is_zero(b->m_delta)) { + slope_at_entering += delta; + } else { + slope_at_entering += 2 * delta; + } + break; + case low_break: + case upper_break: + slope_at_entering += delta; + break; + default: + lean_assert(false); + } +} + + +template void lp_primal_core_solver::try_add_breakpoint_in_row(unsigned i) { + lean_assert(i < this->m_m()); + const T & d = this->m_ed[i]; // the coefficient before m_entering in the i-th row + if (d == 0) return; // the change of x[m_entering] will not change the corresponding basis x + unsigned j = this->m_basis[i]; + const X & x = this->m_x[j]; + switch (this->m_column_types[j]) { + case column_type::fixed: + try_add_breakpoint(j, x, d, fixed_break, this->m_low_bounds[j]); + break; + case column_type::boxed: + try_add_breakpoint(j, x, d, low_break, this->m_low_bounds[j]); + try_add_breakpoint(j, x, d, upper_break, this->m_upper_bounds[j]); + break; + case column_type::low_bound: + try_add_breakpoint(j, x, d, low_break, this->m_low_bounds[j]); + break; + case column_type::upper_bound: + try_add_breakpoint(j, x, d, upper_break, this->m_upper_bounds[j]); + break; + case column_type::free_column: + break; + default: + lean_assert(false); + break; + } +} + + +template void lp_primal_core_solver::print_bound_info_and_x(unsigned j, std::ostream & out) { + out << "type of " << this->column_name(j) << " is " << column_type_to_string(this->m_column_types[j]) << std::endl; + out << "x[" << this->column_name(j) << "] = " << this->m_x[j] << std::endl; + switch (this->m_column_types[j]) { + case column_type::fixed: + case column_type::boxed: + out << "[" << this->m_low_bounds[j] << "," << this->m_upper_bounds[j] << "]" << std::endl; + break; + case column_type::low_bound: + out << "[" << this->m_low_bounds[j] << ", inf" << std::endl; + break; + case column_type::upper_bound: + out << "inf ," << this->m_upper_bounds[j] << "]" << std::endl; + break; + case column_type::free_column: + out << "inf, inf" << std::endl; + break; + default: + lean_assert(false); + break; + } +} + + +} diff --git a/src/util/lp/lp_primal_core_solver_instances.cpp b/src/util/lp/lp_primal_core_solver_instances.cpp new file mode 100644 index 000000000..3b5a3fedb --- /dev/null +++ b/src/util/lp/lp_primal_core_solver_instances.cpp @@ -0,0 +1,24 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include +#include +#include +#include "util/vector.h" +#include +#include "util/lp/lar_solver.h" +#include "util/lp/lp_primal_core_solver.hpp" +#include "util/lp/lp_primal_core_solver_tableau.hpp" +namespace lean { +template void lp_primal_core_solver::find_feasible_solution(); +template void lean::lp_primal_core_solver >::find_feasible_solution(); + +template unsigned lp_primal_core_solver::solve(); +template unsigned lp_primal_core_solver::solve_with_tableau(); +template unsigned lp_primal_core_solver::solve(); +template void lean::lp_primal_core_solver::clear_breakpoints(); +template bool lean::lp_primal_core_solver::update_basis_and_x_tableau(int, int, lean::mpq const&); +template bool lean::lp_primal_core_solver::update_basis_and_x_tableau(int, int, double const&); +template bool lean::lp_primal_core_solver >::update_basis_and_x_tableau(int, int, lean::numeric_pair const&); +} diff --git a/src/util/lp/lp_primal_core_solver_tableau.hpp b/src/util/lp/lp_primal_core_solver_tableau.hpp new file mode 100644 index 000000000..e48e0ddbd --- /dev/null +++ b/src/util/lp/lp_primal_core_solver_tableau.hpp @@ -0,0 +1,393 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +// this is a part of lp_primal_core_solver that deals with the tableau +#include "util/lp/lp_primal_core_solver.h" +namespace lean { +template void lp_primal_core_solver::one_iteration_tableau() { + int entering = choose_entering_column_tableau(); + if (entering == -1) { + decide_on_status_when_cannot_find_entering(); + } + else { + advance_on_entering_tableau(entering); + } + lean_assert(this->inf_set_is_correct()); +} + +template void lp_primal_core_solver::advance_on_entering_tableau(int entering) { + X t; + int leaving = find_leaving_and_t_tableau(entering, t); + if (leaving == -1) { + this->set_status(UNBOUNDED); + return; + } + advance_on_entering_and_leaving_tableau(entering, leaving, t); +} +/* +template int lp_primal_core_solver::choose_entering_column_tableau_rows() { + int i = find_inf_row(); + if (i == -1) + return -1; + return find_shortest_beneficial_column_in_row(i); + } +*/ + template int lp_primal_core_solver::choose_entering_column_tableau() { + //this moment m_y = cB * B(-1) + unsigned number_of_benefitial_columns_to_go_over = get_number_of_non_basic_column_to_try_for_enter(); + + lean_assert(numeric_traits::precise()); + if (number_of_benefitial_columns_to_go_over == 0) + return -1; + if (this->m_basis_sort_counter == 0) { + sort_non_basis(); + this->m_basis_sort_counter = 20; + } + else { + this->m_basis_sort_counter--; + } + unsigned j_nz = this->m_m() + 1; // this number is greater than the max column size + std::list::iterator entering_iter = m_non_basis_list.end(); + for (auto non_basis_iter = m_non_basis_list.begin(); number_of_benefitial_columns_to_go_over && non_basis_iter != m_non_basis_list.end(); ++non_basis_iter) { + unsigned j = *non_basis_iter; + if (!column_is_benefitial_for_entering_basis(j)) + continue; + + // if we are here then j is a candidate to enter the basis + unsigned t = this->m_A.number_of_non_zeroes_in_column(j); + if (t < j_nz) { + j_nz = t; + entering_iter = non_basis_iter; + if (number_of_benefitial_columns_to_go_over) + number_of_benefitial_columns_to_go_over--; + } + else if (t == j_nz && my_random() % 2 == 0) { + entering_iter = non_basis_iter; + } + }// while (number_of_benefitial_columns_to_go_over && initial_offset_in_non_basis != offset_in_nb); + if (entering_iter == m_non_basis_list.end()) + return -1; + unsigned entering = *entering_iter; + m_sign_of_entering_delta = this->m_d[entering] > 0 ? 1 : -1; + if (this->m_using_infeas_costs && this->m_settings.use_breakpoints_in_feasibility_search) + m_sign_of_entering_delta = -m_sign_of_entering_delta; + m_non_basis_list.erase(entering_iter); + m_non_basis_list.push_back(entering); + return entering; + +} + + + + +template +unsigned lp_primal_core_solver::solve_with_tableau() { + init_run_tableau(); + if (this->current_x_is_feasible() && this->m_look_for_feasible_solution_only) { + this->set_status(FEASIBLE); + return 0; + } + + if ((!numeric_traits::precise()) && this->A_mult_x_is_off()) { + this->set_status(FLOATING_POINT_ERROR); + return 0; + } + do { + if (this->print_statistics_with_iterations_and_nonzeroes_and_cost_and_check_that_the_time_is_over((this->m_using_infeas_costs? "inf t" : "feas t"), * this->m_settings.get_message_ostream())) { + return this->total_iterations(); + } + if (this->m_settings.use_tableau_rows()) + one_iteration_tableau_rows(); + else + one_iteration_tableau(); + switch (this->get_status()) { + case OPTIMAL: // double check that we are at optimum + case INFEASIBLE: + if (this->m_look_for_feasible_solution_only && this->current_x_is_feasible()) + break; + if (!numeric_traits::precise()) { + if(this->m_look_for_feasible_solution_only) + break; + this->init_lu(); + + if (this->m_factorization->get_status() != LU_status::OK) { + this->set_status(FLOATING_POINT_ERROR); + break; + } + init_reduced_costs(); + if (choose_entering_column(1) == -1) { + decide_on_status_when_cannot_find_entering(); + break; + } + this->set_status(UNKNOWN); + } else { // precise case + if ((!this->infeasibility_costs_are_correct())) { + init_reduced_costs_tableau(); // forcing recalc + if (choose_entering_column_tableau() == -1) { + decide_on_status_when_cannot_find_entering(); + break; + } + this->set_status(UNKNOWN); + } + } + break; + case TENTATIVE_UNBOUNDED: + this->init_lu(); + if (this->m_factorization->get_status() != LU_status::OK) { + this->set_status(FLOATING_POINT_ERROR); + break; + } + + init_reduced_costs(); + break; + case UNBOUNDED: + if (this->current_x_is_infeasible()) { + init_reduced_costs(); + this->set_status(UNKNOWN); + } + break; + + case UNSTABLE: + lean_assert(! (numeric_traits::precise())); + this->init_lu(); + if (this->m_factorization->get_status() != LU_status::OK) { + this->set_status(FLOATING_POINT_ERROR); + break; + } + init_reduced_costs(); + break; + + default: + break; // do nothing + } + } while (this->get_status() != FLOATING_POINT_ERROR + && + this->get_status() != UNBOUNDED + && + this->get_status() != OPTIMAL + && + this->get_status() != INFEASIBLE + && + this->m_iters_with_no_cost_growing <= this->m_settings.max_number_of_iterations_with_no_improvements + && + this->total_iterations() <= this->m_settings.max_total_number_of_iterations + && + !(this->current_x_is_feasible() && this->m_look_for_feasible_solution_only)); + + lean_assert(this->get_status() == FLOATING_POINT_ERROR + || + this->current_x_is_feasible() == false + || + this->calc_current_x_is_feasible_include_non_basis()); + return this->total_iterations(); + +} +template void lp_primal_core_solver::advance_on_entering_and_leaving_tableau(int entering, int leaving, X & t) { + lean_assert(this->A_mult_x_is_off() == false); + lean_assert(leaving >= 0 && entering >= 0); + lean_assert((this->m_settings.simplex_strategy() == + simplex_strategy_enum::tableau_rows) || + m_non_basis_list.back() == static_cast(entering)); + lean_assert(this->m_using_infeas_costs || !is_neg(t)); + lean_assert(entering != leaving || !is_zero(t)); // otherwise nothing changes + if (entering == leaving) { + advance_on_entering_equal_leaving_tableau(entering, t); + return; + } + if (!is_zero(t)) { + if (this->current_x_is_feasible() || !this->m_settings.use_breakpoints_in_feasibility_search ) { + if (m_sign_of_entering_delta == -1) + t = -t; + } + this->update_basis_and_x_tableau(entering, leaving, t); + lean_assert(this->A_mult_x_is_off() == false); + this->m_iters_with_no_cost_growing = 0; + } else { + this->pivot_column_tableau(entering, this->m_basis_heading[leaving]); + this->change_basis(entering, leaving); + } + + if (this->m_look_for_feasible_solution_only && this->current_x_is_feasible()) + return; + + if (this->m_settings.simplex_strategy() != simplex_strategy_enum::tableau_rows) { + if (need_to_switch_costs()) { + this->init_reduced_costs_tableau(); + } + + lean_assert(!need_to_switch_costs()); + std::list::iterator it = m_non_basis_list.end(); + it--; + * it = static_cast(leaving); + } +} + +template +void lp_primal_core_solver::advance_on_entering_equal_leaving_tableau(int entering, X & t) { + lean_assert(!this->A_mult_x_is_off() ); + this->update_x_tableau(entering, t * m_sign_of_entering_delta); + if (this->m_look_for_feasible_solution_only && this->current_x_is_feasible()) + return; + + if (need_to_switch_costs()) { + init_reduced_costs_tableau(); + } + this->m_iters_with_no_cost_growing = 0; +} +template int lp_primal_core_solver::find_leaving_and_t_tableau(unsigned entering, X & t) { + unsigned k = 0; + bool unlimited = true; + unsigned row_min_nz = this->m_n() + 1; + m_leaving_candidates.clear(); + auto & col = this->m_A.m_columns[entering]; + unsigned col_size = col.size(); + for (;k < col_size && unlimited; k++) { + const column_cell & c = col[k]; + unsigned i = c.m_i; + const T & ed = this->m_A.get_val(c); + lean_assert(!numeric_traits::is_zero(ed)); + unsigned j = this->m_basis[i]; + limit_theta_on_basis_column(j, - ed * m_sign_of_entering_delta, t, unlimited); + if (!unlimited) { + m_leaving_candidates.push_back(j); + row_min_nz = this->m_A.m_rows[i].size(); + } + } + if (unlimited) { + if (try_jump_to_another_bound_on_entering_unlimited(entering, t)) + return entering; + return -1; + } + + X ratio; + for (;k < col_size; k++) { + const column_cell & c = col[k]; + unsigned i = c.m_i; + const T & ed = this->m_A.get_val(c); + lean_assert(!numeric_traits::is_zero(ed)); + unsigned j = this->m_basis[i]; + unlimited = true; + limit_theta_on_basis_column(j, -ed * m_sign_of_entering_delta, ratio, unlimited); + if (unlimited) continue; + unsigned i_nz = this->m_A.m_rows[i].size(); + if (ratio < t) { + t = ratio; + m_leaving_candidates.clear(); + m_leaving_candidates.push_back(j); + row_min_nz = i_nz; + } else if (ratio == t && i_nz < row_min_nz) { + m_leaving_candidates.clear(); + m_leaving_candidates.push_back(j); + row_min_nz = this->m_A.m_rows[i].size(); + } else if (ratio == t && i_nz == row_min_nz) { + m_leaving_candidates.push_back(j); + } + } + + ratio = t; + unlimited = false; + if (try_jump_to_another_bound_on_entering(entering, t, ratio, unlimited)) { + t = ratio; + return entering; + } + if (m_leaving_candidates.size() == 1) + return m_leaving_candidates[0]; + k = my_random() % m_leaving_candidates.size(); + return m_leaving_candidates[k]; +} +template void lp_primal_core_solver::init_run_tableau() { + // print_matrix(&(this->m_A), std::cout); + lean_assert(this->A_mult_x_is_off() == false); + lean_assert(basis_columns_are_set_correctly()); + this->m_basis_sort_counter = 0; // to initiate the sort of the basis + this->set_total_iterations(0); + this->m_iters_with_no_cost_growing = 0; + lean_assert(this->inf_set_is_correct()); + if (this->current_x_is_feasible() && this->m_look_for_feasible_solution_only) + return; + if (this->m_settings.backup_costs) + backup_and_normalize_costs(); + m_epsilon_of_reduced_cost = numeric_traits::precise() ? zero_of_type() : T(1) / T(10000000); + if (this->m_settings.use_breakpoints_in_feasibility_search) + m_breakpoint_indices_queue.resize(this->m_n()); + if (!numeric_traits::precise()) { + this->m_column_norm_update_counter = 0; + init_column_norms(); + } + if (this->m_settings.m_simplex_strategy == simplex_strategy_enum::tableau_rows) + init_tableau_rows(); + lean_assert(this->reduced_costs_are_correct_tableau()); + lean_assert(!this->need_to_pivot_to_basis_tableau()); +} + +template bool lp_primal_core_solver:: +update_basis_and_x_tableau(int entering, int leaving, X const & tt) { + lean_assert(this->use_tableau()); + update_x_tableau(entering, tt); + this->pivot_column_tableau(entering, this->m_basis_heading[leaving]); + this->change_basis(entering, leaving); + return true; +} +template void lp_primal_core_solver:: +update_x_tableau(unsigned entering, const X& delta) { + if (!this->m_using_infeas_costs) { + this->m_x[entering] += delta; + for (const auto & c : this->m_A.m_columns[entering]) { + unsigned i = c.m_i; + this->m_x[this->m_basis[i]] -= delta * this->m_A.get_val(c); + this->update_column_in_inf_set(this->m_basis[i]); + } + } else { // m_using_infeas_costs == true + this->m_x[entering] += delta; + lean_assert(this->column_is_feasible(entering)); + lean_assert(this->m_costs[entering] == zero_of_type()); + // m_d[entering] can change because of the cost change for basic columns. + for (const auto & c : this->m_A.m_columns[entering]) { + unsigned i = c.m_i; + unsigned j = this->m_basis[i]; + this->m_x[j] -= delta * this->m_A.get_val(c); + update_inf_cost_for_column_tableau(j); + if (is_zero(this->m_costs[j])) + this->m_inf_set.erase(j); + else + this->m_inf_set.insert(j); + } + } + lean_assert(this->A_mult_x_is_off() == false); +} + +template void lp_primal_core_solver:: +update_inf_cost_for_column_tableau(unsigned j) { + lean_assert(this->m_settings.simplex_strategy() != simplex_strategy_enum::tableau_rows); + lean_assert(this->m_using_infeas_costs); + T new_cost = get_infeasibility_cost_for_column(j); + T delta = this->m_costs[j] - new_cost; + if (is_zero(delta)) + return; + this->m_costs[j] = new_cost; + update_reduced_cost_for_basic_column_cost_change(delta, j); +} + +template void lp_primal_core_solver::init_reduced_costs_tableau() { + if (this->current_x_is_infeasible() && !this->m_using_infeas_costs) { + init_infeasibility_costs(); + } else if (this->current_x_is_feasible() && this->m_using_infeas_costs) { + if (this->m_look_for_feasible_solution_only) + return; + this->m_costs = m_costs_backup; + this->m_using_infeas_costs = false; + } + unsigned size = this->m_basis_heading.size(); + for (unsigned j = 0; j < size; j++) { + if (this->m_basis_heading[j] >= 0) + this->m_d[j] = zero_of_type(); + else { + T& d = this->m_d[j] = this->m_costs[j]; + for (auto & cc : this->m_A.m_columns[j]) { + d -= this->m_costs[this->m_basis[cc.m_i]] * this->m_A.get_val(cc); + } + } + } +} +} diff --git a/src/util/lp/lp_primal_simplex.h b/src/util/lp/lp_primal_simplex.h new file mode 100644 index 000000000..3b1288fc7 --- /dev/null +++ b/src/util/lp/lp_primal_simplex.h @@ -0,0 +1,96 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#pragma once +#include "util/vector.h" +#include +#include +#include +#include "util/lp/lp_utils.h" +#include "util/lp/column_info.h" +#include "util/lp/lp_primal_core_solver.h" +#include "util/lp/lp_solver.h" +#include "util/lp/iterator_on_row.h" +namespace lean { +template +class lp_primal_simplex: public lp_solver { + lp_primal_core_solver * m_core_solver = nullptr; + vector m_low_bounds; +private: + unsigned original_rows() { return this->m_external_rows_to_core_solver_rows.size(); } + + void fill_costs_and_x_for_first_stage_solver(unsigned original_number_of_columns); + + void init_buffer(unsigned k, vector & r); + + void refactor(); + + void set_scaled_costs(); +public: + lp_primal_simplex() {} + + column_info * get_or_create_column_info(unsigned column); + + void set_status(lp_status status) { + this->m_status = status; + } + + lp_status get_status() { + return this->m_status; + } + + void fill_acceptable_values_for_x(); + + + void set_zero_bound(bool * bound_is_set, T * bounds, unsigned i); + + void fill_costs_and_x_for_first_stage_solver_for_row( + int row, + unsigned & slack_var, + unsigned & artificial); + + + + + void set_core_solver_bounds(); + + void update_time_limit_from_starting_time(int start_time) { + this->m_settings.time_limit -= (get_millisecond_span(start_time) / 1000.); + } + + void find_maximal_solution(); + + void fill_A_x_and_basis_for_stage_one_total_inf(); + + void fill_A_x_and_basis_for_stage_one_total_inf_for_row(unsigned row); + + void solve_with_total_inf(); + + + ~lp_primal_simplex(); + + bool bounds_hold(std::unordered_map const & solution); + + T get_row_value(unsigned i, std::unordered_map const & solution, std::ostream * out); + + bool row_constraint_holds(unsigned i, std::unordered_map const & solution, std::ostream * out); + + bool row_constraints_hold(std::unordered_map const & solution); + + + T * get_array_from_map(std::unordered_map const & solution); + + bool solution_is_feasible(std::unordered_map const & solution) { + return bounds_hold(solution) && row_constraints_hold(solution); + } + + virtual T get_column_value(unsigned column) const { + return this->get_column_value_with_core_solver(column, m_core_solver); + } + + T get_current_cost() const; + + +}; +} diff --git a/src/util/lp/lp_primal_simplex.hpp b/src/util/lp/lp_primal_simplex.hpp new file mode 100644 index 000000000..9e1af2bbe --- /dev/null +++ b/src/util/lp/lp_primal_simplex.hpp @@ -0,0 +1,355 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include +#include "util/vector.h" +#include "util/lp/lp_primal_simplex.h" + +namespace lean { +template void lp_primal_simplex::fill_costs_and_x_for_first_stage_solver(unsigned original_number_of_columns) { + unsigned slack_var = original_number_of_columns; + unsigned artificial = original_number_of_columns + this->m_slacks; + + for (unsigned row = 0; row < this->row_count(); row++) { + fill_costs_and_x_for_first_stage_solver_for_row(row, slack_var, artificial); + } +} + +template void lp_primal_simplex::init_buffer(unsigned k, vector & r) { + for (unsigned i = 0; i < k; i++) { + r[i] = 0; + } + r[k] = 1; + for (unsigned i = this->row_count() -1; i > k; i--) { + r[i] = 0; + } +} + +template void lp_primal_simplex::refactor() { + m_core_solver->init_lu(); + if (m_core_solver->factorization()->get_status() != LU_status::OK) { + throw_exception("cannot refactor"); + } +} + +template void lp_primal_simplex::set_scaled_costs() { + unsigned j = this->number_of_core_structurals(); + while (j-- > 0) { + this->set_scaled_cost(j); + } +} + +template column_info * lp_primal_simplex::get_or_create_column_info(unsigned column) { + auto it = this->m_columns.find(column); + return (it == this->m_columns.end())? ( this->m_columns[column] = new column_info) : it->second; +} + +template void lp_primal_simplex::fill_acceptable_values_for_x() { + for (auto t : this->m_core_solver_columns_to_external_columns) { + this->m_x[t.first] = numeric_traits::zero(); + } +} + + +template void lp_primal_simplex::set_zero_bound(bool * bound_is_set, T * bounds, unsigned i) { + bound_is_set[i] = true; + bounds[i] = numeric_traits::zero(); +} + +template void lp_primal_simplex::fill_costs_and_x_for_first_stage_solver_for_row( + int row, + unsigned & slack_var, + unsigned & artificial) { + lean_assert(row >= 0 && row < this->row_count()); + auto & constraint = this->m_constraints[this->m_core_solver_rows_to_external_rows[row]]; + // we need to bring the program to the form Ax = b + T rs = this->m_b[row]; + T artificial_cost = - numeric_traits::one(); + switch (constraint.m_relation) { + case Equal: // no slack variable here + this->m_column_types[artificial] = column_type::low_bound; + this->m_costs[artificial] = artificial_cost; // we are maximizing, so the artificial, which is non-negatiive, will be pushed to zero + this->m_basis[row] = artificial; + if (rs >= 0) { + (*this->m_A)(row, artificial) = numeric_traits::one(); + this->m_x[artificial] = rs; + } else { + (*this->m_A)(row, artificial) = - numeric_traits::one(); + this->m_x[artificial] = - rs; + } + artificial++; + break; + + case Greater_or_equal: + this->m_column_types[slack_var] = column_type::low_bound; + (*this->m_A)(row, slack_var) = - numeric_traits::one(); + + if (rs > 0) { + lean_assert(numeric_traits::is_zero(this->m_x[slack_var])); + // adding one artificial + this->m_column_types[artificial] = column_type::low_bound; + (*this->m_A)(row, artificial) = numeric_traits::one(); + this->m_costs[artificial] = artificial_cost; + this->m_basis[row] = artificial; + this->m_x[artificial] = rs; + artificial++; + } else { + // we can put a slack_var into the basis, and atemplate void lp_primal_simplex::adding an artificial variable + this->m_basis[row] = slack_var; + this->m_x[slack_var] = - rs; + } + slack_var++; + break; + case Less_or_equal: + // introduce a non-negative slack variable + this->m_column_types[slack_var] = column_type::low_bound; + (*this->m_A)(row, slack_var) = numeric_traits::one(); + + if (rs < 0) { + // adding one artificial + lean_assert(numeric_traits::is_zero(this->m_x[slack_var])); + this->m_column_types[artificial] = column_type::low_bound; + (*this->m_A)(row, artificial) = - numeric_traits::one(); + this->m_costs[artificial] = artificial_cost; + this->m_x[artificial] = - rs; + this->m_basis[row] = artificial++; + } else { + // we can put slack_var into the basis, and atemplate void lp_primal_simplex::adding an artificial variable + this->m_basis[row] = slack_var; + this->m_x[slack_var] = rs; + } + slack_var++; + break; + } +} + + + + + +template void lp_primal_simplex::set_core_solver_bounds() { + unsigned total_vars = this->m_A->column_count() + this->m_slacks + this->m_artificials; + this->m_column_types.resize(total_vars); + this->m_upper_bounds.resize(total_vars); + for (auto cit : this->m_map_from_var_index_to_column_info) { + column_info * ci = cit.second; + unsigned j = ci->get_column_index(); + if (!is_valid(j)) + continue; // the variable is not mapped to a column + switch (this->m_column_types[j] = ci->get_column_type()){ + case column_type::fixed: + this->m_upper_bounds[j] = numeric_traits::zero(); + break; + case column_type::boxed: + this->m_upper_bounds[j] = ci->get_adjusted_upper_bound() / this->m_column_scale[j]; + break; + + default: break; // do nothing + } + } +} + + +template void lp_primal_simplex::find_maximal_solution() { + int preprocessing_start_time = get_millisecond_count(); + if (this->problem_is_empty()) { + this->m_status = lp_status::EMPTY; + return; + } + + this->cleanup(); + this->fill_matrix_A_and_init_right_side(); + if (this->m_status == lp_status::INFEASIBLE) { + return; + } + this->m_x.resize(this->m_A->column_count()); + this->fill_m_b(); + this->scale(); + fill_acceptable_values_for_x(); + this->count_slacks_and_artificials(); + set_core_solver_bounds(); + update_time_limit_from_starting_time(preprocessing_start_time); + solve_with_total_inf(); +} + +template void lp_primal_simplex::fill_A_x_and_basis_for_stage_one_total_inf() { + for (unsigned row = 0; row < this->row_count(); row++) + fill_A_x_and_basis_for_stage_one_total_inf_for_row(row); +} + +template void lp_primal_simplex::fill_A_x_and_basis_for_stage_one_total_inf_for_row(unsigned row) { + lean_assert(row < this->row_count()); + auto ext_row_it = this->m_core_solver_rows_to_external_rows.find(row); + lean_assert(ext_row_it != this->m_core_solver_rows_to_external_rows.end()); + unsigned ext_row = ext_row_it->second; + auto constr_it = this->m_constraints.find(ext_row); + lean_assert(constr_it != this->m_constraints.end()); + auto & constraint = constr_it->second; + unsigned j = this->m_A->column_count(); // j is a slack variable + this->m_A->add_column(); + // we need to bring the program to the form Ax = b + this->m_basis[row] = j; + switch (constraint.m_relation) { + case Equal: + this->m_x[j] = this->m_b[row]; + (*this->m_A)(row, j) = numeric_traits::one(); + this->m_column_types[j] = column_type::fixed; + this->m_upper_bounds[j] = m_low_bounds[j] = zero_of_type(); + break; + + case Greater_or_equal: + this->m_x[j] = - this->m_b[row]; + (*this->m_A)(row, j) = - numeric_traits::one(); + this->m_column_types[j] = column_type::low_bound; + this->m_upper_bounds[j] = zero_of_type(); + break; + case Less_or_equal: + this->m_x[j] = this->m_b[row]; + (*this->m_A)(row, j) = numeric_traits::one(); + this->m_column_types[j] = column_type::low_bound; + this->m_upper_bounds[j] = m_low_bounds[j] = zero_of_type(); + break; + default: + lean_unreachable(); + } +} + +template void lp_primal_simplex::solve_with_total_inf() { + int total_vars = this->m_A->column_count() + this->row_count(); + if (total_vars == 0) { + this->m_status = OPTIMAL; + return; + } + m_low_bounds.clear(); + m_low_bounds.resize(total_vars, zero_of_type()); // low bounds are shifted ot zero + this->m_x.resize(total_vars, numeric_traits::zero()); + this->m_basis.resize(this->row_count()); + this->m_costs.clear(); + this->m_costs.resize(total_vars, zero_of_type()); + fill_A_x_and_basis_for_stage_one_total_inf(); + if (this->m_settings.get_message_ostream() != nullptr) + this->print_statistics_on_A(*this->m_settings.get_message_ostream()); + set_scaled_costs(); + + m_core_solver = new lp_primal_core_solver(*this->m_A, + this->m_b, + this->m_x, + this->m_basis, + this->m_nbasis, + this->m_heading, + this->m_costs, + this->m_column_types, + m_low_bounds, + this->m_upper_bounds, + this->m_settings, *this); + m_core_solver->solve(); + this->set_status(m_core_solver->get_status()); + this->m_total_iterations = m_core_solver->total_iterations(); +} + + +template lp_primal_simplex::~lp_primal_simplex() { + if (m_core_solver != nullptr) { + delete m_core_solver; + } +} + +template bool lp_primal_simplex::bounds_hold(std::unordered_map const & solution) { + for (auto it : this->m_map_from_var_index_to_column_info) { + auto sol_it = solution.find(it.second->get_name()); + if (sol_it == solution.end()) { + std::stringstream s; + s << "cannot find column " << it.first << " in solution"; + throw_exception(s.str() ); + } + + if (!it.second->bounds_hold(sol_it->second)) { + // std::cout << "bounds do not hold for " << it.second->get_name() << std::endl; + it.second->bounds_hold(sol_it->second); + return false; + } + } + return true; +} + +template T lp_primal_simplex::get_row_value(unsigned i, std::unordered_map const & solution, std::ostream * out) { + auto it = this->m_A_values.find(i); + if (it == this->m_A_values.end()) { + std::stringstream s; + s << "cannot find row " << i; + throw_exception(s.str() ); + } + T ret = numeric_traits::zero(); + for (auto & pair : it->second) { + auto cit = this->m_map_from_var_index_to_column_info.find(pair.first); + lean_assert(cit != this->m_map_from_var_index_to_column_info.end()); + column_info * ci = cit->second; + auto sol_it = solution.find(ci->get_name()); + lean_assert(sol_it != solution.end()); + T column_val = sol_it->second; + if (out != nullptr) { + (*out) << pair.second << "(" << ci->get_name() << "=" << column_val << ") "; + } + ret += pair.second * column_val; + } + if (out != nullptr) { + (*out) << " = " << ret << std::endl; + } + return ret; +} + +template bool lp_primal_simplex::row_constraint_holds(unsigned i, std::unordered_map const & solution, std::ostream *out) { + T row_val = get_row_value(i, solution, out); + auto & constraint = this->m_constraints[i]; + T rs = constraint.m_rs; + bool print = out != nullptr; + switch (constraint.m_relation) { + case Equal: + if (fabs(numeric_traits::get_double(row_val - rs)) > 0.00001) { + if (print) { + (*out) << "should be = " << rs << std::endl; + } + return false; + } + return true; + case Greater_or_equal: + if (numeric_traits::get_double(row_val - rs) < -0.00001) { + if (print) { + (*out) << "should be >= " << rs << std::endl; + } + return false; + } + return true;; + + case Less_or_equal: + if (numeric_traits::get_double(row_val - rs) > 0.00001) { + if (print) { + (*out) << "should be <= " << rs << std::endl; + } + return false; + } + return true;; + } + lean_unreachable(); + return false; // it is unreachable +} + +template bool lp_primal_simplex::row_constraints_hold(std::unordered_map const & solution) { + for (auto it : this->m_A_values) { + if (!row_constraint_holds(it.first, solution, nullptr)) { + row_constraint_holds(it.first, solution, nullptr); + return false; + } + } + return true; +} + +template T lp_primal_simplex::get_current_cost() const { + T ret = numeric_traits::zero(); + for (auto it : this->m_map_from_var_index_to_column_info) { + ret += this->get_column_cost_value(it.first, it.second); + } + return ret; +} +} diff --git a/src/util/lp/lp_primal_simplex_instances.cpp b/src/util/lp/lp_primal_simplex_instances.cpp new file mode 100644 index 000000000..37b639489 --- /dev/null +++ b/src/util/lp/lp_primal_simplex_instances.cpp @@ -0,0 +1,20 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include +#include +#include +#include "util/vector.h" +#include +#include "util/lp/lp_primal_simplex.hpp" +template bool lean::lp_primal_simplex::bounds_hold(std::unordered_map, std::equal_to, std::allocator > > const&); +template bool lean::lp_primal_simplex::row_constraints_hold(std::unordered_map, std::equal_to, std::allocator > > const&); +template double lean::lp_primal_simplex::get_current_cost() const; +template double lean::lp_primal_simplex::get_column_value(unsigned int) const; +template lean::lp_primal_simplex::~lp_primal_simplex(); +template lean::lp_primal_simplex::~lp_primal_simplex(); +template lean::mpq lean::lp_primal_simplex::get_current_cost() const; +template lean::mpq lean::lp_primal_simplex::get_column_value(unsigned int) const; +template void lean::lp_primal_simplex::find_maximal_solution(); +template void lean::lp_primal_simplex::find_maximal_solution(); diff --git a/src/util/lp/lp_settings.h b/src/util/lp/lp_settings.h new file mode 100644 index 000000000..7ec132f5e --- /dev/null +++ b/src/util/lp/lp_settings.h @@ -0,0 +1,339 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ + +#pragma once +#include "util/vector.h" +#include +#include +#include +#include +#include +#include "util/lp/lp_utils.h" + +namespace lean { +typedef unsigned var_index; +typedef unsigned constraint_index; +typedef unsigned row_index; +enum class column_type { + free_column = 0, + low_bound = 1, + upper_bound = 2, + boxed = 3, + fixed = 4 + }; + +enum class simplex_strategy_enum { + tableau_rows = 0, + tableau_costs = 1, + no_tableau = 2 +}; + +std::string column_type_to_string(column_type t); + +enum lp_status { + UNKNOWN, + INFEASIBLE, + TENTATIVE_UNBOUNDED, + UNBOUNDED, + TENTATIVE_DUAL_UNBOUNDED, + DUAL_UNBOUNDED, + OPTIMAL, + FEASIBLE, + FLOATING_POINT_ERROR, + TIME_EXHAUSTED, + ITERATIONS_EXHAUSTED, + EMPTY, + UNSTABLE +}; + +// when the ratio of the vector lenth to domain size to is greater than the return value we switch to solve_By_for_T_indexed_only +template +unsigned ratio_of_index_size_to_all_size() { + if (numeric_traits::precise()) + return 10; + return 120; +} + +const char* lp_status_to_string(lp_status status); + +inline std::ostream& operator<<(std::ostream& out, lp_status status) { + return out << lp_status_to_string(status); +} + +lp_status lp_status_from_string(std::string status); + +enum non_basic_column_value_position { at_low_bound, at_upper_bound, at_fixed, free_of_bounds, not_at_bound }; + +template bool is_epsilon_small(const X & v, const double& eps); // forward definition + +int get_millisecond_count(); +int get_millisecond_span(int start_time); +unsigned my_random(); +void my_random_init(long unsigned seed); + + +class lp_resource_limit { +public: + virtual bool get_cancel_flag() = 0; +}; + +struct stats { + unsigned m_total_iterations; + unsigned m_iters_with_no_cost_growing; + unsigned m_num_factorizations; + unsigned m_num_of_implied_bounds; + unsigned m_need_to_solve_inf; + stats() { reset(); } + void reset() { memset(this, 0, sizeof(*this)); } +}; + +struct lp_settings { +private: + class default_lp_resource_limit : public lp_resource_limit { + lp_settings& m_settings; + int m_start_time; + public: + default_lp_resource_limit(lp_settings& s): m_settings(s), m_start_time(get_millisecond_count()) {} + virtual bool get_cancel_flag() { + int span_in_mills = get_millisecond_span(m_start_time); + return (span_in_mills / 1000.0 > m_settings.time_limit); + } + }; + + default_lp_resource_limit m_default_resource_limit; + lp_resource_limit* m_resource_limit; + // used for debug output + std::ostream* m_debug_out = &std::cout; + // used for messages, for example, the computation progress messages + std::ostream* m_message_out = &std::cout; + + stats m_stats; + +public: + unsigned reps_in_scaler = 20; + // when the absolute value of an element is less than pivot_epsilon + // in pivoting, we treat it as a zero + double pivot_epsilon = 0.00000001; + // see Chatal, page 115 + double positive_price_epsilon = 1e-7; + // a quatation "if some choice of the entering vairable leads to an eta matrix + // whose diagonal element in the eta column is less than e2 (entering_diag_epsilon) in magnitude, the this choice is rejected ... + double entering_diag_epsilon = 1e-8; + int c_partial_pivoting = 10; // this is the constant c from page 410 + unsigned depth_of_rook_search = 4; + bool using_partial_pivoting = true; + // dissertation of Achim Koberstein + // if Bx - b is different at any component more that refactor_epsilon then we refactor + double refactor_tolerance = 1e-4; + double pivot_tolerance = 1e-6; + double zero_tolerance = 1e-12; + double drop_tolerance = 1e-14; + double tolerance_for_artificials = 1e-4; + double can_be_taken_to_basis_tolerance = 0.00001; + + unsigned percent_of_entering_to_check = 5; // we try to find a profitable column in a percentage of the columns + bool use_scaling = true; + double scaling_maximum = 1; + double scaling_minimum = 0.5; + double harris_feasibility_tolerance = 1e-7; // page 179 of Istvan Maros + double ignore_epsilon_of_harris = 10e-5; + unsigned max_number_of_iterations_with_no_improvements = 2000000; + unsigned max_total_number_of_iterations = 20000000; + double time_limit = std::numeric_limits::max(); // the maximum time limit of the total run time in seconds + // dual section + double dual_feasibility_tolerance = 1e-7; // // page 71 of the PhD thesis of Achim Koberstein + double primal_feasibility_tolerance = 1e-7; // page 71 of the PhD thesis of Achim Koberstein + double relative_primal_feasibility_tolerance = 1e-9; // page 71 of the PhD thesis of Achim Koberstein + + bool m_bound_propagation = true; + + bool bound_progation() const { + return m_bound_propagation; + } + + bool& bound_propagation() { + return m_bound_propagation; + } + + lp_settings() : m_default_resource_limit(*this), m_resource_limit(&m_default_resource_limit) {} + + void set_resource_limit(lp_resource_limit& lim) { m_resource_limit = &lim; } + bool get_cancel_flag() const { return m_resource_limit->get_cancel_flag(); } + + void set_debug_ostream(std::ostream* out) { m_debug_out = out; } + void set_message_ostream(std::ostream* out) { m_message_out = out; } + + std::ostream* get_debug_ostream() { return m_debug_out; } + std::ostream* get_message_ostream() { return m_message_out; } + stats& st() { return m_stats; } + stats const& st() const { return m_stats; } + + template static bool is_eps_small_general(const T & t, const double & eps) { + return (!numeric_traits::precise())? is_epsilon_small(t, eps) : numeric_traits::is_zero(t); + } + + template + bool abs_val_is_smaller_than_dual_feasibility_tolerance(T const & t) { + return is_eps_small_general(t, dual_feasibility_tolerance); + } + + template + bool abs_val_is_smaller_than_primal_feasibility_tolerance(T const & t) { + return is_eps_small_general(t, primal_feasibility_tolerance); + } + + template + bool abs_val_is_smaller_than_can_be_taken_to_basis_tolerance(T const & t) { + return is_eps_small_general(t, can_be_taken_to_basis_tolerance); + } + + template + bool abs_val_is_smaller_than_drop_tolerance(T const & t) const { + return is_eps_small_general(t, drop_tolerance); + } + + + template + bool abs_val_is_smaller_than_zero_tolerance(T const & t) { + return is_eps_small_general(t, zero_tolerance); + } + + template + bool abs_val_is_smaller_than_refactor_tolerance(T const & t) { + return is_eps_small_general(t, refactor_tolerance); + } + + + template + bool abs_val_is_smaller_than_pivot_tolerance(T const & t) { + return is_eps_small_general(t, pivot_tolerance); + } + + template + bool abs_val_is_smaller_than_harris_tolerance(T const & t) { + return is_eps_small_general(t, harris_feasibility_tolerance); + } + + template + bool abs_val_is_smaller_than_ignore_epslilon_for_harris(T const & t) { + return is_eps_small_general(t, ignore_epsilon_of_harris); + } + + template + bool abs_val_is_smaller_than_artificial_tolerance(T const & t) { + return is_eps_small_general(t, tolerance_for_artificials); + } + // the method of lar solver to use + bool presolve_with_double_solver_for_lar = true; + simplex_strategy_enum m_simplex_strategy = simplex_strategy_enum::tableau_rows; + simplex_strategy_enum simplex_strategy() const { + return m_simplex_strategy; + } + + simplex_strategy_enum & simplex_strategy() { + return m_simplex_strategy; + } + + bool use_tableau() const { + return m_simplex_strategy != simplex_strategy_enum::no_tableau; + } + + bool use_tableau_rows() const { + return m_simplex_strategy == simplex_strategy_enum::tableau_rows; + } + + int report_frequency = 1000; + bool print_statistics = false; + unsigned column_norms_update_frequency = 12000; + bool scale_with_ratio = true; + double density_threshold = 0.7; // need to tune it up, todo +#ifdef LEAN_DEBUG + static unsigned ddd; // used for debugging +#endif + bool use_breakpoints_in_feasibility_search = false; + unsigned random_seed = 1; + static unsigned long random_next; + unsigned max_row_length_for_bound_propagation = 300; + bool backup_costs = true; +}; // end of lp_settings class + + +#define LP_OUT(_settings_, _msg_) { if (_settings_.get_debug_ostream()) { *_settings_.get_debug_ostream() << _msg_; } } + +template +std::string T_to_string(const T & t) { + std::ostringstream strs; + strs << t; + return strs.str(); +} + +inline std::string T_to_string(const numeric_pair & t) { + std::ostringstream strs; + double r = (t.x + t.y / mpq(1000)).get_double(); + strs << r; + return strs.str(); +} + + +inline std::string T_to_string(const mpq & t) { + std::ostringstream strs; + strs << t.get_double(); + return strs.str(); +} + +template +bool val_is_smaller_than_eps(T const & t, double const & eps) { + if (!numeric_traits::precise()) { + return numeric_traits::get_double(t) < eps; + } + return t <= numeric_traits::zero(); +} + +template +bool vectors_are_equal(T * a, vector &b, unsigned n); + +template +bool vectors_are_equal(const vector & a, const buffer &b); + +template +bool vectors_are_equal(const vector & a, const vector &b); + +template +T abs (T const & v) { return v >= zero_of_type() ? v : -v; } + +template +X max_abs_in_vector(vector& t){ + X r(zero_of_type()); + for (auto & v : t) + r = std::max(abs(v) , r); + return r; +} +inline void print_blanks(int n, std::ostream & out) { + while (n--) {out << ' '; } +} + + +// after a push of the last element we ensure that the vector increases +// we also suppose that before the last push the vector was increasing +inline void ensure_increasing(vector & v) { + lean_assert(v.size() > 0); + unsigned j = v.size() - 1; + for (; j > 0; j-- ) + if (v[j] <= v[j - 1]) { + // swap + unsigned t = v[j]; + v[j] = v[j-1]; + v[j-1] = t; + } else { + break; + } +} + + + +#if LEAN_DEBUG +bool D(); +#endif +} diff --git a/src/util/lp/lp_settings.hpp b/src/util/lp/lp_settings.hpp new file mode 100644 index 000000000..d110d51af --- /dev/null +++ b/src/util/lp/lp_settings.hpp @@ -0,0 +1,133 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include +#include +#include "util/vector.h" +#include "util/lp/lp_settings.h" +namespace lean { +std::string column_type_to_string(column_type t) { + switch (t) { + case column_type::fixed: return "fixed"; + case column_type::boxed: return "boxed"; + case column_type::low_bound: return "low_bound"; + case column_type::upper_bound: return "upper_bound"; + case column_type::free_column: return "free_column"; + default: lean_unreachable(); + } + return "unknown"; // it is unreachable +} + +const char* lp_status_to_string(lp_status status) { + switch (status) { + case UNKNOWN: return "UNKNOWN"; + case INFEASIBLE: return "INFEASIBLE"; + case UNBOUNDED: return "UNBOUNDED"; + case TENTATIVE_DUAL_UNBOUNDED: return "TENTATIVE_DUAL_UNBOUNDED"; + case DUAL_UNBOUNDED: return "DUAL_UNBOUNDED"; + case OPTIMAL: return "OPTIMAL"; + case FEASIBLE: return "FEASIBLE"; + case FLOATING_POINT_ERROR: return "FLOATING_POINT_ERROR"; + case TIME_EXHAUSTED: return "TIME_EXHAUSTED"; + case ITERATIONS_EXHAUSTED: return "ITERATIONS_EXHAUSTED"; + case EMPTY: return "EMPTY"; + case UNSTABLE: return "UNSTABLE"; + default: + lean_unreachable(); + } + return "UNKNOWN"; // it is unreachable +} + +lp_status lp_status_from_string(std::string status) { + if (status == "UNKNOWN") return lp_status::UNKNOWN; + if (status == "INFEASIBLE") return lp_status::INFEASIBLE; + if (status == "UNBOUNDED") return lp_status::UNBOUNDED; + if (status == "OPTIMAL") return lp_status::OPTIMAL; + if (status == "FEASIBLE") return lp_status::FEASIBLE; + if (status == "FLOATING_POINT_ERROR") return lp_status::FLOATING_POINT_ERROR; + if (status == "TIME_EXHAUSTED") return lp_status::TIME_EXHAUSTED; + if (status == "ITERATIONS_EXHAUSTED") return lp_status::ITERATIONS_EXHAUSTED; + if (status == "EMPTY") return lp_status::EMPTY; + lean_unreachable(); + return lp_status::UNKNOWN; // it is unreachable +} +int get_millisecond_count() { + timeb tb; + ftime(&tb); + return tb.millitm + (tb.time & 0xfffff) * 1000; +} + +int get_millisecond_span(int start_time) { + int span = get_millisecond_count() - start_time; + if (span < 0) + span += 0x100000 * 1000; + return span; +} + + + +void my_random_init(long unsigned seed) { + lp_settings::random_next = seed; +} + +unsigned my_random() { + lp_settings::random_next = lp_settings::random_next * 1103515245 + 12345; + return((unsigned)(lp_settings::random_next/65536) % 32768); +} + +template +bool vectors_are_equal(T * a, vector &b, unsigned n) { + if (numeric_traits::precise()) { + for (unsigned i = 0; i < n; i ++){ + if (!numeric_traits::is_zero(a[i] - b[i])) { + // std::cout << "a[" << i <<"]" << a[i] << ", " << "b[" << i <<"]" << b[i] << std::endl; + return false; + } + } + } else { + for (unsigned i = 0; i < n; i ++){ + if (std::abs(numeric_traits::get_double(a[i] - b[i])) > 0.000001) { + // std::cout << "a[" << i <<"]" << a[i] << ", " << "b[" << i <<"]" << b[i] << std::endl; + return false; + } + } + } + return true; +} + + +template +bool vectors_are_equal(const vector & a, const vector &b) { + unsigned n = static_cast(a.size()); + if (n != b.size()) return false; + if (numeric_traits::precise()) { + for (unsigned i = 0; i < n; i ++){ + if (!numeric_traits::is_zero(a[i] - b[i])) { + // std::cout << "a[" << i <<"]" << a[i] << ", " << "b[" << i <<"]" << b[i] << std::endl; + return false; + } + } + } else { + for (unsigned i = 0; i < n; i ++){ + double da = numeric_traits::get_double(a[i]); + double db = numeric_traits::get_double(b[i]); + double amax = std::max(fabs(da), fabs(db)); + if (amax > 1) { + da /= amax; + db /= amax; + } + + if (fabs(da - db) > 0.000001) { + // std::cout << "a[" << i <<"] = " << a[i] << ", but " << "b[" << i <<"] = " << b[i] << std::endl; + return false; + } + } + } + return true; +} +unsigned long lp_settings::random_next = 1; +#ifdef LEAN_DEBUG +unsigned lp_settings::ddd = 0; +#endif +} diff --git a/src/util/lp/lp_settings_instances.cpp b/src/util/lp/lp_settings_instances.cpp new file mode 100644 index 000000000..e9a3888ba --- /dev/null +++ b/src/util/lp/lp_settings_instances.cpp @@ -0,0 +1,10 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include "util/vector.h" +#include +#include "util/lp/lp_settings.hpp" +template bool lean::vectors_are_equal(vector const&, vector const&); +template bool lean::vectors_are_equal(vector const&, vector const&); + diff --git a/src/util/lp/lp_solver.h b/src/util/lp/lp_solver.h new file mode 100644 index 000000000..eeb3ff6d3 --- /dev/null +++ b/src/util/lp/lp_solver.h @@ -0,0 +1,253 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ + +#pragma once +#include +#include +#include +#include "util/vector.h" +#include "util/lp/lp_settings.h" +#include "util/lp/column_info.h" +#include "util/lp/static_matrix.h" +#include "util/lp/lp_core_solver_base.h" +#include "util/lp/scaler.h" +#include "util/lp/linear_combination_iterator.h" +#include "util/lp/bound_analyzer_on_row.h" +namespace lean { +enum lp_relation { + Less_or_equal, + Equal, + Greater_or_equal +}; + +template +struct lp_constraint { + X m_rs; // right side of the constraint + lp_relation m_relation; + lp_constraint() {} // empty constructor + lp_constraint(T rs, lp_relation relation): m_rs(rs), m_relation(relation) {} +}; + + +template +class lp_solver : public column_namer { + column_info * get_or_create_column_info(unsigned column); + +protected: + T get_column_cost_value(unsigned j, column_info * ci) const; +public: + unsigned m_total_iterations; + static_matrix* m_A = nullptr; // this is the matrix of constraints + vector m_b; // the right side vector + unsigned m_first_stage_iterations = 0; + unsigned m_second_stage_iterations = 0; + std::unordered_map> m_constraints; + std::unordered_map*> m_map_from_var_index_to_column_info; + std::unordered_map > m_A_values; + std::unordered_map m_names_to_columns; // don't have to use it + std::unordered_map m_external_rows_to_core_solver_rows; + std::unordered_map m_core_solver_rows_to_external_rows; + std::unordered_map m_core_solver_columns_to_external_columns; + vector m_column_scale; + std::unordered_map m_name_map; + unsigned m_artificials = 0; + unsigned m_slacks = 0; + vector m_column_types; + vector m_costs; + vector m_x; + vector m_upper_bounds; + vector m_basis; + vector m_nbasis; + vector m_heading; + + + lp_status m_status = lp_status::UNKNOWN; + + lp_settings m_settings; + lp_solver() {} + + unsigned row_count() const { return this->m_A->row_count(); } + + void add_constraint(lp_relation relation, T right_side, unsigned row_index); + + void set_cost_for_column(unsigned column, T column_cost) { + get_or_create_column_info(column)->set_cost(column_cost); + } + std::string get_column_name(unsigned j) const override; + + void set_row_column_coefficient(unsigned row, unsigned column, T const & val) { + m_A_values[row][column] = val; + } + // returns the current cost + virtual T get_current_cost() const = 0; + // do not have to call it + void give_symbolic_name_to_column(std::string name, unsigned column); + + virtual T get_column_value(unsigned column) const = 0; + + T get_column_value_by_name(std::string name) const; + + // returns -1 if not found + virtual int get_column_index_by_name(std::string name) const; + + void set_low_bound(unsigned i, T bound) { + column_info *ci = get_or_create_column_info(i); + ci->set_low_bound(bound); + } + + void set_upper_bound(unsigned i, T bound) { + column_info *ci = get_or_create_column_info(i); + ci->set_upper_bound(bound); + } + + void unset_low_bound(unsigned i) { + get_or_create_column_info(i)->unset_low_bound(); + } + + void unset_upper_bound(unsigned i) { + get_or_create_column_info(i)->unset_upper_bound(); + } + + void set_fixed_value(unsigned i, T val) { + column_info *ci = get_or_create_column_info(i); + ci->set_fixed_value(val); + } + + void unset_fixed_value(unsigned i) { + get_or_create_column_info(i)->unset_fixed(); + } + + lp_status get_status() const { + return m_status; + } + + void set_status(lp_status st) { + m_status = st; + } + + + virtual ~lp_solver(); + + void flip_costs(); + + virtual void find_maximal_solution() = 0; + void set_time_limit(unsigned time_limit_in_seconds) { + m_settings.time_limit = time_limit_in_seconds; + } + + void set_max_iterations_per_stage(unsigned max_iterations) { + m_settings.max_total_number_of_iterations = max_iterations; + } + + unsigned get_max_iterations_per_stage() const { + return m_settings.max_total_number_of_iterations; + } +protected: + bool problem_is_empty(); + + void scale(); + + + void print_rows_scale_stats(std::ostream & out); + + void print_columns_scale_stats(std::ostream & out); + + void print_row_scale_stats(unsigned i, std::ostream & out); + + void print_column_scale_stats(unsigned j, std::ostream & out); + + void print_scale_stats(std::ostream & out); + + void get_max_abs_in_row(std::unordered_map & row_map); + + void pin_vars_down_on_row(std::unordered_map & row) { + pin_vars_on_row_with_sign(row, - numeric_traits::one()); + } + + void pin_vars_up_on_row(std::unordered_map & row) { + pin_vars_on_row_with_sign(row, numeric_traits::one()); + } + + void pin_vars_on_row_with_sign(std::unordered_map & row, T sign ); + + bool get_minimal_row_value(std::unordered_map & row, T & low_bound); + + bool get_maximal_row_value(std::unordered_map & row, T & low_bound); + + bool row_is_zero(std::unordered_map & row); + + bool row_e_is_obsolete(std::unordered_map & row, unsigned row_index); + + bool row_ge_is_obsolete(std::unordered_map & row, unsigned row_index); + + bool row_le_is_obsolete(std::unordered_map & row, unsigned row_index); + + // analyse possible max and min values that are derived from var boundaries + // Let us say that the we have a "ge" constraint, and the min value is equal to the rs. + // Then we know what values of the variables are. For each positive coeff of the row it has to be + // the low boundary of the var and for a negative - the upper. + + // this routing also pins the variables to the boundaries + bool row_is_obsolete(std::unordered_map & row, unsigned row_index ); + + void remove_fixed_or_zero_columns(); + + void remove_fixed_or_zero_columns_from_row(unsigned i, std::unordered_map & row); + + unsigned try_to_remove_some_rows(); + + void cleanup(); + + void map_external_rows_to_core_solver_rows(); + + void map_external_columns_to_core_solver_columns(); + + unsigned number_of_core_structurals() { + return static_cast(m_core_solver_columns_to_external_columns.size()); + } + + void restore_column_scales_to_one() { + for (unsigned i = 0; i < m_column_scale.size(); i++) m_column_scale[i] = numeric_traits::one(); + } + + void unscale(); + + void fill_A_from_A_values(); + + void fill_matrix_A_and_init_right_side(); + + void count_slacks_and_artificials(); + + void count_slacks_and_artificials_for_row(unsigned i); + + T low_bound_shift_for_row(unsigned i); + + void fill_m_b(); + + T get_column_value_with_core_solver(unsigned column, lp_core_solver_base * core_solver) const; + void set_scaled_cost(unsigned j); + void print_statistics_on_A(std::ostream & out) { + out << "extended A[" << this->m_A->row_count() << "," << this->m_A->column_count() << "]" << std::endl; + } + + struct row_tighten_stats { + unsigned n_of_new_bounds = 0; + unsigned n_of_fixed = 0; + bool is_obsolete = false; + }; + + + +public: + lp_settings & settings() { return m_settings;} + void print_model(std::ostream & s) const { + s << "objective = " << get_current_cost() << std::endl; + s << "column values\n"; + for (auto & it : m_names_to_columns) { + s << it.first << " = " << get_column_value(it.second) << std::endl; + } + } +}; +} diff --git a/src/util/lp/lp_solver.hpp b/src/util/lp/lp_solver.hpp new file mode 100644 index 000000000..135616a69 --- /dev/null +++ b/src/util/lp/lp_solver.hpp @@ -0,0 +1,554 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include +#include +#include "util/vector.h" +#include "util/lp/lp_solver.h" +namespace lean { +template column_info * lp_solver::get_or_create_column_info(unsigned column) { + auto it = m_map_from_var_index_to_column_info.find(column); + return (it == m_map_from_var_index_to_column_info.end())? (m_map_from_var_index_to_column_info[column] = new column_info(static_cast(-1))) : it->second; +} + +template +std::string lp_solver::get_column_name(unsigned j) const { // j here is the core solver index + auto it = this->m_core_solver_columns_to_external_columns.find(j); + if (it == this->m_core_solver_columns_to_external_columns.end()) + return std::string("x")+T_to_string(j); + unsigned external_j = it->second; + auto t = this->m_map_from_var_index_to_column_info.find(external_j); + if (t == this->m_map_from_var_index_to_column_info.end()) { + return std::string("x") +T_to_string(external_j); + } + return t->second->get_name(); +} + +template T lp_solver::get_column_cost_value(unsigned j, column_info * ci) const { + if (ci->is_fixed()) { + return ci->get_cost() * ci->get_fixed_value(); + } + return ci->get_cost() * get_column_value(j); +} +template void lp_solver::add_constraint(lp_relation relation, T right_side, unsigned row_index) { + lean_assert(m_constraints.find(row_index) == m_constraints.end()); + lp_constraint cs(right_side, relation); + m_constraints[row_index] = cs; +} + +template void lp_solver::give_symbolic_name_to_column(std::string name, unsigned column) { + auto it = m_map_from_var_index_to_column_info.find(column); + column_info *ci; + if (it == m_map_from_var_index_to_column_info.end()){ + m_map_from_var_index_to_column_info[column] = ci = new column_info; + } else { + ci = it->second; + } + ci->set_name(name); + m_names_to_columns[name] = column; +} + + +template T lp_solver::get_column_value_by_name(std::string name) const { + auto it = m_names_to_columns.find(name); + if (it == m_names_to_columns.end()) { + std::stringstream s; + s << "get_column_value_by_name " << name; + throw_exception(s.str()); + } + return get_column_value(it -> second); +} + +// returns -1 if not found +template int lp_solver::get_column_index_by_name(std::string name) const { + auto t = m_names_to_columns.find(name); + if (t == m_names_to_columns.end()) { + return -1; + } + return t->second; +} + + +template lp_solver::~lp_solver(){ + if (m_A != nullptr) { + delete m_A; + } + for (auto t : m_map_from_var_index_to_column_info) { + delete t.second; + } +} + +template void lp_solver::flip_costs() { + for (auto t : m_map_from_var_index_to_column_info) { + column_info *ci = t.second; + ci->set_cost(-ci->get_cost()); + } +} + +template bool lp_solver::problem_is_empty() { + for (auto & c : m_A_values) + if (c.second.size()) + return false; + return true; +} + +template void lp_solver::scale() { + if (numeric_traits::precise() || m_settings.use_scaling == false) { + m_column_scale.clear(); + m_column_scale.resize(m_A->column_count(), one_of_type()); + return; + } + + T smin = T(m_settings.scaling_minimum); + T smax = T(m_settings.scaling_maximum); + + scaler scaler(m_b, *m_A, smin, smax, m_column_scale, this->m_settings); + if (!scaler.scale()) { + unscale(); + } +} + + +template void lp_solver::print_rows_scale_stats(std::ostream & out) { + out << "rows max" << std::endl; + for (unsigned i = 0; i < m_A->row_count(); i++) { + print_row_scale_stats(i, out); + } + out << std::endl; +} + +template void lp_solver::print_columns_scale_stats(std::ostream & out) { + out << "columns max" << std::endl; + for (unsigned i = 0; i < m_A->column_count(); i++) { + print_column_scale_stats(i, out); + } + out << std::endl; +} + +template void lp_solver::print_row_scale_stats(unsigned i, std::ostream & out) { + out << "(" << std::min(m_A->get_min_abs_in_row(i), abs(m_b[i])) << " "; + out << std::max(m_A->get_max_abs_in_row(i), abs(m_b[i])) << ")"; +} + +template void lp_solver::print_column_scale_stats(unsigned j, std::ostream & out) { + out << "(" << m_A->get_min_abs_in_row(j) << " "; + out << m_A->get_max_abs_in_column(j) << ")"; +} + +template void lp_solver::print_scale_stats(std::ostream & out) { + print_rows_scale_stats(out); + print_columns_scale_stats(out); +} + +template void lp_solver::get_max_abs_in_row(std::unordered_map & row_map) { + T ret = numeric_traits::zero(); + for (auto jp : row_map) { + T ac = numeric_traits::abs(jp->second); + if (ac > ret) { + ret = ac; + } + } + return ret; +} + +template void lp_solver::pin_vars_on_row_with_sign(std::unordered_map & row, T sign ) { + for (auto t : row) { + unsigned j = t.first; + column_info * ci = m_map_from_var_index_to_column_info[j]; + T a = t.second; + if (a * sign > numeric_traits::zero()) { + lean_assert(ci->upper_bound_is_set()); + ci->set_fixed_value(ci->get_upper_bound()); + } else { + lean_assert(ci->low_bound_is_set()); + ci->set_fixed_value(ci->get_low_bound()); + } + } +} + +template bool lp_solver::get_minimal_row_value(std::unordered_map & row, T & low_bound) { + low_bound = numeric_traits::zero(); + for (auto & t : row) { + T a = t.second; + column_info * ci = m_map_from_var_index_to_column_info[t.first]; + if (a > numeric_traits::zero()) { + if (ci->low_bound_is_set()) { + low_bound += ci->get_low_bound() * a; + } else { + return false; + } + } else { + if (ci->upper_bound_is_set()) { + low_bound += ci->get_upper_bound() * a; + } else { + return false; + } + } + } + return true; +} + +template bool lp_solver::get_maximal_row_value(std::unordered_map & row, T & low_bound) { + low_bound = numeric_traits::zero(); + for (auto & t : row) { + T a = t.second; + column_info * ci = m_map_from_var_index_to_column_info[t.first]; + if (a < numeric_traits::zero()) { + if (ci->low_bound_is_set()) { + low_bound += ci->get_low_bound() * a; + } else { + return false; + } + } else { + if (ci->upper_bound_is_set()) { + low_bound += ci->get_upper_bound() * a; + } else { + return false; + } + } + } + return true; +} + +template bool lp_solver::row_is_zero(std::unordered_map & row) { + for (auto & t : row) { + if (!is_zero(t.second)) + return false; + } + return true; +} + +template bool lp_solver::row_e_is_obsolete(std::unordered_map & row, unsigned row_index) { + T rs = m_constraints[row_index].m_rs; + if (row_is_zero(row)) { + if (!is_zero(rs)) + m_status = INFEASIBLE; + return true; + } + + T low_bound; + bool lb = get_minimal_row_value(row, low_bound); + if (lb) { + T diff = low_bound - rs; + if (!val_is_smaller_than_eps(diff, m_settings.refactor_tolerance)){ + // low_bound > rs + m_settings.refactor_epsilon + m_status = INFEASIBLE; + return true; + } + if (val_is_smaller_than_eps(-diff, m_settings.refactor_tolerance)){ + pin_vars_down_on_row(row); + return true; + } + } + + T upper_bound; + bool ub = get_maximal_row_value(row, upper_bound); + if (ub) { + T diff = rs - upper_bound; + if (!val_is_smaller_than_eps(diff, m_settings.refactor_tolerance)) { + // upper_bound < rs - m_settings.refactor_tolerance + m_status = INFEASIBLE; + return true; + } + if (val_is_smaller_than_eps(-diff, m_settings.refactor_tolerance)){ + pin_vars_up_on_row(row); + return true; + } + } + + return false; +} + +template bool lp_solver::row_ge_is_obsolete(std::unordered_map & row, unsigned row_index) { + T rs = m_constraints[row_index].m_rs; + if (row_is_zero(row)) { + if (rs > zero_of_type()) + m_status = INFEASIBLE; + return true; + } + + T upper_bound; + if (get_maximal_row_value(row, upper_bound)) { + T diff = rs - upper_bound; + if (!val_is_smaller_than_eps(diff, m_settings.refactor_tolerance)) { + // upper_bound < rs - m_settings.refactor_tolerance + m_status = INFEASIBLE; + return true; + } + if (val_is_smaller_than_eps(-diff, m_settings.refactor_tolerance)){ + pin_vars_up_on_row(row); + return true; + } + } + + return false; +} + +template bool lp_solver::row_le_is_obsolete(std::unordered_map & row, unsigned row_index) { + T low_bound; + T rs = m_constraints[row_index].m_rs; + if (row_is_zero(row)) { + if (rs < zero_of_type()) + m_status = INFEASIBLE; + return true; + } + + if (get_minimal_row_value(row, low_bound)) { + T diff = low_bound - rs; + if (!val_is_smaller_than_eps(diff, m_settings.refactor_tolerance)){ + // low_bound > rs + m_settings.refactor_tolerance + m_status = lp_status::INFEASIBLE; + return true; + } + if (val_is_smaller_than_eps(-diff, m_settings.refactor_tolerance)){ + pin_vars_down_on_row(row); + return true; + } + } + + return false; +} + +// analyse possible max and min values that are derived from var boundaries +// Let us say that the we have a "ge" constraint, and the min value is equal to the rs. +// Then we know what values of the variables are. For each positive coeff of the row it has to be +// the low boundary of the var and for a negative - the upper. + +// this routing also pins the variables to the boundaries +template bool lp_solver::row_is_obsolete(std::unordered_map & row, unsigned row_index ) { + auto & constraint = m_constraints[row_index]; + switch (constraint.m_relation) { + case lp_relation::Equal: + return row_e_is_obsolete(row, row_index); + + case lp_relation::Greater_or_equal: + return row_ge_is_obsolete(row, row_index); + + case lp_relation::Less_or_equal: + return row_le_is_obsolete(row, row_index); + } + lean_unreachable(); + return false; // it is unreachable +} + +template void lp_solver::remove_fixed_or_zero_columns() { + for (auto & i_row : m_A_values) { + remove_fixed_or_zero_columns_from_row(i_row.first, i_row.second); + } +} + +template void lp_solver::remove_fixed_or_zero_columns_from_row(unsigned i, std::unordered_map & row) { + auto & constraint = m_constraints[i]; + vector removed; + for (auto & col : row) { + unsigned j = col.first; + lean_assert(m_map_from_var_index_to_column_info.find(j) != m_map_from_var_index_to_column_info.end()); + column_info * ci = m_map_from_var_index_to_column_info[j]; + if (ci->is_fixed()) { + removed.push_back(j); + T aj = col.second; + constraint.m_rs -= aj * ci->get_fixed_value(); + } else { + if (numeric_traits::is_zero(col.second)){ + removed.push_back(j); + } + } + } + + for (auto j : removed) { + row.erase(j); + } +} + +template unsigned lp_solver::try_to_remove_some_rows() { + vector rows_to_delete; + for (auto & t : m_A_values) { + if (row_is_obsolete(t.second, t.first)) { + rows_to_delete.push_back(t.first); + } + + if (m_status == lp_status::INFEASIBLE) { + return 0; + } + } + if (rows_to_delete.size() > 0) { + for (unsigned k : rows_to_delete) { + m_A_values.erase(k); + } + } + remove_fixed_or_zero_columns(); + return static_cast(rows_to_delete.size()); +} + +template void lp_solver::cleanup() { + int n = 0; // number of deleted rows + int d; + while ((d = try_to_remove_some_rows() > 0)) + n += d; + + if (n == 1) { + LP_OUT(m_settings, "deleted one row" << std::endl); + } else if (n) { + LP_OUT(m_settings, "deleted " << n << " rows" << std::endl); + } +} + +template void lp_solver::map_external_rows_to_core_solver_rows() { + unsigned size = 0; + for (auto & row : m_A_values) { + m_external_rows_to_core_solver_rows[row.first] = size; + m_core_solver_rows_to_external_rows[size] = row.first; + size++; + } +} + +template void lp_solver::map_external_columns_to_core_solver_columns() { + unsigned size = 0; + for (auto & row : m_A_values) { + for (auto & col : row.second) { + if (col.second == numeric_traits::zero() || m_map_from_var_index_to_column_info[col.first]->is_fixed()) { + throw_exception("found fixed column"); + } + unsigned j = col.first; + auto column_info_it = m_map_from_var_index_to_column_info.find(j); + lean_assert(column_info_it != m_map_from_var_index_to_column_info.end()); + + auto j_column = column_info_it->second->get_column_index(); + if (!is_valid(j_column)) { // j is a newcomer + m_map_from_var_index_to_column_info[j]->set_column_index(size); + m_core_solver_columns_to_external_columns[size++] = j; + } + } + } +} + +template void lp_solver::unscale() { + delete m_A; + m_A = nullptr; + fill_A_from_A_values(); + restore_column_scales_to_one(); + fill_m_b(); +} + +template void lp_solver::fill_A_from_A_values() { + m_A = new static_matrix(static_cast(m_A_values.size()), number_of_core_structurals()); + for (auto & t : m_A_values) { + auto row_it = m_external_rows_to_core_solver_rows.find(t.first); + lean_assert(row_it != m_external_rows_to_core_solver_rows.end()); + unsigned row = row_it->second; + for (auto k : t.second) { + auto column_info_it = m_map_from_var_index_to_column_info.find(k.first); + lean_assert(column_info_it != m_map_from_var_index_to_column_info.end()); + column_info *ci = column_info_it->second; + unsigned col = ci->get_column_index(); + lean_assert(is_valid(col)); + bool col_is_flipped = m_map_from_var_index_to_column_info[k.first]->is_flipped(); + if (!col_is_flipped) { + (*m_A)(row, col) = k.second; + } else { + (*m_A)(row, col) = - k.second; + } + } + } +} + +template void lp_solver::fill_matrix_A_and_init_right_side() { + map_external_rows_to_core_solver_rows(); + map_external_columns_to_core_solver_columns(); + lean_assert(m_A == nullptr); + fill_A_from_A_values(); + m_b.resize(m_A->row_count()); +} + +template void lp_solver::count_slacks_and_artificials() { + for (int i = row_count() - 1; i >= 0; i--) { + count_slacks_and_artificials_for_row(i); + } +} + +template void lp_solver::count_slacks_and_artificials_for_row(unsigned i) { + lean_assert(this->m_constraints.find(this->m_core_solver_rows_to_external_rows[i]) != this->m_constraints.end()); + auto & constraint = this->m_constraints[this->m_core_solver_rows_to_external_rows[i]]; + switch (constraint.m_relation) { + case Equal: + m_artificials++; + break; + case Greater_or_equal: + m_slacks++; + if (this->m_b[i] > 0) { + m_artificials++; + } + break; + case Less_or_equal: + m_slacks++; + if (this->m_b[i] < 0) { + m_artificials++; + } + break; + } +} + +template T lp_solver::low_bound_shift_for_row(unsigned i) { + T ret = numeric_traits::zero(); + + auto row = this->m_A_values.find(i); + if (row == this->m_A_values.end()) { + throw_exception("cannot find row"); + } + for (auto col : row->second) { + ret += col.second * this->m_map_from_var_index_to_column_info[col.first]->get_shift(); + } + return ret; +} + +template void lp_solver::fill_m_b() { + for (int i = this->row_count() - 1; i >= 0; i--) { + lean_assert(this->m_constraints.find(this->m_core_solver_rows_to_external_rows[i]) != this->m_constraints.end()); + unsigned external_i = this->m_core_solver_rows_to_external_rows[i]; + auto & constraint = this->m_constraints[external_i]; + this->m_b[i] = constraint.m_rs - low_bound_shift_for_row(external_i); + } +} + +template T lp_solver::get_column_value_with_core_solver(unsigned column, lp_core_solver_base * core_solver) const { + auto cit = this->m_map_from_var_index_to_column_info.find(column); + if (cit == this->m_map_from_var_index_to_column_info.end()) { + return numeric_traits::zero(); + } + + column_info * ci = cit->second; + + if (ci->is_fixed()) { + return ci->get_fixed_value(); + } + + unsigned cj = ci->get_column_index(); + if (cj != static_cast(-1)) { + T v = core_solver->get_var_value(cj) * this->m_column_scale[cj]; + if (ci->is_free()) { + return v; + } + if (!ci->is_flipped()) { + return v + ci->get_low_bound(); + } + + // the flipped case when there is only upper bound + return -v + ci->get_upper_bound(); // + } + + return numeric_traits::zero(); // returns zero for out of boundary columns +} + +template void lp_solver::set_scaled_cost(unsigned j) { + // grab original costs but modify it with the column scales + lean_assert(j < this->m_column_scale.size()); + column_info * ci = this->m_map_from_var_index_to_column_info[this->m_core_solver_columns_to_external_columns[j]]; + T cost = ci->get_cost(); + if (ci->is_flipped()){ + cost *= -1; + } + lean_assert(ci->is_fixed() == false); + this->m_costs[j] = cost * this->m_column_scale[j]; +} +} diff --git a/src/util/lp/lp_solver_instances.cpp b/src/util/lp/lp_solver_instances.cpp new file mode 100644 index 000000000..5df490cae --- /dev/null +++ b/src/util/lp/lp_solver_instances.cpp @@ -0,0 +1,40 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include +#include "util/lp/lp_solver.hpp" +template void lean::lp_solver::add_constraint(lean::lp_relation, double, unsigned int); +template void lean::lp_solver::cleanup(); +template void lean::lp_solver::count_slacks_and_artificials(); +template void lean::lp_solver::fill_m_b(); +template void lean::lp_solver::fill_matrix_A_and_init_right_side(); +template void lean::lp_solver::flip_costs(); +template double lean::lp_solver::get_column_cost_value(unsigned int, lean::column_info*) const; +template int lean::lp_solver::get_column_index_by_name(std::string) const; +template double lean::lp_solver::get_column_value_with_core_solver(unsigned int, lean::lp_core_solver_base*) const; +template lean::column_info* lean::lp_solver::get_or_create_column_info(unsigned int); +template void lean::lp_solver::give_symbolic_name_to_column(std::string, unsigned int); +template void lean::lp_solver::print_statistics_on_A(std::ostream & out); +template bool lean::lp_solver::problem_is_empty(); +template void lean::lp_solver::scale(); +template void lean::lp_solver::set_scaled_cost(unsigned int); +template lean::lp_solver::~lp_solver(); +template void lean::lp_solver::add_constraint(lean::lp_relation, lean::mpq, unsigned int); +template void lean::lp_solver::cleanup(); +template void lean::lp_solver::count_slacks_and_artificials(); +template void lean::lp_solver::fill_m_b(); +template void lean::lp_solver::fill_matrix_A_and_init_right_side(); +template void lean::lp_solver::flip_costs(); +template lean::mpq lean::lp_solver::get_column_cost_value(unsigned int, lean::column_info*) const; +template int lean::lp_solver::get_column_index_by_name(std::string) const; +template lean::mpq lean::lp_solver::get_column_value_by_name(std::string) const; +template lean::mpq lean::lp_solver::get_column_value_with_core_solver(unsigned int, lean::lp_core_solver_base*) const; +template lean::column_info* lean::lp_solver::get_or_create_column_info(unsigned int); +template void lean::lp_solver::give_symbolic_name_to_column(std::string, unsigned int); +template void lean::lp_solver::print_statistics_on_A(std::ostream & out); +template bool lean::lp_solver::problem_is_empty(); +template void lean::lp_solver::scale(); +template void lean::lp_solver::set_scaled_cost(unsigned int); +template lean::lp_solver::~lp_solver(); +template double lean::lp_solver::get_column_value_by_name(std::string) const; diff --git a/src/util/lp/lp_utils.cpp b/src/util/lp/lp_utils.cpp new file mode 100644 index 000000000..8cb98974e --- /dev/null +++ b/src/util/lp/lp_utils.cpp @@ -0,0 +1,11 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include "util/lp/lp_utils.h" +#ifdef lp_for_z3 +namespace lean { +double numeric_traits::g_zero = 0.0; +double numeric_traits::g_one = 1.0; +} +#endif diff --git a/src/util/lp/lp_utils.h b/src/util/lp/lp_utils.h new file mode 100644 index 000000000..2be15d79a --- /dev/null +++ b/src/util/lp/lp_utils.h @@ -0,0 +1,141 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson + This file should be present in z3 and in Lean. +*/ +#pragma once +#include +#include "util/lp/numeric_pair.h" +#include "util/debug.h" +#include +template +bool try_get_val(const std::unordered_map & map, const A& key, B & val) { + const auto it = map.find(key); + if (it == map.end()) return false; + val = it->second; + return true; +} + +template +bool contains(const std::unordered_map & map, const A& key) { + return map.find(key) != map.end(); +} + +#ifdef lp_for_z3 + +#ifdef Z3DEBUG +#define LEAN_DEBUG 1 +#endif + +namespace lean { + inline void throw_exception(const std::string & str) { + throw default_exception(str); + } + typedef z3_exception exception; + +#define lean_assert(_x_) { SASSERT(_x_); } + inline void lean_unreachable() { lean_assert(false); } + template inline X zero_of_type() { return numeric_traits::zero(); } + template inline X one_of_type() { return numeric_traits::one(); } + template inline bool is_zero(const X & v) { return numeric_traits::is_zero(v); } + template inline bool is_pos(const X & v) { return numeric_traits::is_pos(v); } + template inline bool is_neg(const X & v) { return numeric_traits::is_neg(v); } + + template inline bool precise() { return numeric_traits::precise(); } +} +namespace std { +template<> +struct hash { + inline size_t operator()(const rational & v) const { + return v.hash(); + } +}; +} + +template +inline void hash_combine(std::size_t & seed, const T & v) { + seed ^= std::hash()(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); +} + +namespace std { +template struct hash> { + inline size_t operator()(const pair & v) const { + size_t seed = 0; + hash_combine(seed, v.first); + hash_combine(seed, v.second); + return seed; + } +}; + +template<> +struct hash> { + inline size_t operator()(const lean::numeric_pair & v) const { + size_t seed = 0; + hash_combine(seed, v.x); + hash_combine(seed, v.y); + return seed; + } +}; + +} +#else // else of #if lp_for_z3 +#include +#include +//include "util/numerics/mpq.h" +//include "util/numerics/numeric_traits.h" +//include "util/numerics/double.h" + +#ifdef __CLANG__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wmismatched-tags" +#endif +namespace std { +template<> +struct hash { + inline size_t operator()(const lean::mpq & v) const { + return v.hash(); + } +}; +} +namespace lean { +template inline bool precise() { return numeric_traits::precise();} +template inline X one_of_type() { return numeric_traits::one(); } +template inline bool is_zero(const X & v) { return numeric_traits::is_zero(v); } +template inline double get_double(const X & v) { return numeric_traits::get_double(v); } +template inline T zero_of_type() {return numeric_traits::zero();} +inline void throw_exception(std::string str) { throw exception(str); } +template inline T from_string(std::string const & ) { lean_unreachable();} +template <> double inline from_string(std::string const & str) { return atof(str.c_str());} +template <> mpq inline from_string(std::string const & str) { + return mpq(atof(str.c_str())); +} + +} // closing lean +template +inline void hash_combine(std::size_t & seed, const T & v) { + seed ^= std::hash()(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); +} + +namespace std { +template struct hash> { + inline size_t operator()(const pair & v) const { + size_t seed = 0; + hash_combine(seed, v.first); + hash_combine(seed, v.second); + return seed; + } +}; +template<> +struct hash> { + inline size_t operator()(const lean::numeric_pair & v) const { + size_t seed = 0; + hash_combine(seed, v.x); + hash_combine(seed, v.y); + return seed; + } +}; +} // std +#ifdef __CLANG__ +#pragma clang diagnostic pop +#endif +#endif diff --git a/src/util/lp/lu.h b/src/util/lp/lu.h new file mode 100644 index 000000000..415b7f978 --- /dev/null +++ b/src/util/lp/lu.h @@ -0,0 +1,359 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ + +#pragma once + +#include "util/vector.h" +#include "util/debug.h" +#include +#include +#include "util/lp/sparse_matrix.h" +#include "util/lp/static_matrix.h" +#include +#include "util/lp/numeric_pair.h" +#include +#include +#include "util/lp/row_eta_matrix.h" +#include "util/lp/square_dense_submatrix.h" +#include "util/lp/dense_matrix.h" +namespace lean { +#ifdef LEAN_DEBUG +template // print the nr x nc submatrix at the top left corner +void print_submatrix(sparse_matrix & m, unsigned mr, unsigned nc); + +template +void print_matrix(static_matrix &m, std::ostream & out); + +template +void print_matrix(sparse_matrix& m, std::ostream & out); +#endif + +template +X dot_product(const vector & a, const vector & b) { + lean_assert(a.size() == b.size()); + auto r = zero_of_type(); + for (unsigned i = 0; i < a.size(); i++) { + r += a[i] * b[i]; + } + return r; +} + + +template +class one_elem_on_diag: public tail_matrix { + unsigned m_i; + T m_val; +public: + one_elem_on_diag(unsigned i, T val) : m_i(i), m_val(val) { +#ifdef LEAN_DEBUG + m_one_over_val = numeric_traits::one() / m_val; +#endif + } + + bool is_dense() const { return false; } + + one_elem_on_diag(const one_elem_on_diag & o); + +#ifdef LEAN_DEBUG + unsigned m_m; + unsigned m_n; + virtual void set_number_of_rows(unsigned m) { m_m = m; m_n = m; } + virtual void set_number_of_columns(unsigned n) { m_m = n; m_n = n; } + T m_one_over_val; + + T get_elem (unsigned i, unsigned j) const; + + unsigned row_count() const { return m_m; } // not defined } + unsigned column_count() const { return m_m; } // not defined } +#endif + void apply_from_left(vector & w, lp_settings &) { + w[m_i] /= m_val; + } + + void apply_from_right(vector & w) { + w[m_i] /= m_val; + } + + void apply_from_right(indexed_vector & w) { + if (is_zero(w.m_data[m_i])) + return; + auto & v = w.m_data[m_i] /= m_val; + if (lp_settings::is_eps_small_general(v, 1e-14)) { + w.erase_from_index(m_i); + v = zero_of_type(); + } + } + + + void apply_from_left_to_T(indexed_vector & w, lp_settings & settings); + + void conjugate_by_permutation(permutation_matrix & p) { + // this = p * this * p(-1) +#ifdef LEAN_DEBUG + // auto rev = p.get_reverse(); + // auto deb = ((*this) * rev); + // deb = p * deb; +#endif + m_i = p.apply_reverse(m_i); + +#ifdef LEAN_DEBUG + // lean_assert(*this == deb); +#endif + } +}; // end of one_elem_on_diag + +enum class LU_status { OK, Degenerated}; + +// This class supports updates of the columns of B, and solves systems Bx=b,and yB=c +// Using Suhl-Suhl method described in the dissertation of Achim Koberstein, Chapter 5 +template +class lu { + LU_status m_status = LU_status::OK; +public: + // the fields + unsigned m_dim; + static_matrix const &m_A; + permutation_matrix m_Q; + permutation_matrix m_R; + permutation_matrix m_r_wave; + sparse_matrix m_U; + square_dense_submatrix* m_dense_LU; + + vector *> m_tail; + lp_settings & m_settings; + bool m_failure = false; + indexed_vector m_row_eta_work_vector; + indexed_vector m_w_for_extension; + indexed_vector m_y_copy; + indexed_vector m_ii; //to optimize the work with the m_index fields + unsigned m_refactor_counter = 0; + // constructor + // if A is an m by n matrix then basis has length m and values in [0,n); the values are all different + // they represent the set of m columns + lu(static_matrix const & A, + vector& basis, + lp_settings & settings); + void debug_test_of_basis(static_matrix const & A, vector & basis); + void solve_Bd_when_w_is_ready(vector & d, indexed_vector& w ); + void solve_By(indexed_vector & y); + + void solve_By(vector & y); + + void solve_By_for_T_indexed_only(indexed_vector& y, const lp_settings &); + + template + void solve_By_when_y_is_ready(indexed_vector & y); + void solve_By_when_y_is_ready_for_X(vector & y); + void solve_By_when_y_is_ready_for_T(vector & y, vector & index); + void print_indexed_vector(indexed_vector & w, std::ofstream & f); + + void print_matrix_compact(std::ostream & f); + + void print(indexed_vector & w, const vector& basis); + void solve_Bd(unsigned a_column, vector & d, indexed_vector & w); + void solve_Bd(unsigned a_column, indexed_vector & d, indexed_vector & w); + void solve_Bd_faster(unsigned a_column, indexed_vector & d); // d is the right side on the input and the solution at the exit + + void solve_yB(vector& y); + + void solve_yB_indexed(indexed_vector& y); + + void add_delta_to_solution_indexed(indexed_vector& y); + + void add_delta_to_solution(const vector& yc, vector& y); + + + void find_error_of_yB(vector& yc, const vector& y, + const vector& basis); + + void find_error_of_yB_indexed(const indexed_vector& y, + const vector& heading, const lp_settings& settings); + + + void solve_yB_with_error_check(vector & y, const vector& basis); + + void solve_yB_with_error_check_indexed(indexed_vector & y, const vector& heading, const vector & basis, const lp_settings &); + + void apply_Q_R_to_U(permutation_matrix & r_wave); + + + LU_status get_status() { return m_status; } + + void set_status(LU_status status) { + m_status = status; + } + + ~lu(); + + void init_vector_y(vector & y); + + void perform_transformations_on_w(indexed_vector& w); + + void init_vector_w(unsigned entering, indexed_vector & w); + void apply_lp_list_to_w(indexed_vector & w); + void apply_lp_list_to_y(vector& y); + + void swap_rows(int j, int k); + + void swap_columns(int j, int pivot_column); + + void push_matrix_to_tail(tail_matrix* tm) { + m_tail.push_back(tm); + } + + bool pivot_the_row(int row); + + eta_matrix * get_eta_matrix_for_pivot(unsigned j); + // we're processing the column j now + eta_matrix * get_eta_matrix_for_pivot(unsigned j, sparse_matrix& copy_of_U); + + // see page 407 of Chvatal + unsigned transform_U_to_V_by_replacing_column(indexed_vector & w, unsigned leaving_column_of_U); + +#ifdef LEAN_DEBUG + void check_vector_w(unsigned entering); + + void check_apply_matrix_to_vector(matrix *lp, T *w); + + void check_apply_lp_lists_to_w(T * w); + + // provide some access operators for testing + permutation_matrix & Q() { return m_Q; } + permutation_matrix & R() { return m_R; } + matrix & U() { return m_U; } + unsigned tail_size() { return m_tail.size(); } + + tail_matrix * get_lp_matrix(unsigned i) { + return m_tail[i]; + } + + T B_(unsigned i, unsigned j, const vector& basis) { + return m_A.get_elem(i, basis[j]); + } + + unsigned dimension() { return m_dim; } + +#endif + + + unsigned get_number_of_nonzeroes() { + return m_U.get_number_of_nonzeroes(); + } + + + void process_column(int j); + + bool is_correct(const vector& basis); + + +#ifdef LEAN_DEBUG + dense_matrix tail_product(); + dense_matrix get_left_side(const vector& basis); + + dense_matrix get_right_side(); +#endif + + // needed for debugging purposes + void copy_w(T *buffer, indexed_vector & w); + + // needed for debugging purposes + void restore_w(T *buffer, indexed_vector & w); + bool all_columns_and_rows_are_active(); + + bool too_dense(unsigned j) const; + + void pivot_in_dense_mode(unsigned i); + + void create_initial_factorization(); + + void calculate_r_wave_and_update_U(unsigned bump_start, unsigned bump_end, permutation_matrix & r_wave); + + void scan_last_row_to_work_vector(unsigned lowest_row_of_the_bump); + + bool diagonal_element_is_off(T /* diag_element */) { return false; } + + void pivot_and_solve_the_system(unsigned replaced_column, unsigned lowest_row_of_the_bump); + // see Achim Koberstein's thesis page 58, but here we solve the system and pivot to the last + // row at the same time + row_eta_matrix *get_row_eta_matrix_and_set_row_vector(unsigned replaced_column, unsigned lowest_row_of_the_bump, const T & pivot_elem_for_checking); + + void replace_column(T pivot_elem, indexed_vector & w, unsigned leaving_column_of_U); + + void calculate_Lwave_Pwave_for_bump(unsigned replaced_column, unsigned lowest_row_of_the_bump); + + void calculate_Lwave_Pwave_for_last_row(unsigned lowest_row_of_the_bump, T diagonal_element); + + void prepare_entering(unsigned entering, indexed_vector & w) { + init_vector_w(entering, w); + } + bool need_to_refactor() { return m_refactor_counter >= 200; } + + void adjust_dimension_with_matrix_A() { + lean_assert(m_A.row_count() >= m_dim); + m_dim = m_A.row_count(); + m_U.resize(m_dim); + m_Q.resize(m_dim); + m_R.resize(m_dim); + m_row_eta_work_vector.resize(m_dim); + } + + + std::unordered_set get_set_of_columns_to_replace_for_add_last_rows(const vector & heading) const { + std::unordered_set columns_to_replace; + unsigned m = m_A.row_count(); + unsigned m_prev = m_U.dimension(); + + lean_assert(m_A.column_count() == heading.size()); + + for (unsigned i = m_prev; i < m; i++) { + for (const row_cell & c : m_A.m_rows[i]) { + int h = heading[c.m_j]; + if (h < 0) { + continue; + } + columns_to_replace.insert(c.m_j); + } + } + return columns_to_replace; + } + + void add_last_rows_to_B(const vector & heading, const std::unordered_set & columns_to_replace) { + unsigned m = m_A.row_count(); + lean_assert(m_A.column_count() == heading.size()); + adjust_dimension_with_matrix_A(); + m_w_for_extension.resize(m); + // At this moment the LU is correct + // for B extended by only by ones at the diagonal in the lower right corner + + for (unsigned j :columns_to_replace) { + lean_assert(heading[j] >= 0); + replace_column_with_only_change_at_last_rows(j, heading[j]); + if (get_status() == LU_status::Degenerated) + break; + } + } + // column j is a basis column, and there is a change in the last rows + void replace_column_with_only_change_at_last_rows(unsigned j, unsigned column_to_change_in_U) { + init_vector_w(j, m_w_for_extension); + replace_column(zero_of_type(), m_w_for_extension, column_to_change_in_U); + } + + bool has_dense_submatrix() const { + for (auto m : m_tail) + if (m->is_dense()) + return true; + return false; + } + +}; // end of lu + +template +void init_factorization(lu* & factorization, static_matrix & m_A, vector & m_basis, lp_settings &m_settings); + +#ifdef LEAN_DEBUG +template +dense_matrix get_B(lu& f, const vector& basis); +#endif +} diff --git a/src/util/lp/lu.hpp b/src/util/lp/lu.hpp new file mode 100644 index 000000000..ba5e092d3 --- /dev/null +++ b/src/util/lp/lu.hpp @@ -0,0 +1,940 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include +#include +#include +#include "util/vector.h" +#include +#include "util/debug.h" +#include "util/lp/lu.h" +namespace lean { +#ifdef LEAN_DEBUG +template // print the nr x nc submatrix at the top left corner +void print_submatrix(sparse_matrix & m, unsigned mr, unsigned nc, std::ostream & out) { + vector> A; + vector widths; + for (unsigned i = 0; i < m.row_count() && i < mr ; i++) { + A.push_back(vector()); + for (unsigned j = 0; j < m.column_count() && j < nc; j++) { + A[i].push_back(T_to_string(static_cast(m(i, j)))); + } + } + + for (unsigned j = 0; j < m.column_count() && j < nc; j++) { + widths.push_back(get_width_of_column(j, A)); + } + + print_matrix_with_widths(A, widths, out); +} + +template +void print_matrix(static_matrix &m, std::ostream & out) { + vector> A; + vector widths; + std::set> domain = m.get_domain(); + for (unsigned i = 0; i < m.row_count(); i++) { + A.push_back(vector()); + for (unsigned j = 0; j < m.column_count(); j++) { + A[i].push_back(T_to_string(static_cast(m(i, j)))); + } + } + + for (unsigned j = 0; j < m.column_count(); j++) { + widths.push_back(get_width_of_column(j, A)); + } + + print_matrix_with_widths(A, widths, out); +} + +template +void print_matrix(sparse_matrix& m, std::ostream & out) { + vector> A; + vector widths; + for (unsigned i = 0; i < m.row_count(); i++) { + A.push_back(vector()); + for (unsigned j = 0; j < m.column_count(); j++) { + A[i].push_back(T_to_string(static_cast(m(i, j)))); + } + } + + for (unsigned j = 0; j < m.column_count(); j++) { + widths.push_back(get_width_of_column(j, A)); + } + + print_matrix_with_widths(A, widths, out); +} +#endif + + +template +one_elem_on_diag::one_elem_on_diag(const one_elem_on_diag & o) { + m_i = o.m_i; + m_val = o.m_val; +#ifdef LEAN_DEBUG + m_m = m_n = o.m_m; + m_one_over_val = numeric_traits::one() / o.m_val; +#endif +} + +#ifdef LEAN_DEBUG +template +T one_elem_on_diag::get_elem(unsigned i, unsigned j) const { + if (i == j){ + if (j == m_i) { + return m_one_over_val; + } + return numeric_traits::one(); + } + + return numeric_traits::zero(); +} +#endif +template +void one_elem_on_diag::apply_from_left_to_T(indexed_vector & w, lp_settings & settings) { + T & t = w[m_i]; + if (numeric_traits::is_zero(t)) { + return; + } + t /= m_val; + if (numeric_traits::precise()) return; + if (settings.abs_val_is_smaller_than_drop_tolerance(t)) { + w.erase_from_index(m_i); + t = numeric_traits::zero(); + } +} + +// This class supports updates of the columns of B, and solves systems Bx=b,and yB=c +// Using Suhl-Suhl method described in the dissertation of Achim Koberstein, Chapter 5 +template +lu::lu(static_matrix const & A, + vector& basis, + lp_settings & settings): + m_dim(A.row_count()), + m_A(A), + m_Q(m_dim), + m_R(m_dim), + m_r_wave(m_dim), + m_U(A, basis), // create the square matrix that eventually will be factorized + m_settings(settings), + m_row_eta_work_vector(A.row_count()){ + lean_assert(!(numeric_traits::precise() && settings.use_tableau())); +#ifdef LEAN_DEBUG + debug_test_of_basis(A, basis); +#endif + ++m_settings.st().m_num_factorizations; + create_initial_factorization(); +#ifdef LEAN_DEBUG + // lean_assert(check_correctness()); +#endif +} +template +void lu::debug_test_of_basis(static_matrix const & A, vector & basis) { + std::set set; + for (unsigned i = 0; i < A.row_count(); i++) { + lean_assert(basis[i]< A.column_count()); + set.insert(basis[i]); + } + lean_assert(set.size() == A.row_count()); +} + + template + void lu::solve_By(indexed_vector & y) { + lean_assert(false); // not implemented + // init_vector_y(y); + // solve_By_when_y_is_ready(y); + } + + +template +void lu::solve_By(vector & y) { + init_vector_y(y); + solve_By_when_y_is_ready_for_X(y); +} + +template +void lu::solve_By_when_y_is_ready_for_X(vector & y) { + if (numeric_traits::precise()) { + m_U.solve_U_y(y); + m_R.apply_reverse_from_left_to_X(y); // see 24.3 from Chvatal + return; + } + m_U.double_solve_U_y(y); + m_R.apply_reverse_from_left_to_X(y); // see 24.3 from Chvatal + unsigned i = m_dim; + while (i--) { + if (is_zero(y[i])) continue; + if (m_settings.abs_val_is_smaller_than_drop_tolerance(y[i])){ + y[i] = zero_of_type(); + } + } +} + +template +void lu::solve_By_when_y_is_ready_for_T(vector & y, vector & index) { + if (numeric_traits::precise()) { + m_U.solve_U_y(y); + m_R.apply_reverse_from_left_to_T(y); // see 24.3 from Chvatal + unsigned j = m_dim; + while (j--) { + if (!is_zero(y[j])) + index.push_back(j); + } + return; + } + m_U.double_solve_U_y(y); + m_R.apply_reverse_from_left_to_T(y); // see 24.3 from Chvatal + unsigned i = m_dim; + while (i--) { + if (is_zero(y[i])) continue; + if (m_settings.abs_val_is_smaller_than_drop_tolerance(y[i])){ + y[i] = zero_of_type(); + } else { + index.push_back(i); + } + } +} + +template +void lu::solve_By_for_T_indexed_only(indexed_vector & y, const lp_settings & settings) { + if (numeric_traits::precise()) { + vector active_rows; + m_U.solve_U_y_indexed_only(y, settings, active_rows); + m_R.apply_reverse_from_left(y); // see 24.3 from Chvatal + return; + } + m_U.double_solve_U_y(y, m_settings); + m_R.apply_reverse_from_left(y); // see 24.3 from Chvatal +} + +template +void lu::print_matrix_compact(std::ostream & f) { + f << "matrix_start" << std::endl; + f << "nrows " << m_A.row_count() << std::endl; + f << "ncolumns " << m_A.column_count() << std::endl; + for (unsigned i = 0; i < m_A.row_count(); i++) { + auto & row = m_A.m_rows[i]; + f << "row " << i << std::endl; + for (auto & t : row) { + f << "column " << t.m_j << " value " << t.m_value << std::endl; + } + f << "row_end" << std::endl; + } + f << "matrix_end" << std::endl; +} +template +void lu::print(indexed_vector & w, const vector& basis) { + std::string dump_file_name("/tmp/lu"); + remove(dump_file_name.c_str()); + std::ofstream f(dump_file_name); + if (!f.is_open()) { + LP_OUT(m_settings, "cannot open file " << dump_file_name << std::endl); + return; + } + LP_OUT(m_settings, "writing lu dump to " << dump_file_name << std::endl); + print_matrix_compact(f); + print_vector(basis, f); + print_indexed_vector(w, f); + f.close(); +} +template +void lu::solve_Bd(unsigned a_column, indexed_vector & d, indexed_vector & w) { + init_vector_w(a_column, w); + + if (w.m_index.size() * ratio_of_index_size_to_all_size() < d.m_data.size()) { // this const might need some tuning + d = w; + solve_By_for_T_indexed_only(d, m_settings); + } else { + d.m_data = w.m_data; + d.m_index.clear(); + solve_By_when_y_is_ready_for_T(d.m_data, d.m_index); + } +} + +template +void lu::solve_Bd_faster(unsigned a_column, indexed_vector & d) { // puts the a_column into d + init_vector_w(a_column, d); + solve_By_for_T_indexed_only(d, m_settings); +} + +template +void lu::solve_yB(vector& y) { + // first solve yU = cb*R(-1) + m_R.apply_reverse_from_right_to_T(y); // got y = cb*R(-1) + m_U.solve_y_U(y); // got y*U=cb*R(-1) + m_Q.apply_reverse_from_right_to_T(y); // + for (auto e = m_tail.rbegin(); e != m_tail.rend(); ++e) { +#ifdef LEAN_DEBUG + (*e)->set_number_of_columns(m_dim); +#endif + (*e)->apply_from_right(y); + } +} + +template +void lu::solve_yB_indexed(indexed_vector& y) { + lean_assert(y.is_OK()); + // first solve yU = cb*R(-1) + m_R.apply_reverse_from_right_to_T(y); // got y = cb*R(-1) + lean_assert(y.is_OK()); + m_U.solve_y_U_indexed(y, m_settings); // got y*U=cb*R(-1) + lean_assert(y.is_OK()); + m_Q.apply_reverse_from_right_to_T(y); + lean_assert(y.is_OK()); + for (auto e = m_tail.rbegin(); e != m_tail.rend(); ++e) { +#ifdef LEAN_DEBUG + (*e)->set_number_of_columns(m_dim); +#endif + (*e)->apply_from_right(y); + lean_assert(y.is_OK()); + } +} + +template +void lu::add_delta_to_solution(const vector& yc, vector& y){ + unsigned i = static_cast(y.size()); + while (i--) + y[i]+=yc[i]; +} + +template +void lu::add_delta_to_solution_indexed(indexed_vector& y) { + // the delta sits in m_y_copy, put result into y + lean_assert(y.is_OK()); + lean_assert(m_y_copy.is_OK()); + m_ii.clear(); + m_ii.resize(y.data_size()); + for (unsigned i : y.m_index) + m_ii.set_value(1, i); + for (unsigned i : m_y_copy.m_index) { + y.m_data[i] += m_y_copy[i]; + if (m_ii[i] == 0) + m_ii.set_value(1, i); + } + lean_assert(m_ii.is_OK()); + y.m_index.clear(); + + for (unsigned i : m_ii.m_index) { + T & v = y.m_data[i]; + if (!lp_settings::is_eps_small_general(v, 1e-14)) + y.m_index.push_back(i); + else if (!numeric_traits::is_zero(v)) + v = zero_of_type(); + } + + lean_assert(y.is_OK()); +} + +template +void lu::find_error_of_yB(vector& yc, const vector& y, const vector& m_basis) { + unsigned i = m_dim; + while (i--) { + yc[i] -= m_A.dot_product_with_column(y, m_basis[i]); + } +} + +template +void lu::find_error_of_yB_indexed(const indexed_vector& y, const vector& heading, const lp_settings& settings) { +#if 0 == 1 + // it is a non efficient version + indexed_vector yc = m_y_copy; + yc.m_index.clear(); + lean_assert(!numeric_traits::precise()); + { + + vector d_basis(y.m_data.size()); + for (unsigned j = 0; j < heading.size(); j++) { + if (heading[j] >= 0) { + d_basis[heading[j]] = j; + } + } + + + unsigned i = m_dim; + while (i--) { + T & v = yc.m_data[i] -= m_A.dot_product_with_column(y.m_data, d_basis[i]); + if (settings.abs_val_is_smaller_than_drop_tolerance(v)) + v = zero_of_type(); + else + yc.m_index.push_back(i); + } + } +#endif + lean_assert(m_ii.is_OK()); + m_ii.clear(); + m_ii.resize(y.data_size()); + lean_assert(m_y_copy.is_OK()); + // put the error into m_y_copy + for (auto k : y.m_index) { + auto & row = m_A.m_rows[k]; + const T & y_k = y.m_data[k]; + for (auto & c : row) { + unsigned j = c.m_j; + int hj = heading[j]; + if (hj < 0) continue; + if (m_ii.m_data[hj] == 0) + m_ii.set_value(1, hj); + m_y_copy.m_data[hj] -= c.get_val() * y_k; + } + } + // add the index of m_y_copy to m_ii + for (unsigned i : m_y_copy.m_index) { + if (m_ii.m_data[i] == 0) + m_ii.set_value(1, i); + } + + // there is no guarantee that m_y_copy is OK here, but its index + // is contained in m_ii index + m_y_copy.m_index.clear(); + // setup the index of m_y_copy + for (auto k : m_ii.m_index) { + T& v = m_y_copy.m_data[k]; + if (settings.abs_val_is_smaller_than_drop_tolerance(v)) + v = zero_of_type(); + else { + m_y_copy.set_value(v, k); + } + } + lean_assert(m_y_copy.is_OK()); + +} + + + + +// solves y*B = y +// y is the input +template +void lu::solve_yB_with_error_check_indexed(indexed_vector & y, const vector& heading, const vector & basis, const lp_settings & settings) { + if (numeric_traits::precise()) { + if (y.m_index.size() * ratio_of_index_size_to_all_size() * 3 < m_A.column_count()) { + solve_yB_indexed(y); + } else { + solve_yB(y.m_data); + y.restore_index_and_clean_from_data(); + } + return; + } + lean_assert(m_y_copy.is_OK()); + lean_assert(y.is_OK()); + if (y.m_index.size() * ratio_of_index_size_to_all_size() < m_A.column_count()) { + m_y_copy = y; + solve_yB_indexed(y); + lean_assert(y.is_OK()); + if (y.m_index.size() * ratio_of_index_size_to_all_size() >= m_A.column_count()) { + find_error_of_yB(m_y_copy.m_data, y.m_data, basis); + solve_yB(m_y_copy.m_data); + add_delta_to_solution(m_y_copy.m_data, y.m_data); + y.restore_index_and_clean_from_data(); + m_y_copy.clear_all(); + } else { + find_error_of_yB_indexed(y, heading, settings); // this works with m_y_copy + solve_yB_indexed(m_y_copy); + add_delta_to_solution_indexed(y); + } + lean_assert(m_y_copy.is_OK()); + } else { + solve_yB_with_error_check(y.m_data, basis); + y.restore_index_and_clean_from_data(); + } +} + + +// solves y*B = y +// y is the input +template +void lu::solve_yB_with_error_check(vector & y, const vector& basis) { + if (numeric_traits::precise()) { + solve_yB(y); + return; + } + auto & yc = m_y_copy.m_data; + yc =y; // copy y aside + solve_yB(y); + find_error_of_yB(yc, y, basis); + solve_yB(yc); + add_delta_to_solution(yc, y); + m_y_copy.clear_all(); +} +template +void lu::apply_Q_R_to_U(permutation_matrix & r_wave) { + m_U.multiply_from_right(r_wave); + m_U.multiply_from_left_with_reverse(r_wave); +} + + +// Solving yB = cb to find the entering variable, +// where cb is the cost vector projected to B. +// The result is stored in cb. + +// solving Bd = a ( to find the column d of B^{-1} A_N corresponding to the entering +// variable +template +lu::~lu(){ + for (auto t : m_tail) { + delete t; + } +} +template +void lu::init_vector_y(vector & y) { + apply_lp_list_to_y(y); + m_Q.apply_reverse_from_left_to_X(y); +} + +template +void lu::perform_transformations_on_w(indexed_vector& w) { + apply_lp_list_to_w(w); + m_Q.apply_reverse_from_left(w); + // TBD does not compile: lean_assert(numeric_traits::precise() || check_vector_for_small_values(w, m_settings)); +} + +// see Chvatal 24.3 +template +void lu::init_vector_w(unsigned entering, indexed_vector & w) { + w.clear(); + m_A.copy_column_to_indexed_vector(entering, w); // w = a, the column + perform_transformations_on_w(w); +} +template +void lu::apply_lp_list_to_w(indexed_vector & w) { + for (unsigned i = 0; i < m_tail.size(); i++) { + m_tail[i]->apply_from_left_to_T(w, m_settings); + // TBD does not compile: lean_assert(check_vector_for_small_values(w, m_settings)); + } +} +template +void lu::apply_lp_list_to_y(vector& y) { + for (unsigned i = 0; i < m_tail.size(); i++) { + m_tail[i]->apply_from_left(y, m_settings); + } +} +template +void lu::swap_rows(int j, int k) { + if (j != k) { + m_Q.transpose_from_left(j, k); + m_U.swap_rows(j, k); + } +} + +template +void lu::swap_columns(int j, int pivot_column) { + if (j == pivot_column) + return; + m_R.transpose_from_right(j, pivot_column); + m_U.swap_columns(j, pivot_column); +} +template +bool lu::pivot_the_row(int row) { + eta_matrix * eta_matrix = get_eta_matrix_for_pivot(row); + if (get_status() != LU_status::OK) { + return false; + } + + if (eta_matrix == nullptr) { + m_U.shorten_active_matrix(row, nullptr); + return true; + } + if (!m_U.pivot_with_eta(row, eta_matrix, m_settings)) + return false; + eta_matrix->conjugate_by_permutation(m_Q); + push_matrix_to_tail(eta_matrix); + return true; +} +// we're processing the column j now +template +eta_matrix * lu::get_eta_matrix_for_pivot(unsigned j) { + eta_matrix *ret; + if(!m_U.fill_eta_matrix(j, &ret)) { + set_status(LU_status::Degenerated); + } + return ret; +} +// we're processing the column j now +template +eta_matrix * lu::get_eta_matrix_for_pivot(unsigned j, sparse_matrix& copy_of_U) { + eta_matrix *ret; + copy_of_U.fill_eta_matrix(j, &ret); + return ret; +} + +// see page 407 of Chvatal +template +unsigned lu::transform_U_to_V_by_replacing_column(indexed_vector & w, + unsigned leaving_column) { + unsigned column_to_replace = m_R.apply_reverse(leaving_column); + m_U.replace_column(column_to_replace, w, m_settings); + return column_to_replace; +} + +#ifdef LEAN_DEBUG +template +void lu::check_vector_w(unsigned entering) { + T * w = new T[m_dim]; + m_A.copy_column_to_vector(entering, w); + check_apply_lp_lists_to_w(w); + delete [] w; +} +template +void lu::check_apply_matrix_to_vector(matrix *lp, T *w) { + if (lp != nullptr) { + lp -> set_number_of_rows(m_dim); + lp -> set_number_of_columns(m_dim); + apply_to_vector(*lp, w); + } +} + +template +void lu::check_apply_lp_lists_to_w(T * w) { + for (unsigned i = 0; i < m_tail.size(); i++) { + check_apply_matrix_to_vector(m_tail[i], w); + } + permutation_matrix qr = m_Q.get_reverse(); + apply_to_vector(qr, w); + for (int i = m_dim - 1; i >= 0; i--) { + lean_assert(abs(w[i] - w[i]) < 0.0000001); + } +} + +#endif +template +void lu::process_column(int j) { + unsigned pi, pj; + bool success = m_U.get_pivot_for_column(pi, pj, m_settings.c_partial_pivoting, j); + if (!success) { + LP_OUT(m_settings, "get_pivot returned false: cannot find the pivot for column " << j << std::endl); + m_failure = true; + return; + } + + if (static_cast(pi) == -1) { + LP_OUT(m_settings, "cannot find the pivot for column " << j << std::endl); + m_failure = true; + return; + } + swap_columns(j, pj); + swap_rows(j, pi); + if (!pivot_the_row(j)) { + // LP_OUT(m_settings, "pivot_the_row(" << j << ") failed" << std::endl); + m_failure = true; + } +} +template +bool lu::is_correct(const vector& basis) { +#ifdef LEAN_DEBUG + if (get_status() != LU_status::OK) { + return false; + } + dense_matrix left_side = get_left_side(basis); + dense_matrix right_side = get_right_side(); + return left_side == right_side; +#else + return true; +#endif +} + + +#ifdef LEAN_DEBUG +template +dense_matrix lu::tail_product() { + lean_assert(tail_size() > 0); + dense_matrix left_side = permutation_matrix(m_dim); + for (unsigned i = 0; i < tail_size(); i++) { + matrix* lp = get_lp_matrix(i); + lp->set_number_of_rows(m_dim); + lp->set_number_of_columns(m_dim); + left_side = ((*lp) * left_side); + } + return left_side; +} +template +dense_matrix lu::get_left_side(const vector& basis) { + dense_matrix left_side = get_B(*this, basis); + for (unsigned i = 0; i < tail_size(); i++) { + matrix* lp = get_lp_matrix(i); + lp->set_number_of_rows(m_dim); + lp->set_number_of_columns(m_dim); + left_side = ((*lp) * left_side); + } + return left_side; +} +template +dense_matrix lu::get_right_side() { + auto ret = U() * R(); + ret = Q() * ret; + return ret; +} +#endif + +// needed for debugging purposes +template +void lu::copy_w(T *buffer, indexed_vector & w) { + unsigned i = m_dim; + while (i--) { + buffer[i] = w[i]; + } +} + +// needed for debugging purposes +template +void lu::restore_w(T *buffer, indexed_vector & w) { + unsigned i = m_dim; + while (i--) { + w[i] = buffer[i]; + } +} +template +bool lu::all_columns_and_rows_are_active() { + unsigned i = m_dim; + while (i--) { + lean_assert(m_U.col_is_active(i)); + lean_assert(m_U.row_is_active(i)); + } + return true; +} +template +bool lu::too_dense(unsigned j) const { + unsigned r = m_dim - j; + if (r < 5) + return false; + // if (j * 5 < m_dim * 4) // start looking for dense only at the bottom of the rows + // return false; + // return r * r * m_settings.density_threshold <= m_U.get_number_of_nonzeroes_below_row(j); + return r * r * m_settings.density_threshold <= m_U.get_n_of_active_elems(); +} +template +void lu::pivot_in_dense_mode(unsigned i) { + int j = m_dense_LU->find_pivot_column_in_row(i); + if (j == -1) { + m_failure = true; + return; + } + if (i != static_cast(j)) { + swap_columns(i, j); + m_dense_LU->swap_columns(i, j); + } + m_dense_LU->pivot(i, m_settings); +} +template +void lu::create_initial_factorization(){ + m_U.prepare_for_factorization(); + unsigned j; + for (j = 0; j < m_dim; j++) { + process_column(j); + if (m_failure) { + set_status(LU_status::Degenerated); + return; + } + if (too_dense(j)) { + break; + } + } + if (j == m_dim) { + // TBD does not compile: lean_assert(m_U.is_upper_triangular_and_maximums_are_set_correctly_in_rows(m_settings)); + // lean_assert(is_correct()); + // lean_assert(m_U.is_upper_triangular_and_maximums_are_set_correctly_in_rows(m_settings)); + return; + } + j++; + m_dense_LU = new square_dense_submatrix(&m_U, j); + for (; j < m_dim; j++) { + pivot_in_dense_mode(j); + if (m_failure) { + set_status(LU_status::Degenerated); + return; + } + } + m_dense_LU->update_parent_matrix(m_settings); + lean_assert(m_dense_LU->is_L_matrix()); + m_dense_LU->conjugate_by_permutation(m_Q); + push_matrix_to_tail(m_dense_LU); + m_refactor_counter = 0; + // lean_assert(is_correct()); + // lean_assert(m_U.is_upper_triangular_and_maximums_are_set_correctly_in_rows(m_settings)); +} + +template +void lu::calculate_r_wave_and_update_U(unsigned bump_start, unsigned bump_end, permutation_matrix & r_wave) { + if (bump_start > bump_end) { + set_status(LU_status::Degenerated); + return; + } + if (bump_start == bump_end) { + return; + } + + r_wave[bump_start] = bump_end; // sending the offensive column to the end of the bump + + for ( unsigned i = bump_start + 1 ; i <= bump_end; i++ ) { + r_wave[i] = i - 1; + } + + m_U.multiply_from_right(r_wave); + m_U.multiply_from_left_with_reverse(r_wave); +} +template +void lu::scan_last_row_to_work_vector(unsigned lowest_row_of_the_bump) { + vector> & last_row_vec = m_U.get_row_values(m_U.adjust_row(lowest_row_of_the_bump)); + for (auto & iv : last_row_vec) { + if (is_zero(iv.m_value)) continue; + lean_assert(!m_settings.abs_val_is_smaller_than_drop_tolerance(iv.m_value)); + unsigned adjusted_col = m_U.adjust_column_inverse(iv.m_index); + if (adjusted_col < lowest_row_of_the_bump) { + m_row_eta_work_vector.set_value(-iv.m_value, adjusted_col); + } else { + m_row_eta_work_vector.set_value(iv.m_value, adjusted_col); // preparing to calculate the real value in the matrix + } + } +} + +template +void lu::pivot_and_solve_the_system(unsigned replaced_column, unsigned lowest_row_of_the_bump) { + // we have the system right side at m_row_eta_work_vector now + // solve the system column wise + for (unsigned j = replaced_column; j < lowest_row_of_the_bump; j++) { + T v = m_row_eta_work_vector[j]; + if (numeric_traits::is_zero(v)) continue; // this column does not contribute to the solution + unsigned aj = m_U.adjust_row(j); + vector> & row = m_U.get_row_values(aj); + for (auto & iv : row) { + unsigned col = m_U.adjust_column_inverse(iv.m_index); + lean_assert(col >= j || numeric_traits::is_zero(iv.m_value)); + if (col == j) continue; + if (numeric_traits::is_zero(iv.m_value)) { + continue; + } + // the -v is for solving the system ( to zero the last row), and +v is for pivoting + T delta = col < lowest_row_of_the_bump? -v * iv.m_value: v * iv.m_value; + lean_assert(numeric_traits::is_zero(delta) == false); + + + + // m_row_eta_work_vector.add_value_at_index_with_drop_tolerance(col, delta); + if (numeric_traits::is_zero(m_row_eta_work_vector[col])) { + if (!m_settings.abs_val_is_smaller_than_drop_tolerance(delta)){ + m_row_eta_work_vector.set_value(delta, col); + } + } else { + T t = (m_row_eta_work_vector[col] += delta); + if (m_settings.abs_val_is_smaller_than_drop_tolerance(t)){ + m_row_eta_work_vector[col] = numeric_traits::zero(); + auto it = std::find(m_row_eta_work_vector.m_index.begin(), m_row_eta_work_vector.m_index.end(), col); + if (it != m_row_eta_work_vector.m_index.end()) + m_row_eta_work_vector.m_index.erase(it); + } + } + } + } +} +// see Achim Koberstein's thesis page 58, but here we solve the system and pivot to the last +// row at the same time +template +row_eta_matrix *lu::get_row_eta_matrix_and_set_row_vector(unsigned replaced_column, unsigned lowest_row_of_the_bump, const T & pivot_elem_for_checking) { + if (replaced_column == lowest_row_of_the_bump) return nullptr; + scan_last_row_to_work_vector(lowest_row_of_the_bump); + pivot_and_solve_the_system(replaced_column, lowest_row_of_the_bump); + if (numeric_traits::precise() == false && !is_zero(pivot_elem_for_checking)) { + T denom = std::max(T(1), abs(pivot_elem_for_checking)); + if ( + !m_settings.abs_val_is_smaller_than_pivot_tolerance((m_row_eta_work_vector[lowest_row_of_the_bump] - pivot_elem_for_checking) / denom)) { + set_status(LU_status::Degenerated); + // LP_OUT(m_settings, "diagonal element is off" << std::endl); + return nullptr; + } + } +#ifdef LEAN_DEBUG + auto ret = new row_eta_matrix(replaced_column, lowest_row_of_the_bump, m_dim); +#else + auto ret = new row_eta_matrix(replaced_column, lowest_row_of_the_bump); +#endif + + for (auto j : m_row_eta_work_vector.m_index) { + if (j < lowest_row_of_the_bump) { + auto & v = m_row_eta_work_vector[j]; + if (!is_zero(v)) { + if (!m_settings.abs_val_is_smaller_than_drop_tolerance(v)){ + ret->push_back(j, v); + } + v = numeric_traits::zero(); + } + } + } // now the lowest_row_of_the_bump contains the rest of the row to the right of the bump with correct values + return ret; +} + +template +void lu::replace_column(T pivot_elem_for_checking, indexed_vector & w, unsigned leaving_column_of_U){ + m_refactor_counter++; + unsigned replaced_column = transform_U_to_V_by_replacing_column( w, leaving_column_of_U); + unsigned lowest_row_of_the_bump = m_U.lowest_row_in_column(replaced_column); + m_r_wave.init(m_dim); + calculate_r_wave_and_update_U(replaced_column, lowest_row_of_the_bump, m_r_wave); + auto row_eta = get_row_eta_matrix_and_set_row_vector(replaced_column, lowest_row_of_the_bump, pivot_elem_for_checking); + + if (get_status() == LU_status::Degenerated) { + m_row_eta_work_vector.clear_all(); + return; + } + m_Q.multiply_by_permutation_from_right(m_r_wave); + m_R.multiply_by_permutation_reverse_from_left(m_r_wave); + if (row_eta != nullptr) { + row_eta->conjugate_by_permutation(m_Q); + push_matrix_to_tail(row_eta); + } + calculate_Lwave_Pwave_for_bump(replaced_column, lowest_row_of_the_bump); + // lean_assert(m_U.is_upper_triangular_and_maximums_are_set_correctly_in_rows(m_settings)); + // lean_assert(w.is_OK() && m_row_eta_work_vector.is_OK()); +} +template +void lu::calculate_Lwave_Pwave_for_bump(unsigned replaced_column, unsigned lowest_row_of_the_bump){ + T diagonal_elem; + if (replaced_column < lowest_row_of_the_bump) { + diagonal_elem = m_row_eta_work_vector[lowest_row_of_the_bump]; + // lean_assert(m_row_eta_work_vector.is_OK()); + m_U.set_row_from_work_vector_and_clean_work_vector_not_adjusted(m_U.adjust_row(lowest_row_of_the_bump), m_row_eta_work_vector, m_settings); + } else { + diagonal_elem = m_U(lowest_row_of_the_bump, lowest_row_of_the_bump); // todo - get it more efficiently + } + if (m_settings.abs_val_is_smaller_than_pivot_tolerance(diagonal_elem)) { + set_status(LU_status::Degenerated); + return; + } + + calculate_Lwave_Pwave_for_last_row(lowest_row_of_the_bump, diagonal_elem); + // lean_assert(m_U.is_upper_triangular_and_maximums_are_set_correctly_in_rows(m_settings)); +} + +template +void lu::calculate_Lwave_Pwave_for_last_row(unsigned lowest_row_of_the_bump, T diagonal_element) { + auto l = new one_elem_on_diag(lowest_row_of_the_bump, diagonal_element); +#ifdef LEAN_DEBUG + l->set_number_of_columns(m_dim); +#endif + push_matrix_to_tail(l); + m_U.divide_row_by_constant(lowest_row_of_the_bump, diagonal_element, m_settings); + l->conjugate_by_permutation(m_Q); +} + +template +void init_factorization(lu* & factorization, static_matrix & m_A, vector & m_basis, lp_settings &m_settings) { + if (factorization != nullptr) + delete factorization; + factorization = new lu(m_A, m_basis, m_settings); + // if (factorization->get_status() != LU_status::OK) + // LP_OUT(m_settings, "failing in init_factorization" << std::endl); +} + +#ifdef LEAN_DEBUG +template +dense_matrix get_B(lu& f, const vector& basis) { + lean_assert(basis.size() == f.dimension()); + lean_assert(basis.size() == f.m_U.dimension()); + dense_matrix B(f.dimension(), f.dimension()); + for (unsigned i = 0; i < f.dimension(); i++) + for (unsigned j = 0; j < f.dimension(); j++) + B.set_elem(i, j, f.B_(i, j, basis)); + + return B; +} +#endif +} diff --git a/src/util/lp/lu_instances.cpp b/src/util/lp/lu_instances.cpp new file mode 100644 index 000000000..c8ff7b2f4 --- /dev/null +++ b/src/util/lp/lu_instances.cpp @@ -0,0 +1,63 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include +#include +#include +#include "util/vector.h" +#include "util/debug.h" +#include "util/lp/lu.hpp" +template double lean::dot_product(vector const&, vector const&); +template lean::lu::lu(lean::static_matrix const&, vector&, lean::lp_settings&); +template void lean::lu::push_matrix_to_tail(lean::tail_matrix*); +template void lean::lu::replace_column(double, lean::indexed_vector&, unsigned); +template void lean::lu::solve_Bd(unsigned int, lean::indexed_vector&, lean::indexed_vector&); +template lean::lu::~lu(); +template void lean::lu::push_matrix_to_tail(lean::tail_matrix*); +template void lean::lu::solve_Bd(unsigned int, lean::indexed_vector&, lean::indexed_vector&); +template lean::lu::~lu(); +template void lean::lu >::push_matrix_to_tail(lean::tail_matrix >*); +template void lean::lu >::solve_Bd(unsigned int, lean::indexed_vector&, lean::indexed_vector&); +template lean::lu >::~lu(); +template lean::mpq lean::dot_product(vector const&, vector const&); +template void lean::init_factorization(lean::lu*&, lean::static_matrix&, vector&, lean::lp_settings&); +template void lean::init_factorization(lean::lu*&, lean::static_matrix&, vector&, lean::lp_settings&); +template void lean::init_factorization >(lean::lu >*&, lean::static_matrix >&, vector&, lean::lp_settings&); +#ifdef LEAN_DEBUG +template void lean::print_matrix(lean::sparse_matrix&, std::ostream & out); +template void lean::print_matrix(lean::static_matrix&, std::ostream&); +template void lean::print_matrix >(lean::static_matrix >&, std::ostream&); +template void lean::print_matrix(lean::static_matrix&, std::ostream & out); +template bool lean::lu::is_correct(const vector& basis); +template bool lean::lu >::is_correct( vector const &); +template lean::dense_matrix lean::get_B(lean::lu&, const vector& basis); +template lean::dense_matrix lean::get_B(lean::lu&, vector const&); + +#endif + +template bool lean::lu::pivot_the_row(int); // NOLINT +template void lean::lu::init_vector_w(unsigned int, lean::indexed_vector&); +template void lean::lu::solve_By(vector&); +template void lean::lu::solve_By_when_y_is_ready_for_X(vector&); +template void lean::lu::solve_yB_with_error_check(vector&, const vector& basis); +template void lean::lu::solve_yB_with_error_check_indexed(lean::indexed_vector&, vector const&, const vector & basis, const lp_settings&); +template void lean::lu::replace_column(lean::mpq, lean::indexed_vector&, unsigned); +template void lean::lu::solve_By(vector&); +template void lean::lu::solve_By_when_y_is_ready_for_X(vector&); +template void lean::lu::solve_yB_with_error_check(vector&, const vector& basis); +template void lean::lu::solve_yB_with_error_check_indexed(lean::indexed_vector&, vector< int > const&, const vector & basis, const lp_settings&); +template void lean::lu >::solve_yB_with_error_check_indexed(lean::indexed_vector&, vector< int > const&, const vector & basis, const lp_settings&); +template void lean::lu >::init_vector_w(unsigned int, lean::indexed_vector&); +template void lean::lu >::replace_column(lean::mpq, lean::indexed_vector&, unsigned); +template void lean::lu >::solve_Bd_faster(unsigned int, lean::indexed_vector&); +template void lean::lu >::solve_By(vector >&); +template void lean::lu >::solve_By_when_y_is_ready_for_X(vector >&); +template void lean::lu >::solve_yB_with_error_check(vector&, const vector& basis); +template void lean::lu::solve_By(lean::indexed_vector&); +template void lean::lu::solve_By(lean::indexed_vector&); +template void lean::lu::solve_yB_indexed(lean::indexed_vector&); +template void lean::lu::solve_yB_indexed(lean::indexed_vector&); +template void lean::lu >::solve_yB_indexed(lean::indexed_vector&); +template void lean::lu::solve_By_for_T_indexed_only(lean::indexed_vector&, lean::lp_settings const&); +template void lean::lu::solve_By_for_T_indexed_only(lean::indexed_vector&, lean::lp_settings const&); diff --git a/src/util/lp/matrix.h b/src/util/lp/matrix.h new file mode 100644 index 000000000..63fd5c01e --- /dev/null +++ b/src/util/lp/matrix.h @@ -0,0 +1,44 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#ifdef Z3DEBUG +#pragma once +#include "util/lp/numeric_pair.h" +#include "util/vector.h" +#include +#include "util/lp/lp_settings.h" +namespace lean { +// used for debugging purposes only +template +class matrix { +public: + virtual T get_elem (unsigned i, unsigned j) const = 0; + virtual unsigned row_count() const = 0; + virtual unsigned column_count() const = 0; + virtual void set_number_of_rows(unsigned m) = 0; + virtual void set_number_of_columns(unsigned n) = 0; + + virtual ~matrix() {} + + bool is_equal(const matrix& other); + bool operator == (matrix const & other) { + return is_equal(other); + } + T operator()(unsigned i, unsigned j) const { return get_elem(i, j); } +}; + +template +void apply_to_vector(matrix & m, T * w); + + + +unsigned get_width_of_column(unsigned j, vector> & A); +void print_matrix_with_widths(vector> & A, vector & ws, std::ostream & out); +void print_string_matrix(vector> & A); + +template +void print_matrix(matrix const * m, std::ostream & out); + +} +#endif diff --git a/src/util/lp/matrix.hpp b/src/util/lp/matrix.hpp new file mode 100644 index 000000000..27cdabd3e --- /dev/null +++ b/src/util/lp/matrix.hpp @@ -0,0 +1,105 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ + +#ifdef Z3DEBUG +#include +#include +#include "util/lp/matrix.h" +namespace lean { +template +bool matrix::is_equal(const matrix& other) { + if (other.row_count() != row_count() || other.column_count() != column_count()) + return false; + for (unsigned i = 0; i < row_count(); i++) { + for (unsigned j = 0; j < column_count(); j++) { + auto a = get_elem(i, j); + auto b = other.get_elem(i, j); + if (numeric_traits::precise()) { + if (a != b) return false; + } else if (fabs(numeric_traits::get_double(a - b)) > 0.000001) { + // cout << "returning false from operator== of matrix comparison" << endl; + // cout << "this matrix is " << endl; + // print_matrix(*this); + // cout << "other matrix is " << endl; + // print_matrix(other); + return false; + } + } + } + return true; +} + +template +void apply_to_vector(matrix & m, T * w) { + // here m is a square matrix + unsigned dim = m.row_count(); + + T * wc = new T[dim]; + + for (unsigned i = 0; i < dim; i++) { + wc[i] = w[i]; + } + + for (unsigned i = 0; i < dim; i++) { + T t = numeric_traits::zero(); + for (unsigned j = 0; j < dim; j++) { + t += m(i, j) * wc[j]; + } + w[i] = t; + } + delete [] wc; +} + + + +unsigned get_width_of_column(unsigned j, vector> & A) { + unsigned r = 0; + for (unsigned i = 0; i < A.size(); i++) { + vector & t = A[i]; + std::string str= t[j]; + unsigned s = str.size(); + if (r < s) { + r = s; + } + } + return r; +} + +void print_matrix_with_widths(vector> & A, vector & ws, std::ostream & out) { + for (unsigned i = 0; i < A.size(); i++) { + for (unsigned j = 0; j < A[i].size(); j++) { + print_blanks(ws[j] - A[i][j].size(), out); + out << A[i][j] << " "; + } + out << std::endl; + } +} + +void print_string_matrix(vector> & A, std::ostream & out) { + vector widths; + + if (A.size() > 0) + for (unsigned j = 0; j < A[0].size(); j++) { + widths.push_back(get_width_of_column(j, A)); + } + + print_matrix_with_widths(A, widths, out); + out << std::endl; +} + +template +void print_matrix(matrix const * m, std::ostream & out) { + vector> A(m->row_count()); + for (unsigned i = 0; i < m->row_count(); i++) { + for (unsigned j = 0; j < m->column_count(); j++) { + A[i].push_back(T_to_string(m->get_elem(i, j))); + } + } + + print_string_matrix(A, out); +} + +} +#endif diff --git a/src/util/lp/matrix_instances.cpp b/src/util/lp/matrix_instances.cpp new file mode 100644 index 000000000..aeee62786 --- /dev/null +++ b/src/util/lp/matrix_instances.cpp @@ -0,0 +1,16 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include "util/lp/lp_settings.h" +#ifdef LEAN_DEBUG +#include "util/lp/matrix.hpp" +#include "util/lp/static_matrix.h" +#include +template void lean::print_matrix(lean::matrix const*, std::ostream & out); +template bool lean::matrix::is_equal(lean::matrix const&); +template void lean::print_matrix >(lean::matrix > const *, std::basic_ostream > &); +template void lean::print_matrix(lean::matrix const*, std::ostream&); +template bool lean::matrix >::is_equal(lean::matrix > const&); +template bool lean::matrix::is_equal(lean::matrix const&); +#endif diff --git a/src/util/lp/mps_reader.h b/src/util/lp/mps_reader.h new file mode 100644 index 000000000..c79a7d426 --- /dev/null +++ b/src/util/lp/mps_reader.h @@ -0,0 +1,867 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ + +#pragma once + +// reads an MPS file reperesenting a Mixed Integer Program +#include +#include +#include +#include "util/vector.h" +#include +#include +#include +#include +#include "util/lp/lp_primal_simplex.h" +#include "util/lp/lp_dual_simplex.h" +#include "util/lp/lar_solver.h" +#include "util/lp/lp_utils.h" +#include "util/lp/lp_solver.h" +namespace lean { +inline bool my_white_space(const char & a) { + return a == ' ' || a == '\t'; +} +inline size_t number_of_whites(const std::string & s) { + size_t i = 0; + for(;i < s.size(); i++) + if (!my_white_space(s[i])) return i; + return i; +} +inline size_t number_of_whites_from_end(const std::string & s) { + size_t ret = 0; + for(int i = static_cast(s.size()) - 1;i >= 0; i--) + if (my_white_space(s[i])) ret++;else break; + + return ret; +} + + + // trim from start +inline std::string <rim(std::string &s) { + s.erase(0, number_of_whites(s)); + return s; +} + + + + + // trim from end +inline std::string &rtrim(std::string &s) { + // s.erase(std::find_if(s.rbegin(), s.rend(), std::not1(std::ptr_fun(std::isspace))).base(), s.end()); + s.erase(s.end() - number_of_whites_from_end(s), s.end()); + return s; +} + // trim from both ends +inline std::string &trim(std::string &s) { + return ltrim(rtrim(s)); +} + +inline std::string trim(std::string const &r) { + std::string s = r; + return ltrim(rtrim(s)); +} + + +inline vector string_split(const std::string &source, const char *delimiter, bool keep_empty) { + vector results; + size_t prev = 0; + size_t next = 0; + while ((next = source.find_first_of(delimiter, prev)) != std::string::npos) { + if (keep_empty || (next - prev != 0)) { + results.push_back(source.substr(prev, next - prev)); + } + prev = next + 1; + } + if (prev < source.size()) { + results.push_back(source.substr(prev)); + } + return results; +} + +inline vector split_and_trim(std::string line) { + auto split = string_split(line, " \t", false); + vector ret; + for (auto s : split) { + ret.push_back(trim(s)); + } + return ret; +} + +template +class mps_reader { + enum row_type { Cost, Less_or_equal, Greater_or_equal, Equal }; + struct bound { + bool m_low_is_set = true; + T m_low; + bool m_upper_is_set = false; + T m_upper; + bool m_value_is_fixed = false; + T m_fixed_value; + bool m_free = false; + // constructor + bound() : m_low(numeric_traits::zero()) {} // it seems all mps files I have seen have the default low value 0 on a variable + }; + + struct column { + std::string m_name; + bound * m_bound = nullptr; + unsigned m_index; + column(std::string name, unsigned index): m_name(name), m_index(index) { + } + }; + + struct row { + row_type m_type; + std::string m_name; + std::unordered_map m_row_columns; + T m_right_side = numeric_traits::zero(); + unsigned m_index; + T m_range = numeric_traits::zero(); + row(row_type type, std::string name, unsigned index) : m_type(type), m_name(name), m_index(index) { + } + }; + + std::string m_file_name; + bool m_is_OK = true; + std::unordered_map m_rows; + std::unordered_map m_columns; + std::unordered_map m_names_to_var_index; + std::string m_line; + std::string m_name; + std::string m_cost_row_name; + std::ifstream m_file_stream; + // needed to adjust the index row + unsigned m_cost_line_count = 0; + unsigned m_line_number = 0; + std::ostream * m_message_stream = & std::cout; + + void set_m_ok_to_false() { + *m_message_stream << "setting m_is_OK to false" << std::endl; + m_is_OK = false; + } + + std::string get_string_from_position(unsigned offset) { + unsigned i = offset; + for (; i < m_line.size(); i++){ + if (m_line[i] == ' ') + break; + } + lean_assert(m_line.size() >= offset); + lean_assert(m_line.size() >> i); + lean_assert(i >= offset); + return m_line.substr(offset, i - offset); + } + + void set_boundary_for_column(unsigned col, bound * b, lp_solver * solver){ + if (b == nullptr) { + solver->set_low_bound(col, numeric_traits::zero()); + return; + } + + if (b->m_free) { + return; + } + if (b->m_low_is_set) { + solver->set_low_bound(col, b->m_low); + } + if (b->m_upper_is_set) { + solver->set_upper_bound(col, b->m_upper); + } + + if (b->m_value_is_fixed) { + solver->set_fixed_value(col, b->m_fixed_value); + } + } + + bool all_white_space() { + for (unsigned i = 0; i < m_line.size(); i++) { + char c = m_line[i]; + if (c != ' ' && c != '\t') { + return false; + } + } + return true; + } + + void read_line() { + while (m_is_OK) { + if (!getline(m_file_stream, m_line)) { + m_line_number++; + set_m_ok_to_false(); + *m_message_stream << "cannot read from file" << std::endl; + } + m_line_number++; + if (m_line.size() != 0 && m_line[0] != '*' && !all_white_space()) + break; + } + } + + void read_name() { + do { + read_line(); + if (m_line.find("NAME") != 0) { + continue; + } + m_line = m_line.substr(4); + m_name = trim(m_line); + break; + } while (m_is_OK); + } + + void read_rows() { + // look for start of the rows + read_line(); + do { + if (static_cast(m_line.find("ROWS")) >= 0) { + break; + } + } while (m_is_OK); + do { + read_line(); + if (m_line.find("COLUMNS") == 0) { + break; + } + add_row(); + } while (m_is_OK); + } + + void read_column_by_columns(std::string column_name, std::string column_data) { + // uph, let us try to work with columns + if (column_data.size() >= 22) { + std::string ss = column_data.substr(0, 8); + std::string row_name = trim(ss); + auto t = m_rows.find(row_name); + + if (t == m_rows.end()) { + *m_message_stream << "cannot find " << row_name << std::endl; + goto fail; + } else { + row * row = t->second; + row->m_row_columns[column_name] = numeric_traits::from_string(column_data.substr(8)); + if (column_data.size() > 24) { + column_data = column_data.substr(25); + if (column_data.size() >= 22) { + read_column_by_columns(column_name, column_data); + } + } + } + } else { + fail: + set_m_ok_to_false(); + *m_message_stream << "cannot understand this line" << std::endl; + *m_message_stream << "line = " << m_line << ", line number is " << m_line_number << std::endl; + return; + } + } + + void read_column(std::string column_name, std::string column_data){ + auto tokens = split_and_trim(column_data); + for (unsigned i = 0; i < tokens.size() - 1; i+= 2) { + auto row_name = tokens[i]; + if (row_name == "'MARKER'") return; // it is the integrality marker, no real data here + auto t = m_rows.find(row_name); + if (t == m_rows.end()) { + read_column_by_columns(column_name, column_data); + return; + } + row *r = t->second; + r->m_row_columns[column_name] = numeric_traits::from_string(tokens[i + 1]); + } + } + + void read_columns(){ + std::string column_name; + do { + read_line(); + if (m_line.find("RHS") == 0) { + // cout << "found RHS" << std::endl; + break; + } + if (m_line.size() < 22) { + (*m_message_stream) << "line is too short for a column" << std::endl; + (*m_message_stream) << m_line << std::endl; + (*m_message_stream) << "line number is " << m_line_number << std::endl; + set_m_ok_to_false(); + return; + } + std::string column_name_tmp = trim(m_line.substr(4, 8)); + if (!column_name_tmp.empty()) { + column_name = column_name_tmp; + } + auto col_it = m_columns.find(column_name); + mps_reader::column * col; + if (col_it == m_columns.end()) { + col = new mps_reader::column(column_name, static_cast(m_columns.size())); + m_columns[column_name] = col; + // (*m_message_stream) << column_name << '[' << col->m_index << ']'<< std::endl; + } else { + col = col_it->second; + } + read_column(column_name, m_line.substr(14)); + } while (m_is_OK); + } + + void read_rhs() { + do { + read_line(); + if (m_line.find("BOUNDS") == 0 || m_line.find("ENDATA") == 0 || m_line.find("RANGES") == 0) { + break; + } + fill_rhs(); + } while (m_is_OK); + } + + + void fill_rhs_by_columns(std::string rhsides) { + // uph, let us try to work with columns + if (rhsides.size() >= 22) { + std::string ss = rhsides.substr(0, 8); + std::string row_name = trim(ss); + auto t = m_rows.find(row_name); + + if (t == m_rows.end()) { + (*m_message_stream) << "cannot find " << row_name << std::endl; + goto fail; + } else { + row * row = t->second; + row->m_right_side = numeric_traits::from_string(rhsides.substr(8)); + if (rhsides.size() > 24) { + rhsides = rhsides.substr(25); + if (rhsides.size() >= 22) { + fill_rhs_by_columns(rhsides); + } + } + } + } else { + fail: + set_m_ok_to_false(); + (*m_message_stream) << "cannot understand this line" << std::endl; + (*m_message_stream) << "line = " << m_line << ", line number is " << m_line_number << std::endl; + return; + } + } + + void fill_rhs() { + if (m_line.size() < 14) { + (*m_message_stream) << "line is too short" << std::endl; + (*m_message_stream) << m_line << std::endl; + (*m_message_stream) << "line number is " << m_line_number << std::endl; + set_m_ok_to_false(); + return; + } + std::string rhsides = m_line.substr(14); + vector splitted_line = split_and_trim(rhsides); + + for (unsigned i = 0; i < splitted_line.size() - 1; i += 2) { + auto t = m_rows.find(splitted_line[i]); + if (t == m_rows.end()) { + fill_rhs_by_columns(rhsides); + return; + } + row * row = t->second; + row->m_right_side = numeric_traits::from_string(splitted_line[i + 1]); + } + } + + void read_bounds() { + if (m_line.find("BOUNDS") != 0) { + return; + } + + do { + read_line(); + if (m_line[0] != ' ') { + break; + } + create_or_update_bound(); + } while (m_is_OK); + } + + void read_ranges() { + if (m_line.find("RANGES") != 0) { + return; + } + do { + read_line(); + auto sl = split_and_trim(m_line); + if (sl.size() < 2) { + break; + } + read_range(sl); + } while (m_is_OK); + } + + + void read_bound_by_columns(std::string colstr) { + if (colstr.size() < 14) { + (*m_message_stream) << "line is too short" << std::endl; + (*m_message_stream) << m_line << std::endl; + (*m_message_stream) << "line number is " << m_line_number << std::endl; + set_m_ok_to_false(); + return; + } + // uph, let us try to work with columns + if (colstr.size() >= 22) { + std::string ss = colstr.substr(0, 8); + std::string column_name = trim(ss); + auto t = m_columns.find(column_name); + + if (t == m_columns.end()) { + (*m_message_stream) << "cannot find " << column_name << std::endl; + goto fail; + } else { + vector bound_string; + bound_string.push_back(column_name); + if (colstr.size() > 14) { + bound_string.push_back(colstr.substr(14)); + } + mps_reader::column * col = t->second; + bound * b = col->m_bound; + if (b == nullptr) { + col->m_bound = b = new bound(); + } + update_bound(b, bound_string); + } + } else { + fail: + set_m_ok_to_false(); + (*m_message_stream) << "cannot understand this line" << std::endl; + (*m_message_stream) << "line = " << m_line << ", line number is " << m_line_number << std::endl; + return; + } + } + + void update_bound(bound * b, vector bound_string) { + /* + UP means an upper bound is applied to the variable. A bound of type LO means a lower bound is applied. A bound type of FX ("fixed") means that the variable has upper and lower bounds equal to a single value. A bound type of FR ("free") means the variable has neither lower nor upper bounds and so can take on negative values. A variation on that is MI for free negative, giving an upper bound of 0 but no lower bound. Bound type PL is for a free positive for zero to plus infinity, but as this is the normal default, it is seldom used. There are also bound types for use in MIP models - BV for binary, being 0 or 1. UI for upper integer and LI for lower integer. SC stands for semi-continuous and indicates that the variable may be zero, but if not must be equal to at least the value given. + */ + + std::string bound_type = get_string_from_position(1); + if (bound_type == "BV") { + b->m_upper_is_set = true; + b->m_upper = 1; + return; + } + + if (bound_type == "UP" || bound_type == "UI" || bound_type == "LIMITMAX") { + if (bound_string.size() <= 1){ + set_m_ok_to_false(); + return; + } + b->m_upper_is_set = true; + b->m_upper= numeric_traits::from_string(bound_string[1]); + } else if (bound_type == "LO" || bound_type == "LI") { + if (bound_string.size() <= 1){ + set_m_ok_to_false(); + return; + } + + b->m_low_is_set = true; + b->m_low = numeric_traits::from_string(bound_string[1]); + } else if (bound_type == "FR") { + b->m_free = true; + } else if (bound_type == "FX") { + if (bound_string.size() <= 1){ + set_m_ok_to_false(); + return; + } + + b->m_value_is_fixed = true; + b->m_fixed_value = numeric_traits::from_string(bound_string[1]); + } else if (bound_type == "PL") { + b->m_low_is_set = true; + b->m_low = 0; + } else if (bound_type == "MI") { + b->m_upper_is_set = true; + b->m_upper = 0; + } else { + (*m_message_stream) << "unexpected bound type " << bound_type << " at line " << m_line_number << std::endl; + set_m_ok_to_false(); + throw; + } + } + + void create_or_update_bound() { + const unsigned name_offset = 14; + lean_assert(m_line.size() >= 14); + vector bound_string = split_and_trim(m_line.substr(name_offset, m_line.size())); + + if (bound_string.size() == 0) { + set_m_ok_to_false(); + (*m_message_stream) << "error at line " << m_line_number << std::endl; + throw m_line; + } + + std::string name = bound_string[0]; + auto it = m_columns.find(name); + if (it == m_columns.end()){ + read_bound_by_columns(m_line.substr(14)); + return; + } + mps_reader::column * col = it->second; + bound * b = col->m_bound; + if (b == nullptr) { + col->m_bound = b = new bound(); + } + update_bound(b, bound_string); + } + + + + void read_range_by_columns(std::string rhsides) { + if (m_line.size() < 14) { + (*m_message_stream) << "line is too short" << std::endl; + (*m_message_stream) << m_line << std::endl; + (*m_message_stream) << "line number is " << m_line_number << std::endl; + set_m_ok_to_false(); + return; + } + // uph, let us try to work with columns + if (rhsides.size() >= 22) { + std::string ss = rhsides.substr(0, 8); + std::string row_name = trim(ss); + auto t = m_rows.find(row_name); + + if (t == m_rows.end()) { + (*m_message_stream) << "cannot find " << row_name << std::endl; + goto fail; + } else { + row * row = t->second; + row->m_range = numeric_traits::from_string(rhsides.substr(8)); + maybe_modify_current_row_and_add_row_for_range(row); + if (rhsides.size() > 24) { + rhsides = rhsides.substr(25); + if (rhsides.size() >= 22) { + read_range_by_columns(rhsides); + } + } + } + } else { + fail: + set_m_ok_to_false(); + (*m_message_stream) << "cannot understand this line" << std::endl; + (*m_message_stream) << "line = " << m_line << ", line number is " << m_line_number << std::endl; + return; + } + } + + + void read_range(vector & splitted_line){ + for (unsigned i = 1; i < splitted_line.size() - 1; i += 2) { + auto it = m_rows.find(splitted_line[i]); + if (it == m_rows.end()) { + read_range_by_columns(m_line.substr(14)); + return; + } + row * row = it->second; + row->m_range = numeric_traits::from_string(splitted_line[i + 1]); + maybe_modify_current_row_and_add_row_for_range(row); + } + } + + void maybe_modify_current_row_and_add_row_for_range(row * row_with_range) { + unsigned index= static_cast(m_rows.size() - m_cost_line_count); + std::string row_name = row_with_range->m_name + "_range"; + row * other_bound_range_row; + switch (row_with_range->m_type) { + case row_type::Greater_or_equal: + m_rows[row_name] = other_bound_range_row = new row(row_type::Less_or_equal, row_name, index); + other_bound_range_row->m_right_side = row_with_range->m_right_side + abs(row_with_range->m_range); + break; + case row_type::Less_or_equal: + m_rows[row_name] = other_bound_range_row = new row(row_type::Greater_or_equal, row_name, index); + other_bound_range_row->m_right_side = row_with_range->m_right_side - abs(row_with_range->m_range); + break; + case row_type::Equal: + if (row_with_range->m_range > 0) { + row_with_range->m_type = row_type::Greater_or_equal; // the existing row type change + m_rows[row_name] = other_bound_range_row = new row(row_type::Less_or_equal, row_name, index); + } else { // row->m_range < 0; + row_with_range->m_type = row_type::Less_or_equal; // the existing row type change + m_rows[row_name] = other_bound_range_row = new row(row_type::Greater_or_equal, row_name, index); + } + other_bound_range_row->m_right_side = row_with_range->m_right_side + row_with_range->m_range; + break; + default: + (*m_message_stream) << "unexpected bound type " << row_with_range->m_type << " at line " << m_line_number << std::endl; + set_m_ok_to_false(); + throw; + } + + for (auto s : row_with_range->m_row_columns) { + lean_assert(m_columns.find(s.first) != m_columns.end()); + other_bound_range_row->m_row_columns[s.first] = s.second; + } + } + + void add_row() { + if (m_line.length() < 2) { + return; + } + + m_line = trim(m_line); + char c = m_line[0]; + m_line = m_line.substr(1); + m_line = trim(m_line); + add_row(c); + } + + void add_row(char c) { + unsigned index= static_cast(m_rows.size() - m_cost_line_count); + switch (c) { + case 'E': + m_rows[m_line] = new row(row_type::Equal, m_line, index); + break; + case 'L': + m_rows[m_line] = new row(row_type::Less_or_equal, m_line, index); + break; + case 'G': + m_rows[m_line] = new row(row_type::Greater_or_equal, m_line, index); + break; + case 'N': + m_rows[m_line] = new row(row_type::Cost, m_line, index); + m_cost_row_name = m_line; + m_cost_line_count++; + break; + } + } + unsigned range_count() { + unsigned ret = 0; + for (auto s : m_rows) { + if (s.second->m_range != 0) { + ret++; + } + } + return ret; + } + + /* + If rhs is a constraint's right-hand-side value and range is the constraint's range value, then the range interval is defined according to the following table: + + sense interval + G [rhs, rhs + |range|] + L [rhs - |range|, rhs] + E [rhs, rhs + |range|] if range ¡Ý 0 [rhs - |range|, rhs] if range < 0 + where |range| is range's absolute value. + */ + + lp_relation get_relation_from_row(row_type rt) { + switch (rt) { + case mps_reader::Less_or_equal: return lp_relation::Less_or_equal; + case mps_reader::Greater_or_equal: return lp_relation::Greater_or_equal; + case mps_reader::Equal: return lp_relation::Equal; + default: + (*m_message_stream) << "Unexpected rt " << rt << std::endl; + set_m_ok_to_false(); + throw; + } + } + + unsigned solver_row_count() { + return m_rows.size() - m_cost_line_count + range_count(); + } + + void fill_solver_on_row(row * row, lp_solver *solver) { + if (row->m_name != m_cost_row_name) { + solver->add_constraint(get_relation_from_row(row->m_type), row->m_right_side, row->m_index); + for (auto s : row->m_row_columns) { + lean_assert(m_columns.find(s.first) != m_columns.end()); + solver->set_row_column_coefficient(row->m_index, m_columns[s.first]->m_index, s.second); + } + } else { + set_solver_cost(row, solver); + } + } + + T abs(T & t) { return t < numeric_traits::zero() ? -t: t; } + + void fill_solver_on_rows(lp_solver * solver) { + for (auto row_it : m_rows) { + fill_solver_on_row(row_it.second, solver); + } + } + + + void fill_solver_on_columns(lp_solver * solver){ + for (auto s : m_columns) { + mps_reader::column * col = s.second; + unsigned index = col->m_index; + set_boundary_for_column(index, col->m_bound, solver); + // optional call + solver->give_symbolic_name_to_column(col->m_name, col->m_index); + } + } + + void fill_solver(lp_solver *solver) { + fill_solver_on_rows(solver); + fill_solver_on_columns(solver); + } + + void set_solver_cost(row * row, lp_solver *solver) { + for (auto s : row->m_row_columns) { + std::string name = s.first; + lean_assert(m_columns.find(name) != m_columns.end()); + mps_reader::column * col = m_columns[name]; + solver->set_cost_for_column(col->m_index, s.second); + } + } + +public: + + void set_message_stream(std::ostream * o) { + lean_assert(o != nullptr); + m_message_stream = o; + } + vector column_names() { + vector v; + for (auto s : m_columns) { + v.push_back(s.first); + } + return v; + } + + ~mps_reader() { + for (auto s : m_rows) { + delete s.second; + } + for (auto s : m_columns) { + auto col = s.second; + auto b = col->m_bound; + if (b != nullptr) { + delete b; + } + delete col; + } + } + + mps_reader(std::string file_name): + m_file_name(file_name), m_file_stream(file_name) { + } + void read() { + if (!m_file_stream.is_open()){ + set_m_ok_to_false(); + return; + } + + read_name(); + read_rows(); + read_columns(); + read_rhs(); + if (m_line.find("BOUNDS") == 0) { + read_bounds(); + read_ranges(); + } else if (m_line.find("RANGES") == 0) { + read_ranges(); + read_bounds(); + } + } + + bool is_ok() { + return m_is_OK; + } + + lp_solver * create_solver(bool dual) { + lp_solver * solver = dual? (lp_solver*)new lp_dual_simplex() : new lp_primal_simplex(); + fill_solver(solver); + return solver; + } + + lconstraint_kind get_lar_relation_from_row(row_type rt) { + switch (rt) { + case Less_or_equal: return LE; + case Greater_or_equal: return GE; + case Equal: return EQ; + default: + (*m_message_stream) << "Unexpected rt " << rt << std::endl; + set_m_ok_to_false(); + throw; + } + } + + unsigned get_var_index(std::string s) { + auto it = m_names_to_var_index.find(s); + if (it != m_names_to_var_index.end()) + return it->second; + unsigned ret = m_names_to_var_index.size(); + m_names_to_var_index[s] = ret; + return ret; + } + + void fill_lar_solver_on_row(row * row, lar_solver *solver) { + if (row->m_name != m_cost_row_name) { + auto kind = get_lar_relation_from_row(row->m_type); + vector> ls; + for (auto s : row->m_row_columns) { + var_index i = solver->add_var(get_var_index(s.first)); + ls.push_back(std::make_pair(s.second, i)); + } + solver->add_constraint(ls, kind, row->m_right_side); + } else { + // ignore the cost row + } + } + + + void fill_lar_solver_on_rows(lar_solver * solver) { + for (auto row_it : m_rows) { + fill_lar_solver_on_row(row_it.second, solver); + } + } + + void create_low_constraint_for_var(column* col, bound * b, lar_solver *solver) { + vector> ls; + var_index i = solver->add_var(col->m_index); + ls.push_back(std::make_pair(numeric_traits::one(), i)); + solver->add_constraint(ls, GE, b->m_low); + } + + void create_upper_constraint_for_var(column* col, bound * b, lar_solver *solver) { + var_index i = solver->add_var(col->m_index); + vector> ls; + ls.push_back(std::make_pair(numeric_traits::one(), i)); + solver->add_constraint(ls, LE, b->m_upper); + } + + void create_equality_contraint_for_var(column* col, bound * b, lar_solver *solver) { + var_index i = solver->add_var(col->m_index); + vector> ls; + ls.push_back(std::make_pair(numeric_traits::one(), i)); + solver->add_constraint(ls, EQ, b->m_fixed_value); + } + + void fill_lar_solver_on_columns(lar_solver * solver) { + for (auto s : m_columns) { + mps_reader::column * col = s.second; + solver->add_var(col->m_index); + auto b = col->m_bound; + if (b == nullptr) return; + + if (b->m_free) continue; + + if (b->m_low_is_set) { + create_low_constraint_for_var(col, b, solver); + } + if (b->m_upper_is_set) { + create_upper_constraint_for_var(col, b, solver); + } + if (b->m_value_is_fixed) { + create_equality_contraint_for_var(col, b, solver); + } + } + } + + + void fill_lar_solver(lar_solver * solver) { + fill_lar_solver_on_columns(solver); + fill_lar_solver_on_rows(solver); + } + + lar_solver * create_lar_solver() { + lar_solver * solver = new lar_solver(); + fill_lar_solver(solver); + return solver; + } +}; +} diff --git a/src/util/lp/numeric_pair.h b/src/util/lp/numeric_pair.h new file mode 100644 index 000000000..2cea4158d --- /dev/null +++ b/src/util/lp/numeric_pair.h @@ -0,0 +1,329 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson + The idea is that it is only one different file in Lean and z3 source inside of LP +*/ +#pragma once +#define lp_for_z3 +#include +#include +#include +#ifdef lp_for_z3 +#include "../rational.h" +#include "../sstream.h" +#include "../z3_exception.h" + +#else + // include "util/numerics/mpq.h" + // include "util/numerics/numeric_traits.h" +#endif +namespace lean { +#ifdef lp_for_z3 // rename rationals + typedef rational mpq; +#else + typedef lean::mpq mpq; +#endif + + +template +std::string T_to_string(const T & t); // forward definition +#ifdef lp_for_z3 +template class numeric_traits {}; + +template <> class numeric_traits { +public: + static bool precise() { return true; } + static unsigned const zero() { return 0; } + static unsigned const one() { return 1; } + static bool is_zero(unsigned v) { return v == 0; } + static double const get_double(unsigned const & d) { return d; } +}; + +template <> class numeric_traits { + public: + static bool precise() { return false; } + static double g_zero; + static double const &zero() { return g_zero; } + static double g_one; + static double const &one() { return g_one; } + static bool is_zero(double v) { return v == 0.0; } + static double const & get_double(double const & d) { return d;} + static double log(double const & d) { NOT_IMPLEMENTED_YET(); return d;} + static double from_string(std::string const & str) { return atof(str.c_str()); } + static bool is_pos(const double & d) {return d > 0.0;} + static bool is_neg(const double & d) {return d < 0.0;} + }; + + template<> + class numeric_traits { + public: + static bool precise() { return true; } + static rational const & zero() { return rational::zero(); } + static rational const & one() { return rational::one(); } + static bool is_zero(const rational & v) { return v.is_zero(); } + static double const get_double(const rational & d) { return d.get_double();} + static rational log(rational const& r) { UNREACHABLE(); return r; } + static rational from_string(std::string const & str) { return rational(str.c_str()); } + static bool is_pos(const rational & d) {return d.is_pos();} + static bool is_neg(const rational & d) {return d.is_neg();} + }; +#endif + +template +struct convert_struct { + static X convert(const Y & y){ return X(y);} + static bool is_epsilon_small(const X & x, const double & y) { return std::abs(numeric_traits::get_double(x)) < y; } + static bool below_bound_numeric(const X &, const X &, const Y &) { /*lean_unreachable();*/ return false;} + static bool above_bound_numeric(const X &, const X &, const Y &) { /*lean_unreachable();*/ return false; } +}; + + +template <> +struct convert_struct { + static double convert(const mpq & q) {return q.get_double();} +}; + + +template <> +struct convert_struct { + static mpq convert(unsigned q) {return mpq(q);} +}; + + + +template +struct numeric_pair { + T x; + T y; + // empty constructor + numeric_pair() {} + // another constructor + + numeric_pair(T xp, T yp) : x(xp), y(yp) {} + + + template + numeric_pair(const X & n) : x(n), y(0) { + } + + template + numeric_pair(const numeric_pair & n) : x(n.x), y(n.y) {} + + template + numeric_pair(X xp, Y yp) : numeric_pair(convert_struct::convert(xp), convert_struct::convert(yp)) {} + + bool operator<(const numeric_pair& a) const { + return x < a.x || (x == a.x && y < a.y); + } + + bool operator>(const numeric_pair& a) const { + return x > a.x || (x == a.x && y > a.y); + } + + bool operator==(const numeric_pair& a) const { + return a.x == x && a.y == y; + } + + bool operator!=(const numeric_pair& a) const { + return !(*this == a); + } + + bool operator<=(const numeric_pair& a) const { + return *this < a || *this == a; + } + + bool operator>=(const numeric_pair& a) const { + return *this > a || a == *this; + } + + numeric_pair operator*(const T & a) const { + return numeric_pair(a * x, a * y); + } + + numeric_pair operator/(const T & a) const { + T a_as_T(a); + return numeric_pair(x / a_as_T, y / a_as_T); + } + + numeric_pair operator/(const numeric_pair &) const { + // lean_unreachable(); + } + + + numeric_pair operator+(const numeric_pair & a) const { + return numeric_pair(a.x + x, a.y + y); + } + + numeric_pair operator*(const numeric_pair & /*a*/) const { + // lean_unreachable(); + } + + numeric_pair& operator+=(const numeric_pair & a) { + x += a.x; + y += a.y; + return *this; + } + + numeric_pair& operator-=(const numeric_pair & a) { + x -= a.x; + y -= a.y; + return *this; + } + + numeric_pair& operator/=(const T & a) { + x /= a; + y /= a; + return *this; + } + + numeric_pair& operator*=(const T & a) { + x *= a; + y *= a; + return *this; + } + + numeric_pair operator-(const numeric_pair & a) const { + return numeric_pair(x - a.x, y - a.y); + } + + numeric_pair operator-() const { + return numeric_pair(-x, -y); + } + + static bool precize() { return lean::numeric_traits::precize();} + + bool is_zero() const { return x.is_zero() && y.is_zero(); } + + bool is_pos() const { return x.is_pos() || (x.is_zero() && y.is_pos());} + + bool is_neg() const { return x.is_neg() || (x.is_zero() && y.is_neg());} + + std::string to_string() const { + return std::string("(") + T_to_string(x) + ", " + T_to_string(y) + ")"; + } +}; + + +template +std::ostream& operator<<(std::ostream& os, numeric_pair const & obj) { + os << obj.to_string(); + return os; +} + +template +numeric_pair operator*(const X & a, const numeric_pair & r) { + return numeric_pair(a * r.x, a * r.y); +} + +template +numeric_pair operator*(const numeric_pair & r, const X & a) { + return numeric_pair(a * r.x, a * r.y); +} + + +template +numeric_pair operator/(const numeric_pair & r, const X & a) { + return numeric_pair(r.x / a, r.y / a); +} + +// template bool precise() { return numeric_traits::precise();} +template double get_double(const lean::numeric_pair & ) { /* lean_unreachable(); */ return 0;} +template +class numeric_traits> { + public: + static bool precise() { return numeric_traits::precise();} + static lean::numeric_pair zero() { return lean::numeric_pair(numeric_traits::zero(), numeric_traits::zero()); } + static bool is_zero(const lean::numeric_pair & v) { return numeric_traits::is_zero(v.x) && numeric_traits::is_zero(v.y); } + static double get_double(const lean::numeric_pair & v){ return numeric_traits::get_double(v.x); } // just return the double of the first coordinate + static double one() { /*lean_unreachable();*/ return 0;} + static bool is_pos(const numeric_pair &p) { + return numeric_traits::is_pos(p.x) || + (numeric_traits::is_zero(p.x) && numeric_traits::is_pos(p.y)); + } + static bool is_neg(const numeric_pair &p) { + return numeric_traits::is_neg(p.x) || + (numeric_traits::is_zero(p.x) && numeric_traits::is_neg(p.y)); + } + +}; + +template <> +struct convert_struct> { + static double convert(const numeric_pair & q) {return q.x;} +}; + +typedef numeric_pair impq; + +template bool is_epsilon_small(const X & v, const double& eps); // forward definition { return convert_struct::is_epsilon_small(v, eps);} + +template +struct convert_struct, double> { + static numeric_pair convert(const double & q) { + return numeric_pair(convert_struct::convert(q), numeric_traits::zero()); + } + static bool is_epsilon_small(const numeric_pair & p, const double & eps) { + return convert_struct::is_epsilon_small(p.x, eps) && convert_struct::is_epsilon_small(p.y, eps); + } + static bool below_bound_numeric(const numeric_pair &, const numeric_pair &, const double &) { + // lean_unreachable(); + return false; + } + static bool above_bound_numeric(const numeric_pair &, const numeric_pair &, const double &) { + // lean_unreachable(); + return false; + } +}; +template <> +struct convert_struct, double> { + static numeric_pair convert(const double & q) { + return numeric_pair(q, 0.0); + } + static bool is_epsilon_small(const numeric_pair & p, const double & eps) { + return std::abs(p.x) < eps && std::abs(p.y) < eps; + } + + static int compare_on_coord(const double & x, const double & bound, const double eps) { + if (bound == 0) return (x < - eps)? -1: (x > eps? 1 : 0); // it is an important special case + double relative = (bound > 0)? - eps: eps; + return (x < bound * (1.0 + relative) - eps)? -1 : ((x > bound * (1.0 - relative) + eps)? 1 : 0); + } + + static bool below_bound_numeric(const numeric_pair & x, const numeric_pair & bound, const double & eps) { + int r = compare_on_coord(x.x, bound.x, eps); + if (r == 1) return false; + if (r == -1) return true; + // the first coordinates are almost the same + return compare_on_coord(x.y, bound.y, eps) == -1; + } + + static bool above_bound_numeric(const numeric_pair & x, const numeric_pair & bound, const double & eps) { + int r = compare_on_coord(x.x, bound.x, eps); + if (r == -1) return false; + if (r == 1) return true; + // the first coordinates are almost the same + return compare_on_coord(x.y, bound.y, eps) == 1; + } +}; + +template <> +struct convert_struct { + static bool is_epsilon_small(const double& x, const double & eps) { + return x < eps && x > -eps; + } + static double convert(const double & y){ return y;} + static bool below_bound_numeric(const double & x, const double & bound, const double & eps) { + if (bound == 0) return x < - eps; + double relative = (bound > 0)? - eps: eps; + return x < bound * (1.0 + relative) - eps; + } + static bool above_bound_numeric(const double & x, const double & bound, const double & eps) { + if (bound == 0) return x > eps; + double relative = (bound > 0)? eps: - eps; + return x > bound * (1.0 + relative) + eps; + } +}; + +template bool is_epsilon_small(const X & v, const double &eps) { return convert_struct::is_epsilon_small(v, eps);} +template bool below_bound_numeric(const X & x, const X & bound, const double& eps) { return convert_struct::below_bound_numeric(x, bound, eps);} +template bool above_bound_numeric(const X & x, const X & bound, const double& eps) { return convert_struct::above_bound_numeric(x, bound, eps);} +} diff --git a/src/util/lp/permutation_matrix.h b/src/util/lp/permutation_matrix.h new file mode 100644 index 000000000..6c0367482 --- /dev/null +++ b/src/util/lp/permutation_matrix.h @@ -0,0 +1,173 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#pragma once +#include "util/vector.h" +#include +#include "util/debug.h" +#include +#include "util/lp/sparse_vector.h" +#include "util/lp/indexed_vector.h" +#include "util/lp/lp_settings.h" +#include "util/lp/matrix.h" +#include "util/lp/tail_matrix.h" +namespace lean { +#ifdef LEAN_DEBUG + inline bool is_even(int k) { return (k/2)*2 == k; } +#endif + + template +class permutation_matrix : public tail_matrix { + vector m_permutation; + vector m_rev; + vector m_work_array; + vector m_T_buffer; + vector m_X_buffer; + + + class ref { + permutation_matrix & m_p; + unsigned m_i; + public: + ref(permutation_matrix & m, unsigned i):m_p(m), m_i(i) {} + + ref & operator=(unsigned v) { m_p.set_val(m_i, v); return *this; } + + ref & operator=(ref const & v) { + m_p.set_val(m_i, v.m_p.m_permutation[v.m_i]); + return *this; + } + operator unsigned & () const { return m_p.m_permutation[m_i]; } + }; + + public: + permutation_matrix() {} + permutation_matrix(unsigned length); + + permutation_matrix(unsigned length, vector const & values); + // create a unit permutation of the given length + void init(unsigned length); + unsigned get_rev(unsigned i) { return m_rev[i]; } + bool is_dense() const { return false; } +#ifdef LEAN_DEBUG + permutation_matrix get_inverse() const { + return permutation_matrix(size(), m_rev); + } + void print(std::ostream & out) const; +#endif + + ref operator[](unsigned i) { return ref(*this, i); } + + unsigned operator[](unsigned i) const { return m_permutation[i]; } + + void apply_from_left(vector & w, lp_settings &); + + void apply_from_left_to_T(indexed_vector & w, lp_settings & settings); + + void apply_from_right(vector & w); + + void apply_from_right(indexed_vector & w); + + template + void copy_aside(vector & t, vector & tmp_index, indexed_vector & w); + + template + void clear_data(indexed_vector & w); + + template + void apply_reverse_from_left(indexed_vector & w); + + void apply_reverse_from_left_to_T(vector & w); + void apply_reverse_from_left_to_X(vector & w); + + void apply_reverse_from_right_to_T(vector & w); + void apply_reverse_from_right_to_T(indexed_vector & w); + void apply_reverse_from_right_to_X(vector & w); + + void set_val(unsigned i, unsigned pi) { + lean_assert(i < size() && pi < size()); m_permutation[i] = pi; m_rev[pi] = i; } + + void transpose_from_left(unsigned i, unsigned j); + + unsigned apply_reverse(unsigned i) const { return m_rev[i]; } + + void transpose_from_right(unsigned i, unsigned j); +#ifdef LEAN_DEBUG + T get_elem(unsigned i, unsigned j) const{ + return m_permutation[i] == j? numeric_traits::one() : numeric_traits::zero(); + } + unsigned row_count() const{ return size(); } + unsigned column_count() const { return size(); } + virtual void set_number_of_rows(unsigned /*m*/) { } + virtual void set_number_of_columns(unsigned /*n*/) { } +#endif + void multiply_by_permutation_from_left(permutation_matrix & p); + + // this is multiplication in the matrix sense + void multiply_by_permutation_from_right(permutation_matrix & p); + + void multiply_by_reverse_from_right(permutation_matrix & q); + + void multiply_by_permutation_reverse_from_left(permutation_matrix & r); + + void shrink_by_one_identity(); + + bool is_identity() const; + + unsigned size() const { return static_cast(m_rev.size()); } + + unsigned * values() const { return m_permutation; } + + void resize(unsigned size) { + unsigned old_size = m_permutation.size(); + m_permutation.resize(size); + m_rev.resize(size); + m_T_buffer.resize(size); + m_X_buffer.resize(size); + for (unsigned i = old_size; i < size; i++) { + m_permutation[i] = m_rev[i] = i; + } + } + + }; // end of the permutation class + +#ifdef LEAN_DEBUG +template +class permutation_generator { + unsigned m_n; + permutation_generator* m_lower; + bool m_done = false; + permutation_matrix m_current; + unsigned m_last; +public: + permutation_generator(unsigned n); + permutation_generator(const permutation_generator & o); + bool move_next(); + + ~permutation_generator() { + if (m_lower != nullptr) { + delete m_lower; + } + } + + permutation_matrix *current() { + return &m_current; + } +}; + +template +inline unsigned number_of_inversions(permutation_matrix & p); + +template +int sign(permutation_matrix & p) { + return is_even(number_of_inversions(p))? 1: -1; +} + +template +T det_val_on_perm(permutation_matrix* u, const matrix& m); + +template +T determinant(const matrix& m); +#endif +} diff --git a/src/util/lp/permutation_matrix.hpp b/src/util/lp/permutation_matrix.hpp new file mode 100644 index 000000000..09435674d --- /dev/null +++ b/src/util/lp/permutation_matrix.hpp @@ -0,0 +1,419 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include "util/vector.h" +#include "util/lp/permutation_matrix.h" +namespace lean { +template permutation_matrix::permutation_matrix(unsigned length): m_permutation(length), m_rev(length), m_T_buffer(length), m_X_buffer(length) { + for (unsigned i = 0; i < length; i++) { // do not change the direction of the loop because of the vectorization bug in clang3.3 + m_permutation[i] = m_rev[i] = i; + } +} + +template permutation_matrix::permutation_matrix(unsigned length, vector const & values): m_permutation(length), m_rev(length) , m_T_buffer(length), m_X_buffer(length) { + for (unsigned i = 0; i < length; i++) { + set_val(i, values[i]); + } +} +// create a unit permutation of the given length +template void permutation_matrix::init(unsigned length) { + m_permutation.resize(length); + m_rev.resize(length); + m_T_buffer.resize(length); + m_X_buffer.resize(length); + for (unsigned i = 0; i < length; i++) { + m_permutation[i] = m_rev[i] = i; + } +} + +#ifdef LEAN_DEBUG +template void permutation_matrix::print(std::ostream & out) const { + out << "["; + for (unsigned i = 0; i < size(); i++) { + out << m_permutation[i]; + if (i < size() - 1) { + out << ","; + } else { + out << "]"; + } + } + out << std::endl; +} +#endif + +template +void permutation_matrix::apply_from_left(vector & w, lp_settings & ) { +#ifdef LEAN_DEBUG + // dense_matrix deb(*this); + // L * deb_w = clone_vector(w, row_count()); + // deb.apply_from_left(deb_w); +#endif + // std::cout << " apply_from_left " << std::endl; + lean_assert(m_X_buffer.size() == w.size()); + unsigned i = size(); + while (i-- > 0) { + m_X_buffer[i] = w[m_permutation[i]]; + } + i = size(); + while (i-- > 0) { + w[i] = m_X_buffer[i]; + } +#ifdef LEAN_DEBUG + // lean_assert(vectors_are_equal(deb_w, w, row_count())); + // delete [] deb_w; +#endif +} + +template +void permutation_matrix::apply_from_left_to_T(indexed_vector & w, lp_settings & ) { + vector t(w.m_index.size()); + vector tmp_index(w.m_index.size()); + copy_aside(t, tmp_index, w); // todo: is it too much copying + clear_data(w); + // set the new values + for (unsigned i = static_cast(t.size()); i > 0;) { + i--; + unsigned j = m_rev[tmp_index[i]]; + w[j] = t[i]; + w.m_index[i] = j; + } +} + +template void permutation_matrix::apply_from_right(vector & w) { +#ifdef LEAN_DEBUG + // dense_matrix deb(*this); + // T * deb_w = clone_vector(w, row_count()); + // deb.apply_from_right(deb_w); +#endif + lean_assert(m_T_buffer.size() == w.size()); + for (unsigned i = 0; i < size(); i++) { + m_T_buffer[i] = w[m_rev[i]]; + } + + for (unsigned i = 0; i < size(); i++) { + w[i] = m_T_buffer[i]; + } +#ifdef LEAN_DEBUG + // lean_assert(vectors_are_equal(deb_w, w, row_count())); + // delete [] deb_w; +#endif +} + +template void permutation_matrix::apply_from_right(indexed_vector & w) { +#ifdef LEAN_DEBUG + vector wcopy(w.m_data); + apply_from_right(wcopy); +#endif + vector buffer(w.m_index.size()); + vector index_copy(w.m_index); + for (unsigned i = 0; i < w.m_index.size(); i++) { + buffer[i] = w.m_data[w.m_index[i]]; + } + w.clear(); + + for (unsigned i = 0; i < index_copy.size(); i++) { + unsigned j = index_copy[i]; + unsigned pj = m_permutation[j]; + w.set_value(buffer[i], pj); + } + lean_assert(w.is_OK()); +#ifdef LEAN_DEBUG + lean_assert(vectors_are_equal(wcopy, w.m_data)); +#endif +} + + +template template +void permutation_matrix::copy_aside(vector & t, vector & tmp_index, indexed_vector & w) { + for (unsigned i = static_cast(t.size()); i > 0;) { + i--; + unsigned j = w.m_index[i]; + t[i] = w[j]; // copy aside all non-zeroes + tmp_index[i] = j; // and the indices too + } +} + +template template +void permutation_matrix::clear_data(indexed_vector & w) { + // clear old non-zeroes + for (unsigned i = static_cast(w.m_index.size()); i > 0;) { + i--; + unsigned j = w.m_index[i]; + w[j] = zero_of_type(); + } +} + +template template +void permutation_matrix::apply_reverse_from_left(indexed_vector & w) { + // the result will be w = p(-1) * w +#ifdef LEAN_DEBUG + // dense_matrix deb(get_reverse()); + // L * deb_w = clone_vector(w.m_data, row_count()); + // deb.apply_from_left(deb_w); +#endif + vector t(w.m_index.size()); + vector tmp_index(w.m_index.size()); + + copy_aside(t, tmp_index, w); + clear_data(w); + + // set the new values + for (unsigned i = static_cast(t.size()); i > 0;) { + i--; + unsigned j = m_permutation[tmp_index[i]]; + w[j] = t[i]; + w.m_index[i] = j; + } +#ifdef LEAN_DEBUG + // lean_assert(vectors_are_equal(deb_w, w.m_data, row_count())); + // delete [] deb_w; +#endif +} + +template +void permutation_matrix::apply_reverse_from_left_to_T(vector & w) { + // the result will be w = p(-1) * w + lean_assert(m_T_buffer.size() == w.size()); + unsigned i = size(); + while (i-- > 0) { + m_T_buffer[m_permutation[i]] = w[i]; + } + i = size(); + while (i-- > 0) { + w[i] = m_T_buffer[i]; + } +} +template +void permutation_matrix::apply_reverse_from_left_to_X(vector & w) { + // the result will be w = p(-1) * w + lean_assert(m_X_buffer.size() == w.size()); + unsigned i = size(); + while (i-- > 0) { + m_X_buffer[m_permutation[i]] = w[i]; + } + i = size(); + while (i-- > 0) { + w[i] = m_X_buffer[i]; + } +} + +template +void permutation_matrix::apply_reverse_from_right_to_T(vector & w) { + // the result will be w = w * p(-1) + lean_assert(m_T_buffer.size() == w.size()); + unsigned i = size(); + while (i-- > 0) { + m_T_buffer[i] = w[m_permutation[i]]; + } + i = size(); + while (i-- > 0) { + w[i] = m_T_buffer[i]; + } +} + +template +void permutation_matrix::apply_reverse_from_right_to_T(indexed_vector & w) { + // the result will be w = w * p(-1) +#ifdef LEAN_DEBUG + // vector wcopy(w.m_data); + // apply_reverse_from_right_to_T(wcopy); +#endif + lean_assert(w.is_OK()); + vector tmp; + vector tmp_index(w.m_index); + for (auto i : w.m_index) { + tmp.push_back(w[i]); + } + w.clear(); + + for (unsigned k = 0; k < tmp_index.size(); k++) { + unsigned j = tmp_index[k]; + w.set_value(tmp[k], m_rev[j]); + } + + // lean_assert(w.is_OK()); + // lean_assert(vectors_are_equal(w.m_data, wcopy)); +} + + +template +void permutation_matrix::apply_reverse_from_right_to_X(vector & w) { + // the result will be w = w * p(-1) + lean_assert(m_X_buffer.size() == w.size()); + unsigned i = size(); + while (i-- > 0) { + m_X_buffer[i] = w[m_permutation[i]]; + } + i = size(); + while (i-- > 0) { + w[i] = m_X_buffer[i]; + } +} + +template void permutation_matrix::transpose_from_left(unsigned i, unsigned j) { + // the result will be this = (i,j)*this + lean_assert(i < size() && j < size() && i != j); + auto pi = m_rev[i]; + auto pj = m_rev[j]; + set_val(pi, j); + set_val(pj, i); +} + +template void permutation_matrix::transpose_from_right(unsigned i, unsigned j) { + // the result will be this = this * (i,j) + lean_assert(i < size() && j < size() && i != j); + auto pi = m_permutation[i]; + auto pj = m_permutation[j]; + set_val(i, pj); + set_val(j, pi); +} + +template void permutation_matrix::multiply_by_permutation_from_left(permutation_matrix & p) { + m_work_array = m_permutation; + lean_assert(p.size() == size()); + unsigned i = size(); + while (i-- > 0) { + set_val(i, m_work_array[p[i]]); // we have m(P)*m(Q) = m(QP), where m is the matrix of the permutation + } +} + +// this is multiplication in the matrix sense +template void permutation_matrix::multiply_by_permutation_from_right(permutation_matrix & p) { + m_work_array = m_permutation; + lean_assert(p.size() == size()); + unsigned i = size(); + while (i-- > 0) + set_val(i, p[m_work_array[i]]); // we have m(P)*m(Q) = m(QP), where m is the matrix of the permutation + +} + +template void permutation_matrix::multiply_by_reverse_from_right(permutation_matrix & q){ // todo : condensed permutations ? + lean_assert(q.size() == size()); + m_work_array = m_permutation; + // the result is this = this*q(-1) + unsigned i = size(); + while (i-- > 0) { + set_val(i, q.m_rev[m_work_array[i]]); // we have m(P)*m(Q) = m(QP), where m is the matrix of the permutation + } +} + +template void permutation_matrix::multiply_by_permutation_reverse_from_left(permutation_matrix & r){ // todo : condensed permutations? + // the result is this = r(-1)*this + m_work_array = m_permutation; + // the result is this = this*q(-1) + unsigned i = size(); + while (i-- > 0) { + set_val(i, m_work_array[r.m_rev[i]]); + } +} + + +template bool permutation_matrix::is_identity() const { + unsigned i = size(); + while (i-- > 0) { + if (m_permutation[i] != i) { + return false; + } + } + return true; +} + + +#ifdef LEAN_DEBUG +template +permutation_generator::permutation_generator(unsigned n): m_n(n), m_current(n) { + lean_assert(n > 0); + if (n > 1) { + m_lower = new permutation_generator(n - 1); + } else { + m_lower = nullptr; + } + + m_last = 0; +} + +template +permutation_generator::permutation_generator(const permutation_generator & o): m_n(o.m_n), m_done(o.m_done), m_current(o.m_current), m_last(o.m_last) { + if (m_lower != nullptr) { + m_lower = new permutation_generator(o.m_lower); + } else { + m_lower = nullptr; + } +} + +template bool +permutation_generator::move_next() { + if (m_done) { + return false; + } + + if (m_lower == nullptr) { + if (m_last == 0) { + m_last++; + return true; + } else { + m_done = true; + return false; + } + } else { + if (m_last < m_n && m_last > 0) { + m_current[m_last - 1] = m_current[m_last]; + m_current[m_last] = m_n - 1; + m_last++; + return true; + } else { + if (m_lower -> move_next()) { + auto lower_curr = m_lower -> current(); + for ( unsigned i = 1; i < m_n; i++ ){ + m_current[i] = (*lower_curr)[i - 1]; + } + m_current[0] = m_n - 1; + m_last = 1; + return true; + } else { + m_done = true; + return false; + } + } + } +} + +template +inline unsigned number_of_inversions(permutation_matrix & p) { + unsigned ret = 0; + unsigned n = p.size(); + for (unsigned i = 0; i < n; i++) { + for (unsigned j = i + 1; j < n; j++) { + if (p[i] > p[j]) { + ret++; + } + } + } + return ret; +} + +template +T det_val_on_perm(permutation_matrix* u, const matrix& m) { + unsigned n = m.row_count(); + T ret = numeric_traits::one(); + for (unsigned i = 0; i < n; i++) { + unsigned j = (*u)[i]; + ret *= m(i, j); + } + return ret * sign(*u); +} + +template +T determinant(const matrix& m) { + lean_assert(m.column_count() == m.row_count()); + unsigned n = m.row_count(); + permutation_generator allp(n); + T ret = numeric_traits::zero(); + while (allp.move_next()){ + ret += det_val_on_perm(allp.current(), m); + } + return ret; +} +#endif +} diff --git a/src/util/lp/permutation_matrix_instances.cpp b/src/util/lp/permutation_matrix_instances.cpp new file mode 100644 index 000000000..e0cc1a144 --- /dev/null +++ b/src/util/lp/permutation_matrix_instances.cpp @@ -0,0 +1,60 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include +#include "util/vector.h" +#include "util/lp/permutation_matrix.hpp" +#include "util/lp/numeric_pair.h" +template void lean::permutation_matrix::apply_from_right(vector&); +template void lean::permutation_matrix::init(unsigned int); +template void lean::permutation_matrix::init(unsigned int); +template void lean::permutation_matrix>::init(unsigned int); +template bool lean::permutation_matrix::is_identity() const; +template void lean::permutation_matrix::multiply_by_permutation_from_left(lean::permutation_matrix&); +template void lean::permutation_matrix::multiply_by_permutation_reverse_from_left(lean::permutation_matrix&); +template void lean::permutation_matrix::multiply_by_reverse_from_right(lean::permutation_matrix&); +template lean::permutation_matrix::permutation_matrix(unsigned int, vector const&); +template void lean::permutation_matrix::transpose_from_left(unsigned int, unsigned int); + +template void lean::permutation_matrix::apply_from_right(vector&); +template bool lean::permutation_matrix::is_identity() const; +template void lean::permutation_matrix::multiply_by_permutation_from_left(lean::permutation_matrix&); +template void lean::permutation_matrix::multiply_by_permutation_from_right(lean::permutation_matrix&); +template void lean::permutation_matrix::multiply_by_permutation_reverse_from_left(lean::permutation_matrix&); +template void lean::permutation_matrix::multiply_by_reverse_from_right(lean::permutation_matrix&); +template lean::permutation_matrix::permutation_matrix(unsigned int); +template void lean::permutation_matrix::transpose_from_left(unsigned int, unsigned int); +template void lean::permutation_matrix::transpose_from_right(unsigned int, unsigned int); +template void lean::permutation_matrix >::apply_from_right(vector&); +template bool lean::permutation_matrix >::is_identity() const; +template void lean::permutation_matrix >::multiply_by_permutation_from_left(lean::permutation_matrix >&); +template void lean::permutation_matrix >::multiply_by_permutation_from_right(lean::permutation_matrix >&); +template void lean::permutation_matrix >::multiply_by_permutation_reverse_from_left(lean::permutation_matrix >&); +template void lean::permutation_matrix >::multiply_by_reverse_from_right(lean::permutation_matrix >&); +template lean::permutation_matrix >::permutation_matrix(unsigned int); +template void lean::permutation_matrix >::transpose_from_left(unsigned int, unsigned int); +template void lean::permutation_matrix >::transpose_from_right(unsigned int, unsigned int); +template void lean::permutation_matrix::apply_reverse_from_left(lean::indexed_vector&); +template void lean::permutation_matrix::apply_reverse_from_left_to_T(vector&); +template void lean::permutation_matrix::apply_reverse_from_right_to_T(vector&); +template void lean::permutation_matrix::transpose_from_right(unsigned int, unsigned int); +template void lean::permutation_matrix::apply_reverse_from_left(lean::indexed_vector&); +template void lean::permutation_matrix::apply_reverse_from_left_to_T(vector&); +template void lean::permutation_matrix::apply_reverse_from_right_to_T(vector&); +template void lean::permutation_matrix >::apply_reverse_from_left(lean::indexed_vector&); +template void lean::permutation_matrix >::apply_reverse_from_left_to_T(vector&); +template void lean::permutation_matrix >::apply_reverse_from_right_to_T(vector&); +template void lean::permutation_matrix::multiply_by_permutation_from_right(lean::permutation_matrix&); + +#ifdef LEAN_DEBUG +template bool lean::permutation_generator::move_next(); +template lean::permutation_generator::permutation_generator(unsigned int); +#endif +template lean::permutation_matrix::permutation_matrix(unsigned int); +template void lean::permutation_matrix::apply_reverse_from_left_to_X(vector &); +template void lean::permutation_matrix< lean::mpq, lean::mpq>::apply_reverse_from_left_to_X(vector &); +template void lean::permutation_matrix< lean::mpq, lean::numeric_pair< lean::mpq> >::apply_reverse_from_left_to_X(vector> &); +template void lean::permutation_matrix::apply_reverse_from_right_to_T(lean::indexed_vector&); +template void lean::permutation_matrix::apply_reverse_from_right_to_T(lean::indexed_vector&); +template void lean::permutation_matrix >::apply_reverse_from_right_to_T(lean::indexed_vector&); diff --git a/src/util/lp/quick_xplain.cpp b/src/util/lp/quick_xplain.cpp new file mode 100644 index 000000000..a4b6fb0e6 --- /dev/null +++ b/src/util/lp/quick_xplain.cpp @@ -0,0 +1,138 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include "util/lp/lar_solver.h" +namespace lean { +quick_xplain::quick_xplain(vector> & explanation, const lar_solver & ls, lar_solver & qsol) : + m_explanation(explanation), + m_parent_solver(ls), + m_qsol(qsol) { +} +void quick_xplain::add_constraint_to_qsol(unsigned j) { + auto & lar_c = m_constraints_in_local_vars[j]; + auto ls = lar_c.get_left_side_coefficients(); + auto ci = m_qsol.add_constraint(ls, lar_c.m_kind, lar_c.m_right_side); + m_local_ci_to_constraint_offsets[ci] = j; +} + +void quick_xplain::copy_constraint_and_add_constraint_vars(const lar_constraint& lar_c) { + vector < std::pair> ls; + for (auto & p : lar_c.get_left_side_coefficients()) { + unsigned j = p.second; + unsigned lj = m_qsol.add_var(j); + ls.push_back(std::make_pair(p.first, lj)); + } + m_constraints_in_local_vars.push_back(lar_constraint(ls, lar_c.m_kind, lar_c.m_right_side)); + +} + +bool quick_xplain::infeasible() { + m_qsol.solve(); + return m_qsol.get_status() == INFEASIBLE; +} + +// u - unexplored constraints +// c and x are assumed, in other words, all constrains of x and c are already added to m_qsol +void quick_xplain::minimize(const vector& u) { + unsigned k = 0; + unsigned initial_stack_size = m_qsol.constraint_stack_size(); + for (; k < u.size();k++) { + m_qsol.push(); + add_constraint_to_qsol(u[k]); + if (infeasible()) + break; + } + m_x.insert(u[k]); + unsigned m = k / 2; // the split + if (m < k) { + m_qsol.pop(k + 1 - m); + add_constraint_to_qsol(u[k]); + if (!infeasible()) { + vector un; + for (unsigned j = m; j < k; j++) + un.push_back(u[j]); + minimize(un); + } + } + if (m > 0) { + lean_assert(m_qsol.constraint_stack_size() >= initial_stack_size); + m_qsol.pop(m_qsol.constraint_stack_size() - initial_stack_size); + for (auto j : m_x) + add_constraint_to_qsol(j); + if (!infeasible()) { + vector un; + for (unsigned j = 0; j < m; j++) + un.push_back(u[j]); + minimize(un); + } + } +} + + +void quick_xplain::run(vector> & explanation, const lar_solver & ls){ + if (explanation.size() <= 2) return; + lar_solver qsol; + lean_assert(ls.explanation_is_correct(explanation)); + quick_xplain q(explanation, ls, qsol); + q.solve(); +} + +void quick_xplain::copy_constraints_to_local_constraints() { + for (auto & p : m_explanation) { + const auto & lar_c = m_parent_solver.get_constraint(p.second); + m_local_constraint_offset_to_external_ci.push_back(p.second); + copy_constraint_and_add_constraint_vars(lar_c); + } +} + +bool quick_xplain::is_feasible(const vector & x, unsigned k) const { + lar_solver l; + for (unsigned i : x) { + if (i == k) + continue; + vector < std::pair> ls; + const lar_constraint & c = m_constraints_in_local_vars[i]; + for (auto & p : c.get_left_side_coefficients()) { + unsigned lj = l.add_var(p.second); + ls.push_back(std::make_pair(p.first, lj)); + } + l.add_constraint(ls, c.m_kind, c.m_right_side); + } + l.solve(); + return l.get_status() != INFEASIBLE; +} + +bool quick_xplain::x_is_minimal() const { + vector x; + for (auto j : m_x) + x.push_back(j); + + for (unsigned k = 0; k < x.size(); k++) { + lean_assert(is_feasible(x, x[k])); + } + return true; +} + +void quick_xplain::solve() { + copy_constraints_to_local_constraints(); + m_qsol.push(); + lean_assert(m_qsol.constraint_count() == 0) + vector u; + for (unsigned k = 0; k < m_constraints_in_local_vars.size(); k++) + u.push_back(k); + minimize(u); + while (m_qsol.constraint_count() > 0) + m_qsol.pop(); + for (unsigned i : m_x) + add_constraint_to_qsol(i); + m_qsol.solve(); + lean_assert(m_qsol.get_status() == INFEASIBLE); + m_qsol.get_infeasibility_explanation(m_explanation); + lean_assert(m_qsol.explanation_is_correct(m_explanation)); + lean_assert(x_is_minimal()); + for (auto & p : m_explanation) { + p.second = this->m_local_constraint_offset_to_external_ci[m_local_ci_to_constraint_offsets[p.second]]; + } +} +} diff --git a/src/util/lp/quick_xplain.h b/src/util/lp/quick_xplain.h new file mode 100644 index 000000000..9faa5f41c --- /dev/null +++ b/src/util/lp/quick_xplain.h @@ -0,0 +1,33 @@ +/* +Copyright (c) 2017 Microsoft Corporation +Author: Lev Nachmanson +*/ + +#pragma once +#include "util/vector.h" +#include + +namespace lean { + class lar_solver; // forward definition + + class quick_xplain { + std::unordered_set m_x; // the minimal set of constraints, the core - it is empty at the begining + vector m_constraints_in_local_vars; + vector> & m_explanation; + const lar_solver& m_parent_solver; + lar_solver & m_qsol; + vector m_local_constraint_offset_to_external_ci; + std::unordered_map m_local_ci_to_constraint_offsets; + quick_xplain(vector> & explanation, const lar_solver & parent_lar_solver, lar_solver & qsol); + void minimize(const vector & u); + void add_constraint_to_qsol(unsigned j); + void copy_constraint_and_add_constraint_vars(const lar_constraint& lar_c); + void copy_constraints_to_local_constraints(); + bool infeasible(); + bool is_feasible(const vector & x, unsigned k) const; + bool x_is_minimal() const; + public: + static void run(vector> & explanation,const lar_solver & ls); + void solve(); + }; +} diff --git a/src/util/lp/random_updater.h b/src/util/lp/random_updater.h new file mode 100644 index 000000000..3dbab8323 --- /dev/null +++ b/src/util/lp/random_updater.h @@ -0,0 +1,77 @@ +/* +Copyright (c) 2017 Microsoft Corporation +Author: Lev Nachmanson +*/ +#pragma once +#include +#include "util/vector.h" +#include +#include +#include +#include "util/lp/lp_settings.h" +#include "util/lp/linear_combination_iterator.h" +// see http://research.microsoft.com/projects/z3/smt07.pdf +// The class searches for a feasible solution with as many different values of variables as it can find +namespace lean { +template struct numeric_pair; // forward definition +class lar_core_solver; // forward definition +class random_updater { + unsigned range = 100000; + struct interval { + bool upper_bound_is_set = false; + numeric_pair upper_bound; + bool low_bound_is_set = false; + numeric_pair low_bound; + void set_low_bound(const numeric_pair & v) { + if (low_bound_is_set) { + low_bound = std::max(v, low_bound); + } else { + low_bound = v; + low_bound_is_set = true; + } + } + void set_upper_bound(const numeric_pair & v) { + if (upper_bound_is_set) { + upper_bound = std::min(v, upper_bound); + } else { + upper_bound = v; + upper_bound_is_set = true; + } + } + bool is_empty() const {return + upper_bound_is_set && low_bound_is_set && low_bound >= upper_bound; + } + + bool low_bound_holds(const numeric_pair & a) const { + return low_bound_is_set == false || a >= low_bound; + } + bool upper_bound_holds(const numeric_pair & a) const { + return upper_bound_is_set == false || a <= upper_bound; + } + + bool contains(const numeric_pair & a) const { + return low_bound_holds(a) && upper_bound_holds(a); + } + std::string lbs() { return low_bound_is_set ? T_to_string(low_bound):std::string("inf");} + std::string rbs() { return upper_bound_is_set? T_to_string(upper_bound):std::string("inf");} + std::string to_str() { return std::string("[")+ lbs() + ", " + rbs() + "]";} + }; + std::set m_var_set; + lar_core_solver & m_core_solver; + linear_combination_iterator* m_column_j; // the actual column + interval find_shift_interval(unsigned j); + interval get_interval_of_non_basic_var(unsigned j); + void add_column_to_sets(unsigned j); + void random_shift_var(unsigned j); + std::unordered_map, unsigned> m_values; // it maps a value to the number of time it occurs + void diminish_interval_to_leave_basic_vars_feasible(numeric_pair &nb_x, interval & inter); + void shift_var(unsigned j, interval & r); + void diminish_interval_for_basic_var(numeric_pair &nb_x, unsigned j, mpq & a, interval & r); + numeric_pair get_random_from_interval(interval & r); + void add_value(numeric_pair& v); + void remove_value(numeric_pair & v); + public: + random_updater(lar_core_solver & core_solver, const vector & column_list); + void update(); +}; +} diff --git a/src/util/lp/random_updater.hpp b/src/util/lp/random_updater.hpp new file mode 100644 index 000000000..67bfcc49b --- /dev/null +++ b/src/util/lp/random_updater.hpp @@ -0,0 +1,205 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include "util/lp/random_updater.h" +#include "util/lp/static_matrix.h" +#include "util/lp/lar_solver.h" +#include "util/vector.h" +namespace lean { + + + +random_updater::random_updater( + lar_core_solver & lar_core_solver, + const vector & column_indices) : m_core_solver(lar_core_solver) { + for (unsigned j : column_indices) + add_column_to_sets(j); +} + +random_updater::interval random_updater::get_interval_of_non_basic_var(unsigned j) { + interval ret; + switch (m_core_solver.get_column_type(j)) { + case column_type::free_column: + break; + case column_type::low_bound: + ret.set_low_bound(m_core_solver.m_r_low_bounds[j]); + break; + case column_type::upper_bound: + ret.set_upper_bound(m_core_solver.m_r_upper_bounds[j]); + break; + case column_type::boxed: + case column_type::fixed: + ret.set_low_bound(m_core_solver.m_r_low_bounds[j]); + ret.set_upper_bound(m_core_solver.m_r_upper_bounds[j]); + break; + default: + lean_assert(false); + } + return ret; +} + +void random_updater::diminish_interval_for_basic_var(numeric_pair& nb_x, unsigned j, + mpq & a, + interval & r) { + lean_assert(m_core_solver.m_r_heading[j] >= 0); + numeric_pair delta; + lean_assert(a != zero_of_type()); + switch (m_core_solver.get_column_type(j)) { + case column_type::free_column: + break; + case column_type::low_bound: + delta = m_core_solver.m_r_x[j] - m_core_solver.m_r_low_bounds[j]; + lean_assert(delta >= zero_of_type>()); + if (a > 0) { + r.set_upper_bound(nb_x + delta / a); + } else { + r.set_low_bound(nb_x + delta / a); + } + break; + case column_type::upper_bound: + delta = m_core_solver.m_r_upper_bounds()[j] - m_core_solver.m_r_x[j]; + lean_assert(delta >= zero_of_type>()); + if (a > 0) { + r.set_low_bound(nb_x - delta / a); + } else { + r.set_upper_bound(nb_x - delta / a); + } + break; + case column_type::boxed: + if (a > 0) { + delta = m_core_solver.m_r_x[j] - m_core_solver.m_r_low_bounds[j]; + lean_assert(delta >= zero_of_type>()); + r.set_upper_bound(nb_x + delta / a); + delta = m_core_solver.m_r_upper_bounds()[j] - m_core_solver.m_r_x[j]; + lean_assert(delta >= zero_of_type>()); + r.set_low_bound(nb_x - delta / a); + } else { // a < 0 + delta = m_core_solver.m_r_upper_bounds()[j] - m_core_solver.m_r_x[j]; + lean_assert(delta >= zero_of_type>()); + r.set_upper_bound(nb_x - delta / a); + delta = m_core_solver.m_r_x[j] - m_core_solver.m_r_low_bounds[j]; + lean_assert(delta >= zero_of_type>()); + r.set_low_bound(nb_x + delta / a); + } + break; + case column_type::fixed: + r.set_low_bound(nb_x); + r.set_upper_bound(nb_x); + break; + default: + lean_assert(false); + } +} + + +void random_updater::diminish_interval_to_leave_basic_vars_feasible(numeric_pair &nb_x, interval & r) { + m_column_j->reset(); + unsigned i; + mpq a; + while (m_column_j->next(a, i)) { + diminish_interval_for_basic_var(nb_x, m_core_solver.m_r_basis[i], a, r); + if (r.is_empty()) + break; + } +} + +random_updater::interval random_updater::find_shift_interval(unsigned j) { + interval ret = get_interval_of_non_basic_var(j); + diminish_interval_to_leave_basic_vars_feasible(m_core_solver.m_r_x[j], ret); + return ret; +} + +void random_updater::shift_var(unsigned j, interval & r) { + lean_assert(r.contains(m_core_solver.m_r_x[j])); + lean_assert(m_core_solver.m_r_solver.column_is_feasible(j)); + auto old_x = m_core_solver.m_r_x[j]; + remove_value(old_x); + auto new_val = m_core_solver.m_r_x[j] = get_random_from_interval(r); + add_value(new_val); + + lean_assert(r.contains(m_core_solver.m_r_x[j])); + lean_assert(m_core_solver.m_r_solver.column_is_feasible(j)); + auto delta = m_core_solver.m_r_x[j] - old_x; + + unsigned i; + m_column_j->reset(); + mpq a; + while(m_column_j->next(a, i)) { + unsigned bj = m_core_solver.m_r_basis[i]; + m_core_solver.m_r_x[bj] -= a * delta; + lean_assert(m_core_solver.m_r_solver.column_is_feasible(bj)); + } + lean_assert(m_core_solver.m_r_solver.A_mult_x_is_off() == false); +} + +numeric_pair random_updater::get_random_from_interval(interval & r) { + unsigned rand = my_random(); + if ((!r.low_bound_is_set) && (!r.upper_bound_is_set)) + return numeric_pair(rand % range, 0); + if (r.low_bound_is_set && (!r.upper_bound_is_set)) + return r.low_bound + numeric_pair(rand % range, 0); + if ((!r.low_bound_is_set) && r.upper_bound_is_set) + return r.upper_bound - numeric_pair(rand % range, 0); + lean_assert(r.low_bound_is_set && r.upper_bound_is_set); + return r.low_bound + (rand % range) * (r.upper_bound - r.low_bound)/ range; +} + +void random_updater::random_shift_var(unsigned j) { + m_column_j = m_core_solver.get_column_iterator(j); + if (m_column_j->size() >= 50) { + delete m_column_j; + return; + } + interval interv = find_shift_interval(j); + if (interv.is_empty()) { + delete m_column_j; + return; + } + + shift_var(j, interv); + delete m_column_j; +} + +void random_updater::update() { + for (auto j : m_var_set) { + if (m_var_set.size() <= m_values.size()) { + break; // we are done + } + random_shift_var(j); + } +} + +void random_updater::add_value(numeric_pair& v) { + auto it = m_values.find(v); + if (it == m_values.end()) { + m_values[v] = 1; + } else { + it->second++; + } +} + +void random_updater::remove_value(numeric_pair& v) { + auto it = m_values.find(v); + lean_assert(it != m_values.end()); + it->second--; + if (it->second == 0) + m_values.erase(it); +} + +void random_updater::add_column_to_sets(unsigned j) { + if (m_core_solver.m_r_heading[j] < 0) { + m_var_set.insert(j); + add_value(m_core_solver.m_r_x[j]); + } else { + unsigned row = m_core_solver.m_r_heading[j]; + for (auto row_c : m_core_solver.m_r_A.m_rows[row]) { + unsigned cj = row_c.m_j; + if (m_core_solver.m_r_heading[cj] < 0) { + m_var_set.insert(cj); + add_value(m_core_solver.m_r_x[cj]); + } + } + } +} +} diff --git a/src/util/lp/random_updater_instances.cpp b/src/util/lp/random_updater_instances.cpp new file mode 100644 index 000000000..5b4c89bd5 --- /dev/null +++ b/src/util/lp/random_updater_instances.cpp @@ -0,0 +1,5 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include "util/lp/random_updater.hpp" diff --git a/src/util/lp/row_eta_matrix.h b/src/util/lp/row_eta_matrix.h new file mode 100644 index 000000000..90acb89f3 --- /dev/null +++ b/src/util/lp/row_eta_matrix.h @@ -0,0 +1,74 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ + +#pragma once +#include "util/vector.h" +#include "util/debug.h" +#include +#include "util/lp/sparse_vector.h" +#include "util/lp/indexed_vector.h" +#include "util/lp/permutation_matrix.h" +namespace lean { + // This is the sum of a unit matrix and a lower triangular matrix + // with non-zero elements only in one row +template +class row_eta_matrix + : public tail_matrix { +#ifdef LEAN_DEBUG + unsigned m_dimension; +#endif + unsigned m_row_start; + unsigned m_row; + sparse_vector m_row_vector; +public: +#ifdef LEAN_DEBUG + row_eta_matrix(unsigned row_start, unsigned row, unsigned dim): +#else + row_eta_matrix(unsigned row_start, unsigned row): +#endif + +#ifdef LEAN_DEBUG + m_dimension(dim), +#endif + m_row_start(row_start), m_row(row) { + } + + bool is_dense() const { return false; } + + void print(std::ostream & out) { + print_matrix(*this, out); + } + + const T & get_diagonal_element() const { + return m_row_vector.m_data[m_row]; + } + + void apply_from_left(vector & w, lp_settings &); + + void apply_from_left_local_to_T(indexed_vector & w, lp_settings & settings); + void apply_from_left_local_to_X(indexed_vector & w, lp_settings & settings); + + void apply_from_left_to_T(indexed_vector & w, lp_settings & settings) { + apply_from_left_local_to_T(w, settings); + } + + void push_back(unsigned row_index, T val ) { + lean_assert(row_index != m_row); + m_row_vector.push_back(row_index, val); + } + + void apply_from_right(vector & w); + void apply_from_right(indexed_vector & w); + + void conjugate_by_permutation(permutation_matrix & p); +#ifdef LEAN_DEBUG + T get_elem(unsigned row, unsigned col) const; + unsigned row_count() const { return m_dimension; } + unsigned column_count() const { return m_dimension; } + void set_number_of_rows(unsigned m) { m_dimension = m; } + void set_number_of_columns(unsigned n) { m_dimension = n; } +#endif +}; // end of row_eta_matrix +} diff --git a/src/util/lp/row_eta_matrix.hpp b/src/util/lp/row_eta_matrix.hpp new file mode 100644 index 000000000..5758abeb8 --- /dev/null +++ b/src/util/lp/row_eta_matrix.hpp @@ -0,0 +1,171 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include "util/vector.h" +#include "util/lp/row_eta_matrix.h" +namespace lean { +template +void row_eta_matrix::apply_from_left(vector & w, lp_settings &) { + // #ifdef LEAN_DEBUG + // dense_matrix deb(*this); + // auto clone_w = clone_vector(w, m_dimension); + // deb.apply_from_left(clone_w, settings); + // #endif + + auto & w_at_row = w[m_row]; + for (auto & it : m_row_vector.m_data) { + w_at_row += w[it.first] * it.second; + } + // w[m_row] = w_at_row; + // #ifdef LEAN_DEBUG + // lean_assert(vectors_are_equal(clone_w, w, m_dimension)); + // delete [] clone_w; + // #endif +} + +template +void row_eta_matrix::apply_from_left_local_to_T(indexed_vector & w, lp_settings & settings) { + auto w_at_row = w[m_row]; + bool was_zero_at_m_row = is_zero(w_at_row); + + for (auto & it : m_row_vector.m_data) { + w_at_row += w[it.first] * it.second; + } + + if (!settings.abs_val_is_smaller_than_drop_tolerance(w_at_row)){ + if (was_zero_at_m_row) { + w.m_index.push_back(m_row); + } + w[m_row] = w_at_row; + } else if (!was_zero_at_m_row){ + w[m_row] = zero_of_type(); + auto it = std::find(w.m_index.begin(), w.m_index.end(), m_row); + w.m_index.erase(it); + } + // TBD: lean_assert(check_vector_for_small_values(w, settings)); +} + +template +void row_eta_matrix::apply_from_left_local_to_X(indexed_vector & w, lp_settings & settings) { + auto w_at_row = w[m_row]; + bool was_zero_at_m_row = is_zero(w_at_row); + + for (auto & it : m_row_vector.m_data) { + w_at_row += w[it.first] * it.second; + } + + if (!settings.abs_val_is_smaller_than_drop_tolerance(w_at_row)){ + if (was_zero_at_m_row) { + w.m_index.push_back(m_row); + } + w[m_row] = w_at_row; + } else if (!was_zero_at_m_row){ + w[m_row] = zero_of_type(); + auto it = std::find(w.m_index.begin(), w.m_index.end(), m_row); + w.m_index.erase(it); + } + // TBD: does not compile lean_assert(check_vector_for_small_values(w, settings)); +} + +template +void row_eta_matrix::apply_from_right(vector & w) { + const T & w_row = w[m_row]; + if (numeric_traits::is_zero(w_row)) return; +#ifdef LEAN_DEBUG + // dense_matrix deb(*this); + // auto clone_w = clone_vector(w, m_dimension); + // deb.apply_from_right(clone_w); +#endif + for (auto & it : m_row_vector.m_data) { + w[it.first] += w_row * it.second; + } +#ifdef LEAN_DEBUG + // lean_assert(vectors_are_equal(clone_w, w, m_dimension)); + // delete clone_w; +#endif +} + +template +void row_eta_matrix::apply_from_right(indexed_vector & w) { + lean_assert(w.is_OK()); + const T & w_row = w[m_row]; + if (numeric_traits::is_zero(w_row)) return; +#ifdef LEAN_DEBUG + // vector wcopy(w.m_data); + // apply_from_right(wcopy); +#endif + if (numeric_traits::precise()) { + for (auto & it : m_row_vector.m_data) { + unsigned j = it.first; + bool was_zero = numeric_traits::is_zero(w[j]); + const T & v = w[j] += w_row * it.second; + + if (was_zero) { + if (!numeric_traits::is_zero(v)) + w.m_index.push_back(j); + } else { + if (numeric_traits::is_zero(v)) + w.erase_from_index(j); + } + } + } else { // the non precise version + const double drop_eps = 1e-14; + for (auto & it : m_row_vector.m_data) { + unsigned j = it.first; + bool was_zero = numeric_traits::is_zero(w[j]); + T & v = w[j] += w_row * it.second; + + if (was_zero) { + if (!lp_settings::is_eps_small_general(v, drop_eps)) + w.m_index.push_back(j); + else + v = zero_of_type(); + } else { + if (lp_settings::is_eps_small_general(v, drop_eps)) { + w.erase_from_index(j); + v = zero_of_type(); + } + } + } + } +#ifdef LEAN_DEBUG + // lean_assert(vectors_are_equal(wcopy, w.m_data)); + +#endif +} + +template +void row_eta_matrix::conjugate_by_permutation(permutation_matrix & p) { + // this = p * this * p(-1) +#ifdef LEAN_DEBUG + // auto rev = p.get_reverse(); + // auto deb = ((*this) * rev); + // deb = p * deb; +#endif + m_row = p.apply_reverse(m_row); + // copy aside the column indices + vector columns; + for (auto & it : m_row_vector.m_data) + columns.push_back(it.first); + for (unsigned i = static_cast(columns.size()); i-- > 0;) + m_row_vector.m_data[i].first = p.get_rev(columns[i]); +#ifdef LEAN_DEBUG + // lean_assert(deb == *this); +#endif +} +#ifdef LEAN_DEBUG +template +T row_eta_matrix::get_elem(unsigned row, unsigned col) const { + if (row == m_row){ + if (col == row) { + return numeric_traits::one(); + } + return m_row_vector[col]; + } + + return col == row ? numeric_traits::one() : numeric_traits::zero(); +} +#endif +} + diff --git a/src/util/lp/row_eta_matrix_instances.cpp b/src/util/lp/row_eta_matrix_instances.cpp new file mode 100644 index 000000000..c32023164 --- /dev/null +++ b/src/util/lp/row_eta_matrix_instances.cpp @@ -0,0 +1,33 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include "util/vector.h" +#include +#include "util/lp/row_eta_matrix.hpp" +#include "util/lp/lu.h" +namespace lean { +template void row_eta_matrix::conjugate_by_permutation(permutation_matrix&); +template void row_eta_matrix >::conjugate_by_permutation(permutation_matrix >&); +template void row_eta_matrix::conjugate_by_permutation(permutation_matrix&); +#ifdef LEAN_DEBUG +template mpq row_eta_matrix::get_elem(unsigned int, unsigned int) const; +template mpq row_eta_matrix >::get_elem(unsigned int, unsigned int) const; +template double row_eta_matrix::get_elem(unsigned int, unsigned int) const; +#endif +template void row_eta_matrix::apply_from_left(vector&, lp_settings&); +template void row_eta_matrix::apply_from_right(vector&); +template void row_eta_matrix::apply_from_right(indexed_vector&); +template void row_eta_matrix >::apply_from_left(vector>&, lp_settings&); +template void row_eta_matrix >::apply_from_right(vector&); +template void row_eta_matrix >::apply_from_right(indexed_vector&); +template void row_eta_matrix::apply_from_left(vector&, lp_settings&); +template void row_eta_matrix::apply_from_right(vector&); +template void row_eta_matrix::apply_from_right(indexed_vector&); +template void row_eta_matrix::apply_from_left_to_T(indexed_vector&, lp_settings&); +template void row_eta_matrix::apply_from_left_local_to_T(indexed_vector&, lp_settings&); +template void row_eta_matrix >::apply_from_left_to_T(indexed_vector&, lp_settings&); +template void row_eta_matrix >::apply_from_left_local_to_T(indexed_vector&, lp_settings&); +template void row_eta_matrix::apply_from_left_to_T(indexed_vector&, lp_settings&); +template void row_eta_matrix::apply_from_left_local_to_T(indexed_vector&, lp_settings&); +} diff --git a/src/util/lp/scaler.h b/src/util/lp/scaler.h new file mode 100644 index 000000000..33c5a6cc4 --- /dev/null +++ b/src/util/lp/scaler.h @@ -0,0 +1,79 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ + +#pragma once +#include "util/vector.h" +#include +#include +#include /* printf, fopen */ +#include /* exit, EXIT_FAILURE */ +#include "util/lp/lp_utils.h" +#include "util/lp/static_matrix.h" +namespace lean { +// for scaling an LP +template +class scaler { + vector & m_b; // right side + static_matrix &m_A; // the constraint matrix + const T & m_scaling_minimum; + const T & m_scaling_maximum; + vector& m_column_scale; + lp_settings & m_settings; +public: + // constructor + scaler(vector & b, static_matrix &A, const T & scaling_minimum, const T & scaling_maximum, vector & column_scale, + lp_settings & settings): + m_b(b), + m_A(A), + m_scaling_minimum(scaling_minimum), + m_scaling_maximum(scaling_maximum), + m_column_scale(column_scale), + m_settings(settings) { + lean_assert(m_column_scale.size() == 0); + m_column_scale.resize(m_A.column_count(), numeric_traits::one()); + } + + T right_side_balance(); + + T get_balance() { return m_A.get_balance(); } + + T A_min() const; + + T A_max() const; + + T get_A_ratio() const; + + T get_max_ratio_on_rows() const; + + T get_max_ratio_on_columns() const; + + void scale_rows_with_geometric_mean(); + + void scale_columns_with_geometric_mean(); + + void scale_once_for_ratio(); + + bool scale_with_ratio(); + + void bring_row_maximums_to_one(); + + void bring_column_maximums_to_one(); + + void bring_rows_and_columns_maximums_to_one(); + + bool scale_with_log_balance(); + // Returns true if and only if the scaling was successful. + // It is the caller responsibility to restore the matrix + bool scale(); + + void scale_rows(); + + void scale_row(unsigned i); + + void scale_column(unsigned i); + + void scale_columns(); +}; +} diff --git a/src/util/lp/scaler.hpp b/src/util/lp/scaler.hpp new file mode 100644 index 000000000..69427eea0 --- /dev/null +++ b/src/util/lp/scaler.hpp @@ -0,0 +1,254 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include +#include "util/lp/scaler.h" +#include "util/lp/numeric_pair.h" +namespace lean { +// for scaling an LP +template T scaler::right_side_balance() { + T ret = zero_of_type(); + unsigned i = m_A.row_count(); + while (i--) { + T rs = abs(convert_struct::convert(m_b[i])); + if (!is_zero(rs)) { + numeric_traits::log(rs); + ret += rs * rs; + } + } + return ret; +} + +template T scaler::A_min() const { + T min = zero_of_type(); + for (unsigned i = 0; i < m_A.row_count(); i++) { + T t = m_A.get_min_abs_in_row(i); + min = i == 0 ? t : std::min(t, min); + } + return min; +} + +template T scaler::A_max() const { + T max = zero_of_type(); + for (unsigned i = 0; i < m_A.row_count(); i++) { + T t = m_A.get_max_abs_in_row(i); + max = i == 0? t : std::max(t, max); + } + return max; +} + +template T scaler::get_A_ratio() const { + T min = A_min(); + T max = A_max(); + lean_assert(!m_settings.abs_val_is_smaller_than_zero_tolerance(min)); + T ratio = max / min; + return ratio; +} + +template T scaler::get_max_ratio_on_rows() const { + T ret = T(1); + unsigned i = m_A.row_count(); + while (i--) { + T den = m_A.get_min_abs_in_row(i); + lean_assert(!m_settings.abs_val_is_smaller_than_zero_tolerance(den)); + T t = m_A.get_max_abs_in_row(i)/ den; + if (t > ret) + ret = t; + } + return ret; +} + +template T scaler::get_max_ratio_on_columns() const { + T ret = T(1); + unsigned i = m_A.column_count(); + while (i--) { + T den = m_A.get_min_abs_in_column(i); + if (m_settings.abs_val_is_smaller_than_zero_tolerance(den)) + continue; // got a zero column + T t = m_A.get_max_abs_in_column(i)/den; + if (t > ret) + ret = t; + } + return ret; +} + +template void scaler::scale_rows_with_geometric_mean() { + unsigned i = m_A.row_count(); + while (i--) { + T max = m_A.get_max_abs_in_row(i); + T min = m_A.get_min_abs_in_row(i); + lean_assert(max > zero_of_type() && min > zero_of_type()); + if (is_zero(max) || is_zero(min)) + continue; + T gm = T(sqrt(numeric_traits::get_double(max*min))); + if (m_settings.is_eps_small_general(gm, 0.01)) { + continue; + } + m_A.multiply_row(i, one_of_type() / gm); + m_b[i] /= gm; + } +} + +template void scaler::scale_columns_with_geometric_mean() { + unsigned i = m_A.column_count(); + while (i--) { + T max = m_A.get_max_abs_in_column(i); + T min = m_A.get_min_abs_in_column(i); + T den = T(sqrt(numeric_traits::get_double(max*min))); + if (m_settings.is_eps_small_general(den, 0.01)) + continue; // got a zero column + T gm = T(1)/ den; + T cs = m_column_scale[i] * gm; + if (m_settings.is_eps_small_general(cs, 0.1)) + continue; + m_A.multiply_column(i, gm); + m_column_scale[i] = cs; + } +} + +template void scaler::scale_once_for_ratio() { + T max_ratio_on_rows = get_max_ratio_on_rows(); + T max_ratio_on_columns = get_max_ratio_on_columns(); + bool scale_rows_first = max_ratio_on_rows > max_ratio_on_columns; + // if max_ratio_on_columns is the largerst then the rows are in worser shape then columns + if (scale_rows_first) { + scale_rows_with_geometric_mean(); + scale_columns_with_geometric_mean(); + } else { + scale_columns_with_geometric_mean(); + scale_rows_with_geometric_mean(); + } +} + +template bool scaler::scale_with_ratio() { + T ratio = get_A_ratio(); + // The ratio is greater than or equal to one. We would like to diminish it and bring it as close to 1 as possible + unsigned reps = m_settings.reps_in_scaler; + do { + scale_once_for_ratio(); + T new_r = get_A_ratio(); + if (new_r >= T(0.9) * ratio) + break; + } while (reps--); + + bring_rows_and_columns_maximums_to_one(); + return true; +} + +template void scaler::bring_row_maximums_to_one() { + unsigned i = m_A.row_count(); + while (i--) { + T t = m_A.get_max_abs_in_row(i); + if (m_settings.abs_val_is_smaller_than_zero_tolerance(t)) continue; + m_A.multiply_row(i, one_of_type() / t); + m_b[i] /= t; + } +} + +template void scaler::bring_column_maximums_to_one() { + unsigned i = m_A.column_count(); + while (i--) { + T max = m_A.get_max_abs_in_column(i); + if (m_settings.abs_val_is_smaller_than_zero_tolerance(max)) continue; + T t = T(1) / max; + m_A.multiply_column(i, t); + m_column_scale[i] *= t; + } +} + +template void scaler::bring_rows_and_columns_maximums_to_one() { + if (get_max_ratio_on_rows() > get_max_ratio_on_columns()) { + bring_row_maximums_to_one(); + bring_column_maximums_to_one(); + } else { + bring_column_maximums_to_one(); + bring_row_maximums_to_one(); + } +} + +template bool scaler::scale_with_log_balance() { + T balance = get_balance(); + T balance_before_scaling = balance; + // todo : analyze the scale order : rows-columns, or columns-rows. Iterate if needed + for (int i = 0; i < 10; i++) { + scale_rows(); + scale_columns(); + T nb = get_balance(); + if (nb < T(0.9) * balance) { + balance = nb; + } else { + balance = nb; + break; + } + } + return balance <= balance_before_scaling; +} +// Returns true if and only if the scaling was successful. +// It is the caller responsibility to restore the matrix +template bool scaler::scale() { + if (numeric_traits::precise()) return true; + if (m_settings.scale_with_ratio) + return scale_with_ratio(); + return scale_with_log_balance(); +} + +template void scaler::scale_rows() { + for (unsigned i = 0; i < m_A.row_count(); i++) + scale_row(i); +} + +template void scaler::scale_row(unsigned i) { + T row_max = std::max(m_A.get_max_abs_in_row(i), abs(convert_struct::convert(m_b[i]))); + T alpha = numeric_traits::one(); + if (numeric_traits::is_zero(row_max)) { + return; + } + if (numeric_traits::get_double(row_max) < m_scaling_minimum) { + do { + alpha *= 2; + row_max *= 2; + } while (numeric_traits::get_double(row_max) < m_scaling_minimum); + m_A.multiply_row(i, alpha); + m_b[i] *= alpha; + } else if (numeric_traits::get_double(row_max) > m_scaling_maximum) { + do { + alpha /= 2; + row_max /= 2; + } while (numeric_traits::get_double(row_max) > m_scaling_maximum); + m_A.multiply_row(i, alpha); + m_b[i] *= alpha; + } +} + +template void scaler::scale_column(unsigned i) { + T column_max = m_A.get_max_abs_in_column(i); + T alpha = numeric_traits::one(); + + if (numeric_traits::is_zero(column_max)){ + return; // the column has zeros only + } + + if (numeric_traits::get_double(column_max) < m_scaling_minimum) { + do { + alpha *= 2; + column_max *= 2; + } while (numeric_traits::get_double(column_max) < m_scaling_minimum); + } else if (numeric_traits::get_double(column_max) > m_scaling_maximum) { + do { + alpha /= 2; + column_max /= 2; + } while (numeric_traits::get_double(column_max) > m_scaling_maximum); + } else { + return; + } + m_A.multiply_column(i, alpha); + m_column_scale[i] = alpha; +} + +template void scaler::scale_columns() { + for (unsigned i = 0; i < m_A.column_count(); i++) { + scale_column(i); + } +} +} diff --git a/src/util/lp/scaler_instances.cpp b/src/util/lp/scaler_instances.cpp new file mode 100644 index 000000000..f97e8098f --- /dev/null +++ b/src/util/lp/scaler_instances.cpp @@ -0,0 +1,7 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include "util/lp/scaler.hpp" +template bool lean::scaler::scale(); +template bool lean::scaler::scale(); diff --git a/src/util/lp/signature_bound_evidence.h b/src/util/lp/signature_bound_evidence.h new file mode 100644 index 000000000..a22c188b4 --- /dev/null +++ b/src/util/lp/signature_bound_evidence.h @@ -0,0 +1,23 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#pragma once +#include "util/lp/lp_settings.h" +#include "util/lp/lar_constraints.h" +namespace lean { +struct bound_signature { + unsigned m_i; + bool m_at_low; + bound_signature(unsigned i, bool at_low) :m_i(i), m_at_low(m_at_low) {} + bool at_upper_bound() const { return !m_at_low_bound;} + bool at_low_bound() const { return m_at_low;} +}; +template +struct signature_bound_evidence { + vector m_evidence; + unsigned m_j; // found new bound + bool m_low_bound; + X m_bound; +}; +} diff --git a/src/util/lp/sparse_matrix.h b/src/util/lp/sparse_matrix.h new file mode 100644 index 000000000..96f0cf2ae --- /dev/null +++ b/src/util/lp/sparse_matrix.h @@ -0,0 +1,417 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ + +#pragma once +#include "util/vector.h" +#include "util/lp/permutation_matrix.h" +#include +#include "util/lp/static_matrix.h" +#include +#include +#include +#include +#include +#include "util/lp/indexed_value.h" +#include "util/lp/indexed_vector.h" +#include +#include "util/lp/lp_settings.h" +#include "util/lp/eta_matrix.h" +#include "util/lp/binary_heap_upair_queue.h" +#include "util/lp/numeric_pair.h" +#include "util/lp/int_set.h" +namespace lean { +// it is a square matrix +template +class sparse_matrix +#ifdef LEAN_DEBUG + : public matrix +#endif +{ + struct col_header { + unsigned m_shortened_markovitz = 0; + vector> m_values; // the actual column values + + col_header() {} + + void shorten_markovich_by_one() { + m_shortened_markovitz++; + } + + void zero_shortened_markovitz() { + m_shortened_markovitz = 0; + } + }; + + unsigned m_n_of_active_elems = 0; + binary_heap_upair_queue m_pivot_queue; +public: + vector>> m_rows; + vector m_columns; + permutation_matrix m_row_permutation; + permutation_matrix m_column_permutation; + // m_work_pivot_vector[j] = offset of elementh of j-th column in the row we are pivoting to + // if the column is not present then m_work_pivot_vector[j] is -1 + vector m_work_pivot_vector; + vector m_processed; + unsigned get_n_of_active_elems() const { return m_n_of_active_elems; } + +#ifdef LEAN_DEBUG + // dense_matrix m_dense; +#endif + /* + the rule is: row i is mapped to m_row_permutation[i] and + column j is mapped to m_column_permutation.apply_reverse(j) + */ + + unsigned adjust_row(unsigned row) const{ + return m_row_permutation[row]; + } + + unsigned adjust_column(unsigned col) const{ + return m_column_permutation.apply_reverse(col); + } + + unsigned adjust_row_inverse(unsigned row) const{ + return m_row_permutation.apply_reverse(row); + } + + unsigned adjust_column_inverse(unsigned col) const{ + return m_column_permutation[col]; + } + + void copy_column_from_static_matrix(unsigned col, static_matrix const &A, unsigned col_index_in_the_new_matrix); + void copy_B(static_matrix const &A, vector & basis); + +public: + // constructor that copies columns of the basis from A + sparse_matrix(static_matrix const &A, vector & basis); + + class ref_matrix_element { + sparse_matrix & m_matrix; + unsigned m_row; + unsigned m_col; + public: + ref_matrix_element(sparse_matrix & m, unsigned row, unsigned col):m_matrix(m), m_row(row), m_col(col) {} + ref_matrix_element & operator=(T const & v) { m_matrix.set( m_row, m_col, v); return *this; } + ref_matrix_element & operator=(ref_matrix_element const & v) { m_matrix.set(m_row, m_col, v.m_matrix.get(v.m_row, v.m_col)); return *this; } + operator T () const { return m_matrix.get(m_row, m_col); } + }; + + class ref_row { + sparse_matrix & m_matrix; + unsigned m_row; + public: + ref_row(sparse_matrix & m, unsigned row) : m_matrix(m), m_row(row) {} + ref_matrix_element operator[](unsigned col) const { return ref_matrix_element(m_matrix, m_row, col); } + }; + + void set_with_no_adjusting_for_row(unsigned row, unsigned col, T val); + void set_with_no_adjusting_for_col(unsigned row, unsigned col, T val); + + void set_with_no_adjusting(unsigned row, unsigned col, T val); + + void set(unsigned row, unsigned col, T val); + + T const & get_not_adjusted(unsigned row, unsigned col) const; + T const & get(unsigned row, unsigned col) const; + + ref_row operator[](unsigned row) { return ref_row(*this, row); } + + ref_matrix_element operator()(unsigned row, unsigned col) { return ref_matrix_element(*this, row, col); } + + T operator() (unsigned row, unsigned col) const { return get(row, col); } + + vector> & get_row_values(unsigned row) { + return m_rows[row]; + } + + vector> const & get_row_values(unsigned row) const { + return m_rows[row]; + } + + vector> & get_column_values(unsigned col) { + return m_columns[col].m_values; + } + + vector> const & get_column_values(unsigned col) const { + return m_columns[col].m_values; + } + + // constructor creating a zero matrix of dim*dim + sparse_matrix(unsigned dim); + + + + unsigned dimension() const {return static_cast(m_row_permutation.size());} + +#ifdef LEAN_DEBUG + unsigned row_count() const {return dimension();} + unsigned column_count() const {return dimension();} +#endif + + void init_row_headers(); + + void init_column_headers(); + + unsigned lowest_row_in_column(unsigned j); + + indexed_value & column_iv_other(indexed_value & iv) { + return m_rows[iv.m_index][iv.m_other]; + } + + indexed_value & row_iv_other(indexed_value & iv) { + return m_columns[iv.m_index].m_values[iv.m_other]; + } + + void remove_element(vector> & row_vals, unsigned row_offset, vector> & column_vals, unsigned column_offset); + + void remove_element(vector> & row_chunk, indexed_value & row_el_iv); + + void put_max_index_to_0(vector> & row_vals, unsigned max_index); + + void set_max_in_row(unsigned row) { + set_max_in_row(m_rows[row]); + } + + + void set_max_in_row(vector> & row_vals); + + bool pivot_with_eta(unsigned i, eta_matrix *eta_matrix, lp_settings & settings); + + void scan_row_to_work_vector_and_remove_pivot_column(unsigned row, unsigned pivot_column); + + // This method pivots row i to row i0 by muliplying row i by + // alpha and adding it to row i0. + // After pivoting the row i0 has a max abs value set correctly at the beginning of m_start, + // Returns false if the resulting row is all zeroes, and true otherwise + bool pivot_row_to_row(unsigned i, const T& alpha, unsigned i0, lp_settings & settings ); + + // set the max val as well + // returns false if the resulting row is all zeroes, and true otherwise + bool set_row_from_work_vector_and_clean_work_vector_not_adjusted(unsigned i0, indexed_vector & work_vec, + lp_settings & settings); + + + // set the max val as well + // returns false if the resulting row is all zeroes, and true otherwise + bool set_row_from_work_vector_and_clean_work_vector(unsigned i0); + + void remove_zero_elements_and_set_data_on_existing_elements(unsigned row); + + // work_vec here has not adjusted column indices + void remove_zero_elements_and_set_data_on_existing_elements_not_adjusted(unsigned row, indexed_vector & work_vec, lp_settings & settings); + + void multiply_from_right(permutation_matrix& p) { + // m_dense = m_dense * p; + m_column_permutation.multiply_by_permutation_from_right(p); + // lean_assert(*this == m_dense); + } + + void multiply_from_left(permutation_matrix& p) { + // m_dense = p * m_dense; + m_row_permutation.multiply_by_permutation_from_left(p); + // lean_assert(*this == m_dense); + } + + void multiply_from_left_with_reverse(permutation_matrix& p) { + // m_dense = p * m_dense; + m_row_permutation.multiply_by_permutation_reverse_from_left(p); + // lean_assert(*this == m_dense); + } + + // adding delta columns at the end of the matrix + void add_columns_at_the_end(unsigned delta); + + void delete_column(int i); + + void swap_columns(unsigned a, unsigned b) { + // cout << "swaapoiiin" << std::endl; + // dense_matrix d(*this); + m_column_permutation.transpose_from_left(a, b); + // d.swap_columns(a, b); + // lean_assert(*this == d); + } + + void swap_rows(unsigned a, unsigned b) { + m_row_permutation.transpose_from_right(a, b); + // m_dense.swap_rows(a, b); + // lean_assert(*this == m_dense); + } + + void divide_row_by_constant(unsigned i, const T & t, lp_settings & settings); + + bool close(T a, T b) { + return // (numeric_traits::precise() && numeric_traits::is_zero(a - b)) + // || + fabs(numeric_traits::get_double(a - b)) < 0.0000001; + } + + // solving x * this = y, and putting the answer into y + // the matrix here has to be upper triangular + void solve_y_U(vector & y) const; + + // solving x * this = y, and putting the answer into y + // the matrix here has to be upper triangular + void solve_y_U_indexed(indexed_vector & y, const lp_settings &); + + // fills the indices for such that y[i] can be not a zero + // sort them so the smaller indices come first + void fill_reachable_indices(std::set & rset, T *y); + + template + void find_error_in_solution_U_y(vector& y_orig, vector & y); + + template + void find_error_in_solution_U_y_indexed(indexed_vector& y_orig, indexed_vector & y, const vector& sorted_active_rows); + + template + void add_delta_to_solution(const vector& del, vector & y); + + template + void add_delta_to_solution(const indexed_vector& del, indexed_vector & y); + + template + void double_solve_U_y(indexed_vector& y, const lp_settings & settings); + + template + void double_solve_U_y(vector& y); + // solving this * x = y, and putting the answer into y + // the matrix here has to be upper triangular + template + void solve_U_y(vector & y); + // solving this * x = y, and putting the answer into y + // the matrix here has to be upper triangular + template + void solve_U_y_indexed_only(indexed_vector & y, const lp_settings&, vector & sorted_active_rows ); + +#ifdef LEAN_DEBUG + T get_elem(unsigned i, unsigned j) const { return get(i, j); } + unsigned get_number_of_rows() const { return dimension(); } + unsigned get_number_of_columns() const { return dimension(); } + virtual void set_number_of_rows(unsigned /*m*/) { } + virtual void set_number_of_columns(unsigned /*n*/) { } +#endif + template + L dot_product_with_row (unsigned row, const vector & y) const; + + template + L dot_product_with_row (unsigned row, const indexed_vector & y) const; + + unsigned get_number_of_nonzeroes() const; + + bool get_non_zero_column_in_row(unsigned i, unsigned *j) const; + + void remove_element_that_is_not_in_w(vector> & column_vals, indexed_value & col_el_iv); + + + // w contains the new column + // the old column inside of the matrix has not been changed yet + void remove_elements_that_are_not_in_w_and_update_common_elements(unsigned column_to_replace, indexed_vector & w); + + void add_new_element(unsigned row, unsigned col, const T& val); + + // w contains the "rest" of the new column; all common elements of w and the old column has been zeroed + // the old column inside of the matrix has not been changed yet + void add_new_elements_of_w_and_clear_w(unsigned column_to_replace, indexed_vector & w, lp_settings & settings); + + void replace_column(unsigned column_to_replace, indexed_vector & w, lp_settings &settings); + + unsigned pivot_score(unsigned i, unsigned j); + + void enqueue_domain_into_pivot_queue(); + + void set_max_in_rows(); + + void zero_shortened_markovitz_numbers(); + + void prepare_for_factorization(); + + void recover_pivot_queue(vector & rejected_pivots); + + int elem_is_too_small(unsigned i, unsigned j, int c_partial_pivoting); + + bool remove_row_from_active_pivots_and_shorten_columns(unsigned row); + + void remove_pivot_column(unsigned row); + + void update_active_pivots(unsigned row); + + bool shorten_active_matrix(unsigned row, eta_matrix *eta_matrix); + + unsigned pivot_score_without_shortened_counters(unsigned i, unsigned j, unsigned k); +#ifdef LEAN_DEBUG + bool can_improve_score_for_row(unsigned row, unsigned score, T const & c_partial_pivoting, unsigned k); + bool really_best_pivot(unsigned i, unsigned j, T const & c_partial_pivoting, unsigned k); + void print_active_matrix(unsigned k, std::ostream & out); +#endif + bool pivot_queue_is_correct_for_row(unsigned i, unsigned k); + + bool pivot_queue_is_correct_after_pivoting(int k); + + bool get_pivot_for_column(unsigned &i, unsigned &j, int c_partial_pivoting, unsigned k); + + bool elem_is_too_small(vector> & row_chunk, indexed_value & iv, int c_partial_pivoting); + + unsigned number_of_non_zeroes_in_row(unsigned row) const { + return static_cast(m_rows[row].size()); + } + + unsigned number_of_non_zeroes_in_column(unsigned col) const { + return m_columns[col].m_values.size(); + } + + bool shorten_columns_by_pivot_row(unsigned i, unsigned pivot_column); + + bool col_is_active(unsigned j, unsigned pivot) { + return adjust_column_inverse(j) > pivot; + } + + bool row_is_active(unsigned i, unsigned pivot) { + return adjust_row_inverse(i) > pivot; + } + + bool fill_eta_matrix(unsigned j, eta_matrix ** eta); +#ifdef LEAN_DEBUG + bool is_upper_triangular_and_maximums_are_set_correctly_in_rows(lp_settings & settings) const; + + bool is_upper_triangular_until(unsigned k) const; + void check_column_vs_rows(unsigned col); + + void check_row_vs_columns(unsigned row); + + void check_rows_vs_columns(); + + void check_columns_vs_rows(); + + void check_matrix(); +#endif + void create_graph_G(const vector & active_rows, vector & sorted_active_rows); + void process_column_recursively(unsigned i, vector & sorted_rows); + void extend_and_sort_active_rows(const vector & active_rows, vector & sorted_active_rows); + void process_index_recursively_for_y_U(unsigned j, vector & sorted_rows); + void resize(unsigned new_dim) { + unsigned old_dim = dimension(); + lean_assert(new_dim >= old_dim); + for (unsigned j = old_dim; j < new_dim; j++) { + m_rows.push_back(vector>()); + m_columns.push_back(col_header()); + } + m_pivot_queue.resize(new_dim); + m_row_permutation.resize(new_dim); + m_column_permutation.resize(new_dim); + m_work_pivot_vector.resize(new_dim); + m_processed.resize(new_dim); + for (unsigned j = old_dim; j < new_dim; j++) { + add_new_element(j, j, numeric_traits::one()); + } + } +#ifdef LEAN_DEBUG +vector get_full_row(unsigned i) const; +#endif + unsigned pivot_queue_size() const { return m_pivot_queue.size(); } +}; +}; + + diff --git a/src/util/lp/sparse_matrix.hpp b/src/util/lp/sparse_matrix.hpp new file mode 100644 index 000000000..0d2a90ea0 --- /dev/null +++ b/src/util/lp/sparse_matrix.hpp @@ -0,0 +1,1255 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ + +#include "util/vector.h" +#include "util/lp/sparse_matrix.h" +#include +#include +namespace lean { +template +void sparse_matrix::copy_column_from_static_matrix(unsigned col, static_matrix const &A, unsigned col_index_in_the_new_matrix) { + vector const & A_col_vector = A.m_columns[col]; + unsigned size = static_cast(A_col_vector.size()); + vector> & new_column_vector = m_columns[col_index_in_the_new_matrix].m_values; + for (unsigned l = 0; l < size; l++) { + column_cell const & col_cell = A_col_vector[l]; + unsigned col_offset = static_cast(new_column_vector.size()); + vector> & row_vector = m_rows[col_cell.m_i]; + unsigned row_offset = static_cast(row_vector.size()); + const T & val = A.get_val(col_cell); + new_column_vector.push_back(indexed_value(val, col_cell.m_i, row_offset)); + row_vector.push_back(indexed_value(val, col_index_in_the_new_matrix, col_offset)); + m_n_of_active_elems++; + } +} + +template +void sparse_matrix::copy_B(static_matrix const &A, vector & basis) { + unsigned m = A.row_count(); // this should be the size of basis + for (unsigned j = m; j-- > 0;) { + copy_column_from_static_matrix(basis[j], A, j); + } +} + +// constructor that copies columns of the basis from A +template +sparse_matrix::sparse_matrix(static_matrix const &A, vector & basis) : + m_pivot_queue(A.row_count()), + m_row_permutation(A.row_count()), + m_column_permutation(A.row_count()), + m_work_pivot_vector(A.row_count(), -1), + m_processed(A.row_count()) { + init_row_headers(); + init_column_headers(); + copy_B(A, basis); +} + +template +void sparse_matrix::set_with_no_adjusting_for_row(unsigned row, unsigned col, T val) { // should not be used in efficient code + vector> & row_vec = m_rows[row]; + for (auto & iv : row_vec) { + if (iv.m_index == col) { + iv.set_value(val); + return; + } + } + // have not found the column between the indices + row_vec.push_back(indexed_value(val, col)); // what about m_other ??? +} + +template +void sparse_matrix::set_with_no_adjusting_for_col(unsigned row, unsigned col, T val) { // should not be used in efficient code + vector> & col_vec = m_columns[col].m_values; + for (auto & iv : col_vec) { + if (iv.m_index == row) { + iv.set_value(val); + return; + } + } + // have not found the column between the indices + col_vec.push_back(indexed_value(val, row)); // what about m_other ??? +} + + +template +void sparse_matrix::set_with_no_adjusting(unsigned row, unsigned col, T val) { // should not be used in efficient code + set_with_no_adjusting_for_row(row, col, val); + set_with_no_adjusting_for_col(row, col, val); +} + +template +void sparse_matrix::set(unsigned row, unsigned col, T val) { // should not be used in efficient code + lean_assert(row < dimension() && col < dimension()); + // m_dense.set_elem(row, col, val); + row = adjust_row(row); + col = adjust_column(col); + set_with_no_adjusting(row, col, val); + // lean_assert(*this == m_dense); +} + +template +T const & sparse_matrix::get_not_adjusted(unsigned row, unsigned col) const { + for (indexed_value const & iv : m_rows[row]) { + if (iv.m_index == col) { + return iv.m_value; + } + } + return numeric_traits::zero(); +} + +template +T const & sparse_matrix::get(unsigned row, unsigned col) const { // should not be used in efficient code + row = adjust_row(row); + auto & row_chunk = m_rows[row]; + col = adjust_column(col); + for (indexed_value const & iv : row_chunk) { + if (iv.m_index == col) { + return iv.m_value; + } + } + return numeric_traits::zero(); +} + +// constructor creating a zero matrix of dim*dim +template +sparse_matrix::sparse_matrix(unsigned dim) : + m_pivot_queue(dim), // dim will be the initial size of the queue + m_row_permutation(dim), + m_column_permutation(dim), + m_work_pivot_vector(dim, -1), + m_processed(dim) { + init_row_headers(); + init_column_headers(); + } + +template +void sparse_matrix::init_row_headers() { + for (unsigned l = 0; l < m_row_permutation.size(); l++) { + m_rows.push_back(vector>()); + } +} + +template +void sparse_matrix::init_column_headers() { // we alway have only square sparse_matrix + for (unsigned l = 0; l < m_row_permutation.size(); l++) { + m_columns.push_back(col_header()); + } +} + +template +unsigned sparse_matrix::lowest_row_in_column(unsigned j) { + auto & mc = get_column_values(adjust_column(j)); + unsigned ret = 0; + for (auto & iv : mc) { + unsigned row = adjust_row_inverse(iv.m_index); + if (row > ret) { + ret = row; + } + } + return ret; +} + +template +void sparse_matrix::remove_element(vector> & row_vals, unsigned row_offset, vector> & column_vals, unsigned column_offset) { + if (column_offset != column_vals.size() - 1) { + auto & column_iv = column_vals[column_offset] = column_vals.back(); // copy from the tail + column_iv_other(column_iv).m_other = column_offset; + if (row_offset != row_vals.size() - 1) { + auto & row_iv = row_vals[row_offset] = row_vals.back(); // copy from the tail + row_iv_other(row_iv).m_other = row_offset; + } + } else if (row_offset != row_vals.size() - 1) { + auto & row_iv = row_vals[row_offset] = row_vals.back(); // copy from the tail + row_iv_other(row_iv).m_other = row_offset; + } + // do nothing - just decrease the sizes + column_vals.pop_back(); + row_vals.pop_back(); + m_n_of_active_elems--; // the value is correct only when refactoring +} + +template +void sparse_matrix::remove_element(vector> & row_chunk, indexed_value & row_el_iv) { + auto & column_chunk = get_column_values(row_el_iv.m_index); + indexed_value & col_el_iv = column_chunk[row_el_iv.m_other]; + remove_element(row_chunk, col_el_iv.m_other, column_chunk, row_el_iv.m_other); +} + +template +void sparse_matrix::put_max_index_to_0(vector> & row_vals, unsigned max_index) { + if (max_index == 0) return; + indexed_value * max_iv = & row_vals[max_index]; + indexed_value * start_iv = & row_vals[0]; + // update the "other" columns elements which are bound to the start_iv and max_iv + m_columns[max_iv->m_index].m_values[max_iv->m_other].m_other = 0; + m_columns[start_iv->m_index].m_values[start_iv->m_other].m_other = max_index; + + // swap the elements + indexed_value t = * max_iv; + * max_iv = * start_iv; + * start_iv = t; +} + +template +void sparse_matrix::set_max_in_row(vector> & row_vals) { + if (row_vals.size() == 0) + return; + T max_val = abs(row_vals[0].m_value); + unsigned max_index = 0; + for (unsigned i = 1; i < row_vals.size(); i++) { + T iabs = abs(row_vals[i].m_value); + if (iabs > max_val) { + max_val = iabs; + max_index = i; + } + } + put_max_index_to_0(row_vals, max_index); +} + +template +bool sparse_matrix::pivot_with_eta(unsigned i, eta_matrix *eta_matrix, lp_settings & settings) { + const T& pivot = eta_matrix->get_diagonal_element(); + for (auto & it : eta_matrix->m_column_vector.m_data) { + if (!pivot_row_to_row(i, it.second, it.first, settings)) { + return false; + } + } + + divide_row_by_constant(i, pivot, settings); + if (!shorten_active_matrix(i, eta_matrix)) { + return false; + } + + return true; +} + +// returns the offset of the pivot column in the row +template +void sparse_matrix::scan_row_to_work_vector_and_remove_pivot_column(unsigned row, unsigned pivot_column) { + auto & rvals = m_rows[row]; + unsigned size = rvals.size(); + for (unsigned j = 0; j < size; j++) { + auto & iv = rvals[j]; + if (iv.m_index != pivot_column) { + m_work_pivot_vector[iv.m_index] = j; + } else { + remove_element(rvals, iv); + j--; + size--; + } + } +} + +#ifdef LEAN_DEBUG +template +vector sparse_matrix::get_full_row(unsigned i) const { + vector r; + for (unsigned j = 0; j < column_count(); j++) + r.push_back(get(i, j)); + return r; +} +#endif + + + +// This method pivots row i to row i0 by muliplying row i by +// alpha and adding it to row i0. +// After pivoting the row i0 has a max abs value set correctly at the beginning of m_start, +// Returns false if the resulting row is all zeroes, and true otherwise +template +bool sparse_matrix::pivot_row_to_row(unsigned i, const T& alpha, unsigned i0, lp_settings & settings ) { + lean_assert(i < dimension() && i0 < dimension()); + lean_assert(i != i0); + unsigned pivot_col = adjust_column(i); + i = adjust_row(i); + i0 = adjust_row(i0); + vector became_zeros; + // the offset of element of the pivot column in row i0 + scan_row_to_work_vector_and_remove_pivot_column(i0, pivot_col); + auto & i0_row_vals = m_rows[i0]; + // run over the pivot row and update row i0 + unsigned prev_size_i0 = i0_row_vals.size(); + for (const auto & iv : m_rows[i]) { + unsigned j = iv.m_index; + if (j == pivot_col) continue; + T alv = alpha * iv.m_value; + int j_offs = m_work_pivot_vector[j]; + if (j_offs == -1) { // it is a new element + if (!settings.abs_val_is_smaller_than_drop_tolerance(alv)) { + add_new_element(i0, j, alv); + } + } + else { + auto & row_el_iv = i0_row_vals[j_offs]; + row_el_iv.m_value += alv; + if (settings.abs_val_is_smaller_than_drop_tolerance(row_el_iv.m_value)) { + became_zeros.push_back(j_offs); + ensure_increasing(became_zeros); + } + else { + m_columns[j].m_values[row_el_iv.m_other].set_value(row_el_iv.m_value); + } + } + } + + + // clean the work vector + for (unsigned k = 0; k < prev_size_i0; k++) { + m_work_pivot_vector[i0_row_vals[k].m_index] = -1; + } + + for (unsigned k = became_zeros.size(); k-- > 0; ) { + unsigned j = became_zeros[k]; + remove_element(i0_row_vals, i0_row_vals[j]); + if (i0_row_vals.empty()) + return false; + } + + if (numeric_traits::precise() == false) + set_max_in_row(i0_row_vals); + + return !i0_row_vals.empty(); +} + + + +// set the max val as well +// returns false if the resulting row is all zeroes, and true otherwise +template +bool sparse_matrix::set_row_from_work_vector_and_clean_work_vector_not_adjusted(unsigned i0, indexed_vector & work_vec, + lp_settings & settings) { + remove_zero_elements_and_set_data_on_existing_elements_not_adjusted(i0, work_vec, settings); + // all non-zero elements in m_work_pivot_vector are new + for (unsigned j : work_vec.m_index) { + if (numeric_traits::is_zero(work_vec[j])) { + continue; + } + lean_assert(!settings.abs_val_is_smaller_than_drop_tolerance(work_vec[j])); + add_new_element(i0, adjust_column(j), work_vec[j]); + work_vec[j] = numeric_traits::zero(); + } + work_vec.m_index.clear(); + auto & row_vals = m_rows[i0]; + if (row_vals.size() == 0) { + return false; + } + set_max_in_row(row_vals); // it helps to find larger pivots + return true; +} + + + +template +void sparse_matrix::remove_zero_elements_and_set_data_on_existing_elements(unsigned row) { + auto & row_vals = m_rows[row]; + for (unsigned k = static_cast(row_vals.size()); k-- > 0;) { // we cannot simply run the iterator since we are removing + // elements from row_vals + auto & row_el_iv = row_vals[k]; + unsigned j = row_el_iv.m_index; + T & wj = m_work_pivot_vector[j]; + if (is_zero(wj)) { + remove_element(row_vals, row_el_iv); + } else { + m_columns[j].m_values[row_el_iv.m_other].set_value(wj); + row_el_iv.set_value(wj); + wj = zero_of_type(); + } + } +} + +// work_vec here has not adjusted column indices +template +void sparse_matrix::remove_zero_elements_and_set_data_on_existing_elements_not_adjusted(unsigned row, indexed_vector & work_vec, lp_settings & settings) { + auto & row_vals = m_rows[row]; + for (unsigned k = static_cast(row_vals.size()); k-- > 0;) { // we cannot simply run the iterator since we are removing + // elements from row_vals + auto & row_el_iv = row_vals[k]; + unsigned j = row_el_iv.m_index; + unsigned rj = adjust_column_inverse(j); + T val = work_vec[rj]; + if (settings.abs_val_is_smaller_than_drop_tolerance(val)) { + remove_element(row_vals, row_el_iv); + lean_assert(numeric_traits::is_zero(val)); + } else { + m_columns[j].m_values[row_el_iv.m_other].set_value(row_el_iv.m_value = val); + work_vec[rj] = numeric_traits::zero(); + } + } +} + + +// adding delta columns at the end of the matrix +template +void sparse_matrix::add_columns_at_the_end(unsigned delta) { + for (unsigned i = 0; i < delta; i++) { + col_header col_head; + m_columns.push_back(col_head); + } + m_column_permutation.enlarge(delta); +} + +template +void sparse_matrix::delete_column(int i) { + lean_assert(i < dimension()); + for (auto cell = m_columns[i].m_head; cell != nullptr;) { + auto next_cell = cell->m_down; + kill_cell(cell); + cell = next_cell; + } +} + +template +void sparse_matrix::divide_row_by_constant(unsigned i, const T & t, lp_settings & settings) { + lean_assert(!settings.abs_val_is_smaller_than_zero_tolerance(t)); + i = adjust_row(i); + for (auto & iv : m_rows[i]) { + T &v = iv.m_value; + v /= t; + if (settings.abs_val_is_smaller_than_drop_tolerance(v)){ + v = numeric_traits::zero(); + } + m_columns[iv.m_index].m_values[iv.m_other].set_value(v); + } +} + + +// solving x * this = y, and putting the answer into y +// the matrix here has to be upper triangular +template +void sparse_matrix::solve_y_U(vector & y) const { // works by rows +#ifdef LEAN_DEBUG + // T * rs = clone_vector(y, dimension()); +#endif + unsigned end = dimension(); + for (unsigned i = 0; i + 1 < end; i++) { + // all y[i] has correct values already + const T & yv = y[i]; + if (numeric_traits::is_zero(yv)) continue; + auto & mc = get_row_values(adjust_row(i)); + for (auto & c : mc) { + unsigned col = adjust_column_inverse(c.m_index); + if (col != i) { + y[col] -= c.m_value * yv; + } + } + } +#ifdef LEAN_DEBUG + // dense_matrix deb(*this); + // T * clone_y = clone_vector(y, dimension()); + // deb.apply_from_right(clone_y); + // lean_assert(vectors_are_equal(rs, clone_y, dimension())); + // delete [] clone_y; + // delete [] rs; +#endif +} + +// solving x * this = y, and putting the answer into y +// the matrix here has to be upper triangular +template +void sparse_matrix::solve_y_U_indexed(indexed_vector & y, const lp_settings & settings) { +#if 0 && LEAN_DEBUG + vector ycopy(y.m_data); + if (numeric_traits::precise() == false) + solve_y_U(ycopy); +#endif + vector sorted_active_columns; + extend_and_sort_active_rows(y.m_index, sorted_active_columns); + for (unsigned k = sorted_active_columns.size(); k-- > 0; ) { + unsigned j = sorted_active_columns[k]; + auto & yj = y[j]; + for (auto & c: m_columns[adjust_column(j)].m_values) { + unsigned i = adjust_row_inverse(c.m_index); + if (i == j) continue; + yj -= y[i] * c.m_value; + } + } + y.m_index.clear(); + for (auto j : sorted_active_columns) { + if (!settings.abs_val_is_smaller_than_drop_tolerance(y[j])) + y.m_index.push_back(j); + else if (!numeric_traits::precise()) + y.m_data[j] = zero_of_type(); + } + + lean_assert(y.is_OK()); +#if 0 && LEAN_DEBUG + if (numeric_traits::precise() == false) + lean_assert(vectors_are_equal(ycopy, y.m_data)); +#endif +} + + +// fills the indices for such that y[i] can be not a zero +// sort them so the smaller indices come first +// void fill_reachable_indices(std::set & rset, T *y) { +// std::queue q; +// int m = dimension(); +// for (int i = m - 1; i >= 0; i--) { +// if (!numeric_traits::is_zero(y[i])){ +// for (cell * c = m_columns[adjust_column(i)].m_head; c != nullptr; c = c->m_down) { +// unsigned row = adjust_row_inverse(c->m_i); +// q.push(row); +// } +// } +// } +// while (!q.empty()) { +// unsigned i = q.front(); +// q.pop(); +// for (cell * c = m_columns[adjust_column(i)].m_head; c != nullptr; c = c->m_down) { +// unsigned row = adjust_row_inverse(c->m_i); +// if (rset.find(row) == rset.end()){ +// rset.insert(row); +// q.push(row); +// } +// } +// } +// } + +template +template +void sparse_matrix::find_error_in_solution_U_y(vector& y_orig, vector & y) { + unsigned i = dimension(); + while (i--) { + y_orig[i] -= dot_product_with_row(i, y); + } +} + +template +template +void sparse_matrix::find_error_in_solution_U_y_indexed(indexed_vector& y_orig, indexed_vector & y, const vector& sorted_active_rows) { + for (unsigned i: sorted_active_rows) + y_orig.add_value_at_index(i, -dot_product_with_row(i, y)); // cannot round up here!!! + // y_orig can contain very small values +} + + +template +template +void sparse_matrix::add_delta_to_solution(const vector& del, vector & y) { + unsigned i = dimension(); + while (i--) { + y[i] += del[i]; + } +} +template +template +void sparse_matrix::add_delta_to_solution(const indexed_vector& del, indexed_vector & y) { +// lean_assert(del.is_OK()); + // lean_assert(y.is_OK()); + for (auto i : del.m_index) { + y.add_value_at_index(i, del[i]); + } +} +template +template +void sparse_matrix::double_solve_U_y(indexed_vector& y, const lp_settings & settings){ + lean_assert(y.is_OK()); + indexed_vector y_orig(y); // copy y aside + vector active_rows; + solve_U_y_indexed_only(y, settings, active_rows); + lean_assert(y.is_OK()); + find_error_in_solution_U_y_indexed(y_orig, y, active_rows); + // y_orig contains the error now + if (y_orig.m_index.size() * ratio_of_index_size_to_all_size() < 32 * dimension()) { + active_rows.clear(); + solve_U_y_indexed_only(y_orig, settings, active_rows); + add_delta_to_solution(y_orig, y); + y.clean_up(); + } else { // the dense version + solve_U_y(y_orig.m_data); + add_delta_to_solution(y_orig.m_data, y.m_data); + y.restore_index_and_clean_from_data(); + } + lean_assert(y.is_OK()); +} +template +template +void sparse_matrix::double_solve_U_y(vector& y){ + vector y_orig(y); // copy y aside + solve_U_y(y); + find_error_in_solution_U_y(y_orig, y); + // y_orig contains the error now + solve_U_y(y_orig); + add_delta_to_solution(y_orig, y); +} + +// solving this * x = y, and putting the answer into y +// the matrix here has to be upper triangular +template +template +void sparse_matrix::solve_U_y(vector & y) { // it is a column wise version +#ifdef LEAN_DEBUG + // T * rs = clone_vector(y, dimension()); +#endif + + for (unsigned j = dimension(); j--; ) { + const L & yj = y[j]; + if (is_zero(yj)) continue; + for (const auto & iv : m_columns[adjust_column(j)].m_values) { + unsigned i = adjust_row_inverse(iv.m_index); + if (i != j) { + y[i] -= iv.m_value * yj; + } + } + } +#ifdef LEAN_DEBUG + // dense_matrix deb(*this); + // T * clone_y = clone_vector(y, dimension()); + // deb.apply_from_left(clone_y); + // lean_assert(vectors_are_equal(rs, clone_y, dimension())); +#endif +} +template +void sparse_matrix::process_index_recursively_for_y_U(unsigned j, vector & sorted_active_rows) { + lean_assert(m_processed[j] == false); + m_processed[j]=true; + auto & row = m_rows[adjust_row(j)]; + for (auto & c : row) { + unsigned i = adjust_column_inverse(c.m_index); + if (i == j) continue; + if (!m_processed[i]) { + process_index_recursively_for_y_U(i, sorted_active_rows); + } + } + sorted_active_rows.push_back(j); +} + +template +void sparse_matrix::process_column_recursively(unsigned j, vector & sorted_active_rows) { + lean_assert(m_processed[j] == false); + auto & mc = m_columns[adjust_column(j)].m_values; + for (auto & iv : mc) { + unsigned i = adjust_row_inverse(iv.m_index); + if (i == j) continue; + if (!m_processed[i]) { + process_column_recursively(i, sorted_active_rows); + } + } + m_processed[j]=true; + sorted_active_rows.push_back(j); +} + + +template +void sparse_matrix::create_graph_G(const vector & index_or_right_side, vector & sorted_active_rows) { + for (auto i : index_or_right_side) { + if (m_processed[i]) continue; + process_column_recursively(i, sorted_active_rows); + } + + for (auto i : sorted_active_rows) { + m_processed[i] = false; + } +} + + +template +void sparse_matrix::extend_and_sort_active_rows(const vector & index_or_right_side, vector & sorted_active_rows) { + for (auto i : index_or_right_side) { + if (m_processed[i]) continue; + process_index_recursively_for_y_U(i, sorted_active_rows); + } + + for (auto i : sorted_active_rows) { + m_processed[i] = false; + } +} + + +template +template +void sparse_matrix::solve_U_y_indexed_only(indexed_vector & y, const lp_settings & settings, vector & sorted_active_rows) { // it is a column wise version + create_graph_G(y.m_index, sorted_active_rows); + + for (auto k = sorted_active_rows.size(); k-- > 0;) { + unsigned j = sorted_active_rows[k]; + const L & yj = y[j]; + if (is_zero(yj)) continue; + auto & mc = m_columns[adjust_column(j)].m_values; + for (auto & iv : mc) { + unsigned i = adjust_row_inverse(iv.m_index); + if (i != j) { + y[i] -= iv.m_value * yj; + } + } + } + y.m_index.clear(); + for (auto j : sorted_active_rows) { + if (!settings.abs_val_is_smaller_than_drop_tolerance(y[j])) + y.m_index.push_back(j); + else if (!numeric_traits::precise()) + y[j] = zero_of_type(); + } + + lean_assert(y.is_OK()); +#ifdef LEAN_DEBUG + // dense_matrix deb(this); + // vector clone_y(y.m_data); + // deb.apply_from_left(clone_y); + // lean_assert(vectors_are_equal(rs, clone_y)); +#endif +} + +template +template +L sparse_matrix::dot_product_with_row (unsigned row, const vector & y) const { + L ret = zero_of_type(); + auto & mc = get_row_values(adjust_row(row)); + for (auto & c : mc) { + unsigned col = m_column_permutation[c.m_index]; + ret += c.m_value * y[col]; + } + return ret; +} + +template +template +L sparse_matrix::dot_product_with_row (unsigned row, const indexed_vector & y) const { + L ret = zero_of_type(); + auto & mc = get_row_values(adjust_row(row)); + for (auto & c : mc) { + unsigned col = m_column_permutation[c.m_index]; + ret += c.m_value * y[col]; + } + return ret; +} + + +template +unsigned sparse_matrix::get_number_of_nonzeroes() const { + unsigned ret = 0; + for (unsigned i = dimension(); i--; ) { + ret += number_of_non_zeroes_in_row(i); + } + return ret; +} + +template +bool sparse_matrix::get_non_zero_column_in_row(unsigned i, unsigned *j) const { + // go over the i-th row + auto & mc = get_row_values(adjust_row(i)); + if (mc.size() > 0) { + *j = m_column_permutation[mc[0].m_index]; + return true; + } + return false; +} + +template +void sparse_matrix::remove_element_that_is_not_in_w(vector> & column_vals, indexed_value & col_el_iv) { + auto & row_chunk = m_rows[col_el_iv.m_index]; + indexed_value & row_el_iv = row_chunk[col_el_iv.m_other]; + unsigned index_in_row = col_el_iv.m_other; + remove_element(row_chunk, col_el_iv.m_other, column_vals, row_el_iv.m_other); + if (index_in_row == 0) + set_max_in_row(row_chunk); +} + + +// w contains the new column +// the old column inside of the matrix has not been changed yet +template +void sparse_matrix::remove_elements_that_are_not_in_w_and_update_common_elements(unsigned column_to_replace, indexed_vector & w) { + // -------------------------------- + // column_vals represents the old column + auto & column_vals = m_columns[column_to_replace].m_values; + for (unsigned k = static_cast(column_vals.size()); k-- > 0;) { + indexed_value & col_el_iv = column_vals[k]; + unsigned i = col_el_iv.m_index; + T &w_data_at_i = w[adjust_row_inverse(i)]; + if (numeric_traits::is_zero(w_data_at_i)) { + remove_element_that_is_not_in_w(column_vals, col_el_iv); + } else { + auto& row_chunk = m_rows[i]; + unsigned index_in_row = col_el_iv.m_other; + if (index_in_row == 0) { + bool look_for_max = abs(w_data_at_i) < abs(row_chunk[0].m_value); + row_chunk[0].set_value(col_el_iv.m_value = w_data_at_i); + if (look_for_max) + set_max_in_row(i); + } else { + row_chunk[index_in_row].set_value(col_el_iv.m_value = w_data_at_i); + if (abs(w_data_at_i) > abs(row_chunk[0].m_value)) + put_max_index_to_0(row_chunk, index_in_row); + } + w_data_at_i = numeric_traits::zero(); + } + } +} + +template +void sparse_matrix::add_new_element(unsigned row, unsigned col, const T& val) { + auto & row_vals = m_rows[row]; + auto & col_vals = m_columns[col].m_values; + unsigned row_el_offs = static_cast(row_vals.size()); + unsigned col_el_offs = static_cast(col_vals.size()); + row_vals.push_back(indexed_value(val, col, col_el_offs)); + col_vals.push_back(indexed_value(val, row, row_el_offs)); + m_n_of_active_elems++; +} + +// w contains the "rest" of the new column; all common elements of w and the old column has been zeroed +// the old column inside of the matrix has not been changed yet +template +void sparse_matrix::add_new_elements_of_w_and_clear_w(unsigned column_to_replace, indexed_vector & w, lp_settings & settings) { + for (unsigned i : w.m_index) { + T w_at_i = w[i]; + if (numeric_traits::is_zero(w_at_i)) continue; // was dealt with already + if (!settings.abs_val_is_smaller_than_drop_tolerance(w_at_i)) { + unsigned ai = adjust_row(i); + add_new_element(ai, column_to_replace, w_at_i); + auto & row_chunk = m_rows[ai]; + lean_assert(row_chunk.size() > 0); + if (abs(w_at_i) > abs(row_chunk[0].m_value)) + put_max_index_to_0(row_chunk, static_cast(row_chunk.size()) - 1); + } + w[i] = numeric_traits::zero(); + } + w.m_index.clear(); +} + +template +void sparse_matrix::replace_column(unsigned column_to_replace, indexed_vector & w, lp_settings &settings) { + column_to_replace = adjust_column(column_to_replace); + remove_elements_that_are_not_in_w_and_update_common_elements(column_to_replace, w); + add_new_elements_of_w_and_clear_w(column_to_replace, w, settings); +} + +template +unsigned sparse_matrix::pivot_score(unsigned i, unsigned j) { + // It goes like this (rnz-1)(cnz-1) is the Markovitz number, that is the max number of + // new non zeroes we can obtain after the pivoting. + // In addition we will get another cnz - 1 elements in the eta matrix created for this pivot, + // which gives rnz(cnz-1). For example, is 0 for a column singleton, but not for + // a row singleton ( which is not a column singleton). + + auto col_header = m_columns[j]; + + return static_cast(get_row_values(i).size() * (col_header.m_values.size() - col_header.m_shortened_markovitz - 1)); +} + +template +void sparse_matrix::enqueue_domain_into_pivot_queue() { + lean_assert(m_pivot_queue.size() == 0); + for (unsigned i = 0; i < dimension(); i++) { + auto & rh = m_rows[i]; + unsigned rnz = static_cast(rh.size()); + for (auto iv : rh) { + unsigned j = iv.m_index; + m_pivot_queue.enqueue(i, j, rnz * (static_cast(m_columns[j].m_values.size()) - 1)); + } + } +} + +template +void sparse_matrix::set_max_in_rows() { + unsigned i = dimension(); + while (i--) + set_max_in_row(i); +} + + +template +void sparse_matrix::zero_shortened_markovitz_numbers() { + for (auto & ch : m_columns) + ch.zero_shortened_markovitz(); +} + +template +void sparse_matrix::prepare_for_factorization() { + zero_shortened_markovitz_numbers(); + set_max_in_rows(); + enqueue_domain_into_pivot_queue(); +} + +template +void sparse_matrix::recover_pivot_queue(vector & rejected_pivots) { + for (auto p : rejected_pivots) { + m_pivot_queue.enqueue(p.first, p.second, pivot_score(p.first, p.second)); + } +} + +template +int sparse_matrix::elem_is_too_small(unsigned i, unsigned j, int c_partial_pivoting) { + auto & row_chunk = m_rows[i]; + + if (j == row_chunk[0].m_index) { + return 0; // the max element is at the head + } + T max = abs(row_chunk[0].m_value); + for (unsigned k = 1; k < row_chunk.size(); k++) { + auto &iv = row_chunk[k]; + if (iv.m_index == j) + return abs(iv.m_value) * c_partial_pivoting < max ? 1: 0; + } + return 2; // the element became zero but it still sits in the active pivots? +} + +template +bool sparse_matrix::remove_row_from_active_pivots_and_shorten_columns(unsigned row) { + unsigned arow = adjust_row(row); + for (auto & iv : m_rows[arow]) { + m_pivot_queue.remove(arow, iv.m_index); + m_n_of_active_elems--; // the value is correct only when refactoring + if (adjust_column_inverse(iv.m_index) <= row) + continue; // this column will be removed anyway + auto & col = m_columns[iv.m_index]; + + col.shorten_markovich_by_one(); + if (col.m_values.size() <= col.m_shortened_markovitz) + return false; // got a zero column + } + return true; +} + +template +void sparse_matrix::remove_pivot_column(unsigned row) { + unsigned acol = adjust_column(row); + for (const auto & iv : m_columns[acol].m_values) + if (adjust_row_inverse(iv.m_index) >= row) + m_pivot_queue.remove(iv.m_index, acol); +} + +template +void sparse_matrix::update_active_pivots(unsigned row) { + unsigned arow = adjust_row(row); + for (const auto & iv : m_rows[arow]) { + col_header & ch = m_columns[iv.m_index]; + int cols = static_cast(ch.m_values.size()) - ch.m_shortened_markovitz - 1; + lean_assert(cols >= 0); + for (const auto &ivc : ch.m_values) { + unsigned i = ivc.m_index; + if (adjust_row_inverse(i) <= row) continue; // the i is not an active row + m_pivot_queue.enqueue(i, iv.m_index, static_cast(m_rows[i].size())*cols); + } + } +} + +template +bool sparse_matrix::shorten_active_matrix(unsigned row, eta_matrix *eta_matrix) { + if (!remove_row_from_active_pivots_and_shorten_columns(row)) + return false; + remove_pivot_column(row); + // need to know the max priority of the queue here + update_active_pivots(row); + if (eta_matrix == nullptr) return true; + // it looks like double work, but the pivot scores have changed for all rows + // touched by eta_matrix + for (auto & it : eta_matrix->m_column_vector.m_data) { + unsigned row = adjust_row(it.first); + const auto & row_values = m_rows[row]; + unsigned rnz = static_cast(row_values.size()); + for (auto & iv : row_values) { + const col_header& ch = m_columns[iv.m_index]; + int cnz = static_cast(ch.m_values.size()) - ch.m_shortened_markovitz - 1; + lean_assert(cnz >= 0); + m_pivot_queue.enqueue(row, iv.m_index, rnz * cnz); + } + } + + return true; +} + +template +unsigned sparse_matrix::pivot_score_without_shortened_counters(unsigned i, unsigned j, unsigned k) { + auto &cols = m_columns[j].m_values; + unsigned cnz = cols.size(); + for (auto & iv : cols) { + if (adjust_row_inverse(iv.m_index) < k) + cnz--; + } + lean_assert(cnz > 0); + return m_rows[i].m_values.size() * (cnz - 1); +} +#ifdef LEAN_DEBUG +template +bool sparse_matrix::can_improve_score_for_row(unsigned row, unsigned score, T const & c_partial_pivoting, unsigned k) { + unsigned arow = adjust_row(row); + auto & row_vals = m_rows[arow].m_values; + auto & begin_iv = row_vals[0]; + T row_max = abs(begin_iv.m_value); + lean_assert(adjust_column_inverse(begin_iv.m_index) >= k); + if (pivot_score_without_shortened_counters(arow, begin_iv.m_index, k) < score) { + print_active_matrix(k); + return true; + } + for (unsigned jj = 1; jj < row_vals.size(); jj++) { + auto & iv = row_vals[jj]; + lean_assert(adjust_column_inverse(iv.m_index) >= k); + lean_assert(abs(iv.m_value) <= row_max); + if (c_partial_pivoting * abs(iv.m_value) < row_max) continue; + if (pivot_score_without_shortened_counters(arow, iv.m_index, k) < score) { + print_active_matrix(k); + return true; + } + } + return false; +} + +template +bool sparse_matrix::really_best_pivot(unsigned i, unsigned j, T const & c_partial_pivoting, unsigned k) { + unsigned queue_pivot_score = pivot_score_without_shortened_counters(i, j, k); + for (unsigned ii = k; ii < dimension(); ii++) { + lean_assert(!can_improve_score_for_row(ii, queue_pivot_score, c_partial_pivoting, k)); + } + return true; +} +template +void sparse_matrix::print_active_matrix(unsigned k, std::ostream & out) { + out << "active matrix for k = " << k << std::endl; + if (k >= dimension()) { + out << "empty" << std::endl; + return; + } + unsigned dim = dimension() - k; + dense_matrix b(dim, dim); + for (unsigned i = 0; i < dim; i++) + for (unsigned j = 0; j < dim; j++ ) + b.set_elem(i, j, zero_of_type()); + for (int i = k; i < dimension(); i++) { + unsigned col = adjust_column(i); + for (auto &iv : get_column_values(col)) { + unsigned row = iv.m_index; + unsigned row_ex = this->adjust_row_inverse(row); + if (row_ex < k) continue; + auto v = this->get_not_adjusted(row, col); + b.set_elem(row_ex - k, i -k, v); + } + } + print_matrix(b, out); +} + +template +bool sparse_matrix::pivot_queue_is_correct_for_row(unsigned i, unsigned k) { + unsigned arow = adjust_row(i); + for (auto & iv : m_rows[arow].m_values) { + lean_assert(pivot_score_without_shortened_counters(arow, iv.m_index, k + 1) == + m_pivot_queue.get_priority(arow, iv.m_index)); + } + return true; +} + +template +bool sparse_matrix::pivot_queue_is_correct_after_pivoting(int k) { + for (unsigned i = k + 1; i < dimension(); i++ ) + lean_assert(pivot_queue_is_correct_for_row(i, k)); + lean_assert(m_pivot_queue.is_correct()); + return true; +} +#endif + +template +bool sparse_matrix::get_pivot_for_column(unsigned &i, unsigned &j, int c_partial_pivoting, unsigned k) { + vector pivots_candidates_that_are_too_small; + while (!m_pivot_queue.is_empty()) { + m_pivot_queue.dequeue(i, j); + unsigned i_inv = adjust_row_inverse(i); + if (i_inv < k) continue; + unsigned j_inv = adjust_column_inverse(j); + if (j_inv < k) continue; + int small = elem_is_too_small(i, j, c_partial_pivoting); + if (!small) { +#ifdef LEAN_DEBUG + // if (!really_best_pivot(i, j, c_partial_pivoting, k)) { + // print_active_matrix(k); + // lean_assert(false); + // } +#endif + recover_pivot_queue(pivots_candidates_that_are_too_small); + i = i_inv; + j = j_inv; + return true; + } + if (small != 2) { // 2 means that the pair is not in the matrix + pivots_candidates_that_are_too_small.push_back(std::make_pair(i, j)); + } + } + recover_pivot_queue(pivots_candidates_that_are_too_small); + return false; +} + +template +bool sparse_matrix::elem_is_too_small(vector> & row_chunk, indexed_value & iv, int c_partial_pivoting) { + if (&iv == &row_chunk[0]) { + return false; // the max element is at the head + } + T val = abs(iv.m_value); + T max = abs(row_chunk[0].m_value); + return val * c_partial_pivoting < max; +} + +template +bool sparse_matrix::shorten_columns_by_pivot_row(unsigned i, unsigned pivot_column) { + vector> & row_chunk = get_row_values(i); + + for (indexed_value & iv : row_chunk) { + unsigned j = iv.m_index; + if (j == pivot_column) { + lean_assert(!col_is_active(j)); + continue; + } + m_columns[j].shorten_markovich_by_one(); + + if (m_columns[j].m_shortened_markovitz >= get_column_values(j).size()) { // got the zero column under the row! + return false; + } + } + return true; +} + +template +bool sparse_matrix::fill_eta_matrix(unsigned j, eta_matrix ** eta) { + const vector> & col_chunk = get_column_values(adjust_column(j)); + bool is_unit = true; + for (const auto & iv : col_chunk) { + unsigned i = adjust_row_inverse(iv.m_index); + if (i > j) { + is_unit = false; + break; + } + if (i == j && iv.m_value != 1) { + is_unit = false; + break; + } + } + + if (is_unit) { + *eta = nullptr; + return true; + } + +#ifdef LEAN_DEBUG + *eta = new eta_matrix(j, dimension()); +#else + *eta = new eta_matrix(j); +#endif + for (const auto & iv : col_chunk) { + unsigned i = adjust_row_inverse(iv.m_index); + if (i < j) { + continue; + } + if (i > j) { + (*eta)->push_back(i, - iv.m_value); + } else { // i == j + if ( !(*eta)->set_diagonal_element(iv.m_value)) { + delete *eta; + *eta = nullptr; + return false; + } + + } + } + + (*eta)->divide_by_diagonal_element(); + return true; +} +#ifdef LEAN_DEBUG +template +bool sparse_matrix::is_upper_triangular_and_maximums_are_set_correctly_in_rows(lp_settings & settings) const { + for (unsigned i = 0; i < dimension(); i++) { + vector> const & row_chunk = get_row_values(i); + lean_assert(row_chunk.size()); + T const & max = abs(row_chunk[0].m_value); + unsigned ai = adjust_row_inverse(i); + for (auto & iv : row_chunk) { + lean_assert(abs(iv.m_value) <= max); + unsigned aj = adjust_column_inverse(iv.m_index); + if (!(ai <= aj || numeric_traits::is_zero(iv.m_value))) + return false; + if (aj == ai) { + if (iv.m_value != 1) { + // std::cout << "value at diagonal = " << iv.m_value << std::endl; + return false; + } + } + if (settings.abs_val_is_smaller_than_drop_tolerance(iv.m_value) && (!is_zero(iv.m_value))) + return false; + } + } + return true; +} + +template +bool sparse_matrix::is_upper_triangular_until(unsigned k) const { + for (unsigned j = 0; j < dimension() && j < k; j++) { + unsigned aj = adjust_column(j); + auto & col = get_column_values(aj); + for (auto & iv : col) { + unsigned row = adjust_row_inverse(iv.m_index); + if (row > j) + return false; + } + } + return true; +} + +template +void sparse_matrix::check_column_vs_rows(unsigned col) { + auto & mc = get_column_values(col); + for (indexed_value & column_iv : mc) { + indexed_value & row_iv = column_iv_other(column_iv); + if (row_iv.m_index != col) { + // std::cout << "m_other in row does not belong to column " << col << ", but to column " << row_iv.m_index << std::endl; + lean_assert(false); + } + + if (& row_iv_other(row_iv) != &column_iv) { + // std::cout << "row and col do not point to each other" << std::endl; + lean_assert(false); + } + + if (row_iv.m_value != column_iv.m_value) { + // std::cout << "the data from col " << col << " for row " << column_iv.m_index << " is different in the column " << std::endl; + // std::cout << "in the col it is " << column_iv.m_value << ", but in the row it is " << row_iv.m_value << std::endl; + lean_assert(false); + } + } +} + +template +void sparse_matrix::check_row_vs_columns(unsigned row) { + auto & mc = get_row_values(row); + for (indexed_value & row_iv : mc) { + indexed_value & column_iv = row_iv_other(row_iv); + + if (column_iv.m_index != row) { + // std::cout << "col_iv does not point to correct row " << row << " but to " << column_iv.m_index << std::endl; + lean_assert(false); + } + + if (& row_iv != & column_iv_other(column_iv)) { + // std::cout << "row and col do not point to each other" << std::endl; + lean_assert(false); + } + + if (row_iv.m_value != column_iv.m_value) { + // std::cout << "the data from col " << column_iv.m_index << " for row " << row << " is different in the column " << std::endl; + // std::cout << "in the col it is " << column_iv.m_value << ", but in the row it is " << row_iv.m_value << std::endl; + lean_assert(false); + } + } +} + +template +void sparse_matrix::check_rows_vs_columns() { + for (unsigned i = 0; i < dimension(); i++) { + check_row_vs_columns(i); + } +} + +template +void sparse_matrix::check_columns_vs_rows() { + for (unsigned i = 0; i < dimension(); i++) { + check_column_vs_rows(i); + } +} +template +void sparse_matrix::check_matrix() { + check_rows_vs_columns(); + check_columns_vs_rows(); +} +#endif +} + diff --git a/src/util/lp/sparse_matrix_instances.cpp b/src/util/lp/sparse_matrix_instances.cpp new file mode 100644 index 000000000..f80b60365 --- /dev/null +++ b/src/util/lp/sparse_matrix_instances.cpp @@ -0,0 +1,101 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include "util/vector.h" +#include +#include "util/lp/lp_settings.h" +#include "util/lp/lu.h" +#include "util/lp/sparse_matrix.hpp" +#include "util/lp/dense_matrix.h" +namespace lean { +template double sparse_matrix::dot_product_with_row(unsigned int, vector const&) const; +template void sparse_matrix::add_new_element(unsigned int, unsigned int, const double&); +template void sparse_matrix::divide_row_by_constant(unsigned int, const double&, lp_settings&); +template bool sparse_matrix::fill_eta_matrix(unsigned int, eta_matrix**); +template const double & sparse_matrix::get(unsigned int, unsigned int) const; +template unsigned sparse_matrix::get_number_of_nonzeroes() const; +template bool sparse_matrix::get_pivot_for_column(unsigned int&, unsigned int&, int, unsigned int); +template unsigned sparse_matrix::lowest_row_in_column(unsigned int); +template bool sparse_matrix::pivot_row_to_row(unsigned int, const double&, unsigned int, lp_settings&); +template bool sparse_matrix::pivot_with_eta(unsigned int, eta_matrix*, lp_settings&); +template void sparse_matrix::prepare_for_factorization(); +template void sparse_matrix::remove_element(vector >&, indexed_value&); +template void sparse_matrix::replace_column(unsigned int, indexed_vector&, lp_settings&); +template void sparse_matrix::set(unsigned int, unsigned int, double); +template void sparse_matrix::set_max_in_row(vector >&); +template bool sparse_matrix::set_row_from_work_vector_and_clean_work_vector_not_adjusted(unsigned int, indexed_vector&, lp_settings&); +template bool sparse_matrix::shorten_active_matrix(unsigned int, eta_matrix*); +template void sparse_matrix::solve_y_U(vector&) const; +template sparse_matrix::sparse_matrix(static_matrix const&, vector&); +template sparse_matrix::sparse_matrix(unsigned int); +template void sparse_matrix::add_new_element(unsigned int, unsigned int, const mpq&); +template void sparse_matrix::divide_row_by_constant(unsigned int, const mpq&, lp_settings&); +template bool sparse_matrix::fill_eta_matrix(unsigned int, eta_matrix**); +template mpq const & sparse_matrix::get(unsigned int, unsigned int) const; +template unsigned sparse_matrix::get_number_of_nonzeroes() const; +template bool sparse_matrix::get_pivot_for_column(unsigned int&, unsigned int&, int, unsigned int); +template unsigned sparse_matrix::lowest_row_in_column(unsigned int); +template bool sparse_matrix::pivot_with_eta(unsigned int, eta_matrix*, lp_settings&); +template void sparse_matrix::prepare_for_factorization(); +template void sparse_matrix::remove_element(vector> &, indexed_value&); +template void sparse_matrix::replace_column(unsigned int, indexed_vector&, lp_settings&); +template void sparse_matrix::set_max_in_row(vector>&); +template bool sparse_matrix::set_row_from_work_vector_and_clean_work_vector_not_adjusted(unsigned int, indexed_vector&, lp_settings&); +template bool sparse_matrix::shorten_active_matrix(unsigned int, eta_matrix*); +template void sparse_matrix::solve_y_U(vector&) const; +template sparse_matrix::sparse_matrix(static_matrix const&, vector&); +template void sparse_matrix>::add_new_element(unsigned int, unsigned int, const mpq&); +template void sparse_matrix>::divide_row_by_constant(unsigned int, const mpq&, lp_settings&); +template bool sparse_matrix>::fill_eta_matrix(unsigned int, eta_matrix >**); +template const mpq & sparse_matrix>::get(unsigned int, unsigned int) const; +template unsigned sparse_matrix>::get_number_of_nonzeroes() const; +template bool sparse_matrix>::get_pivot_for_column(unsigned int&, unsigned int&, int, unsigned int); +template unsigned sparse_matrix>::lowest_row_in_column(unsigned int); +template bool sparse_matrix>::pivot_with_eta(unsigned int, eta_matrix >*, lp_settings&); +template void sparse_matrix>::prepare_for_factorization(); +template void sparse_matrix>::remove_element(vector>&, indexed_value&); +template void sparse_matrix>::replace_column(unsigned int, indexed_vector&, lp_settings&); +template void sparse_matrix>::set_max_in_row(vector>&); +template bool sparse_matrix>::set_row_from_work_vector_and_clean_work_vector_not_adjusted(unsigned int, indexed_vector&, lp_settings&); +template bool sparse_matrix>::shorten_active_matrix(unsigned int, eta_matrix >*); +template void sparse_matrix>::solve_y_U(vector&) const; +template sparse_matrix>::sparse_matrix(static_matrix > const&, vector&); +template void sparse_matrix::double_solve_U_y(indexed_vector&, const lp_settings &); +template void sparse_matrix::double_solve_U_y(indexed_vector&, const lp_settings&); +template void sparse_matrix>::double_solve_U_y(indexed_vector&, const lp_settings&); +template void sparse_matrix >::double_solve_U_y >(indexed_vector>&, const lp_settings&); +template void lean::sparse_matrix::solve_U_y_indexed_only(lean::indexed_vector&, const lp_settings&, vector &); +template void lean::sparse_matrix::solve_U_y_indexed_only(lean::indexed_vector&, const lp_settings &, vector &); +#ifdef LEAN_DEBUG +template bool sparse_matrix::is_upper_triangular_and_maximums_are_set_correctly_in_rows(lp_settings&) const; +template bool sparse_matrix::is_upper_triangular_and_maximums_are_set_correctly_in_rows(lp_settings&) const; +template bool sparse_matrix >::is_upper_triangular_and_maximums_are_set_correctly_in_rows(lp_settings&) const; +#endif +} +template void lean::sparse_matrix >::solve_U_y_indexed_only(lean::indexed_vector&, const lp_settings &, vector &); +template void lean::sparse_matrix::solve_U_y(vector&); +template void lean::sparse_matrix::double_solve_U_y(vector&); +template void lean::sparse_matrix::solve_U_y(vector&); +template void lean::sparse_matrix::double_solve_U_y(vector&); +template void lean::sparse_matrix >::solve_U_y >(vector >&); +template void lean::sparse_matrix >::double_solve_U_y >(vector >&); +template void lean::sparse_matrix::find_error_in_solution_U_y_indexed(lean::indexed_vector&, lean::indexed_vector&, const vector &); +template double lean::sparse_matrix::dot_product_with_row(unsigned int, lean::indexed_vector const&) const; +template void lean::sparse_matrix::find_error_in_solution_U_y_indexed(lean::indexed_vector&, lean::indexed_vector&, const vector &); +template lean::mpq lean::sparse_matrix::dot_product_with_row(unsigned int, lean::indexed_vector const&) const; +template void lean::sparse_matrix >::find_error_in_solution_U_y_indexed(lean::indexed_vector&, lean::indexed_vector&, const vector &); +template lean::mpq lean::sparse_matrix >::dot_product_with_row(unsigned int, lean::indexed_vector const&) const; +template void lean::sparse_matrix >::find_error_in_solution_U_y_indexed >(lean::indexed_vector >&, lean::indexed_vector >&, const vector &); +template lean::numeric_pair lean::sparse_matrix >::dot_product_with_row >(unsigned int, lean::indexed_vector > const&) const; +template void lean::sparse_matrix::extend_and_sort_active_rows(vector const&, vector&); + +template void lean::sparse_matrix >::extend_and_sort_active_rows(vector const&, vector&); + +template void lean::sparse_matrix >::solve_U_y(vector&); +template void lean::sparse_matrix >::double_solve_U_y(vector&); +template void lean::sparse_matrix< lean::mpq,lean::numeric_pair< lean::mpq> >::set(unsigned int,unsigned int, lean::mpq); +template void lean::sparse_matrix::solve_y_U_indexed(lean::indexed_vector&, const lp_settings & ); +template void lean::sparse_matrix::solve_y_U_indexed(lean::indexed_vector&, const lp_settings &); +template void lean::sparse_matrix >::solve_y_U_indexed(lean::indexed_vector&, const lp_settings &); + diff --git a/src/util/lp/sparse_vector.h b/src/util/lp/sparse_vector.h new file mode 100644 index 000000000..78d7ff5be --- /dev/null +++ b/src/util/lp/sparse_vector.h @@ -0,0 +1,38 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ + +#pragma once +#include "util/vector.h" +#include +#include "util/debug.h" +#include "util/lp/lp_utils.h" +#include "util/lp/lp_settings.h" +namespace lean { + +template +class sparse_vector { +public: + vector> m_data; + void push_back(unsigned index, T val) { + m_data.push_back(std::make_pair(index, val)); + } +#ifdef LEAN_DEBUG + T operator[] (unsigned i) const { + for (auto t : m_data) { + if (t.first == i) return t.second; + } + return numeric_traits::zero(); + } +#endif + void divide(T const & a) { + lean_assert(!lp_settings::is_eps_small_general(a, 1e-12)); + for (auto & t : m_data) { t.second /= a; } + } + + unsigned size() const { + return m_data.size(); + } +}; +} diff --git a/src/util/lp/square_dense_submatrix.h b/src/util/lp/square_dense_submatrix.h new file mode 100644 index 000000000..10ae973d6 --- /dev/null +++ b/src/util/lp/square_dense_submatrix.h @@ -0,0 +1,210 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ + +#pragma once +#include "util/vector.h" +#include "util/lp/permutation_matrix.h" +#include +#include "util/lp/static_matrix.h" +#include +#include +#include +#include +#include +#include "util/lp/indexed_value.h" +#include "util/lp/indexed_vector.h" +#include +#include "util/lp/lp_settings.h" +#include "util/lp/eta_matrix.h" +#include "util/lp/binary_heap_upair_queue.h" +#include "util/lp/sparse_matrix.h" +namespace lean { +template +class square_dense_submatrix : public tail_matrix { + // the submatrix uses the permutations of the parent matrix to access the elements + struct ref { + unsigned m_i_offset; + square_dense_submatrix & m_s; + ref(unsigned i, square_dense_submatrix & s) : + m_i_offset((i - s.m_index_start) * s.m_dim), m_s(s){} + T & operator[] (unsigned j) { + lean_assert(j >= m_s.m_index_start); + return m_s.m_v[m_i_offset + m_s.adjust_column(j) - m_s.m_index_start]; + } + const T & operator[] (unsigned j) const { + lean_assert(j >= m_s.m_index_start); + return m_s.m_v[m_i_offset + m_s.adjust_column(j) - m_s.m_index_start]; + } + }; +public: + unsigned m_index_start; + unsigned m_dim; + vector m_v; + sparse_matrix * m_parent = nullptr; + permutation_matrix m_row_permutation; + indexed_vector m_work_vector; +public: + permutation_matrix m_column_permutation; + bool is_active() const { return m_parent != nullptr; } + + square_dense_submatrix() {} + + square_dense_submatrix (sparse_matrix *parent_matrix, unsigned index_start); + + void init(sparse_matrix *parent_matrix, unsigned index_start); + + bool is_dense() const { return true; } + + ref operator[] (unsigned i) { + lean_assert(i >= m_index_start); + lean_assert(i < m_parent->dimension()); + return ref(i, *this); + } + + int find_pivot_column_in_row(unsigned i) const; + + void swap_columns(unsigned i, unsigned j) { + if (i != j) + m_column_permutation.transpose_from_left(i, j); + } + + unsigned adjust_column(unsigned col) const{ + if (col >= m_column_permutation.size()) + return col; + return m_column_permutation.apply_reverse(col); + } + + unsigned adjust_column_inverse(unsigned col) const{ + if (col >= m_column_permutation.size()) + return col; + return m_column_permutation[col]; + } + unsigned adjust_row(unsigned row) const{ + if (row >= m_row_permutation.size()) + return row; + return m_row_permutation[row]; + } + + unsigned adjust_row_inverse(unsigned row) const{ + if (row >= m_row_permutation.size()) + return row; + return m_row_permutation.apply_reverse(row); + } + + void pivot(unsigned i, lp_settings & settings); + + void pivot_row_to_row(unsigned i, unsigned row, lp_settings & settings);; + + void divide_row_by_pivot(unsigned i); + + void update_parent_matrix(lp_settings & settings); + + void update_existing_or_delete_in_parent_matrix_for_row(unsigned i, lp_settings & settings); + + void push_new_elements_to_parent_matrix(lp_settings & settings); + + template + L row_by_vector_product(unsigned i, const vector & v); + + template + L column_by_vector_product(unsigned j, const vector & v); + + template + L row_by_indexed_vector_product(unsigned i, const indexed_vector & v); + + template + void apply_from_left_local(indexed_vector & w, lp_settings & settings); + + template + void apply_from_left_to_vector(vector & w); + + bool is_L_matrix() const; + + void apply_from_left_to_T(indexed_vector & w, lp_settings & settings) { + apply_from_left_local(w, settings); + } + + + + void apply_from_right(indexed_vector & w) { +#if 1==0 + indexed_vector wcopy = w; + apply_from_right(wcopy.m_data); + wcopy.m_index.clear(); + if (numeric_traits::precise()) { + for (unsigned i = 0; i < m_parent->dimension(); i++) { + if (!is_zero(wcopy.m_data[i])) + wcopy.m_index.push_back(i); + } + } else { + for (unsigned i = 0; i < m_parent->dimension(); i++) { + T & v = wcopy.m_data[i]; + if (!lp_settings::is_eps_small_general(v, 1e-14)){ + wcopy.m_index.push_back(i); + } else { + v = zero_of_type(); + } + } + } + lean_assert(wcopy.is_OK()); + apply_from_right(w.m_data); + w.m_index.clear(); + if (numeric_traits::precise()) { + for (unsigned i = 0; i < m_parent->dimension(); i++) { + if (!is_zero(w.m_data[i])) + w.m_index.push_back(i); + } + } else { + for (unsigned i = 0; i < m_parent->dimension(); i++) { + T & v = w.m_data[i]; + if (!lp_settings::is_eps_small_general(v, 1e-14)){ + w.m_index.push_back(i); + } else { + v = zero_of_type(); + } + } + } +#else + lean_assert(w.is_OK()); + lean_assert(m_work_vector.is_OK()); + m_work_vector.resize(w.data_size()); + m_work_vector.clear(); + lean_assert(m_work_vector.is_OK()); + unsigned end = m_index_start + m_dim; + for (unsigned k : w.m_index) { + // find j such that k = adjust_row_inverse(j) + unsigned j = adjust_row(k); + if (j < m_index_start || j >= end) { + m_work_vector.set_value(w[k], adjust_column_inverse(j)); + } else { // j >= m_index_start and j < end + unsigned offset = (j - m_index_start) * m_dim; // this is the row start + const T& wv = w[k]; + for (unsigned col = m_index_start; col < end; col++, offset ++) { + unsigned adj_col = adjust_column_inverse(col); + m_work_vector.add_value_at_index(adj_col, m_v[offset] * wv); + } + } + } + m_work_vector.clean_up(); + lean_assert(m_work_vector.is_OK()); + w = m_work_vector; +#endif + } + void apply_from_left(vector & w, lp_settings & /*settings*/) { + apply_from_left_to_vector(w);// , settings); + } + + void apply_from_right(vector & w); + +#ifdef LEAN_DEBUG + T get_elem (unsigned i, unsigned j) const; + unsigned row_count() const { return m_parent->row_count();} + unsigned column_count() const { return row_count();} + void set_number_of_rows(unsigned) {} + void set_number_of_columns(unsigned) {}; +#endif + void conjugate_by_permutation(permutation_matrix & q); +}; +} diff --git a/src/util/lp/square_dense_submatrix.hpp b/src/util/lp/square_dense_submatrix.hpp new file mode 100644 index 000000000..365c9d7f0 --- /dev/null +++ b/src/util/lp/square_dense_submatrix.hpp @@ -0,0 +1,353 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include "util/vector.h" +#include "util/lp/square_dense_submatrix.h" +namespace lean { +template +square_dense_submatrix::square_dense_submatrix (sparse_matrix *parent_matrix, unsigned index_start) : + m_index_start(index_start), + m_dim(parent_matrix->dimension() - index_start), + m_v(m_dim * m_dim), + m_parent(parent_matrix), + m_row_permutation(m_parent->dimension()), + m_column_permutation(m_parent->dimension()) { + int row_offset = - static_cast(m_index_start); + for (unsigned i = index_start; i < parent_matrix->dimension(); i++) { + unsigned row = parent_matrix->adjust_row(i); + for (auto & iv : parent_matrix->get_row_values(row)) { + unsigned j = parent_matrix->adjust_column_inverse(iv.m_index); + lean_assert(j>= m_index_start); + m_v[row_offset + j] = iv.m_value; + } + row_offset += m_dim; + } +} + +template void square_dense_submatrix::init(sparse_matrix *parent_matrix, unsigned index_start) { + m_index_start = index_start; + m_dim = parent_matrix->dimension() - index_start; + m_v.resize(m_dim * m_dim); + m_parent = parent_matrix; + m_column_permutation.init(m_parent->dimension()); + for (unsigned i = index_start; i < parent_matrix->dimension(); i++) { + unsigned row = parent_matrix->adjust_row(i); + for (auto & iv : parent_matrix->get_row_values(row)) { + unsigned j = parent_matrix->adjust_column_inverse(iv.m_index); + (*this)[i][j] = iv.m_value; + } + } +} + +template int square_dense_submatrix::find_pivot_column_in_row(unsigned i) const { + int j = -1; + T max = zero_of_type(); + lean_assert(i >= m_index_start); + unsigned row_start = (i - m_index_start) * m_dim; + for (unsigned k = i; k < m_parent->dimension(); k++) { + unsigned col = adjust_column(k); // this is where the column is in the row + unsigned offs = row_start + col - m_index_start; + T t = abs(m_v[offs]); + if (t > max) { + j = k; + max = t; + } + } + return j; +} + +template void square_dense_submatrix::pivot(unsigned i, lp_settings & settings) { + divide_row_by_pivot(i); + for (unsigned k = i + 1; k < m_parent->dimension(); k++) + pivot_row_to_row(i, k, settings); +} + +template void square_dense_submatrix::pivot_row_to_row(unsigned i, unsigned row, lp_settings & settings) { + lean_assert(i < row); + unsigned pj = adjust_column(i); // the pivot column + unsigned pjd = pj - m_index_start; + unsigned pivot_row_offset = (i-m_index_start)*m_dim; + T pivot = m_v[pivot_row_offset + pjd]; + unsigned row_offset= (row-m_index_start)*m_dim; + T m = m_v[row_offset + pjd]; + lean_assert(!is_zero(pivot)); + m_v[row_offset + pjd] = -m * pivot; // creating L matrix + for (unsigned j = m_index_start; j < m_parent->dimension(); j++) { + if (j == pj) { + pivot_row_offset++; + row_offset++; + continue; + } + auto t = m_v[row_offset] - m_v[pivot_row_offset] * m; + if (settings.abs_val_is_smaller_than_drop_tolerance(t)) { + m_v[row_offset] = zero_of_type(); + } else { + m_v[row_offset] = t; + } + row_offset++; pivot_row_offset++; + // at the same time we pivot the L too + } +} + +template void square_dense_submatrix::divide_row_by_pivot(unsigned i) { + unsigned pj = adjust_column(i); // the pivot column + unsigned irow_offset = (i - m_index_start) * m_dim; + T pivot = m_v[irow_offset + pj - m_index_start]; + lean_assert(!is_zero(pivot)); + for (unsigned k = m_index_start; k < m_parent->dimension(); k++) { + if (k == pj){ + m_v[irow_offset++] = one_of_type() / pivot; // creating the L matrix diagonal + continue; + } + m_v[irow_offset++] /= pivot; + } +} + +template void square_dense_submatrix::update_parent_matrix(lp_settings & settings) { + for (unsigned i = m_index_start; i < m_parent->dimension(); i++) + update_existing_or_delete_in_parent_matrix_for_row(i, settings); + push_new_elements_to_parent_matrix(settings); + for (unsigned i = m_index_start; i < m_parent->dimension(); i++) + m_parent->set_max_in_row(m_parent->adjust_row(i)); +} + +template void square_dense_submatrix::update_existing_or_delete_in_parent_matrix_for_row(unsigned i, lp_settings & settings) { + bool diag_updated = false; + unsigned ai = m_parent->adjust_row(i); + auto & row_vals = m_parent->get_row_values(ai); + for (unsigned k = 0; k < row_vals.size(); k++) { + auto & iv = row_vals[k]; + unsigned j = m_parent->adjust_column_inverse(iv.m_index); + if (j < i) { + m_parent->remove_element(row_vals, iv); + k--; + } else if (i == j) { + m_parent->m_columns[iv.m_index].m_values[iv.m_other].set_value(iv.m_value = one_of_type()); + diag_updated = true; + } else { // j > i + T & v = (*this)[i][j]; + if (settings.abs_val_is_smaller_than_drop_tolerance(v)) { + m_parent->remove_element(row_vals, iv); + k--; + } else { + m_parent->m_columns[iv.m_index].m_values[iv.m_other].set_value(iv.m_value = v); + v = zero_of_type(); // only new elements are left above the diagonal + } + } + } + if (!diag_updated) { + unsigned aj = m_parent->adjust_column(i); + m_parent->add_new_element(ai, aj, one_of_type()); + } +} + +template void square_dense_submatrix::push_new_elements_to_parent_matrix(lp_settings & settings) { + for (unsigned i = m_index_start; i < m_parent->dimension() - 1; i++) { + unsigned ai = m_parent->adjust_row(i); + for (unsigned j = i + 1; j < m_parent->dimension(); j++) { + T & v = (*this)[i][j]; + if (!settings.abs_val_is_smaller_than_drop_tolerance(v)) { + unsigned aj = m_parent->adjust_column(j); + m_parent->add_new_element(ai, aj, v); + } + v = zero_of_type(); // leave only L elements now + } + } +} +template +template +L square_dense_submatrix::row_by_vector_product(unsigned i, const vector & v) { + lean_assert(i >= m_index_start); + + unsigned row_in_subm = i - m_index_start; + unsigned row_offset = row_in_subm * m_dim; + L r = zero_of_type(); + for (unsigned j = 0; j < m_dim; j++) + r += m_v[row_offset + j] * v[adjust_column_inverse(m_index_start + j)]; + return r; +} + +template +template +L square_dense_submatrix::column_by_vector_product(unsigned j, const vector & v) { + lean_assert(j >= m_index_start); + + unsigned offset = j - m_index_start; + L r = zero_of_type(); + for (unsigned i = 0; i < m_dim; i++, offset += m_dim) + r += m_v[offset] * v[adjust_row_inverse(m_index_start + i)]; + return r; +} +template +template +L square_dense_submatrix::row_by_indexed_vector_product(unsigned i, const indexed_vector & v) { + lean_assert(i >= m_index_start); + + unsigned row_in_subm = i - m_index_start; + unsigned row_offset = row_in_subm * m_dim; + L r = zero_of_type(); + for (unsigned j = 0; j < m_dim; j++) + r += m_v[row_offset + j] * v[adjust_column_inverse(m_index_start + j)]; + return r; +} +template +template +void square_dense_submatrix::apply_from_left_local(indexed_vector & w, lp_settings & settings) { +#ifdef LEAN_DEBUG + // dense_matrix deb(*this); + // vector deb_w(w.m_data.size()); + // for (unsigned i = 0; i < w.m_data.size(); i++) + // deb_w[i] = w[i]; + + // deb.apply_from_left(deb_w); +#endif // use indexed vector here + +#ifndef DO_NOT_USE_INDEX + vector t(m_parent->dimension(), zero_of_type()); + for (auto k : w.m_index) { + unsigned j = adjust_column(k); // k-th element will contribute only to column j + if (j < m_index_start || j >= this->m_index_start + this->m_dim) { // it is a unit matrix outside + t[adjust_row_inverse(j)] = w[k]; + } else { + const L & v = w[k]; + for (unsigned i = 0; i < m_dim; i++) { + unsigned row = adjust_row_inverse(m_index_start + i); + unsigned offs = i * m_dim + j - m_index_start; + t[row] += m_v[offs] * v; + } + } + } + w.m_index.clear(); + for (unsigned i = 0; i < m_parent->dimension(); i++) { + const L & v = t[i]; + if (!settings.abs_val_is_smaller_than_drop_tolerance(v)){ + w.m_index.push_back(i); + w.m_data[i] = v; + } else { + w.m_data[i] = zero_of_type(); + } + } +#else + vector t(m_parent->dimension()); + for (unsigned i = 0; i < m_index_start; i++) { + t[adjust_row_inverse(i)] = w[adjust_column_inverse(i)]; + } + for (unsigned i = m_index_start; i < m_parent->dimension(); i++){ + t[adjust_row_inverse(i)] = row_by_indexed_vector_product(i, w); + } + for (unsigned i = 0; i < m_parent->dimension(); i++) { + w.set_value(t[i], i); + } + for (unsigned i = 0; i < m_parent->dimension(); i++) { + const L & v = t[i]; + if (!is_zero(v)) + w.m_index.push_back(i); + w.m_data[i] = v; + } +#endif +#ifdef LEAN_DEBUG + // cout << "w final" << endl; + // print_vector(w.m_data); + // lean_assert(vectors_are_equal(deb_w, w.m_data)); + // lean_assert(w.is_OK()); +#endif +} + +template +template +void square_dense_submatrix::apply_from_left_to_vector(vector & w) { + // lp_settings & settings) { + // dense_matrix deb(*this); + // vector deb_w(w); + // deb.apply_from_left_to_X(deb_w, settings); + // // cout << "deb" << endl; + // // print_matrix(deb); + // // cout << "w" << endl; + // // print_vector(w.m_data); + // // cout << "deb_w" << endl; + // // print_vector(deb_w); + vector t(m_parent->dimension()); + for (unsigned i = 0; i < m_index_start; i++) { + t[adjust_row_inverse(i)] = w[adjust_column_inverse(i)]; + } + for (unsigned i = m_index_start; i < m_parent->dimension(); i++){ + t[adjust_row_inverse(i)] = row_by_vector_product(i, w); + } + for (unsigned i = 0; i < m_parent->dimension(); i++) { + w[i] = t[i]; + } +#ifdef LEAN_DEBUG + // cout << "w final" << endl; + // print_vector(w.m_data); + // lean_assert(vectors_are_equal(deb_w, w)); +#endif +} + +template bool square_dense_submatrix::is_L_matrix() const { +#ifdef LEAN_DEBUG + lean_assert(m_row_permutation.is_identity()); + for (unsigned i = 0; i < m_parent->dimension(); i++) { + if (i < m_index_start) { + lean_assert(m_column_permutation[i] == i); + continue; + } + unsigned row_offs = (i-m_index_start)*m_dim; + for (unsigned k = 0; k < m_dim; k++) { + unsigned j = m_index_start + k; + unsigned jex = adjust_column_inverse(j); + if (jex > i) { + lean_assert(is_zero(m_v[row_offs + k])); + } else if (jex == i) { + lean_assert(!is_zero(m_v[row_offs + k])); + } + } + } +#endif + return true; +} + +template void square_dense_submatrix::apply_from_right(vector & w) { +#ifdef LEAN_DEBUG + // dense_matrix deb(*this); + // vector deb_w(w); + // deb.apply_from_right(deb_w); +#endif + vector t(w.size()); + + for (unsigned j = 0; j < m_index_start; j++) { + t[adjust_column_inverse(j)] = w[adjust_row_inverse(j)]; + } + unsigned end = m_index_start + m_dim; + for (unsigned j = end; j < m_parent->dimension(); j++) { + t[adjust_column_inverse(j)] = w[adjust_row_inverse(j)]; + } + for (unsigned j = m_index_start; j < end; j++) { + t[adjust_column_inverse(j)] = column_by_vector_product(j, w); + } + w = t; +#ifdef LEAN_DEBUG + // lean_assert(vector_are_equal(deb_w, w)); +#endif +} + + + + +#ifdef LEAN_DEBUG + +template T square_dense_submatrix::get_elem (unsigned i, unsigned j) const { + i = adjust_row(i); + j = adjust_column(j); + if (i < m_index_start || j < m_index_start) + return i == j? one_of_type() : zero_of_type(); + unsigned offs = (i - m_index_start)* m_dim + j - m_index_start; + return m_v[offs]; +} + +#endif +template void square_dense_submatrix::conjugate_by_permutation(permutation_matrix & q) { + m_row_permutation.multiply_by_permutation_from_left(q); + m_column_permutation.multiply_by_reverse_from_right(q); +} +} diff --git a/src/util/lp/square_dense_submatrix_instances.cpp b/src/util/lp/square_dense_submatrix_instances.cpp new file mode 100644 index 000000000..7d45aaaa1 --- /dev/null +++ b/src/util/lp/square_dense_submatrix_instances.cpp @@ -0,0 +1,33 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include +#include "util/vector.h" +#include "util/lp/square_dense_submatrix.hpp" +template void lean::square_dense_submatrix::init(lean::sparse_matrix*, unsigned int); +template lean::square_dense_submatrix::square_dense_submatrix(lean::sparse_matrix*, unsigned int); +template void lean::square_dense_submatrix::update_parent_matrix(lean::lp_settings&); +template bool lean::square_dense_submatrix::is_L_matrix() const; +template void lean::square_dense_submatrix::conjugate_by_permutation(lean::permutation_matrix&); +template int lean::square_dense_submatrix::find_pivot_column_in_row(unsigned int) const; +template void lean::square_dense_submatrix::pivot(unsigned int, lean::lp_settings&); +template lean::square_dense_submatrix >::square_dense_submatrix(lean::sparse_matrix >*, unsigned int); +template void lean::square_dense_submatrix >::update_parent_matrix(lean::lp_settings&); +template bool lean::square_dense_submatrix >::is_L_matrix() const; +template void lean::square_dense_submatrix >::conjugate_by_permutation(lean::permutation_matrix >&); +template int lean::square_dense_submatrix >::find_pivot_column_in_row(unsigned int) const; +template void lean::square_dense_submatrix >::pivot(unsigned int, lean::lp_settings&); +#ifdef LEAN_DEBUG +template double lean::square_dense_submatrix::get_elem(unsigned int, unsigned int) const; +#endif +template void lean::square_dense_submatrix::apply_from_right(vector&); + +template void lean::square_dense_submatrix::apply_from_left_local(lean::indexed_vector&, lean::lp_settings&); +template void lean::square_dense_submatrix::apply_from_left_to_vector(vector&); +template lean::square_dense_submatrix::square_dense_submatrix(lean::sparse_matrix*, unsigned int); +template void lean::square_dense_submatrix::update_parent_matrix(lean::lp_settings&); +template bool lean::square_dense_submatrix::is_L_matrix() const; +template void lean::square_dense_submatrix::conjugate_by_permutation(lean::permutation_matrix&); +template int lean::square_dense_submatrix::find_pivot_column_in_row(unsigned int) const; +template void lean::square_dense_submatrix::pivot(unsigned int, lean::lp_settings&); diff --git a/src/util/lp/stacked_map.h b/src/util/lp/stacked_map.h new file mode 100644 index 000000000..4692540dd --- /dev/null +++ b/src/util/lp/stacked_map.h @@ -0,0 +1,179 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ + +#pragma once +// this class implements a map with some stack functionality +#include +#include +#include +namespace lean { + + +template , + typename KeyEqual = std::equal_to, + typename Allocator = std::allocator< std::pair > > +class stacked_map { + struct delta { + std::unordered_set m_new; + std::unordered_map m_original_changed; + // std::unordered_map m_deb_copy; + }; + std::unordered_map m_map; + std::stack m_stack; +public: + class ref { + stacked_map & m_map; + const A & m_key; + public: + ref(stacked_map & m, const A & key) :m_map(m), m_key(key) {} + ref & operator=(const B & b) { + m_map.emplace_replace(m_key, b); + return *this; + } + ref & operator=(const ref & b) { lean_assert(false); return *this; } + operator const B&() const { + auto it = m_map.m_map.find(m_key); + lean_assert(it != m_map.m_map.end()); + return it->second; + } + }; +private: + void emplace_replace(const A & a, const B & b) { + if (!m_stack.empty()) { + delta & d = m_stack.top(); + auto it = m_map.find(a); + if (it == m_map.end()) { + d.m_new.insert(a); + m_map.emplace(a, b); + } else if (it->second != b) { + auto nit = d.m_new.find(a); + if (nit == d.m_new.end()) { // we do not have the old key + auto & orig_changed= d.m_original_changed; + auto itt = orig_changed.find(a); + if (itt == orig_changed.end()) { + orig_changed.emplace(a, it->second); + } else if (itt->second == b) { + orig_changed.erase(itt); + } + } + it->second = b; + } + } else { // there is no stack: just emplace or replace + m_map[a] = b; + } + } +public: + ref operator[] (const A & a) { + return ref(*this, a); + } + + const B & operator[]( const A & a) const { + auto it = m_map.find(a); + if (it == m_map.end()) { + lean_assert(false); + } + + return it->second; + } + + bool try_get_value(const A& key, B& val) const { + auto it = m_map.find(key); + if (it == m_map.end()) + return false; + + val = it->second; + return true; + } + bool try_get_value(const A&& key, B& val) const { + auto it = m_map.find(std::move(key)); + if (it == m_map.end()) + return false; + + val = it->second; + return true; + } + + unsigned size() const { + return m_map.size(); + } + + bool contains(const A & key) const { + return m_map.find(key) != m_map.end(); + } + + bool contains(const A && key) const { + return m_map.find(std::move(key)) != m_map.end(); + } + + void push() { + delta d; + // d.m_deb_copy = m_map; + m_stack.push(d); + } + + void pop() { + pop(1); + } + void pop(unsigned k) { + while (k-- > 0) { + if (m_stack.empty()) + return; + delta & d = m_stack.top(); + for (auto & t : d.m_new) { + m_map.erase(t); + } + for (auto & t: d.m_original_changed) { + m_map[t.first] = t.second; + } + // lean_assert(d.m_deb_copy == m_map); + m_stack.pop(); + } + } + + void erase(const A & key) { + if (m_stack.empty()) { + m_map.erase(key); + return; + } + + delta & d = m_stack.top(); + auto it = m_map.find(key); + if (it == m_map.end()) { + lean_assert(d.m_new.find(key) == d.m_new.end()); + return; + } + auto &orig_changed = d.m_original_changed; + auto nit = d.m_new.find(key); + if (nit == d.m_new.end()) { // key is old + if (orig_changed.find(key) == orig_changed.end()) + orig_changed.emplace(it->first, it->second); // need to restore + } else { // k is new + lean_assert(orig_changed.find(key) == orig_changed.end()); + d.m_new.erase(nit); + } + + m_map.erase(it); + } + + void clear() { + if (m_stack.empty()) { + m_map.clear(); + return; + } + + delta & d = m_stack.top(); + auto & oc = d.m_original_changed; + for (auto & p : m_map) { + const auto & it = oc.find(p.first); + if (it == oc.end() && d.m_new.find(p.first) == d.m_new.end()) + oc.emplace(p.first, p.second); + } + m_map.clear(); + } + + const std::unordered_map& operator()() const { return m_map;} +}; +} diff --git a/src/util/lp/stacked_unordered_set.h b/src/util/lp/stacked_unordered_set.h new file mode 100644 index 000000000..69c4cf03b --- /dev/null +++ b/src/util/lp/stacked_unordered_set.h @@ -0,0 +1,92 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ + +#pragma once +// this class implements an unordered_set with some stack functionality +#include +#include +#include +namespace lean { + +template , + typename KeyEqual = std::equal_to, + typename Allocator = std::allocator + > class stacked_unordered_set { + struct delta { + std::unordered_set m_inserted; + std::unordered_set m_erased; + std::unordered_set m_deb_copy; + }; + std::unordered_set m_set; + std::stack m_stack; +public: + void insert(const A & a) { + if (m_stack.empty()) { + m_set.insert(a); + } else if (m_set.find(a) == m_set.end()) { + m_set.insert(a); + size_t in_erased = m_stack.top().m_erased.erase(a); + if (in_erased == 0) { + m_stack.top().m_inserted.insert(a); + } + } + } + + void erase(const A &a) { + if (m_stack.empty()) { + m_set.erase(a); + return; + } + auto erased = m_set.erase(a); + if (erased == 1) { + auto was_new = m_stack.top().m_inserted.erase(a); + if (was_new == 0) { + m_stack.top().m_erased.insert(a); + } + } + } + + unsigned size() const { + return m_set.size(); + } + + bool contains(A & key) const { + return m_set.find(key) != m_set.end(); + } + + bool contains(A && key) const { + return m_set.find(std::move(key)) != m_set.end(); + } + + void push() { + delta d; + d.m_deb_copy = m_set; + m_stack.push(d); + } + + void pop() { + pop(1); + } + void pop(unsigned k) { + while (k-- > 0) { + if (m_stack.empty()) + return; + delta & d = m_stack.top(); + for (auto & t : d.m_inserted) { + m_set.erase(t); + } + for (auto & t : d.m_erased) { + m_set.insert(t); + } + lean_assert(d.m_deb_copy == m_set); + m_stack.pop(); + } + } + + const std::unordered_set& operator()() const { return m_set;} +}; +} + diff --git a/src/util/lp/stacked_value.h b/src/util/lp/stacked_value.h new file mode 100644 index 000000000..2a1e85be7 --- /dev/null +++ b/src/util/lp/stacked_value.h @@ -0,0 +1,65 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ + +#pragma once +// add to value the stack semantics +#include +namespace lean { +template class stacked_value { + T m_value; + std::stack m_stack; +public: + void push() { + m_stack.push(m_value); + } + + unsigned stack_size() const { + return static_cast(m_stack.size()); + } + + void pop() { + pop(1); + } + void pop(unsigned k) { + while (k-- > 0) { + if (m_stack.empty()) + return; + m_value = m_stack.top(); + m_stack.pop(); + } + } + + stacked_value() {} + stacked_value(const T& m) { + m_value = m; + } + stacked_value(const T&& m) { + m_value = std::move(m); + } + + T& operator=(T arg) { // copy/move constructor + m_value = arg; + return m_value; + } + + operator T&() { + return m_value; + } + + operator const T&() const { + return m_value; + } + + T & operator()() { + return m_value; + } + + const T & operator()() const { + return m_value; + } + + +}; +} diff --git a/src/util/lp/stacked_vector.h b/src/util/lp/stacked_vector.h new file mode 100644 index 000000000..3f39dd346 --- /dev/null +++ b/src/util/lp/stacked_vector.h @@ -0,0 +1,167 @@ +/* +Copyright (c) 2017 Microsoft Corporation +Author: Lev Nachmanson +*/ +#pragma once +#include +#include +#include +#include "util/vector.h" +namespace lean { +template < typename B> class stacked_vector { + vector m_stack_of_vector_sizes; + vector m_stack_of_change_sizes; + vector> m_changes; + vector m_vector; +public: + class ref { + stacked_vector & m_vec; + unsigned m_i; + public: + ref(stacked_vector &m, unsigned key) :m_vec(m), m_i(key) { + lean_assert(key < m.size()); + } + ref & operator=(const B & b) { + m_vec.emplace_replace(m_i, b); + return *this; + } + ref & operator=(const ref & b) { + m_vec.emplace_replace(m_i, b.m_vec.m_vector[b.m_i]); + return *this; + } + operator const B&() const { + return m_vec.m_vector[m_i]; + } + + }; + + class ref_const { + const stacked_vector & m_vec; + unsigned m_i; + public: + ref_const(const stacked_vector &m, unsigned key) :m_vec(m), m_i(key) { + lean_assert(key < m.size()); + } + + operator const B&() const { + return m_vec.m_vector[m_i]; + } + + }; + +private: + void emplace_replace(unsigned i,const B & b) { + if (m_vector[i] != b) { + m_changes.push_back(std::make_pair(i, m_vector[i])); + m_vector[i] = b; + } + } +public: + + ref operator[] (unsigned a) { + return ref(*this, a); + } + + ref_const operator[] (unsigned a) const { + return ref_const(*this, a); + } + + /* + const B & operator[](unsigned a) const { + lean_assert(a < m_vector.size()); + return m_vector[a]; + } + */ + unsigned size() const { + return m_vector.size(); + } + + + void push() { + m_stack_of_change_sizes.push_back(m_changes.size()); + m_stack_of_vector_sizes.push_back(m_vector.size()); + } + + void pop() { + pop(1); + } + + template + void pop_tail(vector & v, unsigned k) { + lean_assert(v.size() >= k); + v.resize(v.size() - k); + } + + template + void resize(vector & v, unsigned new_size) { + v.resize(new_size); + } + + void pop(unsigned k) { + lean_assert(m_stack_of_vector_sizes.size() >= k); + lean_assert(k > 0); + resize(m_vector, m_stack_of_vector_sizes[m_stack_of_vector_sizes.size() - k]); + pop_tail(m_stack_of_vector_sizes, k); + unsigned first_change = m_stack_of_change_sizes[m_stack_of_change_sizes.size() - k]; + pop_tail(m_stack_of_change_sizes, k); + for (unsigned j = m_changes.size(); j-- > first_change; ) { + const auto & p = m_changes[j]; + unsigned jc = p.first; + if (jc < m_vector.size()) + m_vector[jc] = p.second; // restore the old value + } + resize(m_changes, first_change); + + /* + while (k-- > 0) { + + if (m_stack.empty()) + return; + + delta & d = m_stack.back(); + lean_assert(m_vector.size() >= d.m_size); + while (m_vector.size() > d.m_size) + m_vector.pop_back(); + + for (auto & t : d.m_original_changed) { + lean_assert(t.first < m_vector.size()); + m_vector[t.first] = t.second; + } + // lean_assert(d.m_deb_copy == m_vector); + m_stack.pop_back();*/ + } + + + // void clear() { + // if (m_stack.empty()) { + // m_vector.clear(); + // return; + // } + + // delta & d = m_stack.top(); + // auto & oc = d.m_original_changed; + // for (auto & p : m_vector) { + // const auto & it = oc.find(p.first); + // if (it == oc.end() && d.m_new.find(p.first) == d.m_new.end()) + // oc.emplace(p.first, p.second); + // } + // m_vector.clear(); + // } + + void push_back(const B & b) { + m_vector.push_back(b); + } + + void increase_size_by_one() { + m_vector.resize(m_vector.size() + 1); + } + + unsigned peek_size(unsigned k) const { + lean_assert(k > 0 && k <= m_stack_of_vector_sizes.size()); + return m_stack_of_vector_sizes[m_stack_of_vector_sizes.size() - k]; + } + + const vector& operator()() const { return m_vector; } +}; +} + diff --git a/src/util/lp/static_matrix.h b/src/util/lp/static_matrix.h new file mode 100644 index 000000000..5ef4b449f --- /dev/null +++ b/src/util/lp/static_matrix.h @@ -0,0 +1,364 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ + +#pragma once +#include "util/vector.h" +#include +#include +#include +#include "util/lp/sparse_vector.h" +#include "util/lp/indexed_vector.h" +#include "util/lp/permutation_matrix.h" +#include "util/lp/linear_combination_iterator.h" +#include +namespace lean { + +struct column_cell { + unsigned m_i; // points to the row + unsigned m_offset; // the offset of the element in the matrix row + column_cell(unsigned i, unsigned offset) : m_i(i), m_offset(offset) { + } +}; + +template +struct row_cell { + unsigned m_j; // points to the column + unsigned m_offset; // offset in column + row_cell(unsigned j, unsigned offset, T const & val) : m_j(j), m_offset(offset), m_value(val) { + } + const T & get_val() const { return m_value;} + T & get_val() { return m_value;} + void set_val(const T& v) { m_value = v; } + T m_value; +}; + +// each assignment for this matrix should be issued only once!!! +template +class static_matrix +#ifdef LEAN_DEBUG + : public matrix +#endif +{ + struct dim { + unsigned m_m; + unsigned m_n; + dim(unsigned m, unsigned n) :m_m(m), m_n(n) {} + }; + std::stack m_stack; + vector m_became_zeros; // the row indices that became zeroes during the pivoting +public: + typedef vector> row_strip; + typedef vector column_strip; + vector m_vector_of_row_offsets; + indexed_vector m_work_vector; + vector m_rows; + vector m_columns; + // starting inner classes + class ref { + static_matrix & m_matrix; + unsigned m_row; + unsigned m_col; + public: + ref(static_matrix & m, unsigned row, unsigned col):m_matrix(m), m_row(row), m_col(col) {} + ref & operator=(T const & v) { m_matrix.set( m_row, m_col, v); return *this; } + + ref & operator=(ref const & v) { m_matrix.set(m_row, m_col, v.m_matrix.get(v.m_row, v.m_col)); return *this; } + + operator T () const { return m_matrix.get_elem(m_row, m_col); } + }; + + class ref_row { + static_matrix & m_matrix; + unsigned m_row; + public: + ref_row(static_matrix & m, unsigned row):m_matrix(m), m_row(row) {} + ref operator[](unsigned col) const { return ref(m_matrix, m_row, col); } + }; + +public: + + const T & get_val(const column_cell & c) const { + return m_rows[c.m_i][c.m_offset].get_val(); + } + + void init_row_columns(unsigned m, unsigned n); + + // constructor with no parameters + static_matrix() {} + + // constructor + static_matrix(unsigned m, unsigned n): m_vector_of_row_offsets(n, -1) { + init_row_columns(m, n); + } + // constructor that copies columns of the basis from A + static_matrix(static_matrix const &A, unsigned * basis); + + void clear(); + + void init_vector_of_row_offsets(); + + void init_empty_matrix(unsigned m, unsigned n); + + unsigned row_count() const { return static_cast(m_rows.size()); } + + unsigned column_count() const { return static_cast(m_columns.size()); } + + unsigned lowest_row_in_column(unsigned col); + + void add_columns_at_the_end(unsigned delta); + void add_new_element(unsigned i, unsigned j, const T & v); + + void add_row() {m_rows.push_back(row_strip());} + void add_column() { + m_columns.push_back(column_strip()); + m_vector_of_row_offsets.push_back(-1); + } + + void forget_last_columns(unsigned how_many_to_forget); + + void remove_last_column(unsigned j); + + void remove_element(vector> & row, row_cell & elem_to_remove); + + void multiply_column(unsigned column, T const & alpha) { + for (auto & t : m_columns[column]) { + auto & r = m_rows[t.m_i][t.m_offset]; + r.m_value *= alpha; + } + } + + +#ifdef LEAN_DEBUG + void regen_domain(); +#endif + + // offs - offset in columns + row_cell make_row_cell(unsigned row, unsigned offs, T const & val) { + return row_cell(row, offs, val); + } + + column_cell make_column_cell(unsigned column, unsigned offset) { + return column_cell(column, offset); + } + + void set(unsigned row, unsigned col, T const & val); + + ref operator()(unsigned row, unsigned col) { return ref(*this, row, col); } + + std::set> get_domain(); + + void copy_column_to_indexed_vector(unsigned j, indexed_vector & v) const; + + T get_max_abs_in_row(unsigned row) const; + void add_column_to_vector (const T & a, unsigned j, T * v) const { + for (const auto & it : m_columns[j]) { + v[it.m_i] += a * get_val(it); + } + } + + T get_min_abs_in_row(unsigned row) const; + T get_max_abs_in_column(unsigned column) const; + + T get_min_abs_in_column(unsigned column) const; + +#ifdef LEAN_DEBUG + void check_consistency(); +#endif + + + void cross_out_row(unsigned k); + + // + void fix_row_indices_in_each_column_for_crossed_row(unsigned k); + + void cross_out_row_from_columns(unsigned k, row_strip & row); + + void cross_out_row_from_column(unsigned col, unsigned k); + + T get_elem(unsigned i, unsigned j) const; + + + unsigned number_of_non_zeroes_in_column(unsigned j) const { return m_columns[j].size(); } + + unsigned number_of_non_zeroes_in_row(unsigned i) const { return m_rows[i].size(); } + + unsigned number_of_non_zeroes() const { + unsigned ret = 0; + for (unsigned i = 0; i < row_count(); i++) + ret += number_of_non_zeroes_in_row(i); + return ret; + } + + void scan_row_to_work_vector(unsigned i); + + void clean_row_work_vector(unsigned i); + + +#ifdef LEAN_DEBUG + unsigned get_number_of_rows() const { return row_count(); } + unsigned get_number_of_columns() const { return column_count(); } + virtual void set_number_of_rows(unsigned /*m*/) { } + virtual void set_number_of_columns(unsigned /*n*/) { } +#endif + + T get_max_val_in_row(unsigned /* i */) const { lean_unreachable(); } + + T get_balance() const; + + T get_row_balance(unsigned row) const; + + bool is_correct() const; + void push() { + dim d(row_count(), column_count()); + m_stack.push(d); + } + + void pop_row_columns(const vector> & row) { + for (auto & c : row) { + unsigned j = c.m_j; + auto & col = m_columns[j]; + lean_assert(col[col.size() - 1].m_i == m_rows.size() -1 ); // todo : start here!!!! + col.pop_back(); + } + } + + + + void pop(unsigned k) { +#ifdef LEAN_DEBUG + std::set> pairs_to_remove_from_domain; +#endif + + + while (k-- > 0) { + if (m_stack.empty()) break; + unsigned m = m_stack.top().m_m; + while (m < row_count()) { + unsigned i = m_rows.size() -1 ; + auto & row = m_rows[i]; + pop_row_columns(row); + m_rows.pop_back(); // delete the last row + } + unsigned n = m_stack.top().m_n; + while (n < column_count()) + m_columns.pop_back(); // delete the last column + m_stack.pop(); + } + lean_assert(is_correct()); + } + + void multiply_row(unsigned row, T const & alpha) { + for (auto & t : m_rows[row]) { + t.m_value *= alpha; + } + } + + void divide_row(unsigned row, T const & alpha) { + for (auto & t : m_rows[row]) { + t.m_value /= alpha; + } + } + + T dot_product_with_column(const vector & y, unsigned j) const { + lean_assert(j < column_count()); + T ret = numeric_traits::zero(); + for (auto & it : m_columns[j]) { + ret += y[it.m_i] * get_val(it); // get_value_of_column_cell(it); + } + return ret; + } + + // pivot row i to row ii + bool pivot_row_to_row_given_cell(unsigned i, column_cell& c, unsigned); + void scan_row_ii_to_offset_vector(unsigned ii); + + void transpose_rows(unsigned i, unsigned ii) { + auto t = m_rows[i]; + m_rows[i] = m_rows[ii]; + m_rows[ii] = t; + // now fix the columns + for (auto & rc : m_rows[i]) { + column_cell & cc = m_columns[rc.m_j][rc.m_offset]; + lean_assert(cc.m_i == ii); + cc.m_i = i; + } + for (auto & rc : m_rows[ii]) { + column_cell & cc = m_columns[rc.m_j][rc.m_offset]; + lean_assert(cc.m_i == i); + cc.m_i = ii; + } + + } + + void fill_last_row_with_pivoting(linear_combination_iterator & it, const vector & basis_heading) { + lean_assert(numeric_traits::precise()); + lean_assert(row_count() > 0); + m_work_vector.resize(column_count()); + T a; + unsigned j; + while (it.next(a, j)) { + m_work_vector.set_value(-a, j); // we use the form -it + 1 = 0 + // but take care of the basis 1 later + } + + it.reset(); + // not iterate with pivoting + while (it.next(j)) { + int row_index = basis_heading[j]; + if (row_index < 0) + continue; + + T & alpha = m_work_vector[j]; // the pivot alpha + if (is_zero(alpha)) + continue; + + for (const auto & c : m_rows[row_index]) { + if (c.m_j == j) { + continue; + } + T & wv = m_work_vector.m_data[c.m_j]; + bool was_zero = is_zero(wv); + wv -= alpha * c.m_value; + if (was_zero) + m_work_vector.m_index.push_back(c.m_j); + else { + if (is_zero(wv)) { + m_work_vector.erase_from_index(c.m_j); + } + } + } + alpha = zero_of_type(); + m_work_vector.erase_from_index(j); + } + lean_assert(m_work_vector.is_OK()); + unsigned last_row = row_count() - 1; + + for (unsigned j : m_work_vector.m_index) { + set (last_row, j, m_work_vector.m_data[j]); + } + lean_assert(column_count() > 0); + set(last_row, column_count() - 1, one_of_type()); + } + + void copy_column_to_vector (unsigned j, vector & v) const { + v.resize(row_count(), numeric_traits::zero()); + for (auto & it : m_columns[j]) { + const T& val = get_val(it); + if (!is_zero(val)) + v[it.m_i] = val; + } + } + + template + L dot_product_with_row(unsigned row, const vector & w) const { + L ret = zero_of_type(); + lean_assert(row < m_rows.size()); + for (auto & it : m_rows[row]) { + ret += w[it.m_j] * it.get_val(); + } + return ret; + } +}; +} diff --git a/src/util/lp/static_matrix.hpp b/src/util/lp/static_matrix.hpp new file mode 100644 index 000000000..29357f296 --- /dev/null +++ b/src/util/lp/static_matrix.hpp @@ -0,0 +1,409 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include "util/vector.h" +#include +#include +#include "util/lp/static_matrix.h" +namespace lean { +// each assignment for this matrix should be issued only once!!! +template +void static_matrix::init_row_columns(unsigned m, unsigned n) { + lean_assert(m_rows.size() == 0 && m_columns.size() == 0); + for (unsigned i = 0; i < m; i++){ + m_rows.push_back(row_strip()); + } + for (unsigned j = 0; j < n; j++){ + m_columns.push_back(column_strip()); + } +} + + +template void static_matrix::scan_row_ii_to_offset_vector(unsigned ii) { + auto & rvals = m_rows[ii]; + unsigned size = rvals.size(); + for (unsigned j = 0; j < size; j++) + m_vector_of_row_offsets[rvals[j].m_j] = j; +} + + +template bool static_matrix::pivot_row_to_row_given_cell(unsigned i, column_cell & c, unsigned pivot_col) { + // std::cout << "ddd = " << ++lp_settings::ddd<< std::endl; + unsigned ii = c.m_i; + lean_assert(i < row_count() && ii < column_count()); + lean_assert(i != ii); + + m_became_zeros.reset(); + T alpha = -get_val(c); + lean_assert(!is_zero(alpha)); + auto & ii_row_vals = m_rows[ii]; + remove_element(ii_row_vals, ii_row_vals[c.m_offset]); + scan_row_ii_to_offset_vector(ii); + lean_assert(!is_zero(alpha)); + unsigned prev_size_ii = ii_row_vals.size(); + // run over the pivot row and update row ii + for (const auto & iv : m_rows[i]) { + unsigned j = iv.m_j; + if (j == pivot_col) continue; + T alv = alpha * iv.m_value; + lean_assert(!is_zero(iv.m_value)); + int j_offs = m_vector_of_row_offsets[j]; + if (j_offs == -1) { // it is a new element + add_new_element(ii, j, alv); + } + else { + auto & row_el_iv = ii_row_vals[j_offs]; + row_el_iv.m_value += alv; + if (is_zero(row_el_iv.m_value)) { + m_became_zeros.push_back(j_offs); + ensure_increasing(m_became_zeros); + } + } + } + + // clean the work vector + for (unsigned k = 0; k < prev_size_ii; k++) { + m_vector_of_row_offsets[ii_row_vals[k].m_j] = -1; + } + + for (unsigned k = m_became_zeros.size(); k-- > 0; ) { + unsigned j = m_became_zeros[k]; + remove_element(ii_row_vals, ii_row_vals[j]); + } + return !ii_row_vals.empty(); +} + + +// constructor that copies columns of the basis from A +template +static_matrix::static_matrix(static_matrix const &A, unsigned * /* basis */) : + m_vector_of_row_offsets(A.column_count(), numeric_traits::zero()) { + unsigned m = A.row_count(); + init_row_columns(m, m); + while (m--) { + for (auto & col : A.m_columns[m]){ + set(col.m_i, m, A.get_value_of_column_cell(col)); + } + } +} + +template void static_matrix::clear() { + m_vector_of_row_offsets.clear(); + m_rows.clear(); + m_columns.clear(); +} + +template void static_matrix::init_vector_of_row_offsets() { + m_vector_of_row_offsets.clear(); + m_vector_of_row_offsets.resize(column_count(), -1); +} + +template void static_matrix::init_empty_matrix(unsigned m, unsigned n) { + init_vector_of_row_offsets(); + init_row_columns(m, n); +} + +template unsigned static_matrix::lowest_row_in_column(unsigned col) { + lean_assert(col < column_count()); + column_strip & colstrip = m_columns[col]; + lean_assert(colstrip.size() > 0); + unsigned ret = 0; + for (auto & t : colstrip) { + if (t.m_i > ret) { + ret = t.m_i; + } + } + return ret; +} + +template void static_matrix::add_columns_at_the_end(unsigned delta) { + for (unsigned i = 0; i < delta; i++) + add_column(); +} + +template void static_matrix::forget_last_columns(unsigned how_many_to_forget) { + lean_assert(m_columns.size() >= how_many_to_forget); + unsigned j = column_count() - 1; + for (; how_many_to_forget > 0; how_many_to_forget--) { + remove_last_column(j --); + } +} + +template void static_matrix::remove_last_column(unsigned j) { + column_strip & col = m_columns.back(); + for (auto & it : col) { + auto & row = m_rows[it.m_i]; + unsigned offset = row.size() - 1; + for (auto row_it = row.rbegin(); row_it != row.rend(); row_it ++) { + if (row_it->m_j == j) { + row.erase(row.begin() + offset); + break; + } + offset--; + } + } + m_columns.pop_back(); + m_vector_of_row_offsets.pop_back(); +} + + + + +template void static_matrix::set(unsigned row, unsigned col, T const & val) { + if (numeric_traits::is_zero(val)) return; + lean_assert(row < row_count() && col < column_count()); + auto & r = m_rows[row]; + unsigned offs_in_cols = static_cast(m_columns[col].size()); + m_columns[col].push_back(make_column_cell(row, static_cast(r.size()))); + r.push_back(make_row_cell(col, offs_in_cols, val)); +} + +template +std::set> static_matrix::get_domain() { + std::set> ret; + for (unsigned i = 0; i < m_rows.size(); i++) { + for (auto it : m_rows[i]) { + ret.insert(std::make_pair(i, it.m_j)); + } + } + return ret; +} + + +template void static_matrix::copy_column_to_indexed_vector (unsigned j, indexed_vector & v) const { + lean_assert(j < m_columns.size()); + for (auto & it : m_columns[j]) { + const T& val = get_val(it); + if (!is_zero(val)) + v.set_value(val, it.m_i); + } +} + + + +template T static_matrix::get_max_abs_in_row(unsigned row) const { + T ret = numeric_traits::zero(); + for (auto & t : m_rows[row]) { + T a = abs(t.get_val()); + if (a > ret) { + ret = a; + } + } + return ret; +} + +template T static_matrix::get_min_abs_in_row(unsigned row) const { + bool first_time = true; + T ret = numeric_traits::zero(); + for (auto & t : m_rows[row]) { + T a = abs(t.get_val()); + if (first_time) { + ret = a; + first_time = false; + } else if (a < ret) { + ret = a; + } + } + return ret; +} + + +template T static_matrix::get_max_abs_in_column(unsigned column) const { + T ret = numeric_traits::zero(); + for (const auto & t : m_columns[column]) { + T a = abs(get_val(t)); + if (a > ret) { + ret = a; + } + } + return ret; +} + +template T static_matrix::get_min_abs_in_column(unsigned column) const { + bool first_time = true; + T ret = numeric_traits::zero(); + for (auto & t : m_columns[column]) { + T a = abs(get_val(t)); + if (first_time) { + first_time = false; + ret = a; + } else if (a < ret) { + ret = a; + } + } + return ret; +} + +#ifdef LEAN_DEBUG +template void static_matrix::check_consistency() { + std::unordered_map, T> by_rows; + for (int i = 0; i < m_rows.size(); i++){ + for (auto & t : m_rows[i]) { + std::pair p(i, t.m_j); + lean_assert(by_rows.find(p) == by_rows.end()); + by_rows[p] = t.get_val(); + } + } + std::unordered_map, T> by_cols; + for (int i = 0; i < m_columns.size(); i++){ + for (auto & t : m_columns[i]) { + std::pair p(t.m_i, i); + lean_assert(by_cols.find(p) == by_cols.end()); + by_cols[p] = get_val(t); + } + } + lean_assert(by_rows.size() == by_cols.size()); + + for (auto & t : by_rows) { + auto ic = by_cols.find(t.first); + if (ic == by_cols.end()){ + //std::cout << "rows have pair (" << t.first.first <<"," << t.first.second + // << "), but columns don't " << std::endl; + } + lean_assert(ic != by_cols.end()); + lean_assert(t.second == ic->second); + } +} +#endif + + +template void static_matrix::cross_out_row(unsigned k) { +#ifdef LEAN_DEBUG + check_consistency(); +#endif + cross_out_row_from_columns(k, m_rows[k]); + fix_row_indices_in_each_column_for_crossed_row(k); + m_rows.erase(m_rows.begin() + k); +#ifdef LEAN_DEBUG + regen_domain(); + check_consistency(); +#endif +} + + +template void static_matrix::fix_row_indices_in_each_column_for_crossed_row(unsigned k) { + for (unsigned j = 0; j < m_columns.size(); j++) { + auto & col = m_columns[j]; + for (int i = 0; i < col.size(); i++) { + if (col[i].m_i > k) { + col[i].m_i--; + } + } + } +} + +template void static_matrix::cross_out_row_from_columns(unsigned k, row_strip & row) { + for (auto & t : row) { + cross_out_row_from_column(t.m_j, k); + } +} + +template void static_matrix::cross_out_row_from_column(unsigned col, unsigned k) { + auto & s = m_columns[col]; + for (unsigned i = 0; i < s.size(); i++) { + if (s[i].m_i == k) { + s.erase(s.begin() + i); + break; + } + } +} + +template T static_matrix::get_elem(unsigned i, unsigned j) const { // should not be used in efficient code !!!! + for (auto & t : m_rows[i]) { + if (t.m_j == j) { + return t.get_val(); + } + } + return numeric_traits::zero(); +} + + +template T static_matrix::get_balance() const { + T ret = zero_of_type(); + for (unsigned i = 0; i < row_count(); i++) { + ret += get_row_balance(i); + } + return ret; +} + +template T static_matrix::get_row_balance(unsigned row) const { + T ret = zero_of_type(); + for (auto & t : m_rows[row]) { + if (numeric_traits::is_zero(t.get_val())) continue; + T a = abs(t.get_val()); + numeric_traits::log(a); + ret += a * a; + } + return ret; +} + +template bool static_matrix::is_correct() const { + for (auto & r : m_rows) { + std::unordered_set s; + for (auto & rc : r) { + if (s.find(rc.m_j) != s.end()) { + std::cout << "found column " << rc.m_j << " twice in a row" << std::endl; + return false; + } + s.insert(rc.m_j); + if (rc.m_j >= m_columns.size()) + return false; + if (rc.m_offset >= m_columns[rc.m_j].size()) + return false; + if (rc.get_val() != get_val(m_columns[rc.m_j][rc.m_offset])) + return false; + } + } + for (auto & c : m_columns) { + std::unordered_set s; + for (auto & cc : c) { + if (s.find(cc.m_i) != s.end()) { + std::cout << "found row " << cc.m_i << " twice in a column" << std::endl; + return false; + } + s.insert(cc.m_i); + if (cc.m_i >= m_rows.size()) + return false; + if (cc.m_offset >= m_rows[cc.m_i].size()) + return false; + if (get_val(cc) != m_rows[cc.m_i][cc.m_offset].get_val()) + return false; + } + } + + + + return true; +} + +template +void static_matrix::remove_element(vector> & row_vals, row_cell & row_el_iv) { + unsigned column_offset = row_el_iv.m_offset; + auto & column_vals = m_columns[row_el_iv.m_j]; + column_cell& cs = m_columns[row_el_iv.m_j][column_offset]; + unsigned row_offset = cs.m_offset; + if (column_offset != column_vals.size() - 1) { + auto & cc = column_vals[column_offset] = column_vals.back(); // copy from the tail + m_rows[cc.m_i][cc.m_offset].m_offset = column_offset; + } + + if (row_offset != row_vals.size() - 1) { + auto & rc = row_vals[row_offset] = row_vals.back(); // copy from the tail + m_columns[rc.m_j][rc.m_offset].m_offset = row_offset; + } + + column_vals.pop_back(); + row_vals.pop_back(); +} +template +void static_matrix::add_new_element(unsigned row, unsigned col, const T& val) { + auto & row_vals = m_rows[row]; + auto & col_vals = m_columns[col]; + unsigned row_el_offs = static_cast(row_vals.size()); + unsigned col_el_offs = static_cast(col_vals.size()); + row_vals.push_back(row_cell(col, col_el_offs, val)); + col_vals.push_back(column_cell(row, row_el_offs)); +} + +} diff --git a/src/util/lp/static_matrix_instances.cpp b/src/util/lp/static_matrix_instances.cpp new file mode 100644 index 000000000..5d6be17ec --- /dev/null +++ b/src/util/lp/static_matrix_instances.cpp @@ -0,0 +1,67 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include "util/vector.h" +#include +#include +#include +#include "util/lp/static_matrix.hpp" +#include "util/lp/lp_core_solver_base.h" +#include "util/lp/lp_dual_core_solver.h" +#include "util/lp/lp_dual_simplex.h" +#include "util/lp/lp_primal_core_solver.h" +#include "util/lp/scaler.h" +#include "util/lp/lar_solver.h" +namespace lean { +template void static_matrix::add_columns_at_the_end(unsigned int); +template void static_matrix::clear(); +#ifdef LEAN_DEBUG +template bool static_matrix::is_correct() const; +#endif +template void static_matrix::copy_column_to_indexed_vector(unsigned int, indexed_vector&) const; + +template double static_matrix::get_balance() const; +template std::set> static_matrix::get_domain(); +template std::set> lean::static_matrix::get_domain(); +template std::set> lean::static_matrix >::get_domain(); +template double static_matrix::get_elem(unsigned int, unsigned int) const; +template double static_matrix::get_max_abs_in_column(unsigned int) const; +template double static_matrix::get_min_abs_in_column(unsigned int) const; +template double static_matrix::get_min_abs_in_row(unsigned int) const; +template void static_matrix::init_empty_matrix(unsigned int, unsigned int); +template void static_matrix::init_row_columns(unsigned int, unsigned int); +template static_matrix::ref & static_matrix::ref::operator=(double const&); +template void static_matrix::set(unsigned int, unsigned int, double const&); +template static_matrix::static_matrix(unsigned int, unsigned int); +template void static_matrix::add_column_to_vector(mpq const&, unsigned int, mpq*) const; +template void static_matrix::add_columns_at_the_end(unsigned int); +template bool static_matrix::is_correct() const; +template void static_matrix::copy_column_to_indexed_vector(unsigned int, indexed_vector&) const; + +template mpq static_matrix::get_balance() const; +template mpq static_matrix::get_elem(unsigned int, unsigned int) const; +template mpq static_matrix::get_max_abs_in_column(unsigned int) const; +template mpq static_matrix::get_max_abs_in_row(unsigned int) const; +template double static_matrix::get_max_abs_in_row(unsigned int) const; +template mpq static_matrix::get_min_abs_in_column(unsigned int) const; +template mpq static_matrix::get_min_abs_in_row(unsigned int) const; +template void static_matrix::init_row_columns(unsigned int, unsigned int); +template static_matrix::ref& static_matrix::ref::operator=(mpq const&); +template void static_matrix::set(unsigned int, unsigned int, mpq const&); + +template static_matrix::static_matrix(unsigned int, unsigned int); +#ifdef LEAN_DEBUG +template bool static_matrix >::is_correct() const; +#endif +template void static_matrix >::copy_column_to_indexed_vector(unsigned int, indexed_vector&) const; +template mpq static_matrix >::get_elem(unsigned int, unsigned int) const; +template void static_matrix >::init_empty_matrix(unsigned int, unsigned int); +template void static_matrix >::set(unsigned int, unsigned int, mpq const&); + + +template bool lean::static_matrix::pivot_row_to_row_given_cell(unsigned int, column_cell &, unsigned int); +template bool lean::static_matrix::pivot_row_to_row_given_cell(unsigned int, column_cell& , unsigned int); +template bool lean::static_matrix >::pivot_row_to_row_given_cell(unsigned int, column_cell&, unsigned int); +} + diff --git a/src/util/lp/tail_matrix.h b/src/util/lp/tail_matrix.h new file mode 100644 index 000000000..c337b0933 --- /dev/null +++ b/src/util/lp/tail_matrix.h @@ -0,0 +1,28 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ + +#pragma once +#include "util/vector.h" +#include "util/lp/indexed_vector.h" +#include "util/lp/matrix.h" +#include "util/lp/lp_settings.h" +// These matrices appear at the end of the list + +namespace lean { +template +class tail_matrix +#ifdef LEAN_DEBUG + : public matrix +#endif +{ +public: + virtual void apply_from_left_to_T(indexed_vector & w, lp_settings & settings) = 0; + virtual void apply_from_left(vector & w, lp_settings & settings) = 0; + virtual void apply_from_right(vector & w) = 0; + virtual void apply_from_right(indexed_vector & w) = 0; + virtual ~tail_matrix() {} + virtual bool is_dense() const = 0; +}; +} diff --git a/src/util/lp/test_bound_analyzer.h b/src/util/lp/test_bound_analyzer.h new file mode 100644 index 000000000..262c610c7 --- /dev/null +++ b/src/util/lp/test_bound_analyzer.h @@ -0,0 +1,260 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#pragma once +#include "util/vector.h" +#include "util/lp/linear_combination_iterator.h" +#include "util/lp/implied_bound.h" +#include "util/lp/lp_settings.h" +#include +// this class is for testing only + +// We have an equality : sum by j of row[j]*x[j] = rs +// We try to pin a var by pushing the total by using the variable bounds +// In a loop we drive the partial sum down, denoting the variables of this process by _u. +// In the same loop trying to pin variables by pushing the partial sum up, denoting the variable related to it by _l + +// here in addition we assume that all coefficient in the row are positive +namespace lean { + +class test_bound_analyzer { + linear_combination_iterator & m_it; + std::function& m_low_bounds; + std::function& m_upper_bounds; + std::function m_column_types; + vector & m_implied_bounds; + vector m_coeffs; + int m_coeff_sign; + vector m_index; + unsigned m_row_or_term_index; + std::function & m_try_get_found_bound; +public : + // constructor + test_bound_analyzer(linear_combination_iterator &it, + std::function & low_bounds, + std::function & upper_bounds, + std::function column_types, + vector & evidence_vector, + unsigned row_or_term_index, + std::function & try_get_found_bound) : + m_it(it), + m_low_bounds(low_bounds), + m_upper_bounds(upper_bounds), + m_column_types(column_types), + m_implied_bounds(evidence_vector), + m_row_or_term_index(row_or_term_index), + m_try_get_found_bound(try_get_found_bound) + { + m_it.reset(); + unsigned i; + mpq a; + while (m_it.next(a, i) ) { + m_coeffs.push_back(a); + m_index.push_back(i); + } + } + + static int sign (const mpq & t) { return is_pos(t) ? 1: -1;} + + void analyze() { + // We have the equality sum by j of row[j]*x[j] = m_rs + // We try to pin a var by pushing the total of the partial sum down, denoting the variable of this process by _u. + for (unsigned i = 0; i < m_index.size(); i++) { + analyze_i(i); + } + } + void analyze_i(unsigned i) { + // set the m_coeff_is_pos + m_coeff_sign = sign(m_coeffs[i]); + analyze_i_for_lower(i); + analyze_i_for_upper(i); + } + + void analyze_i_for_upper(unsigned i) { + mpq l; + bool strict = false; + lean_assert(is_zero(l)); + for (unsigned k = 0; k < m_index.size(); k++) { + if (k == i) + continue; + mpq lb; + bool str; + if (!upper_bound_of_monoid(k, lb, str)) { + return; + } + l += lb; + if (str) + strict = true; + } + l /= m_coeffs[i]; + unsigned j = m_index[i]; + switch(m_column_types(j)) { + case column_type::fixed: + case column_type::boxed: + case column_type::upper_bound: + { + const auto & lb = m_upper_bounds(j); + if (l > lb.x || (l == lb.x && !(is_zero(lb.y) && strict))) { + break; // no improvement on the existing upper bound + } + } + default: + // std::cout << " got an upper bound with " << T_to_string(l) << "\n"; + m_implied_bounds.push_back(implied_bound(l, j, false, is_pos(m_coeffs[i]), m_row_or_term_index, strict)); + } + } + + + bool low_bound_of_monoid(unsigned k, mpq & lb, bool &strict) const { + int s = - m_coeff_sign * sign(m_coeffs[k]); + unsigned j = m_index[k]; + if (s > 0) { + switch(m_column_types(j)) { + case column_type::fixed: + case column_type::boxed: + case column_type::low_bound: + lb = -m_coeffs[k] * m_low_bounds(j).x; + strict = !is_zero(m_low_bounds(j).y); + return true; + default: + return false; + } + } + + switch(m_column_types(j)) { + case column_type::fixed: + case column_type::boxed: + case column_type::upper_bound: + lb = -m_coeffs[k] * m_upper_bounds(j).x; + strict = !is_zero(m_upper_bounds(j).y); + return true; + default: + return false; + } + } + + bool upper_bound_of_monoid(unsigned k, mpq & lb, bool & strict) const { + int s = - m_coeff_sign * sign(m_coeffs[k]); + unsigned j = m_index[k]; + if (s > 0) { + switch(m_column_types(j)) { + case column_type::fixed: + case column_type::boxed: + case column_type::upper_bound: + lb = -m_coeffs[k] * m_upper_bounds(j).x; + strict = !is_zero(m_upper_bounds(j).y); + + return true; + default: + return false; + } + } + + switch(m_column_types(j)) { + case column_type::fixed: + case column_type::boxed: + case column_type::low_bound: + lb = -m_coeffs[k] * m_low_bounds(j).x; + strict = !is_zero(m_low_bounds(j).y); + return true; + default: + return false; + } + } + + void analyze_i_for_lower(unsigned i) { + mpq l; + lean_assert(is_zero(l)); + bool strict = false; + for (unsigned k = 0; k < m_index.size(); k++) { + if (k == i) + continue; + mpq lb; + bool str; + if (!low_bound_of_monoid(k, lb, str)) { + return; + } + if (str) + strict = true; + l += lb; + } + l /= m_coeffs[i]; + unsigned j = m_index[i]; + switch(m_column_types(j)) { + case column_type::fixed: + case column_type::boxed: + case column_type::low_bound: + { + const auto & lb = m_low_bounds(j); + if (l < lb.x || (l == lb.x && !(is_zero(lb.y) && strict))) { + break; // no improvement on the existing upper bound + } + } + default: + // std::cout << " got a lower bound with " << T_to_string(l) << "\n"; + m_implied_bounds.push_back(implied_bound(l, j, true, is_pos(m_coeffs[i]), m_row_or_term_index, strict)); + } + } + + bool low_bound_is_available(unsigned j) const { + switch(m_column_types(j)) { + case column_type::fixed: + case column_type::boxed: + case column_type::low_bound: + return true; + default: + return false; + } + } + + bool upper_bound_is_available(unsigned j) const { + switch(m_column_types(j)) { + case column_type::fixed: + case column_type::boxed: + case column_type::upper_bound: + return true; + default: + return false; + } + } + + bool try_get_best_avail_bound(unsigned j, bool is_lower_bound, mpq & best_bound, bool & strict_of_best_bound) const { + if (m_try_get_found_bound(j, is_lower_bound, best_bound, strict_of_best_bound)) { + return true; + } + if (is_lower_bound) { + if (low_bound_is_available(j)) { + best_bound = m_low_bounds(j).x; + strict_of_best_bound = !is_zero(m_low_bounds(j).y); + return true; + } + } else { + if (upper_bound_is_available(j)) { + best_bound = m_upper_bounds(j).x; + strict_of_best_bound = !is_zero(m_low_bounds(j).y); + return true; + } + } + return false; + } + + bool bound_is_new(unsigned j, const mpq& b, bool is_lower_bound, bool strict) const { + mpq best_bound; + bool strict_of_best_bound; + if (try_get_best_avail_bound(j, is_lower_bound, best_bound, strict_of_best_bound)) { + if (is_lower_bound) { + if (b > best_bound || ( b != best_bound && (strict && !strict_of_best_bound))) // the second clouse stands for strong inequality + return true; + } else { + if (b < best_bound || ( b == best_bound && (strict && !strict_of_best_bound))) + return true; + } + return false; + } + return true; + } + +}; + +} diff --git a/src/util/lp/ul_pair.h b/src/util/lp/ul_pair.h new file mode 100644 index 000000000..0e96364ce --- /dev/null +++ b/src/util/lp/ul_pair.h @@ -0,0 +1,58 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ + +#pragma once +#include "util/vector.h" +#include +#include +#include +#include "util/lp/column_info.h" + +namespace lean { + + enum lconstraint_kind { + LE = -2, LT = -1 , GE = 2, GT = 1, EQ = 0 + }; + + inline std::ostream& operator<<(std::ostream& out, lconstraint_kind k) { + switch (k) { + case LE: return out << "<="; + case LT: return out << "<"; + case GE: return out << ">="; + case GT: return out << ">"; + case EQ: return out << "="; + } + return out << "??"; + } + +inline bool compare(const std::pair & a, const std::pair & b) { + return a.second < b.second; +} + +class ul_pair { + constraint_index m_low_bound_witness = static_cast(-1); + constraint_index m_upper_bound_witness = static_cast(-1); +public: + constraint_index& low_bound_witness() {return m_low_bound_witness;} + constraint_index low_bound_witness() const {return m_low_bound_witness;} + constraint_index& upper_bound_witness() { return m_upper_bound_witness;} + constraint_index upper_bound_witness() const {return m_upper_bound_witness;} + row_index m_i = static_cast(-1); + bool operator!=(const ul_pair & p) const { + return !(*this == p); + } + + bool operator==(const ul_pair & p) const { + return m_low_bound_witness == p.m_low_bound_witness + && m_upper_bound_witness == p.m_upper_bound_witness && + m_i == p.m_i; + } + // empty constructor + ul_pair(){} + ul_pair(row_index ri) : m_i(ri) {} + ul_pair(const ul_pair & o): m_low_bound_witness(o.m_low_bound_witness), m_upper_bound_witness(o.m_upper_bound_witness), m_i(o.m_i) {} +}; + +} 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/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_ */