diff --git a/CMakeLists.txt b/CMakeLists.txt index 185405490..0d188d0b0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -188,6 +188,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>) @@ -213,6 +214,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 ################################################################################ @@ -245,6 +256,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 @@ -263,18 +275,6 @@ else() message(STATUS "Not using libgmp") endif() -################################################################################ -# FOCI2 support -################################################################################ -# FIXME: What is this? -option(USE_FOCI2 "Use FOCI2" OFF) -if (USE_FOCI2) - message(FATAL_ERROR "TODO") - message(STATUS "Using FOCI2") -else() - message(STATUS "Not using FOCI2") -endif() - ################################################################################ # OpenMP support ################################################################################ @@ -288,6 +288,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. @@ -306,6 +307,23 @@ else() set(USE_OPENMP OFF CACHE BOOL "Use OpenMP" FORCE) endif() +################################################################################ +# API Log sync +################################################################################ +option(API_LOG_SYNC + "Use locking when logging Z3 API calls (experimental)" + OFF +) +if (API_LOG_SYNC) + if (NOT USE_OPENMP) + message(FATAL_ERROR "API_LOG_SYNC feature requires OpenMP") + endif() + list(APPEND Z3_COMPONENT_CXX_DEFINES "-DZ3_LOG_SYNC") + message(STATUS "Using API_LOG_SYNC") +else() + message(STATUS "Not using API_LOG_SYNC") +endif() + ################################################################################ # FP math ################################################################################ @@ -326,6 +344,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/README-CMake.md b/README-CMake.md index 9289d40f1..5b3cdfd36 100644 --- a/README-CMake.md +++ b/README-CMake.md @@ -1,4 +1,4 @@ -# Z3's CMake build system + # Z3's CMake build system [CMake](https://cmake.org/) is a "meta build system" that reads a description of the project written in the ``CMakeLists.txt`` files and emits a build @@ -293,6 +293,7 @@ The following useful options can be passed to CMake whilst configuring. * ``ALWAYS_BUILD_DOCS`` - BOOL. If set to ``TRUE`` and ``BUILD_DOCUMENTATION`` is ``TRUE`` then documentation for API bindings will always be built. Disabling this is useful for faster incremental builds. The documentation can be manually built by invoking the ``api_docs`` target. * ``LINK_TIME_OPTIMIZATION`` - BOOL. If set to ``TRUE`` link time optimization will be enabled. +* ``API_LOG_SYNC`` - BOOL. If set to ``TRUE`` will enable experimental API log sync feature. On the command line these can be passed to ``cmake`` using the ``-D`` option. In ``ccmake`` and ``cmake-gui`` these can be set in the user interface. 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/interp/CMakeLists.txt b/contrib/cmake/src/interp/CMakeLists.txt index 949811b93..c3d8e3d5e 100644 --- a/contrib/cmake/src/interp/CMakeLists.txt +++ b/contrib/cmake/src/interp/CMakeLists.txt @@ -2,7 +2,6 @@ z3_add_component(interp SOURCES iz3base.cpp iz3checker.cpp - iz3foci.cpp iz3interp.cpp iz3mgr.cpp iz3pp.cpp 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..200d98897 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,6 @@ 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}) + + + diff --git a/contrib/cmake/src/util/lp/CMakeLists.txt b/contrib/cmake/src/util/lp/CMakeLists.txt new file mode 100644 index 000000000..57ebecc8d --- /dev/null +++ b/contrib/cmake/src/util/lp/CMakeLists.txt @@ -0,0 +1,35 @@ +z3_add_component(lp + SOURCES + lp_utils.cpp + binary_heap_priority_queue_instances.cpp + binary_heap_upair_queue_instances.cpp + bound_propagator.cpp + core_solver_pretty_printer_instances.cpp + dense_matrix_instances.cpp + eta_matrix_instances.cpp + indexed_vector_instances.cpp + lar_core_solver_instances.cpp + lp_core_solver_base_instances.cpp + lp_dual_core_solver_instances.cpp + lp_dual_simplex_instances.cpp + lp_primal_core_solver_instances.cpp + lp_primal_simplex_instances.cpp + lp_settings_instances.cpp + lp_solver_instances.cpp + lu_instances.cpp + matrix_instances.cpp + permutation_matrix_instances.cpp + quick_xplain.cpp + row_eta_matrix_instances.cpp + scaler_instances.cpp + sparse_matrix_instances.cpp + square_dense_submatrix_instances.cpp + static_matrix_instances.cpp + random_updater_instances.cpp + COMPONENT_DEPENDENCIES + util + PYG_FILES + lp_params.pyg +) + +include_directories(${src_SOURCE_DIR}) diff --git a/scripts/mk_project.py b/scripts/mk_project.py index 7ba88c1b3..8655346f2 100644 --- a/scripts/mk_project.py +++ b/scripts/mk_project.py @@ -11,6 +11,7 @@ from mk_util import * def init_project_def(): set_version(4, 5, 1, 0) add_lib('util', []) + add_lib('lp', ['util'], 'util/lp') add_lib('polynomial', ['util'], 'math/polynomial') add_lib('sat', ['util']) add_lib('nlsat', ['polynomial', 'sat']) @@ -52,7 +53,7 @@ def init_project_def(): add_lib('smt_params', ['ast', 'simplifier', 'pattern', 'bit_blaster'], 'smt/params') add_lib('proto_model', ['model', 'simplifier', 'smt_params'], 'smt/proto_model') add_lib('smt', ['bit_blaster', 'macros', 'normal_forms', 'cmd_context', 'proto_model', - 'substitution', 'grobner', 'euclid', 'simplex', 'proof_checker', 'pattern', 'parser_util', 'fpa']) + 'substitution', 'grobner', 'euclid', 'simplex', 'proof_checker', 'pattern', 'parser_util', 'fpa', 'lp']) add_lib('bv_tactics', ['tactic', 'bit_blaster', 'core_tactics'], 'tactic/bv') add_lib('fuzzing', ['ast'], 'test/fuzzing') add_lib('smt_tactic', ['smt'], 'smt/tactic') diff --git a/scripts/mk_util.py b/scripts/mk_util.py index d1bbd6ca4..e7e35817f 100644 --- a/scripts/mk_util.py +++ b/scripts/mk_util.py @@ -96,8 +96,6 @@ VER_BUILD=None VER_REVISION=None PREFIX=sys.prefix GMP=False -FOCI2=False -FOCI2LIB='' VS_PAR=False VS_PAR_NUM=8 GPROF=False @@ -257,13 +255,6 @@ def test_gmp(cc): t.commit() return exec_compiler_cmd([cc, CPPFLAGS, 'tstgmp.cpp', LDFLAGS, '-lgmp']) == 0 -def test_foci2(cc,foci2lib): - if is_verbose(): - print("Testing FOCI2...") - t = TempFile('tstfoci2.cpp') - t.add('#include\nint main() { foci2 *f = foci2::create("lia"); return 0; }\n') - t.commit() - return exec_compiler_cmd([cc, CPPFLAGS, '-Isrc/interp', 'tstfoci2.cpp', LDFLAGS, foci2lib]) == 0 def test_openmp(cc): if not USE_OMP: @@ -650,7 +641,6 @@ def display_help(exit_code): if not IS_WINDOWS: print(" -g, --gmp use GMP.") print(" --gprof enable gprof") - print(" -f --foci2= use foci2 library at path") print(" --noomp disable OpenMP and all features that require it.") print(" --log-sync synchronize access to API log files to enable multi-thread API logging.") print("") @@ -678,13 +668,13 @@ def display_help(exit_code): # Parse configuration option for mk_make script def parse_options(): global VERBOSE, DEBUG_MODE, IS_WINDOWS, VS_X64, ONLY_MAKEFILES, SHOW_CPPS, VS_PROJ, TRACE, VS_PAR, VS_PAR_NUM - global DOTNET_ENABLED, DOTNET_KEY_FILE, JAVA_ENABLED, ML_ENABLED, STATIC_LIB, STATIC_BIN, PREFIX, GMP, FOCI2, FOCI2LIB, PYTHON_PACKAGE_DIR, GPROF, GIT_HASH, GIT_DESCRIBE, PYTHON_INSTALL_ENABLED, PYTHON_ENABLED + global DOTNET_ENABLED, DOTNET_KEY_FILE, JAVA_ENABLED, ML_ENABLED, STATIC_LIB, STATIC_BIN, PREFIX, GMP, PYTHON_PACKAGE_DIR, GPROF, GIT_HASH, GIT_DESCRIBE, PYTHON_INSTALL_ENABLED, PYTHON_ENABLED global LINUX_X64, SLOW_OPTIMIZE, USE_OMP, LOG_SYNC try: options, remainder = getopt.gnu_getopt(sys.argv[1:], 'b:df:sxhmcvtnp:gj', ['build=', 'debug', 'silent', 'x64', 'help', 'makefiles', 'showcpp', 'vsproj', - 'trace', 'dotnet', 'dotnet-key=', 'staticlib', 'prefix=', 'gmp', 'foci2=', 'java', 'parallel=', 'gprof', + 'trace', 'dotnet', 'dotnet-key=', 'staticlib', 'prefix=', 'gmp', 'java', 'parallel=', 'gprof', 'githash=', 'git-describe', 'x86', 'ml', 'optimize', 'noomp', 'pypkgdir=', 'python', 'staticbin', 'log-sync']) except: print("ERROR: Invalid command line option") @@ -735,9 +725,6 @@ def parse_options(): VS_PAR_NUM = int(arg) elif opt in ('-g', '--gmp'): GMP = True - elif opt in ('-f', '--foci2'): - FOCI2 = True - FOCI2LIB = arg elif opt in ('-j', '--java'): JAVA_ENABLED = True elif opt == '--gprof': @@ -774,8 +761,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 +991,7 @@ class Component: out.write('%s =' % include_defs) for dep in self.deps: out.write(' -I%s' % get_component(dep).to_src_dir) + out.write(' -I%s' % os.path.join(REV_BUILD_DIR,"src")) out.write('\n') mk_dir(os.path.join(BUILD_DIR, self.build_dir)) if VS_PAR and IS_WINDOWS: @@ -1175,7 +1168,6 @@ class ExeComponent(Component): for dep in deps: c_dep = get_component(dep) out.write(' ' + c_dep.get_link_name()) - out.write(' ' + FOCI2LIB) out.write(' $(LINK_EXTRA_FLAGS)\n') out.write('%s: %s\n\n' % (self.name, exefile)) @@ -1301,7 +1293,6 @@ class DLLComponent(Component): if dep not in self.reexports: c_dep = get_component(dep) out.write(' ' + c_dep.get_link_name()) - out.write(' ' + FOCI2LIB) out.write(' $(SLINK_EXTRA_FLAGS)') if IS_WINDOWS: out.write(' /DEF:%s.def' % os.path.join(self.to_src_dir, self.name)) @@ -2301,7 +2292,7 @@ def mk_config(): if ONLY_MAKEFILES: return config = open(os.path.join(BUILD_DIR, 'config.mk'), 'w') - global CXX, CC, GMP, FOCI2, CPPFLAGS, CXXFLAGS, LDFLAGS, EXAMP_DEBUG_FLAG, FPMATH_FLAGS, HAS_OMP, LOG_SYNC + global CXX, CC, GMP, CPPFLAGS, CXXFLAGS, LDFLAGS, EXAMP_DEBUG_FLAG, FPMATH_FLAGS, HAS_OMP, LOG_SYNC if IS_WINDOWS: config.write( 'CC=cl\n' @@ -2411,14 +2402,6 @@ def mk_config(): SLIBEXTRAFLAGS = '%s -lgmp' % SLIBEXTRAFLAGS else: CPPFLAGS = '%s -D_MP_INTERNAL' % CPPFLAGS - if FOCI2: - if test_foci2(CXX,FOCI2LIB): - LDFLAGS = '%s %s' % (LDFLAGS,FOCI2LIB) - SLIBEXTRAFLAGS = '%s %s' % (SLIBEXTRAFLAGS,FOCI2LIB) - CPPFLAGS = '%s -D_FOCI2' % CPPFLAGS - else: - print("FAILED\n") - FOCI2 = False if GIT_HASH: CPPFLAGS = '%s -DZ3GITHASH=%s' % (CPPFLAGS, GIT_HASH) CXXFLAGS = '%s -std=c++11' % CXXFLAGS @@ -2953,7 +2936,11 @@ def get_platform_toolset_str(): if len(tokens) < 2: return default else: - return 'v' + tokens[0] + tokens[1] + if tokens[0] == "15": + # Visual Studio 2017 reports 15.* but the PlatformToolsetVersion is 141 + return "v141" + else: + return 'v' + tokens[0] + tokens[1] def mk_vs_proj_property_groups(f, name, target_ext, type): f.write(' \n') diff --git a/src/api/dotnet/core/DummyContracts.cs b/src/api/dotnet/core/DummyContracts.cs index 49b498b1a..103cc1288 100644 --- a/src/api/dotnet/core/DummyContracts.cs +++ b/src/api/dotnet/core/DummyContracts.cs @@ -62,4 +62,4 @@ namespace System.Diagnostics.Contracts public static void EndContractBlock() { } public static T ValueAtReturn(out T v) { T[] t = new T[1]; v = t[0]; return v; } } -} \ No newline at end of file +} diff --git a/src/api/dotnet/dotnet35/Example/App.config b/src/api/dotnet/dotnet35/Example/App.config index 88fa4027b..737ed23f2 100644 --- a/src/api/dotnet/dotnet35/Example/App.config +++ b/src/api/dotnet/dotnet35/Example/App.config @@ -3,4 +3,4 @@ - \ No newline at end of file + diff --git a/src/api/dotnet/dotnet35/Example/Example.csproj b/src/api/dotnet/dotnet35/Example/Example.csproj index 2b096ed40..842fbac41 100644 --- a/src/api/dotnet/dotnet35/Example/Example.csproj +++ b/src/api/dotnet/dotnet35/Example/Example.csproj @@ -75,4 +75,4 @@ --> - \ No newline at end of file + diff --git a/src/api/dotnet/dotnet35/Microsoft.Z3.NET35.csproj b/src/api/dotnet/dotnet35/Microsoft.Z3.NET35.csproj index d278b4f1d..15eaac445 100644 --- a/src/api/dotnet/dotnet35/Microsoft.Z3.NET35.csproj +++ b/src/api/dotnet/dotnet35/Microsoft.Z3.NET35.csproj @@ -344,4 +344,4 @@ - \ No newline at end of file + diff --git a/src/api/dotnet/dotnet35/packages.config b/src/api/dotnet/dotnet35/packages.config index daa06aed7..bc9f365bc 100644 --- a/src/api/dotnet/dotnet35/packages.config +++ b/src/api/dotnet/dotnet35/packages.config @@ -1,4 +1,4 @@  - \ No newline at end of file + diff --git a/src/api/python/setup.py b/src/api/python/setup.py index ff3b0736d..24ba59ac2 100644 --- a/src/api/python/setup.py +++ b/src/api/python/setup.py @@ -164,6 +164,6 @@ setup( package_data={ 'z3': [os.path.join('lib', '*'), os.path.join('include', '*.h'), os.path.join('include', 'c++', '*.h')] }, - scripts=[os.path.join('bin', EXECUTABLE_FILE)], + data_files=[('bin',[os.path.join('bin',EXECUTABLE_FILE)])], cmdclass={'build': build, 'develop': develop, 'sdist': sdist, 'bdist_egg': bdist_egg}, ) diff --git a/src/api/python/z3/z3.py b/src/api/python/z3/z3.py index b4cdf834b..84a80ddf7 100644 --- a/src/api/python/z3/z3.py +++ b/src/api/python/z3/z3.py @@ -1300,8 +1300,10 @@ class BoolSortRef(SortRef): if isinstance(val, bool): return BoolVal(val, self.ctx) if __debug__: - _z3_assert(is_expr(val), "True, False or Z3 Boolean expression expected. Received %s" % val) - _z3_assert(self.eq(val.sort()), "Value cannot be converted into a Z3 Boolean value") + if not is_expr(val): + _z3_assert(is_expr(val), "True, False or Z3 Boolean expression expected. Received %s" % val) + if not self.eq(val.sort()): + _z3_assert(self.eq(val.sort()), "Value cannot be converted into a Z3 Boolean value") return val def subsort(self, other): diff --git a/src/ast/arith_decl_plugin.h b/src/ast/arith_decl_plugin.h index 668eebcc9..beb227c33 100644 --- a/src/ast/arith_decl_plugin.h +++ b/src/ast/arith_decl_plugin.h @@ -305,6 +305,7 @@ public: MATCH_UNARY(is_uminus); MATCH_UNARY(is_to_real); MATCH_UNARY(is_to_int); + MATCH_UNARY(is_is_int); MATCH_BINARY(is_sub); MATCH_BINARY(is_add); MATCH_BINARY(is_mul); @@ -377,6 +378,9 @@ public: app * mk_real(int i) { return mk_numeral(rational(i), false); } + app * mk_real(rational const& r) { + return mk_numeral(r, false); + } app * mk_le(expr * arg1, expr * arg2) const { return m_manager.mk_app(m_afid, OP_LE, arg1, arg2); } app * mk_ge(expr * arg1, expr * arg2) const { return m_manager.mk_app(m_afid, OP_GE, arg1, arg2); } app * mk_lt(expr * arg1, expr * arg2) const { return m_manager.mk_app(m_afid, OP_LT, arg1, arg2); } diff --git a/src/ast/ast_pp_util.cpp b/src/ast/ast_pp_util.cpp index f953dbf0e..3021b702b 100644 --- a/src/ast/ast_pp_util.cpp +++ b/src/ast/ast_pp_util.cpp @@ -44,8 +44,11 @@ void ast_pp_util::display_decls(std::ostream& out) { } n = coll.get_num_decls(); for (unsigned i = 0; i < n; ++i) { - ast_smt2_pp(out, coll.get_func_decls()[i], env); - out << "\n"; + func_decl* f = coll.get_func_decls()[i]; + if (f->get_family_id() == null_family_id) { + ast_smt2_pp(out, f, env); + out << "\n"; + } } } diff --git a/src/ast/rewriter/pb2bv_rewriter.cpp b/src/ast/rewriter/pb2bv_rewriter.cpp index 5cef19234..aedd62081 100644 --- a/src/ast/rewriter/pb2bv_rewriter.cpp +++ b/src/ast/rewriter/pb2bv_rewriter.cpp @@ -26,6 +26,7 @@ Notes: #include"ast_pp.h" #include"lbool.h" #include"uint_set.h" +#include"gparams.h" const unsigned g_primes[7] = { 2, 3, 5, 7, 11, 13, 17}; @@ -54,6 +55,8 @@ struct pb2bv_rewriter::imp { vector m_coeffs; bool m_keep_cardinality_constraints; bool m_keep_pb_constraints; + bool m_pb_num_system; + bool m_pb_totalizer; unsigned m_min_arity; template @@ -114,23 +117,24 @@ struct pb2bv_rewriter::imp { return expr_ref((is_le == l_false)?m.mk_true():m.mk_false(), m); } -#if 0 - expr_ref result(m); - switch (is_le) { - case l_true: if (mk_le_tot(sz, args, k, result)) return result; else break; - case l_false: if (mk_ge_tot(sz, args, k, result)) return result; else break; - case l_undef: break; + if (m_pb_totalizer) { + expr_ref result(m); + switch (is_le) { + case l_true: if (mk_le_tot(sz, args, k, result)) return result; else break; + case l_false: if (mk_ge_tot(sz, args, k, result)) return result; else break; + case l_undef: break; + } + } + + if (m_pb_num_system) { + expr_ref result(m); + switch (is_le) { + case l_true: if (mk_le(sz, args, k, result)) return result; else break; + case l_false: if (mk_ge(sz, args, k, result)) return result; else break; + case l_undef: if (mk_eq(sz, args, k, result)) return result; else break; + } } -#endif -#if 0 - expr_ref result(m); - switch (is_le) { - case l_true: if (mk_le(sz, args, k, result)) return result; else break; - case l_false: if (mk_ge(sz, args, k, result)) return result; else break; - case l_undef: if (mk_eq(sz, args, k, result)) return result; else break; - } -#endif // fall back to divide and conquer encoding. SASSERT(k.is_pos()); expr_ref zero(m), bound(m); @@ -397,22 +401,31 @@ struct pb2bv_rewriter::imp { for (unsigned j = 0; j + lim - 1 < out.size(); j += n) { expr_ref tmp(m); tmp = out[j + lim - 1]; - if (j + n < out.size()) { - tmp = m.mk_and(tmp, m.mk_not(out[j + n])); + if (j + n - 1 < out.size()) { + tmp = m.mk_and(tmp, m.mk_not(out[j + n - 1])); } ors.push_back(tmp); } return ::mk_or(ors); } + // x0 + 5x1 + 3x2 >= k + // x0 x1 x1 -> s0 s1 s2 + // s2 x1 x2 -> s3 s4 s5 + // k = 7: s5 or (s4 & not s2 & s0) + // k = 6: s4 + // k = 5: s4 or (s3 & not s2 & s1) + // k = 4: s4 or (s3 & not s2 & s0) + // k = 3: s3 + // bool mk_ge(unsigned sz, expr * const* args, rational bound, expr_ref& result) { if (!create_basis()) return false; if (!bound.is_unsigned()) return false; vector coeffs(m_coeffs); result = m.mk_true(); expr_ref_vector carry(m), new_carry(m); - for (unsigned i = 0; i < m_base.size(); ++i) { - rational b_i = m_base[i]; + m_base.push_back(bound + rational::one()); + for (rational b_i : m_base) { unsigned B = b_i.get_unsigned(); unsigned d_i = (bound % b_i).get_unsigned(); bound = div(bound, b_i); @@ -425,16 +438,16 @@ struct pb2bv_rewriter::imp { coeffs[j] = div(coeffs[j], b_i); } TRACE("pb", tout << "Carry: " << carry << "\n"; - for (unsigned j = 0; j < coeffs.size(); ++j) tout << coeffs[j] << " "; + for (auto c : coeffs) tout << c << " "; tout << "\n"; ); ptr_vector out; m_sort.sorting(carry.size(), carry.c_ptr(), out); - + expr_ref gt = mod_ge(out, B, d_i + 1); expr_ref ge = mod_ge(out, B, d_i); result = mk_or(gt, mk_and(ge, result)); - TRACE("pb", tout << result << "\n";); + TRACE("pb", tout << "b: " << b_i << " d: " << d_i << " gt: " << gt << " ge: " << ge << " " << result << "\n";); new_carry.reset(); for (unsigned j = B - 1; j < out.size(); j += B) { @@ -443,11 +456,11 @@ struct pb2bv_rewriter::imp { carry.reset(); carry.append(new_carry); } - TRACE("pb", tout << result << "\n";); + TRACE("pb", tout << "bound: " << bound << " Carry: " << carry << " result: " << result << "\n";); return true; } - expr_ref mk_and(expr_ref& a, expr_ref& b) { + expr_ref mk_and(expr_ref& a, expr_ref& b) { if (m.is_true(a)) return b; if (m.is_true(b)) return a; if (m.is_false(a)) return a; @@ -491,6 +504,7 @@ struct pb2bv_rewriter::imp { for (unsigned i = 0; i < sz; ++i) { m_coeffs.push_back(pb.get_coeff(f, i)); } + CTRACE("pb", k.is_neg(), tout << expr_ref(m.mk_app(f, sz, args), m) << "\n";); SASSERT(!k.is_neg()); switch (kind) { case OP_PB_GE: @@ -567,6 +581,8 @@ struct pb2bv_rewriter::imp { m_args(m), m_keep_cardinality_constraints(false), m_keep_pb_constraints(false), + m_pb_num_system(false), + m_pb_totalizer(false), m_min_arity(2) {} @@ -761,6 +777,14 @@ struct pb2bv_rewriter::imp { void keep_pb_constraints(bool f) { m_keep_pb_constraints = f; } + + void pb_num_system(bool f) { + m_pb_num_system = f; + } + + void pb_totalizer(bool f) { + m_pb_totalizer = f; + } }; struct card2bv_rewriter_cfg : public default_rewriter_cfg { @@ -774,6 +798,8 @@ struct pb2bv_rewriter::imp { card2bv_rewriter_cfg(imp& i, ast_manager & m):m_r(i, m) {} void keep_cardinality_constraints(bool f) { m_r.keep_cardinality_constraints(f); } void keep_pb_constraints(bool f) { m_r.keep_pb_constraints(f); } + void pb_num_system(bool f) { m_r.pb_num_system(f); } + void pb_totalizer(bool f) { m_r.pb_totalizer(f); } }; class card_pb_rewriter : public rewriter_tpl { @@ -784,27 +810,62 @@ struct pb2bv_rewriter::imp { m_cfg(i, m) {} void keep_cardinality_constraints(bool f) { m_cfg.keep_cardinality_constraints(f); } void keep_pb_constraints(bool f) { m_cfg.keep_pb_constraints(f); } + void pb_num_system(bool f) { m_cfg.pb_num_system(f); } + void pb_totalizer(bool f) { m_cfg.pb_totalizer(f); } }; card_pb_rewriter m_rw; + bool keep_cardinality() const { + params_ref const& p = m_params; + return + p.get_bool("keep_cardinality_constraints", false) || + p.get_bool("sat.cardinality.solver", false) || + p.get_bool("cardinality.solver", false) || + gparams::get_module("sat").get_bool("cardinality.solver", false) || + keep_pb(); + } + + bool keep_pb() const { + params_ref const& p = m_params; + return + p.get_bool("keep_pb_constraints", false) || + p.get_bool("sat.pb.solver", false) || + p.get_bool("pb.solver", false) || + gparams::get_module("sat").get_bool("pb.solver", false); + } + + bool pb_num_system() const { + return m_params.get_bool("pb_num_system", false); + } + + bool pb_totalizer() const { + return m_params.get_bool("pb_totalizer", false); + } + imp(ast_manager& m, params_ref const& p): m(m), m_params(p), m_lemmas(m), m_fresh(m), m_num_translated(0), m_rw(*this, m) { - m_rw.keep_cardinality_constraints(p.get_bool("keep_cardinality_constraints", false)); - m_rw.keep_pb_constraints(p.get_bool("keep_pb_constraints", false)); + m_rw.keep_cardinality_constraints(keep_cardinality()); + m_rw.keep_pb_constraints(keep_pb()); + m_rw.pb_num_system(pb_num_system()); + m_rw.pb_totalizer(pb_totalizer()); } void updt_params(params_ref const & p) { m_params.append(p); - m_rw.keep_cardinality_constraints(m_params.get_bool("keep_cardinality_constraints", false)); - m_rw.keep_pb_constraints(m_params.get_bool("keep_pb_constraints", false)); + m_rw.keep_cardinality_constraints(keep_cardinality()); + m_rw.keep_pb_constraints(keep_pb()); + m_rw.pb_num_system(pb_num_system()); + m_rw.pb_totalizer(pb_totalizer()); } void collect_param_descrs(param_descrs& r) const { r.insert("keep_cardinality_constraints", CPK_BOOL, "(default: true) retain cardinality constraints (don't bit-blast them) and use built-in cardinality solver"); r.insert("keep_pb_constraints", CPK_BOOL, "(default: true) retain pb constraints (don't bit-blast them) and use built-in pb solver"); + r.insert("pb_num_system", CPK_BOOL, "(default: false) use pb number system encoding"); + r.insert("pb_totalizer", CPK_BOOL, "(default: false) use pb totalizer encoding"); } unsigned get_num_steps() const { return m_rw.get_num_steps(); } diff --git a/src/ast/rewriter/pb_rewriter.cpp b/src/ast/rewriter/pb_rewriter.cpp index b193a8a45..7d1eebb5c 100644 --- a/src/ast/rewriter/pb_rewriter.cpp +++ b/src/ast/rewriter/pb_rewriter.cpp @@ -232,6 +232,7 @@ br_status pb_rewriter::mk_app_core(func_decl * f, unsigned num_args, expr * cons } bool is_eq = f->get_decl_kind() == OP_PB_EQ; + br_status st = BR_DONE; pb_ast_rewriter_util pbu(m); pb_rewriter_util util(pbu); @@ -249,11 +250,14 @@ br_status pb_rewriter::mk_app_core(func_decl * f, unsigned num_args, expr * cons default: { bool all_unit = true; unsigned sz = vec.size(); + rational slack(0); m_args.reset(); m_coeffs.reset(); for (unsigned i = 0; i < sz; ++i) { m_args.push_back(vec[i].first); m_coeffs.push_back(vec[i].second); + SASSERT(vec[i].second.is_pos()); + slack += vec[i].second; all_unit &= m_coeffs.back().is_one(); } if (is_eq) { @@ -271,7 +275,42 @@ br_status pb_rewriter::mk_app_core(func_decl * f, unsigned num_args, expr * cons result = mk_and(m, sz, m_args.c_ptr()); } else { - result = m_util.mk_ge(sz, m_coeffs.c_ptr(), m_args.c_ptr(), k); + expr_ref_vector conj(m), disj(m); + for (unsigned i = 0; i < m_args.size(); ++i) { + rational& c = m_coeffs[i]; + if (slack < c + k) { + conj.push_back(m_args[i]); + slack -= c; + k -= c; + } + else if (c >= k) { + slack -= c; + disj.push_back(m_args[i]); + } + else { + continue; + } + m_args[i] = m_args.back(); + m_coeffs[i] = m_coeffs.back(); + --i; + m_args.pop_back(); + m_coeffs.pop_back(); + } + sz = m_coeffs.size(); + if (slack < k) { + conj.push_back(m.mk_false()); + } + else if (k.is_pos() && sz > 0) { + conj.push_back(m_util.mk_ge(sz, m_coeffs.c_ptr(), m_args.c_ptr(), k)); + } + result = mk_and(conj); + if (!disj.empty()) { + disj.push_back(result); + result = mk_or(disj); + } + if (!disj.empty() || conj.size() > 1) { + st = BR_REWRITE3; + } } break; } @@ -286,7 +325,7 @@ br_status pb_rewriter::mk_app_core(func_decl * f, unsigned num_args, expr * cons TRACE("pb_validate", validate_rewrite(f, num_args, args, result);); - return BR_DONE; + return st; } diff --git a/src/ast/rewriter/seq_rewriter.cpp b/src/ast/rewriter/seq_rewriter.cpp index 7aa9329d4..dc1931bac 100644 --- a/src/ast/rewriter/seq_rewriter.cpp +++ b/src/ast/rewriter/seq_rewriter.cpp @@ -440,24 +440,16 @@ br_status seq_rewriter::mk_app_core(func_decl * f, unsigned num_args, expr * con * (seq.unit (_ BitVector 8)) ==> String constant */ br_status seq_rewriter::mk_seq_unit(expr* e, expr_ref& result) { - sort * s = m().get_sort(e); bv_util bvu(m()); - - if (is_sort_of(s, bvu.get_family_id(), BV_SORT)) { - // specifically we want (_ BitVector 8) - rational n_val; - unsigned int n_size; - if (bvu.is_numeral(e, n_val, n_size)) { - if (n_size == 8) { - // convert to string constant - char ch = (char)n_val.get_int32(); - TRACE("seq", tout << "rewrite seq.unit of 8-bit value " << n_val.to_string() << " to string constant \"" << ch << "\"" << std::endl;); - char s_tmp[2] = {ch, '\0'}; - symbol s(s_tmp); - result = m_util.str.mk_string(s); - return BR_DONE; - } - } + rational n_val; + unsigned int n_size; + // specifically we want (_ BitVector 8) + if (bvu.is_bv(e) && bvu.is_numeral(e, n_val, n_size) && n_size == 8) { + // convert to string constant + zstring str(n_val.get_unsigned()); + TRACE("seq", tout << "rewrite seq.unit of 8-bit value " << n_val.to_string() << " to string constant \"" << str<< "\"" << std::endl;); + result = m_util.str.mk_string(str); + return BR_DONE; } return BR_FAILED; @@ -1431,37 +1423,10 @@ br_status seq_rewriter::mk_re_star(expr* a, expr_ref& result) { } /* - * (re.range c_1 c_n) = (re.union (str.to.re c1) (str.to.re c2) ... (str.to.re cn)) + * (re.range c_1 c_n) */ br_status seq_rewriter::mk_re_range(expr* lo, expr* hi, expr_ref& result) { return BR_FAILED; - TRACE("seq", tout << "rewrite re.range [" << mk_pp(lo, m()) << " " << mk_pp(hi, m()) << "]\n";); - zstring str_lo, str_hi; - if (m_util.str.is_string(lo, str_lo) && m_util.str.is_string(hi, str_hi)) { - if (str_lo.length() == 1 && str_hi.length() == 1) { - unsigned int c1 = str_lo[0]; - unsigned int c2 = str_hi[0]; - if (c1 > c2) { - // exchange c1 and c2 - unsigned int tmp = c1; - c2 = c1; - c1 = tmp; - } - zstring s(c1); - expr_ref acc(m_util.re.mk_to_re(m_util.str.mk_string(s)), m()); - for (unsigned int ch = c1 + 1; ch <= c2; ++ch) { - zstring s_ch(ch); - expr_ref acc2(m_util.re.mk_to_re(m_util.str.mk_string(s_ch)), m()); - acc = m_util.re.mk_union(acc, acc2); - } - result = acc; - return BR_REWRITE2; - } else { - m().raise_exception("string constants in re.range must have length 1"); - } - } - - return BR_FAILED; } /* @@ -1506,6 +1471,7 @@ br_status seq_rewriter::mk_re_opt(expr* a, expr_ref& result) { } br_status seq_rewriter::mk_eq_core(expr * l, expr * r, expr_ref & result) { + TRACE("seq", tout << mk_pp(l, m()) << " = " << mk_pp(r, m()) << "\n";); expr_ref_vector lhs(m()), rhs(m()), res(m()); bool changed = false; if (!reduce_eq(l, r, lhs, rhs, changed)) { diff --git a/src/ast/seq_decl_plugin.cpp b/src/ast/seq_decl_plugin.cpp index bf238d8c5..750fe0f9d 100644 --- a/src/ast/seq_decl_plugin.cpp +++ b/src/ast/seq_decl_plugin.cpp @@ -876,6 +876,36 @@ bool seq_decl_plugin::is_value(app* e) const { } } +bool seq_decl_plugin::are_equal(app* a, app* b) const { + if (a == b) return true; + // handle concatenations + return false; +} + +bool seq_decl_plugin::are_distinct(app* a, app* b) const { + if (a == b) { + return false; + } + if (is_app_of(a, m_family_id, OP_STRING_CONST) && + is_app_of(b, m_family_id, OP_STRING_CONST)) { + return true; + } + if (is_app_of(a, m_family_id, OP_SEQ_UNIT) && + is_app_of(b, m_family_id, OP_SEQ_UNIT)) { + return true; + } + if (is_app_of(a, m_family_id, OP_SEQ_EMPTY) && + is_app_of(b, m_family_id, OP_SEQ_UNIT)) { + return true; + } + if (is_app_of(b, m_family_id, OP_SEQ_EMPTY) && + is_app_of(a, m_family_id, OP_SEQ_UNIT)) { + return true; + } + return false; +} + + expr* seq_decl_plugin::get_some_value(sort* s) { seq_util util(*m_manager); if (util.is_seq(s)) { diff --git a/src/ast/seq_decl_plugin.h b/src/ast/seq_decl_plugin.h index 76b5ebe31..e2722aa9a 100644 --- a/src/ast/seq_decl_plugin.h +++ b/src/ast/seq_decl_plugin.h @@ -182,7 +182,11 @@ public: virtual bool is_value(app * e) const; - virtual bool is_unique_value(app * e) const { return is_value(e); } + virtual bool is_unique_value(app * e) const { return false; } + + virtual bool are_equal(app* a, app* b) const; + + virtual bool are_distinct(app* a, app* b) const; virtual expr * get_some_value(sort * s); diff --git a/src/interp/foci2.h b/src/interp/foci2.h deleted file mode 100755 index bd968f11e..000000000 --- a/src/interp/foci2.h +++ /dev/null @@ -1,75 +0,0 @@ -/*++ - Copyright (c) 2011 Microsoft Corporation - - Module Name: - - foci2.h - - Abstract: - - An interface class for foci2. - - Author: - - Ken McMillan (kenmcmil) - - Revision History: - - --*/ - -#ifndef FOCI2_H -#define FOCI2_H - -#include -#include - -#ifdef _WINDOWS -#define FOCI2_EXPORT __declspec(dllexport) -#else -#define FOCI2_EXPORT __attribute__ ((visibility ("default"))) -#endif - -class foci2 { - public: - virtual ~foci2(){} - - typedef int ast; - typedef int symb; - - /** Built-in operators */ - enum ops { - And = 0, Or, Not, Iff, Ite, Equal, Plus, Times, Floor, Leq, Div, Bool, Int, Array, Tsym, Fsym, Forall, Exists, Distinct, LastOp - }; - - virtual symb mk_func(const std::string &s) = 0; - virtual symb mk_pred(const std::string &s) = 0; - virtual ast mk_op(ops op, const std::vector args) = 0; - virtual ast mk_op(ops op, ast) = 0; - virtual ast mk_op(ops op, ast, ast) = 0; - virtual ast mk_op(ops op, ast, ast, ast) = 0; - virtual ast mk_int(const std::string &) = 0; - virtual ast mk_rat(const std::string &) = 0; - virtual ast mk_true() = 0; - virtual ast mk_false() = 0; - virtual ast mk_app(symb,const std::vector args) = 0; - - virtual bool get_func(ast, symb &) = 0; - virtual bool get_pred(ast, symb &) = 0; - virtual bool get_op(ast, ops &) = 0; - virtual bool get_true(ast id) = 0; - virtual bool get_false(ast id) = 0; - virtual bool get_int(ast id, std::string &res) = 0; - virtual bool get_rat(ast id, std::string &res) = 0; - virtual const std::string &get_symb(symb) = 0; - - virtual int get_num_args(ast) = 0; - virtual ast get_arg(ast, int) = 0; - - virtual void show_ast(ast) = 0; - - virtual bool interpolate(const std::vector &frames, std::vector &itps, std::vector parents) = 0; - - FOCI2_EXPORT static foci2 *create(const std::string &); -}; - -#endif diff --git a/src/interp/foci2stub/foci2.cpp b/src/interp/foci2stub/foci2.cpp deleted file mode 100644 index 31908855b..000000000 --- a/src/interp/foci2stub/foci2.cpp +++ /dev/null @@ -1,25 +0,0 @@ -/*++ -Copyright (c) 2011 Microsoft Corporation - -Module Name: - - foci2.cpp - -Abstract: - - Fake foci2, to be replaced - -Author: - - Ken McMillan (kenmcmil) - -Revision History: - ---*/ - - -#include "foci2.h" - -FOCI2_EXPORT foci2 *foci2::create(const std::string &){ - return 0; -} diff --git a/src/interp/foci2stub/foci2.h b/src/interp/foci2stub/foci2.h deleted file mode 100755 index 261dd05dc..000000000 --- a/src/interp/foci2stub/foci2.h +++ /dev/null @@ -1,75 +0,0 @@ -/*++ -Copyright (c) 2011 Microsoft Corporation - -Module Name: - - foci2.h - -Abstract: - - An interface class for foci2. - -Author: - - Ken McMillan (kenmcmil) - -Revision History: - ---*/ - -#ifndef FOCI2_H -#define FOCI2_H - -#include -#include - -#ifdef WIN32 -#define FOCI2_EXPORT __declspec(dllexport) -#else -#define FOCI2_EXPORT __attribute__ ((visibility ("default"))) -#endif - -class foci2 { - public: - virtual ~foci2(){} - - typedef int ast; - typedef int symb; - - /** Built-in operators */ - enum ops { - And = 0, Or, Not, Iff, Ite, Equal, Plus, Times, Floor, Leq, Div, Bool, Int, Array, Tsym, Fsym, Forall, Exists, Distinct, LastOp - }; - - virtual symb mk_func(const std::string &s) = 0; - virtual symb mk_pred(const std::string &s) = 0; - virtual ast mk_op(ops op, const std::vector args) = 0; - virtual ast mk_op(ops op, ast) = 0; - virtual ast mk_op(ops op, ast, ast) = 0; - virtual ast mk_op(ops op, ast, ast, ast) = 0; - virtual ast mk_int(const std::string &) = 0; - virtual ast mk_rat(const std::string &) = 0; - virtual ast mk_true() = 0; - virtual ast mk_false() = 0; - virtual ast mk_app(symb,const std::vector args) = 0; - - virtual bool get_func(ast, symb &) = 0; - virtual bool get_pred(ast, symb &) = 0; - virtual bool get_op(ast, ops &) = 0; - virtual bool get_true(ast id) = 0; - virtual bool get_false(ast id) = 0; - virtual bool get_int(ast id, std::string &res) = 0; - virtual bool get_rat(ast id, std::string &res) = 0; - virtual const std::string &get_symb(symb) = 0; - - virtual int get_num_args(ast) = 0; - virtual ast get_arg(ast, int) = 0; - - virtual void show_ast(ast) = 0; - - virtual bool interpolate(const std::vector &frames, std::vector &itps, std::vector parents) = 0; - - FOCI2_EXPORT static foci2 *create(const std::string &); -}; - -#endif diff --git a/src/interp/iz3foci.cpp b/src/interp/iz3foci.cpp deleted file mode 100755 index 3a873de2c..000000000 --- a/src/interp/iz3foci.cpp +++ /dev/null @@ -1,356 +0,0 @@ -/*++ - Copyright (c) 2011 Microsoft Corporation - - Module Name: - - iz3foci.cpp - - Abstract: - - Implements a secondary solver using foci2. - - Author: - - Ken McMillan (kenmcmil) - - Revision History: - - --*/ - -#include -#include - -#include "iz3hash.h" -#include "foci2.h" -#include "iz3foci.h" - -using namespace stl_ext; - -class iz3foci_impl : public iz3secondary { - - int frames; - int *parents; - foci2 *foci; - foci2::symb select_op; - foci2::symb store_op; - foci2::symb mod_op; - -public: - iz3foci_impl(iz3mgr *mgr, int _frames, int *_parents) : iz3secondary(*mgr) { - frames = _frames; - parents = _parents; - foci = 0; - } - - typedef hash_map AstToNode; - AstToNode ast_to_node; // maps Z3 ast's to foci expressions - - typedef hash_map NodeToAst; - NodeToAst node_to_ast; // maps Z3 ast's to foci expressions - - // We only use this for FuncDeclToSymbol, which has no range destructor - struct symb_hash { - size_t operator()(const symb &s) const { - return (size_t) s; - } - }; - - typedef hash_map FuncDeclToSymbol; - FuncDeclToSymbol func_decl_to_symbol; // maps Z3 func decls to symbols - - typedef hash_map SymbolToFuncDecl; - SymbolToFuncDecl symbol_to_func_decl; // maps symbols to Z3 func decls - - int from_symb(symb func){ - std::string name = string_of_symbol(func); - bool is_bool = is_bool_type(get_range_type(func)); - foci2::symb f; - if(is_bool) - f = foci->mk_pred(name); - else - f = foci->mk_func(name); - symbol_to_func_decl[f] = func; - func_decl_to_symbol[func] = f; - return f; - } - - // create a symbol corresponding to a DeBruijn index of a particular type - // the type has to be encoded into the name because the same index can - // occur with different types - foci2::symb make_deBruijn_symbol(int index, type ty){ - std::ostringstream s; - // s << "#" << index << "#" << type; - return foci->mk_func(s.str()); - } - - int from_Z3_ast(ast t){ - std::pair foo(t,0); - std::pair bar = ast_to_node.insert(foo); - int &res = bar.first->second; - if(!bar.second) return res; - int nargs = num_args(t); - std::vector args(nargs); - for(int i = 0; i < nargs; i++) - args[i] = from_Z3_ast(arg(t,i)); - - switch(op(t)){ - case True: - res = foci->mk_true(); break; - case False: - res = foci->mk_false(); break; - case And: - res = foci->mk_op(foci2::And,args); break; - case Or: - res = foci->mk_op(foci2::Or,args); break; - case Not: - res = foci->mk_op(foci2::Not,args[0]); break; - case Iff: - res = foci->mk_op(foci2::Iff,args); break; - case OP_OEQ: // bit of a mystery, this one... - if(args[0] == args[1]) res = foci->mk_true(); - else res = foci->mk_op(foci2::Iff,args); - break; - case Ite: - if(is_bool_type(get_type(t))) - res = foci->mk_op(foci2::And,foci->mk_op(foci2::Or,foci->mk_op(foci2::Not,args[0]),args[1]),foci->mk_op(foci2::Or,args[0],args[2])); - else - res = foci->mk_op(foci2::Ite,args); - break; - case Equal: - res = foci->mk_op(foci2::Equal,args); break; - case Implies: - args[0] = foci->mk_op(foci2::Not,args[0]); res = foci->mk_op(foci2::Or,args); break; - case Xor: - res = foci->mk_op(foci2::Not,foci->mk_op(foci2::Iff,args)); break; - case Leq: - res = foci->mk_op(foci2::Leq,args); break; - case Geq: - std::swap(args[0],args[1]); res = foci->mk_op(foci2::Leq,args); break; - case Gt: - res = foci->mk_op(foci2::Not,foci->mk_op(foci2::Leq,args)); break; - case Lt: - std::swap(args[0],args[1]); res = foci->mk_op(foci2::Not,foci->mk_op(foci2::Leq,args)); break; - case Plus: - res = foci->mk_op(foci2::Plus,args); break; - case Sub: - args[1] = foci->mk_op(foci2::Times,foci->mk_int("-1"),args[1]); res = foci->mk_op(foci2::Plus,args); break; - case Uminus: - res = foci->mk_op(foci2::Times,foci->mk_int("-1"),args[0]); break; - case Times: - res = foci->mk_op(foci2::Times,args); break; - case Idiv: - res = foci->mk_op(foci2::Div,args); break; - case Mod: - res = foci->mk_app(mod_op,args); break; - case Select: - res = foci->mk_app(select_op,args); break; - case Store: - res = foci->mk_app(store_op,args); break; - case Distinct: - res = foci->mk_op(foci2::Distinct,args); break; - case Uninterpreted: { - symb func = sym(t); - FuncDeclToSymbol::iterator it = func_decl_to_symbol.find(func); - foci2::symb f = (it == func_decl_to_symbol.end()) ? from_symb(func) : it->second; - if(foci->get_symb(f).substr(0,3) == "lbl" && args.size()==1) // HACK to handle Z3 labels - res = args[0]; - else if(foci->get_symb(f).substr(0,3) == "lbl" && args.size()==0) // HACK to handle Z3 labels - res = foci->mk_true(); - else res = foci->mk_app(f,args); - break; - } - case Numeral: { - std::string s = string_of_numeral(t); - res = foci->mk_int(s); - break; - } - case Forall: - case Exists: { - bool is_forall = op(t) == Forall; - foci2::ops qop = is_forall ? foci2::Forall : foci2::Exists; - int bvs = get_quantifier_num_bound(t); - std::vector foci_bvs(bvs); - for(int i = 0; i < bvs; i++){ - std::string name = get_quantifier_bound_name(t,i); - //Z3_string name = Z3_get_symbol_string(ctx,sym); - // type ty = get_quantifier_bound_type(t,i); - foci2::symb f = foci->mk_func(name); - foci2::ast v = foci->mk_app(f,std::vector()); - foci_bvs[i] = v; - } - foci2::ast body = from_Z3_ast(get_quantifier_body(t)); - foci_bvs.push_back(body); - res = foci->mk_op(qop,foci_bvs); - node_to_ast[res] = t; // desperate - break; - } - case Variable: { // a deBruijn index - int index = get_variable_index_value(t); - type ty = get_type(t); - foci2::symb symbol = make_deBruijn_symbol(index,ty); - res = foci->mk_app(symbol,std::vector()); - break; - } - default: - { - std::cerr << "iZ3: unsupported Z3 operator in expression\n "; - print_expr(std::cerr,t); - std::cerr << "\n"; - SASSERT(0 && "iZ3: unsupported Z3 operator"); - } - } - return res; - } - - // convert an expr to Z3 ast - ast to_Z3_ast(foci2::ast i){ - std::pair foo(i,ast()); - std::pair bar = node_to_ast.insert(foo); - if(!bar.second) return bar.first->second; - ast &res = bar.first->second; - - if(i < 0){ - res = mk_not(to_Z3_ast(-i)); - return res; - } - - // get the arguments - unsigned n = foci->get_num_args(i); - std::vector args(n); - for(unsigned j = 0; j < n; j++) - args[j] = to_Z3_ast(foci->get_arg(i,j)); - - // handle operators - foci2::ops o; - foci2::symb f; - std::string nval; - if(foci->get_true(i)) - res = mk_true(); - else if(foci->get_false(i)) - res = mk_false(); - else if(foci->get_op(i,o)){ - switch(o){ - case foci2::And: - res = make(And,args); break; - case foci2::Or: - res = make(Or,args); break; - case foci2::Not: - res = mk_not(args[0]); break; - case foci2::Iff: - res = make(Iff,args[0],args[1]); break; - case foci2::Ite: - res = make(Ite,args[0],args[1],args[2]); break; - case foci2::Equal: - res = make(Equal,args[0],args[1]); break; - case foci2::Plus: - res = make(Plus,args); break; - case foci2::Times: - res = make(Times,args); break; - case foci2::Div: - res = make(Idiv,args[0],args[1]); break; - case foci2::Leq: - res = make(Leq,args[0],args[1]); break; - case foci2::Distinct: - res = make(Distinct,args); - break; - case foci2::Tsym: - res = mk_true(); - break; - case foci2::Fsym: - res = mk_false(); - break; - case foci2::Forall: - case foci2::Exists: - { - int nargs = n; - std::vector bounds(nargs-1); - for(int i = 0; i < nargs-1; i++) - bounds[i] = args[i]; - opr oz = o == foci2::Forall ? Forall : Exists; - res = make_quant(oz,bounds,args[nargs-1]); - } - break; - default: - SASSERT(false && "unknown built-in op"); - } - } - else if(foci->get_int(i,nval)){ - res = make_int(nval); - } - else if(foci->get_func(i,f)){ - if(f == select_op){ - SASSERT(n == 2); - res = make(Select,args[0],args[1]); - } - else if(f == store_op){ - SASSERT(n == 3); - res = make(Store,args[0],args[1],args[2]); - } - else if(f == mod_op){ - SASSERT(n == 2); - res = make(Mod,args[0],args[1]); - } - else { - std::pair foo(f,(symb)0); - std::pair bar = symbol_to_func_decl.insert(foo); - symb &func_decl = bar.first->second; - if(bar.second){ - std::cout << "unknown function symbol:\n"; - foci->show_ast(i); - SASSERT(0); - } - res = make(func_decl,args); - } - } - else { - std::cerr << "iZ3: unknown FOCI expression kind\n"; - SASSERT(0 && "iZ3: unknown FOCI expression kind"); - } - return res; - } - - int interpolate(const std::vector &cnsts, std::vector &itps){ - SASSERT((int)cnsts.size() == frames); - std::string lia("lia"); -#ifdef _FOCI2 - foci = foci2::create(lia); -#else - foci = 0; -#endif - if(!foci){ - std::cerr << "iZ3: cannot find foci lia solver.\n"; - SASSERT(0); - } - select_op = foci->mk_func("select"); - store_op = foci->mk_func("store"); - mod_op = foci->mk_func("mod"); - std::vector foci_cnsts(frames), foci_itps(frames-1), foci_parents; - if(parents) - foci_parents.resize(frames); - for(int i = 0; i < frames; i++){ - foci_cnsts[i] = from_Z3_ast(cnsts[i]); - if(parents) - foci_parents[i] = parents[i]; - } - int res = foci->interpolate(foci_cnsts, foci_itps, foci_parents); - if(res == 0){ - SASSERT((int)foci_itps.size() == frames-1); - itps.resize(frames-1); - for(int i = 0; i < frames-1; i++){ - // foci->show_ast(foci_itps[i]); - itps[i] = to_Z3_ast(foci_itps[i]); - } - } - ast_to_node.clear(); - node_to_ast.clear(); - func_decl_to_symbol.clear(); - symbol_to_func_decl.clear(); - delete foci; - return res; - } - -}; - -iz3secondary *iz3foci::create(iz3mgr *mgr, int num, int *parents){ - return new iz3foci_impl(mgr,num,parents); -} diff --git a/src/interp/iz3foci.h b/src/interp/iz3foci.h deleted file mode 100755 index a84a3c3bf..000000000 --- a/src/interp/iz3foci.h +++ /dev/null @@ -1,32 +0,0 @@ -/*++ - Copyright (c) 2011 Microsoft Corporation - - Module Name: - - iz3foci.h - - Abstract: - - Implements a secondary solver using foci2. - - Author: - - Ken McMillan (kenmcmil) - - Revision History: - - --*/ - -#ifndef IZ3FOCI_H -#define IZ3FOCI_H - -#include "iz3secondary.h" - -/** Secondary prover based on Cadence FOCI. */ - -class iz3foci { - public: - static iz3secondary *create(iz3mgr *mgr, int num, int *parents); -}; - -#endif diff --git a/src/interp/iz3interp.cpp b/src/interp/iz3interp.cpp index 87f782160..bb83349da 100755 --- a/src/interp/iz3interp.cpp +++ b/src/interp/iz3interp.cpp @@ -35,7 +35,6 @@ #include "iz3profiling.h" #include "iz3translate.h" -#include "iz3foci.h" #include "iz3proof.h" #include "iz3hash.h" #include "iz3interp.h" @@ -167,22 +166,6 @@ struct frame_reducer { #endif -#if 0 -static lbool test_secondary(context ctx, - int num, - ast *cnsts, - ast *interps, - int *parents = 0 - ){ - iz3secondary *sp = iz3foci::create(ctx,num,parents); - std::vector frames(num), interpolants(num-1); - std::copy(cnsts,cnsts+num,frames.begin()); - int res = sp->interpolate(frames,interpolants); - if(res == 0) - std::copy(interpolants.begin(),interpolants.end(),interps); - return res ? L_TRUE : L_FALSE; -} -#endif template struct killme { @@ -213,11 +196,7 @@ public: const std::vector &parents, std::vector &interps ){ - int num = cnsts.size(); - iz3secondary *sp = iz3foci::create(this,num,(int *)(parents.empty()?0:&parents[0])); - int res = sp->interpolate(cnsts, interps); - if(res != 0) - throw iz3_exception("secondary failed"); + throw iz3_exception("secondary interpolating prover not supported"); } void proof_to_interpolant(z3pf proof, @@ -248,10 +227,9 @@ public: if(is_linear(parents_vec)) parents_vec.clear(); - // create a secondary prover - iz3secondary *sp = iz3foci::create(this,num,parents_vec.empty()?0:&parents_vec[0]); - sp_killer.set(sp); // kill this on exit - + // secondary prover no longer supported + iz3secondary *sp = NULL; + #define BINARY_INTERPOLATION #ifndef BINARY_INTERPOLATION // create a translator diff --git a/src/interp/iz3translate.h b/src/interp/iz3translate.h index 86b0b3d2d..15e836cd8 100755 --- a/src/interp/iz3translate.h +++ b/src/interp/iz3translate.h @@ -53,12 +53,8 @@ class iz3translation : public iz3base { : iz3base(mgr,_cnsts,_parents,_theory) {} }; -//#define IZ3_TRANSLATE_DIRECT2 -#ifdef _FOCI2 -#define IZ3_TRANSLATE_DIRECT -#else +// To use a secondary prover, define IZ3_TRANSLATE_DIRECT instead of this #define IZ3_TRANSLATE_FULL -#endif #endif diff --git a/src/interp/iz3translate_direct.cpp b/src/interp/iz3translate_direct.cpp index 95d66f617..de1da6ce6 100755 --- a/src/interp/iz3translate_direct.cpp +++ b/src/interp/iz3translate_direct.cpp @@ -332,7 +332,7 @@ public: } } - // get the lits of a Z3 clause as foci terms + // get the lits of a Z3 clause as secondary prover terms void get_Z3_lits(ast t, std::vector &lits){ opr dk = op(t); if(dk == False) @@ -666,9 +666,9 @@ public: #endif // interpolate using secondary prover - profiling::timer_start("foci"); + profiling::timer_start("secondary prover"); int sat = secondary->interpolate(preds,itps); - profiling::timer_stop("foci"); + profiling::timer_stop("secondary prover"); std::cout << "lemma done" << std::endl; @@ -1495,7 +1495,7 @@ public: return find_nll(new_proofs); } - // translate a Z3 proof term into a foci proof term + // translate a Z3 proof term into a secondary prover proof term Iproof::node translate_main(ast proof, non_local_lits *nll, bool expect_clause = true){ non_local_lits *old_nll = nll; 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/sat/card_extension.cpp b/src/sat/card_extension.cpp index df5bb1eed..5032da6ff 100644 --- a/src/sat/card_extension.cpp +++ b/src/sat/card_extension.cpp @@ -46,6 +46,8 @@ namespace sat { m_lit(lit), m_k(k), m_size(wlits.size()), + m_slack(0), + m_num_watch(0), m_max_sum(0) { for (unsigned i = 0; i < wlits.size(); ++i) { m_wlits[i] = wlits[i]; @@ -237,7 +239,6 @@ namespace sat { p.negate(); } - TRACE("sat", display(tout << "init watch: ", p, true);); SASSERT(p.lit() == null_literal || value(p.lit()) == l_true); unsigned sz = p.size(), bound = p.k(); @@ -249,7 +250,7 @@ namespace sat { p.swap(i, j); } if (slack < bound) { - slack += p[i].first; + slack += p[j].first; ++num_watch; } ++j; @@ -266,7 +267,7 @@ namespace sat { if (slack < bound) { literal lit = p[j].second; SASSERT(value(p[j].second) == l_false); - for (unsigned i = j + 1; j < sz; ++i) { + for (unsigned i = j + 1; i < sz; ++i) { if (lvl(lit) < lvl(p[i].second)) { lit = p[i].second; } @@ -280,6 +281,7 @@ namespace sat { p.set_slack(slack); p.set_num_watch(num_watch); } + TRACE("sat", display(tout << "init watch: ", p, true);); } /* @@ -296,7 +298,7 @@ namespace sat { Lw = Lw u {l_s} Lu = Lu \ {l_s} } - if (Sw < bound) conflict + if (Sw < k) conflict while (Sw < k + a_max) { assign (l_max) a_max = max { ai | li in Lw, li = undef } @@ -308,7 +310,19 @@ namespace sat { */ + void card_extension::add_index(pb& p, unsigned index, literal lit) { + if (value(lit) == l_undef) { + m_pb_undef.push_back(index); + if (p[index].first > m_a_max) { + m_a_max = p[index].first; + } + } + } + lbool card_extension::add_assign(pb& p, literal alit) { + + TRACE("sat", display(tout << "assign: " << alit << "\n", p, true);); + SASSERT(!s().inconsistent()); unsigned sz = p.size(); unsigned bound = p.k(); unsigned num_watch = p.num_watch(); @@ -316,63 +330,43 @@ namespace sat { SASSERT(value(alit) == l_false); SASSERT(p.lit() == null_literal || value(p.lit()) == l_true); SASSERT(num_watch <= sz); + SASSERT(num_watch > 0); unsigned index = 0; - unsigned a_max = 0; - unsigned max_index = 0; + m_a_max = 0; m_pb_undef.reset(); for (; index < num_watch; ++index) { literal lit = p[index].second; if (lit == alit) { break; } - if (value(lit) == l_undef) { - m_pb_undef.push_back(index); - if (p[index].first > a_max) { - a_max = p[index].first; - max_index = index; - } - } + add_index(p, index, lit); } - - for (unsigned j = index + 1; a_max == 0 && j < num_watch; ++j) { - literal lit = p[j].second; - if (value(lit) == l_undef) { - m_pb_undef.push_back(j); - a_max = p[j].first; - max_index = j; - } - } - for (unsigned j = num_watch; a_max == 0 && j < sz; ++j) { - literal lit = p[j].second; - if (value(lit) == l_undef) { - p.swap(j, num_watch); - m_pb_undef.push_back(num_watch); - a_max = p[num_watch].first; - max_index = num_watch; - } + SASSERT(index < num_watch); + + unsigned index1 = index + 1; + for (; m_a_max == 0 && index1 < num_watch; ++index1) { + add_index(p, index1, p[index1].second); } unsigned val = p[index].first; - SASSERT(num_watch > 0); - SASSERT(index < num_watch); SASSERT(value(p[index].second) == l_false); SASSERT(val <= slack); slack -= val; // find literals to swap with: - for (unsigned j = num_watch; j < sz && slack < bound + a_max; ++j) { - if (value(p[j].second) != l_false) { + for (unsigned j = num_watch; j < sz && slack < bound + m_a_max; ++j) { + literal lit = p[j].second; + if (value(lit) != l_false) { slack += p[j].first; watch_literal(p, p[j]); p.swap(num_watch, j); - if (value(p[num_watch].second) == l_undef && a_max < p[num_watch].first) { - m_pb_undef.push_back(num_watch); - a_max = p[num_watch].first; - max_index = num_watch; - } + add_index(p, num_watch, lit); ++num_watch; } } + SASSERT(!s().inconsistent()); + DEBUG_CODE(for (auto idx : m_pb_undef) { SASSERT(value(p[idx].second) == l_undef); }); + if (slack < bound) { // maintain watching the literal slack += val; @@ -385,34 +379,38 @@ namespace sat { } // swap out the watched literal. - p.set_slack(slack); --num_watch; SASSERT(num_watch > 0); + p.set_slack(slack); p.set_num_watch(num_watch); p.swap(num_watch, index); - if (num_watch == max_index) { - max_index = index; - } - SASSERT(max_index < sz); - while (slack < bound + a_max && !s().inconsistent()) { - // variable at max-index must be assigned to true. - assign(p, p[max_index].second); - - a_max = 0; - // find the next a_max among m_pb_undef - while (!m_pb_undef.empty() && l_undef != value(p[m_pb_undef.back()].second)) { + if (slack < bound + m_a_max) { + TRACE("sat", display(tout, p, false); for(auto j : m_pb_undef) tout << j << "\n";); + literal_vector to_assign; + while (!m_pb_undef.empty()) { + index1 = m_pb_undef.back(); + literal lit = p[index1].second; + TRACE("sat", tout << index1 << " " << lit << "\n";); + if (slack >= bound + p[index1].first) { + break; + } m_pb_undef.pop_back(); + to_assign.push_back(lit); } - if (m_pb_undef.empty()) { - break; + + for (literal lit : to_assign) { + if (s().inconsistent()) break; + if (value(lit) == l_undef) { + assign(p, lit); + } } - max_index = m_pb_undef.back(); - a_max = p[max_index].first; - m_pb_undef.pop_back(); } - return s().inconsistent() ? l_false : l_true; + + TRACE("sat", display(tout << "assign: " << alit << "\n", p, true);); + + return l_undef; } void card_extension::watch_literal(pb& p, wliteral l) { @@ -467,6 +465,7 @@ namespace sat { set_conflict(p, lit); break; default: + SASSERT(validate_unit_propagation(p, lit)); m_stats.m_num_pb_propagations++; m_num_propagations_since_pop++; if (s().m_config.m_drat) { @@ -483,7 +482,8 @@ namespace sat { } void card_extension::display(std::ostream& out, pb const& p, bool values) const { - out << p.lit() << "[" << p.size() << "]"; + if (p.lit() != null_literal) out << p.lit(); + out << "[watch: " << p.num_watch() << ", slack: " << p.slack() << "]"; if (p.lit() != null_literal && values) { out << "@(" << value(p.lit()); if (value(p.lit()) != l_undef) { @@ -514,7 +514,7 @@ namespace sat { } void card_extension::asserted_pb(literal l, ptr_vector* pbs, pb* p0) { - TRACE("sat", tout << l << " " << !is_tag_empty(pbs) << " " << (p0 != 0) << "\n";); + TRACE("sat", tout << "literal: " << l << " has pb: " << !is_tag_empty(pbs) << " p0 != 0: " << (p0 != 0) << "\n";); if (!is_tag_empty(pbs)) { ptr_vector::iterator begin = pbs->begin(); ptr_vector::iterator it = begin, it2 = it, end = pbs->end(); @@ -524,20 +524,28 @@ namespace sat { continue; } switch (add_assign(p, ~l)) { - case l_false: // conflict - for (; it != end; ++it, ++it2) { - *it2 = *it; - } - SASSERT(s().inconsistent()); - pbs->set_end(it2); - return; case l_true: // unit propagation, keep watching the literal if (it2 != it) { *it2 = *it; } ++it2; break; + case l_false: // conflict. + SASSERT(s().inconsistent()); + for (; it != end; ++it, ++it2) { + *it2 = *it; + } + pbs->set_end(it2); + return; case l_undef: // watch literal was swapped + if (s().inconsistent()) { + ++it; + for (; it != end; ++it, ++it2) { + *it2 = *it; + } + pbs->set_end(it2); + return; + } break; } } @@ -844,6 +852,7 @@ namespace sat { literal consequent = s().m_not_l; justification js = s().m_conflict; TRACE("sat", tout << consequent << " " << js << "\n";); + TRACE("sat", s().display(tout);); m_conflict_lvl = s().get_max_lvl(consequent, js); if (consequent != null_literal) { consequent.neg(); @@ -942,7 +951,7 @@ namespace sat { m_bound += offset; inc_coeff(consequent, offset); get_pb_antecedents(consequent, p, m_lemma); - TRACE("sat", tout << m_lemma << "\n";); + TRACE("sat", display(tout, p, true); tout << m_lemma << "\n";); for (unsigned i = 0; i < m_lemma.size(); ++i) { process_antecedent(~m_lemma[i], offset); } @@ -989,6 +998,7 @@ namespace sat { SASSERT(lvl(v) == m_conflict_lvl); s().reset_mark(v); --idx; + TRACE("sat", tout << "Unmark: v" << v << "\n";); --m_num_marks; js = s().m_justification[v]; offset = get_abs_coeff(v); @@ -1015,7 +1025,6 @@ namespace sat { m_lemma.reset(); -#if 1 m_lemma.push_back(null_literal); for (unsigned i = 0; 0 <= slack && i < m_active_vars.size(); ++i) { bool_var v = m_active_vars[i]; @@ -1038,6 +1047,7 @@ namespace sat { } } +#if 0 if (jus.size() > 1) { std::cout << jus.size() << "\n"; for (unsigned i = 0; i < jus.size(); ++i) { @@ -1047,6 +1057,7 @@ namespace sat { active2pb(m_A); display(std::cout, m_A); } +#endif if (slack >= 0) { @@ -1057,7 +1068,7 @@ namespace sat { if (m_lemma[0] == null_literal) { m_lemma[0] = m_lemma.back(); m_lemma.pop_back(); - unsigned level = lvl(m_lemma[0]); + unsigned level = m_lemma.empty() ? 0 : lvl(m_lemma[0]); for (unsigned i = 1; i < m_lemma.size(); ++i) { if (lvl(m_lemma[i]) > level) { level = lvl(m_lemma[i]); @@ -1066,37 +1077,6 @@ namespace sat { } IF_VERBOSE(2, verbose_stream() << "(sat.card set level to " << level << " < " << m_conflict_lvl << ")\n";); } -#else - ++idx; - while (0 <= slack) { - literal lit = lits[idx]; - bool_var v = lit.var(); - if (m_active_var_set.contains(v)) { - int coeff = get_coeff(v); - if (coeff < 0 && !lit.sign()) { - slack += coeff; - m_lemma.push_back(~lit); - } - else if (coeff > 0 && lit.sign()) { - slack -= coeff; - m_lemma.push_back(~lit); - } - } - if (idx == 0 && slack >= 0) { - IF_VERBOSE(2, verbose_stream() << "(sat.card non-asserting)\n";); - goto bail_out; - } - SASSERT(idx > 0 || slack < 0); - --idx; - } - if (m_lemma.size() >= 2 && lvl(m_lemma[1]) == m_conflict_lvl) { - // TRACE("sat", tout << "Bail out on no progress " << lit << "\n";); - IF_VERBOSE(2, verbose_stream() << "(sat.card bail non-asserting resolvent)\n";); - goto bail_out; - } - -#endif - SASSERT(slack < 0); @@ -1108,7 +1088,7 @@ namespace sat { svector ps; // TBD fill in s().m_drat.add(m_lemma, ps); } - + s().m_lemma.reset(); s().m_lemma.append(m_lemma); for (unsigned i = 1; i < m_lemma.size(); ++i) { @@ -1153,6 +1133,7 @@ namespace sat { if (level > 0 && !s().is_marked(v) && level == m_conflict_lvl) { s().mark(v); + TRACE("sat", tout << "Mark: v" << v << "\n";); ++m_num_marks; } inc_coeff(l, offset); @@ -1347,20 +1328,30 @@ namespace sat { void card_extension::get_pb_antecedents(literal l, pb const& p, literal_vector& r) { if (p.lit() != null_literal) r.push_back(p.lit()); SASSERT(p.lit() == null_literal || value(p.lit()) == l_true); - unsigned k = p.k(); - unsigned max_sum = p.max_sum(); - for (unsigned i = p.size(); i > 0 && max_sum >= k; ) { - --i; - literal lit = p[i].second; - if (lit == l) { - max_sum -= p[i].first; - } - else if (value(lit) == l_false) { - r.push_back(~p[i].second); - max_sum -= p[i].first; + TRACE("sat", display(tout, p, true);); + + // unsigned coeff = get_coeff(p, l); + unsigned coeff = 0; + for (unsigned j = 0; j < p.num_watch(); ++j) { + if (p[j].second == l) { + coeff = p[j].first; + break; } } - SASSERT(max_sum < k); + unsigned slack = p.slack() - coeff; + unsigned i = p.num_watch(); + + // skip entries that are not required for unit propagation. + // slack - coeff + w_head < k + unsigned h = 0; + for (; i < p.size() && p[i].first + h + slack < p.k(); ++i) { + h += p[i].first; + } + for (; i < p.size(); ++i) { + literal lit = p[i].second; + SASSERT(l_false == value(lit)); + r.push_back(~lit); + } } void card_extension::get_card_antecedents(literal l, card const& c, literal_vector& r) { @@ -1720,7 +1711,9 @@ namespace sat { std::ostream& card_extension::display_justification(std::ostream& out, ext_justification_idx idx) const { if (is_card_index(idx)) { card& c = index2card(idx); - out << "bound " << c.lit() << ": "; + out << "bound "; + if (c.lit() != null_literal) out << c.lit(); + out << ": "; for (unsigned i = 0; i < c.size(); ++i) { out << c[i] << " "; } @@ -1728,14 +1721,18 @@ namespace sat { } else if (is_xor_index(idx)) { xor& x = index2xor(idx); - out << "xor " << x.lit() << ": "; + out << "xor "; + if (x.lit() != null_literal) out << x.lit(); + out << ": "; for (unsigned i = 0; i < x.size(); ++i) { out << x[i] << " "; } } else if (is_pb_index(idx)) { pb& p = index2pb(idx); - out << "pb " << p.lit() << ": "; + out << "pb "; + if (p.lit() != null_literal) out << p.lit(); + out << ": "; for (unsigned i = 0; i < p.size(); ++i) { out << p[i].first << "*" << p[i].second << " "; } @@ -1776,6 +1773,21 @@ namespace sat { } return true; } + bool card_extension::validate_unit_propagation(pb const& p, literal alit) { + if (p.lit() != null_literal && value(p.lit()) != l_true) return false; + + unsigned sum = 0; + TRACE("sat", display(tout << "validate: " << alit << "\n", p, true);); + for (unsigned i = 0; i < p.size(); ++i) { + literal lit = p[i].second; + lbool val = value(lit); + if (val != l_false && lit != alit) { + sum += p[i].first; + } + } + return sum < p.k(); + } + bool card_extension::validate_lemma() { int val = -m_bound; normalize_active_coeffs(); diff --git a/src/sat/card_extension.h b/src/sat/card_extension.h index f7e54b843..41219fae5 100644 --- a/src/sat/card_extension.h +++ b/src/sat/card_extension.h @@ -232,10 +232,12 @@ namespace sat { // pb functionality + unsigned m_a_max; void copy_pb(card_extension& result); void asserted_pb(literal l, ptr_vector* pbs, pb* p); void init_watch(pb& p, bool is_true); lbool add_assign(pb& p, literal alit); + void add_index(pb& p, unsigned index, literal lit); void watch_literal(pb& p, wliteral lit); void clear_watch(pb& p); void set_conflict(pb& p, literal lit); @@ -280,6 +282,7 @@ namespace sat { bool validate_assign(literal_vector const& lits, literal lit); bool validate_lemma(); bool validate_unit_propagation(card const& c); + bool validate_unit_propagation(pb const& p, literal lit); bool validate_conflict(literal_vector const& lits, ineq& p); ineq m_A, m_B, m_C; diff --git a/src/sat/sat_solver.cpp b/src/sat/sat_solver.cpp index 1374bf7c8..0e1e771d6 100644 --- a/src/sat/sat_solver.cpp +++ b/src/sat/sat_solver.cpp @@ -1938,23 +1938,25 @@ namespace sat { void solver::learn_lemma_and_backjump() { TRACE("sat_lemma", tout << "new lemma size: " << m_lemma.size() << "\n" << m_lemma << "\n";); - if (m_config.m_minimize_lemmas) { - minimize_lemma(); - reset_lemma_var_marks(); - if (m_config.m_dyn_sub_res) - dyn_sub_res(); - TRACE("sat_lemma", tout << "new lemma (after minimization) size: " << m_lemma.size() << "\n" << m_lemma << "\n";); - } - else - reset_lemma_var_marks(); - - literal_vector::iterator it = m_lemma.begin(); - literal_vector::iterator end = m_lemma.end(); unsigned new_scope_lvl = 0; - ++it; - for(; it != end; ++it) { - bool_var var = (*it).var(); - new_scope_lvl = std::max(new_scope_lvl, lvl(var)); + if (!m_lemma.empty()) { + if (m_config.m_minimize_lemmas) { + minimize_lemma(); + reset_lemma_var_marks(); + if (m_config.m_dyn_sub_res) + dyn_sub_res(); + TRACE("sat_lemma", tout << "new lemma (after minimization) size: " << m_lemma.size() << "\n" << m_lemma << "\n";); + } + else + reset_lemma_var_marks(); + + literal_vector::iterator it = m_lemma.begin(); + literal_vector::iterator end = m_lemma.end(); + ++it; + for(; it != end; ++it) { + bool_var var = (*it).var(); + new_scope_lvl = std::max(new_scope_lvl, lvl(var)); + } } unsigned glue = num_diff_levels(m_lemma.size(), m_lemma.c_ptr()); @@ -2390,6 +2392,7 @@ namespace sat { assigned at level 0. */ void solver::minimize_lemma() { + SASSERT(!m_lemma.empty()); SASSERT(m_unmark.empty()); //m_unmark.reset(); updt_lemma_lvl_set(); diff --git a/src/shell/lp_frontend.cpp b/src/shell/lp_frontend.cpp new file mode 100644 index 000000000..8bed24c83 --- /dev/null +++ b/src/shell/lp_frontend.cpp @@ -0,0 +1,110 @@ +/*++ +Copyright (c) 2016 Microsoft Corporation + +Author: + + Lev Nachmanson 2016-10-27 + +--*/ + +#include "lp_params.hpp" +#include "util/lp/lp_settings.h" +#include "util/lp/mps_reader.h" +#include "timeout.h" +#include "cancel_eh.h" +#include "scoped_timer.h" +#include "rlimit.h" +#include "gparams.h" +#include + +static lean::lp_solver* g_solver = 0; + +static void display_statistics() { + if (g_solver && g_solver->settings().print_statistics) { + // TBD display relevant information about statistics + } +} + +static void STD_CALL on_ctrl_c(int) { + signal (SIGINT, SIG_DFL); + #pragma omp critical (g_display_stats) + { + display_statistics(); + } + raise(SIGINT); +} + +static void on_timeout() { + #pragma omp critical (g_display_stats) + { + display_statistics(); + exit(0); + } +} + +struct front_end_resource_limit : public lean::lp_resource_limit { + reslimit& m_reslim; + + front_end_resource_limit(reslimit& lim): + m_reslim(lim) + {} + + virtual bool get_cancel_flag() { return !m_reslim.inc(); } +}; + +void run_solver(lp_params & params, char const * mps_file_name) { + + reslimit rlim; + unsigned timeout = gparams::get().get_uint("timeout", 0); + unsigned rlimit = gparams::get().get_uint("rlimit", 0); + front_end_resource_limit lp_limit(rlim); + + scoped_rlimit _rlimit(rlim, rlimit); + cancel_eh eh(rlim); + scoped_timer timer(timeout, &eh); + + std::string fn(mps_file_name); + lean::mps_reader reader(fn); + reader.set_message_stream(&std::cout); // can be redirected + reader.read(); + if (!reader.is_ok()) { + std::cerr << "cannot process " << mps_file_name << std::endl; + return; + } + lean::lp_solver * solver = reader.create_solver(false); // false - to create the primal solver + solver->settings().set_resource_limit(lp_limit); + g_solver = solver; + if (params.min()) { + solver->flip_costs(); + } + solver->settings().set_message_ostream(&std::cout); + solver->settings().report_frequency = params.rep_freq(); + solver->settings().print_statistics = params.print_stats(); + solver->find_maximal_solution(); + + *(solver->settings().get_message_ostream()) << "status is " << lp_status_to_string(solver->get_status()) << std::endl; + if (solver->get_status() == lean::OPTIMAL) { + if (params.min()) { + solver->flip_costs(); + } + solver->print_model(std::cout); + } + +// #pragma omp critical (g_display_stats) + { + display_statistics(); + register_on_timeout_proc(0); + g_solver = 0; + } + delete solver; +} + +unsigned read_mps_file(char const * mps_file_name) { + signal(SIGINT, on_ctrl_c); + register_on_timeout_proc(on_timeout); + lp_params p; + param_descrs r; + p.collect_param_descrs(r); + run_solver(p, mps_file_name); + return 0; +} diff --git a/src/shell/lp_frontend.h b/src/shell/lp_frontend.h new file mode 100644 index 000000000..b24be811f --- /dev/null +++ b/src/shell/lp_frontend.h @@ -0,0 +1,7 @@ +/* + Copyright (c) 2013 Microsoft Corporation. All rights reserved. + + Author: Lev Nachmanson +*/ +#pragma once +unsigned read_mps_file(char const * mps_file_name); diff --git a/src/shell/main.cpp b/src/shell/main.cpp index fddce4f69..b29586f8d 100644 --- a/src/shell/main.cpp +++ b/src/shell/main.cpp @@ -35,8 +35,9 @@ Revision History: #include"error_codes.h" #include"gparams.h" #include"env_params.h" +#include "lp_frontend.h" -typedef enum { IN_UNSPECIFIED, IN_SMTLIB, IN_SMTLIB_2, IN_DATALOG, IN_DIMACS, IN_WCNF, IN_OPB, IN_Z3_LOG } input_kind; +typedef enum { IN_UNSPECIFIED, IN_SMTLIB, IN_SMTLIB_2, IN_DATALOG, IN_DIMACS, IN_WCNF, IN_OPB, IN_Z3_LOG, IN_MPS } input_kind; std::string g_aux_input_file; char const * g_input_file = 0; @@ -342,6 +343,10 @@ int STD_CALL main(int argc, char ** argv) { else if (strcmp(ext, "smt") == 0) { g_input_kind = IN_SMTLIB; } + else if (strcmp(ext, "mps") == 0 || strcmp(ext, "sif") == 0 || + strcmp(ext, "MPS") == 0 || strcmp(ext, "SIF") == 0) { + g_input_kind = IN_MPS; + } } } switch (g_input_kind) { @@ -367,6 +372,9 @@ int STD_CALL main(int argc, char ** argv) { case IN_Z3_LOG: replay_z3_log(g_input_file); break; + case IN_MPS: + return_value = read_mps_file(g_input_file); + break; default: UNREACHABLE(); } diff --git a/src/smt/smt2_extra_cmds.cpp b/src/smt/smt2_extra_cmds.cpp index 901810442..61b247207 100644 --- a/src/smt/smt2_extra_cmds.cpp +++ b/src/smt/smt2_extra_cmds.cpp @@ -44,4 +44,4 @@ public: void install_smt2_extra_cmds(cmd_context & ctx) { ctx.insert(alloc(include_cmd)); -} \ No newline at end of file +} diff --git a/src/smt/smt_setup.cpp b/src/smt/smt_setup.cpp index a4f2ee646..4dd1e2510 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,6 +468,10 @@ namespace smt { m_params.m_restart_adaptive = false; } m_params.m_arith_small_lemma_size = 32; + setup_r_arith(); + } + + void setup::setup_QF_LIRA(static_features const& st) { setup_mi_arith(); } @@ -475,7 +480,7 @@ namespace smt { m_params.m_relevancy_lvl = 0; m_params.m_arith_expand_eqs = true; m_params.m_arith_reflect = false; - m_params.m_arith_propagate_eqs = false; + m_params.m_arith_propagate_eqs = false; m_params.m_nnf_cnf = false; setup_i_arith(); } @@ -539,7 +544,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 +723,12 @@ namespace smt { m_context.register_plugin(alloc(smt::theory_i_arith, m_manager, m_params)); } + void setup::setup_r_arith() { + // to disable theory lra + // m_context.register_plugin(alloc(smt::theory_mi_arith, m_manager, m_params)); + 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)); @@ -929,7 +940,9 @@ namespace smt { } if (st.num_theories() == 1 && is_arith(st)) { - if (st.m_has_real) + if ((st.m_has_int && st.m_has_real) || (st.m_num_non_linear != 0)) + setup_QF_LIRA(st); + else if (st.m_has_real) setup_QF_LRA(st); else setup_QF_LIA(st); diff --git a/src/smt/smt_setup.h b/src/smt/smt_setup.h index 80d5d7d1b..f12cc5e09 100644 --- a/src/smt/smt_setup.h +++ b/src/smt/smt_setup.h @@ -65,6 +65,7 @@ namespace smt { void setup_QF_LRA(); void setup_QF_LRA(static_features const & st); void setup_QF_LIA(); + void setup_QF_LIRA(static_features const& st); void setup_QF_LIA(static_features const & st); void setup_QF_UFLIA(); void setup_QF_UFLIA(static_features & st); @@ -99,6 +100,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/tactic/smt_tactic.cpp b/src/smt/tactic/smt_tactic.cpp index eef22fe06..9e3eddafa 100644 --- a/src/smt/tactic/smt_tactic.cpp +++ b/src/smt/tactic/smt_tactic.cpp @@ -21,6 +21,7 @@ Notes: #include"smt_kernel.h" #include"smt_params.h" #include"smt_params_helper.hpp" +#include"lp_params.hpp" #include"rewriter_types.h" #include"filter_model_converter.h" #include"ast_util.h" @@ -64,6 +65,10 @@ public: return m_params; } + params_ref & params() { + return m_params_ref; + } + void updt_params_core(params_ref const & p) { m_candidate_models = p.get_bool("candidate_models", false); m_fail_if_inconclusive = p.get_bool("fail_if_inconclusive", true); @@ -73,6 +78,7 @@ public: TRACE("smt_tactic", tout << "updt_params: " << p << "\n";); updt_params_core(p); fparams().updt_params(p); + m_params_ref.copy(p); m_logic = p.get_sym(symbol("logic"), m_logic); if (m_logic != symbol::null && m_ctx) { m_ctx->set_logic(m_logic); @@ -84,6 +90,7 @@ public: r.insert("candidate_models", CPK_BOOL, "(default: false) create candidate models even when quantifier or theory reasoning is incomplete."); r.insert("fail_if_inconclusive", CPK_BOOL, "(default: true) fail if found unsat (sat) for under (over) approximated goal."); smt_params_helper::collect_param_descrs(r); + lp_params::collect_param_descrs(r); } @@ -112,10 +119,12 @@ public: struct scoped_init_ctx { smt_tactic & m_owner; smt_params m_params; // smt-setup overwrites parameters depending on the current assertions. + params_ref m_params_ref; scoped_init_ctx(smt_tactic & o, ast_manager & m):m_owner(o) { m_params = o.fparams(); - smt::kernel * new_ctx = alloc(smt::kernel, m, m_params); + m_params_ref = o.params(); + smt::kernel * new_ctx = alloc(smt::kernel, m, m_params, m_params_ref); TRACE("smt_tactic", tout << "logic: " << o.m_logic << "\n";); new_ctx->set_logic(o.m_logic); if (o.m_callback) { diff --git a/src/smt/theory_lra.cpp b/src/smt/theory_lra.cpp new file mode 100644 index 000000000..05aa33d13 --- /dev/null +++ b/src/smt/theory_lra.cpp @@ -0,0 +1,2615 @@ +/*++ +Copyright (c) 2016 Microsoft Corporation + +Module Name: + + theory_lra.cpp + +Abstract: + + + +Author: + + Lev Nachmanson (levnach) 2016-25-3 + Nikolaj Bjorner (nbjorner) + +Revision History: + + +--*/ +#include "util/stopwatch.h" +#include "util/lp/lp_solver.h" +#include "util/lp/lp_primal_simplex.h" +#include "util/lp/lp_dual_simplex.h" +#include "util/lp/indexed_value.h" +#include "util/lp/lar_solver.h" +#include "util/nat_set.h" +#include "util/optional.h" +#include "lp_params.hpp" +#include "util/inf_rational.h" +#include "smt/smt_theory.h" +#include "smt/smt_context.h" +#include "smt/theory_lra.h" +#include "smt/proto_model/numeral_factory.h" +#include "smt/smt_model_generator.h" +#include "smt/arith_eq_adapter.h" +#include "util/nat_set.h" +#include "tactic/filter_model_converter.h" + +namespace lp { + enum bound_kind { lower_t, upper_t }; + + std::ostream& operator<<(std::ostream& out, bound_kind const& k) { + switch (k) { + case lower_t: return out << "<="; + case upper_t: return out << ">="; + } + return out; + } + + class bound { + smt::bool_var m_bv; + smt::theory_var m_var; + rational m_value; + bound_kind m_bound_kind; + + public: + bound(smt::bool_var bv, smt::theory_var v, rational const & val, bound_kind k): + m_bv(bv), + m_var(v), + m_value(val), + m_bound_kind(k) { + } + virtual ~bound() {} + smt::theory_var get_var() const { return m_var; } + smt::bool_var get_bv() const { return m_bv; } + bound_kind get_bound_kind() const { return m_bound_kind; } + rational const& get_value() const { return m_value; } + inf_rational get_value(bool is_true) const { + if (is_true) return inf_rational(m_value); // v >= value or v <= value + if (m_bound_kind == lower_t) return inf_rational(m_value, false); // v <= value - epsilon + return inf_rational(m_value, true); // v >= value + epsilon + } + virtual std::ostream& display(std::ostream& out) const { + return out << "v" << get_var() << " " << get_bound_kind() << " " << m_value; + } + }; + + std::ostream& operator<<(std::ostream& out, bound const& b) { + return b.display(out); + } + + struct stats { + unsigned m_assert_lower; + unsigned m_assert_upper; + unsigned m_add_rows; + unsigned m_bounds_propagations; + unsigned m_num_iterations; + unsigned m_num_iterations_with_no_progress; + unsigned m_num_factorizations; + unsigned m_need_to_solve_inf; + unsigned m_fixed_eqs; + unsigned m_conflicts; + unsigned m_bound_propagations1; + unsigned m_bound_propagations2; + unsigned m_assert_diseq; + unsigned m_make_feasible; + unsigned m_max_cols; + unsigned m_max_rows; + stats() { reset(); } + void reset() { + memset(this, 0, sizeof(*this)); + } + }; + + typedef optional opt_inf_rational; + + +} + +namespace smt { + + typedef ptr_vector lp_bounds; + + class theory_lra::imp { + + struct scope { + unsigned m_bounds_lim; + unsigned m_asserted_qhead; + unsigned m_asserted_atoms_lim; + unsigned m_delayed_terms_lim; + unsigned m_delayed_equalities_lim; + unsigned m_delayed_defs_lim; + unsigned m_underspecified_lim; + unsigned m_var_trail_lim; + expr* m_not_handled; + }; + + struct delayed_atom { + unsigned m_bv; + bool m_is_true; + delayed_atom(unsigned b, bool t): m_bv(b), m_is_true(t) {} + }; + + class resource_limit : public lean::lp_resource_limit { + imp& m_imp; + public: + resource_limit(imp& i): m_imp(i) { } + virtual bool get_cancel_flag() { return m_imp.m.canceled(); } + }; + + + theory_lra& th; + ast_manager& m; + theory_arith_params& m_arith_params; + arith_util a; + + arith_eq_adapter m_arith_eq_adapter; + + vector m_columns; + // temporary values kept during internalization + struct internalize_state { + expr_ref_vector m_terms; + vector m_coeffs; + svector m_vars; + rational m_coeff; + ptr_vector m_terms_to_internalize; + internalize_state(ast_manager& m): m_terms(m) {} + void reset() { + m_terms.reset(); + m_coeffs.reset(); + m_coeff.reset(); + m_vars.reset(); + m_terms_to_internalize.reset(); + } + }; + ptr_vector m_internalize_states; + unsigned m_internalize_head; + + class scoped_internalize_state { + imp& m_imp; + internalize_state& m_st; + + internalize_state& push_internalize(imp& i) { + if (i.m_internalize_head == i.m_internalize_states.size()) { + i.m_internalize_states.push_back(alloc(internalize_state, i.m)); + } + internalize_state& st = *i.m_internalize_states[i.m_internalize_head++]; + st.reset(); + return st; + } + public: + scoped_internalize_state(imp& i): m_imp(i), m_st(push_internalize(i)) {} + ~scoped_internalize_state() { --m_imp.m_internalize_head; } + expr_ref_vector& terms() { return m_st.m_terms; } + vector& coeffs() { return m_st.m_coeffs; } + svector& vars() { return m_st.m_vars; } + rational& coeff() { return m_st.m_coeff; } + ptr_vector& terms_to_internalize() { return m_st.m_terms_to_internalize; } + void push(expr* e, rational c) { m_st.m_terms.push_back(e); m_st.m_coeffs.push_back(c); } + void set_back(unsigned i) { + if (terms().size() == i + 1) return; + terms()[i] = terms().back(); + coeffs()[i] = coeffs().back(); + terms().pop_back(); + coeffs().pop_back(); + } + }; + + typedef vector> var_coeffs; + struct delayed_def { + vector m_coeffs; + svector m_vars; + rational m_coeff; + theory_var m_var; + delayed_def(svector const& vars, vector const& coeffs, rational const& r, theory_var v): + m_coeffs(coeffs), m_vars(vars), m_coeff(r), m_var(v) {} + }; + + svector m_theory_var2var_index; // translate from theory variables to lar vars + svector m_var_index2theory_var; // reverse map from lp_solver variables to theory variables + svector m_term_index2theory_var; // reverse map from lp_solver variables to theory variables + var_coeffs m_left_side; // constraint left side + mutable std::unordered_map m_variable_values; // current model + + enum constraint_source { + inequality_source, + equality_source, + definition_source, + null_source + }; + svector m_constraint_sources; + svector m_inequalities; // asserted rows corresponding to inequality literals. + svector m_equalities; // asserted rows corresponding to equalities. + svector m_definitions; // asserted rows corresponding to definitions + + bool m_delay_constraints; // configuration + svector m_asserted_atoms; + app_ref_vector m_delayed_terms; + svector> m_delayed_equalities; + vector m_delayed_defs; + expr* m_not_handled; + ptr_vector m_underspecified; + unsigned_vector m_var_trail; + vector > m_use_list; // bounds where variables are used. + + // attributes for incremental version: + u_map m_bool_var2bound; + vector m_bounds; + unsigned_vector m_unassigned_bounds; + unsigned_vector m_bounds_trail; + unsigned m_asserted_qhead; + + svector m_to_check; // rows that should be checked for theory propagation + + svector > m_assume_eq_candidates; + unsigned m_assume_eq_head; + + unsigned m_num_conflicts; + + + struct var_value_eq { + imp & m_th; + var_value_eq(imp & th):m_th(th) {} + bool operator()(theory_var v1, theory_var v2) const { return m_th.get_ivalue(v1) == m_th.get_ivalue(v2) && m_th.is_int(v1) == m_th.is_int(v2); } + }; + struct var_value_hash { + imp & m_th; + var_value_hash(imp & th):m_th(th) {} + unsigned operator()(theory_var v) const { return (unsigned)std::hash()(m_th.get_ivalue(v)); } + }; + int_hashtable m_model_eqs; + + + svector m_scopes; + lp::stats m_stats; + arith_factory* m_factory; + scoped_ptr m_solver; + resource_limit m_resource_limit; + lp_bounds m_new_bounds; + + + context& ctx() const { return th.get_context(); } + theory_id get_id() const { return th.get_id(); } + bool is_int(theory_var v) const { return is_int(get_enode(v)); } + bool is_int(enode* n) const { return a.is_int(n->get_owner()); } + enode* get_enode(theory_var v) const { return th.get_enode(v); } + enode* get_enode(expr* e) const { return ctx().get_enode(e); } + expr* get_owner(theory_var v) const { return get_enode(v)->get_owner(); } + + void init_solver() { + if (m_solver) return; + lp_params lp(ctx().get_params()); + m_solver = alloc(lean::lar_solver); + m_theory_var2var_index.reset(); + m_solver->settings().set_resource_limit(m_resource_limit); + m_solver->settings().simplex_strategy() = static_cast(lp.simplex_strategy()); + reset_variable_values(); + m_solver->settings().bound_propagation() = BP_NONE != propagation_mode(); + m_solver->set_propagate_bounds_on_pivoted_rows_mode(lp.bprop_on_pivoted_rows()); + //m_solver->settings().set_ostream(0); + } + + void found_not_handled(expr* n) { + m_not_handled = n; + if (is_app(n) && is_underspecified(to_app(n))) { + m_underspecified.push_back(to_app(n)); + } + TRACE("arith", tout << "Unhandled: " << mk_pp(n, m) << "\n";); + } + + bool is_numeral(expr* term, rational& r) { + rational mul(1); + do { + if (a.is_numeral(term, r)) { + r *= mul; + return true; + } + if (a.is_uminus(term, term)) { + mul.neg(); + continue; + } + if (a.is_to_real(term, term)) { + continue; + } + return false; + } + while (false); + return false; + } + + void linearize_term(expr* term, scoped_internalize_state& st) { + st.push(term, rational::one()); + linearize(st); + } + + void linearize_ineq(expr* lhs, expr* rhs, scoped_internalize_state& st) { + st.push(lhs, rational::one()); + st.push(rhs, rational::minus_one()); + linearize(st); + } + + void linearize(scoped_internalize_state& st) { + expr_ref_vector & terms = st.terms(); + svector& vars = st.vars(); + vector& coeffs = st.coeffs(); + rational& coeff = st.coeff(); + rational r; + expr* n1, *n2; + unsigned index = 0; + while (index < terms.size()) { + SASSERT(index >= vars.size()); + expr* n = terms[index].get(); + st.terms_to_internalize().push_back(n); + if (a.is_add(n)) { + unsigned sz = to_app(n)->get_num_args(); + for (unsigned i = 0; i < sz; ++i) { + st.push(to_app(n)->get_arg(i), coeffs[index]); + } + st.set_back(index); + } + else if (a.is_sub(n)) { + unsigned sz = to_app(n)->get_num_args(); + terms[index] = to_app(n)->get_arg(0); + for (unsigned i = 1; i < sz; ++i) { + st.push(to_app(n)->get_arg(i), -coeffs[index]); + } + } + else if (a.is_mul(n, n1, n2) && is_numeral(n1, r)) { + coeffs[index] *= r; + terms[index] = n2; + st.terms_to_internalize().push_back(n1); + } + else if (a.is_mul(n, n1, n2) && is_numeral(n2, r)) { + coeffs[index] *= r; + terms[index] = n1; + st.terms_to_internalize().push_back(n2); + } + else if (a.is_numeral(n, r)) { + coeff += coeffs[index]*r; + ++index; + } + else if (a.is_uminus(n, n1)) { + coeffs[index].neg(); + terms[index] = n1; + } + else if (is_app(n) && a.get_family_id() == to_app(n)->get_family_id()) { + app* t = to_app(n); + found_not_handled(n); + internalize_args(t); + mk_enode(t); + theory_var v = mk_var(n); + coeffs[vars.size()] = coeffs[index]; + vars.push_back(v); + ++index; + } + else { + if (is_app(n)) { + internalize_args(to_app(n)); + } + if (a.is_int(n)) { + found_not_handled(n); + } + theory_var v = mk_var(n); + coeffs[vars.size()] = coeffs[index]; + vars.push_back(v); + ++index; + } + } + for (unsigned i = st.terms_to_internalize().size(); i > 0; ) { + --i; + expr* n = st.terms_to_internalize()[i]; + if (is_app(n)) { + mk_enode(to_app(n)); + } + } + st.terms_to_internalize().reset(); + } + + void internalize_args(app* t) { + for (unsigned i = 0; reflect(t) && i < t->get_num_args(); ++i) { + if (!ctx().e_internalized(t->get_arg(i))) { + ctx().internalize(t->get_arg(i), false); + } + } + } + + enode * mk_enode(app * n) { + if (ctx().e_internalized(n)) { + return get_enode(n); + } + else { + return ctx().mk_enode(n, !reflect(n), false, enable_cgc_for(n)); + } + } + + bool enable_cgc_for(app * n) const { + // Congruence closure is not enabled for (+ ...) and (* ...) applications. + return !(n->get_family_id() == get_id() && (n->get_decl_kind() == OP_ADD || n->get_decl_kind() == OP_MUL)); + } + + + void mk_clause(literal l1, literal l2, unsigned num_params, parameter * params) { + TRACE("arith", literal lits[2]; lits[0] = l1; lits[1] = l2; ctx().display_literals_verbose(tout, 2, lits); tout << "\n";); + ctx().mk_th_axiom(get_id(), l1, l2, num_params, params); + } + + void mk_clause(literal l1, literal l2, literal l3, unsigned num_params, parameter * params) { + TRACE("arith", literal lits[3]; lits[0] = l1; lits[1] = l2; lits[2] = l3; ctx().display_literals_verbose(tout, 3, lits); tout << "\n";); + ctx().mk_th_axiom(get_id(), l1, l2, l3, num_params, params); + } + + bool is_underspecified(app* n) const { + if (n->get_family_id() == get_id()) { + switch (n->get_decl_kind()) { + case OP_DIV: + case OP_IDIV: + case OP_REM: + case OP_MOD: + return true; + default: + break; + } + } + return false; + } + + bool reflect(app* n) const { + return m_arith_params.m_arith_reflect || is_underspecified(n); + } + + theory_var mk_var(expr* n, bool internalize = true) { + if (!ctx().e_internalized(n)) { + ctx().internalize(n, false); + } + enode* e = get_enode(n); + theory_var v; + if (!th.is_attached_to_var(e)) { + v = th.mk_var(e); + SASSERT(m_bounds.size() <= static_cast(v) || m_bounds[v].empty()); + if (m_bounds.size() <= static_cast(v)) { + m_bounds.push_back(lp_bounds()); + m_unassigned_bounds.push_back(0); + } + ctx().attach_th_var(e, &th, v); + } + else { + v = e->get_th_var(get_id()); + } + SASSERT(null_theory_var != v); + return v; + } + + lean::var_index get_var_index(theory_var v) { + lean::var_index result = UINT_MAX; + if (m_theory_var2var_index.size() > static_cast(v)) { + result = m_theory_var2var_index[v]; + } + if (result == UINT_MAX) { + result = m_solver->add_var(v); + m_theory_var2var_index.setx(v, result, UINT_MAX); + m_var_index2theory_var.setx(result, v, UINT_MAX); + m_var_trail.push_back(v); + } + return result; + } + + void init_left_side(scoped_internalize_state& st) { + SASSERT(all_zeros(m_columns)); + svector const& vars = st.vars(); + vector const& coeffs = st.coeffs(); + for (unsigned i = 0; i < vars.size(); ++i) { + theory_var var = vars[i]; + rational const& coeff = coeffs[i]; + if (m_columns.size() <= static_cast(var)) { + m_columns.setx(var, coeff, rational::zero()); + } + else { + m_columns[var] += coeff; + } + } + m_left_side.clear(); + // reset the coefficients after they have been used. + for (unsigned i = 0; i < vars.size(); ++i) { + theory_var var = vars[i]; + rational const& r = m_columns[var]; + if (!r.is_zero()) { + m_left_side.push_back(std::make_pair(r, get_var_index(var))); + m_columns[var].reset(); + } + } + SASSERT(all_zeros(m_columns)); + } + + bool all_zeros(vector const& v) const { + for (unsigned i = 0; i < v.size(); ++i) { + if (!v[i].is_zero()) { + return false; + } + } + return true; + } + + void add_eq_constraint(lean::constraint_index index, enode* n1, enode* n2) { + m_constraint_sources.setx(index, equality_source, null_source); + m_equalities.setx(index, enode_pair(n1, n2), enode_pair(0, 0)); + ++m_stats.m_add_rows; + } + + void add_ineq_constraint(lean::constraint_index index, literal lit) { + m_constraint_sources.setx(index, inequality_source, null_source); + m_inequalities.setx(index, lit, null_literal); + ++m_stats.m_add_rows; + TRACE("arith", m_solver->print_constraint(index, tout); tout << "\n";); + } + + void add_def_constraint(lean::constraint_index index, theory_var v) { + m_constraint_sources.setx(index, definition_source, null_source); + m_definitions.setx(index, v, null_theory_var); + ++m_stats.m_add_rows; + } + + void internalize_eq(delayed_def const& d) { + scoped_internalize_state st(*this); + st.vars().append(d.m_vars); + st.coeffs().append(d.m_coeffs); + init_left_side(st); + add_def_constraint(m_solver->add_constraint(m_left_side, lean::EQ, -d.m_coeff), d.m_var); + } + + void internalize_eq(theory_var v1, theory_var v2) { + enode* n1 = get_enode(v1); + enode* n2 = get_enode(v2); + scoped_internalize_state st(*this); + st.vars().push_back(v1); + st.vars().push_back(v2); + st.coeffs().push_back(rational::one()); + st.coeffs().push_back(rational::minus_one()); + init_left_side(st); + add_eq_constraint(m_solver->add_constraint(m_left_side, lean::EQ, rational::zero()), n1, n2); + TRACE("arith", + tout << "v" << v1 << " = " << "v" << v2 << ": " + << mk_pp(n1->get_owner(), m) << " = " << mk_pp(n2->get_owner(), m) << "\n";); + } + + void del_bounds(unsigned old_size) { + for (unsigned i = m_bounds_trail.size(); i > old_size; ) { + --i; + unsigned v = m_bounds_trail[i]; + lp::bound* b = m_bounds[v].back(); + // del_use_lists(b); + dealloc(b); + m_bounds[v].pop_back(); + } + m_bounds_trail.shrink(old_size); + } + + void updt_unassigned_bounds(theory_var v, int inc) { + TRACE("arith", tout << "v" << v << " " << m_unassigned_bounds[v] << " += " << inc << "\n";); + ctx().push_trail(vector_value_trail(m_unassigned_bounds, v)); + m_unassigned_bounds[v] += inc; + } + + bool is_unit_var(scoped_internalize_state& st) { + return st.coeff().is_zero() && st.vars().size() == 1 && st.coeffs()[0].is_one(); + } + + theory_var internalize_def(app* term, scoped_internalize_state& st) { + linearize_term(term, st); + if (is_unit_var(st)) { + return st.vars()[0]; + } + else { + theory_var v = mk_var(term); + SASSERT(null_theory_var != v); + st.coeffs().resize(st.vars().size() + 1); + st.coeffs()[st.vars().size()] = rational::minus_one(); + st.vars().push_back(v); + return v; + } + } + + // term - v = 0 + theory_var internalize_def(app* term) { + scoped_internalize_state st(*this); + linearize_term(term, st); + if (is_unit_var(st)) { + return st.vars()[0]; + } + else { + init_left_side(st); + theory_var v = mk_var(term); + lean::var_index vi = m_theory_var2var_index.get(v, UINT_MAX); + if (vi == UINT_MAX) { + vi = m_solver->add_term(m_left_side, st.coeff()); + m_theory_var2var_index.setx(v, vi, UINT_MAX); + if (m_solver->is_term(vi)) { + m_term_index2theory_var.setx(m_solver->adjust_term_index(vi), v, UINT_MAX); + } + else { + m_var_index2theory_var.setx(vi, v, UINT_MAX); + } + m_var_trail.push_back(v); + TRACE("arith", tout << "v" << v << " := " << mk_pp(term, m) << " slack: " << vi << " scopes: " << m_scopes.size() << "\n"; + m_solver->print_term(m_solver->get_term(vi), tout); tout << "\n";); + } + rational val; + if (a.is_numeral(term, val)) { + m_fixed_var_table.insert(value_sort_pair(val, is_int(v)), v); + } + return v; + } + } + + + public: + imp(theory_lra& th, ast_manager& m, theory_arith_params& ap): + th(th), m(m), + m_arith_params(ap), + a(m), + m_arith_eq_adapter(th, ap, a), + m_internalize_head(0), + m_delay_constraints(false), + m_delayed_terms(m), + m_not_handled(0), + m_asserted_qhead(0), + m_assume_eq_head(0), + m_num_conflicts(0), + m_model_eqs(DEFAULT_HASHTABLE_INITIAL_CAPACITY, var_value_hash(*this), var_value_eq(*this)), + m_solver(0), + m_resource_limit(*this) { + } + + ~imp() { + del_bounds(0); + std::for_each(m_internalize_states.begin(), m_internalize_states.end(), delete_proc()); + } + + void init(context* ctx) { + init_solver(); + } + + bool internalize_atom(app * atom, bool gate_ctx) { + if (m_delay_constraints) { + return internalize_atom_lazy(atom, gate_ctx); + } + else { + return internalize_atom_strict(atom, gate_ctx); + } + } + + bool internalize_atom_strict(app * atom, bool gate_ctx) { + SASSERT(!ctx().b_internalized(atom)); + bool_var bv = ctx().mk_bool_var(atom); + ctx().set_var_theory(bv, get_id()); + expr* n1, *n2; + rational r; + lp::bound_kind k; + theory_var v = null_theory_var; + if (a.is_le(atom, n1, n2) && is_numeral(n2, r) && is_app(n1)) { + v = internalize_def(to_app(n1)); + k = lp::upper_t; + } + else if (a.is_ge(atom, n1, n2) && is_numeral(n2, r) && is_app(n1)) { + v = internalize_def(to_app(n1)); + k = lp::lower_t; + } + else { + TRACE("arith", tout << "Could not internalize " << mk_pp(atom, m) << "\n";); + found_not_handled(atom); + return true; + } + lp::bound* b = alloc(lp::bound, bv, v, r, k); + m_bounds[v].push_back(b); + updt_unassigned_bounds(v, +1); + m_bounds_trail.push_back(v); + m_bool_var2bound.insert(bv, b); + TRACE("arith", tout << "Internalized " << mk_pp(atom, m) << "\n";); + mk_bound_axioms(*b); + //add_use_lists(b); + return true; + } + + bool internalize_atom_lazy(app * atom, bool gate_ctx) { + SASSERT(!ctx().b_internalized(atom)); + bool_var bv = ctx().mk_bool_var(atom); + ctx().set_var_theory(bv, get_id()); + expr* n1, *n2; + rational r; + lp::bound_kind k; + theory_var v = null_theory_var; + scoped_internalize_state st(*this); + if (a.is_le(atom, n1, n2) && is_numeral(n2, r) && is_app(n1)) { + v = internalize_def(to_app(n1), st); + k = lp::upper_t; + } + else if (a.is_ge(atom, n1, n2) && is_numeral(n2, r) && is_app(n1)) { + v = internalize_def(to_app(n1), st); + k = lp::lower_t; + } + else { + TRACE("arith", tout << "Could not internalize " << mk_pp(atom, m) << "\n";); + found_not_handled(atom); + return true; + } + lp::bound* b = alloc(lp::bound, bv, v, r, k); + m_bounds[v].push_back(b); + updt_unassigned_bounds(v, +1); + m_bounds_trail.push_back(v); + m_bool_var2bound.insert(bv, b); + TRACE("arith", tout << "Internalized " << mk_pp(atom, m) << "\n";); + if (!is_unit_var(st) && m_bounds[v].size() == 1) { + m_delayed_defs.push_back(delayed_def(st.vars(), st.coeffs(), st.coeff(), v)); + } + return true; + } + + bool internalize_term(app * term) { + if (ctx().e_internalized(term) && th.is_attached_to_var(ctx().get_enode(term))) { + // skip + } + else if (m_delay_constraints) { + scoped_internalize_state st(*this); + linearize_term(term, st); // ensure that a theory_var was created. + SASSERT(ctx().e_internalized(term)); + if(!th.is_attached_to_var(ctx().get_enode(term))) { + mk_var(term); + } + m_delayed_terms.push_back(term); + } + else { + internalize_def(term); + } + return true; + } + + void internalize_eq_eh(app * atom, bool_var) { + expr* lhs, *rhs; + VERIFY(m.is_eq(atom, lhs, rhs)); + enode * n1 = get_enode(lhs); + enode * n2 = get_enode(rhs); + if (n1->get_th_var(get_id()) != null_theory_var && + n2->get_th_var(get_id()) != null_theory_var && + n1 != n2) { + TRACE("arith", tout << mk_pp(atom, m) << "\n";); + m_arith_eq_adapter.mk_axioms(n1, n2); + } + } + + void assign_eh(bool_var v, bool is_true) { + TRACE("arith", tout << mk_pp(ctx().bool_var2expr(v), m) << " " << (is_true?"true":"false") << "\n";); + m_asserted_atoms.push_back(delayed_atom(v, is_true)); + } + + void new_eq_eh(theory_var v1, theory_var v2) { + if (m_delay_constraints) { + m_delayed_equalities.push_back(std::make_pair(v1, v2)); + } + else { + // or internalize_eq(v1, v2); + m_arith_eq_adapter.new_eq_eh(v1, v2); + } + } + + bool use_diseqs() const { + return true; + } + + void new_diseq_eh(theory_var v1, theory_var v2) { + TRACE("arith", tout << "v" << v1 << " != " << "v" << v2 << "\n";); + ++m_stats.m_assert_diseq; + m_arith_eq_adapter.new_diseq_eh(v1, v2); + } + + void push_scope_eh() { + m_scopes.push_back(scope()); + scope& s = m_scopes.back(); + s.m_bounds_lim = m_bounds_trail.size(); + s.m_asserted_qhead = m_asserted_qhead; + s.m_asserted_atoms_lim = m_asserted_atoms.size(); + s.m_delayed_terms_lim = m_delayed_terms.size(); + s.m_delayed_equalities_lim = m_delayed_equalities.size(); + s.m_delayed_defs_lim = m_delayed_defs.size(); + s.m_not_handled = m_not_handled; + s.m_underspecified_lim = m_underspecified.size(); + s.m_var_trail_lim = m_var_trail.size(); + if (!m_delay_constraints) m_solver->push(); + } + + void pop_scope_eh(unsigned num_scopes) { + if (num_scopes == 0) { + return; + } + unsigned old_size = m_scopes.size() - num_scopes; + del_bounds(m_scopes[old_size].m_bounds_lim); + for (unsigned i = m_scopes[old_size].m_var_trail_lim; i < m_var_trail.size(); ++i) { + lean::var_index vi = m_theory_var2var_index[m_var_trail[i]]; + if (m_solver->is_term(vi)) { + unsigned ti = m_solver->adjust_term_index(vi); + m_term_index2theory_var[ti] = UINT_MAX; + } + else if (vi < m_var_index2theory_var.size()) { + m_var_index2theory_var[vi] = UINT_MAX; + } + m_theory_var2var_index[m_var_trail[i]] = UINT_MAX; + } + m_asserted_atoms.shrink(m_scopes[old_size].m_asserted_atoms_lim); + m_delayed_terms.shrink(m_scopes[old_size].m_delayed_terms_lim); + m_delayed_defs.shrink(m_scopes[old_size].m_delayed_defs_lim); + m_delayed_equalities.shrink(m_scopes[old_size].m_delayed_equalities_lim); + m_asserted_qhead = m_scopes[old_size].m_asserted_qhead; + m_underspecified.shrink(m_scopes[old_size].m_underspecified_lim); + m_var_trail.shrink(m_scopes[old_size].m_var_trail_lim); + m_not_handled = m_scopes[old_size].m_not_handled; + m_scopes.resize(old_size); + if (!m_delay_constraints) m_solver->pop(num_scopes); + // VERIFY(l_false != make_feasible()); + m_new_bounds.reset(); + m_to_check.reset(); + TRACE("arith", tout << "num scopes: " << num_scopes << " new scope level: " << m_scopes.size() << "\n";); + } + + void restart_eh() { + m_arith_eq_adapter.restart_eh(); + } + + void relevant_eh(app* n) { + TRACE("arith", tout << mk_pp(n, m) << "\n";); + expr* n1, *n2; + if (a.is_mod(n, n1, n2)) + mk_idiv_mod_axioms(n1, n2); + else if (a.is_rem(n, n1, n2)) + mk_rem_axiom(n1, n2); + else if (a.is_div(n, n1, n2)) + mk_div_axiom(n1, n2); + else if (a.is_to_int(n)) + mk_to_int_axiom(n); + else if (a.is_is_int(n)) + mk_is_int_axiom(n); + } + + // n < 0 || rem(a, n) = mod(a, n) + // !n < 0 || rem(a, n) = -mod(a, n) + void mk_rem_axiom(expr* dividend, expr* divisor) { + expr_ref zero(a.mk_int(0), m); + expr_ref rem(a.mk_rem(dividend, divisor), m); + expr_ref mod(a.mk_mod(dividend, divisor), m); + expr_ref mmod(a.mk_uminus(mod), m); + literal dgez = mk_literal(a.mk_ge(divisor, zero)); + mk_axiom(~dgez, th.mk_eq(rem, mod, false)); + mk_axiom( dgez, th.mk_eq(rem, mmod, false)); + } + + // q = 0 or q * (p div q) = p + void mk_div_axiom(expr* p, expr* q) { + if (a.is_zero(q)) return; + literal eqz = th.mk_eq(q, a.mk_real(0), false); + literal eq = th.mk_eq(a.mk_mul(q, a.mk_div(p, q)), p, false); + mk_axiom(eqz, eq); + } + + // to_int (to_real x) = x + // to_real(to_int(x)) <= x < to_real(to_int(x)) + 1 + void mk_to_int_axiom(app* n) { + expr* x, *y; + VERIFY (a.is_to_int(n, x)); + if (a.is_to_real(x, y)) { + mk_axiom(th.mk_eq(y, n, false)); + } + else { + expr_ref to_r(a.mk_to_real(n), m); + expr_ref lo(a.mk_le(a.mk_sub(to_r, x), a.mk_real(0)), m); + expr_ref hi(a.mk_ge(a.mk_sub(x, to_r), a.mk_real(1)), m); + mk_axiom(mk_literal(lo)); + mk_axiom(~mk_literal(hi)); + } + } + + // is_int(x) <=> to_real(to_int(x)) = x + void mk_is_int_axiom(app* n) { + expr* x; + VERIFY(a.is_is_int(n, x)); + literal eq = th.mk_eq(a.mk_to_real(a.mk_to_int(x)), x, false); + literal is_int = ctx().get_literal(n); + mk_axiom(~is_int, eq); + mk_axiom(is_int, ~eq); + } + + void mk_idiv_mod_axioms(expr * p, expr * q) { + if (a.is_zero(q)) { + return; + } + // if q is zero, then idiv and mod are uninterpreted functions. + expr_ref div(a.mk_idiv(p, q), m); + expr_ref mod(a.mk_mod(p, q), m); + expr_ref zero(a.mk_int(0), m); + literal q_ge_0 = mk_literal(a.mk_ge(q, zero)); + literal q_le_0 = mk_literal(a.mk_le(q, zero)); + // literal eqz = th.mk_eq(q, zero, false); + literal eq = th.mk_eq(a.mk_add(a.mk_mul(q, div), mod), p, false); + literal mod_ge_0 = mk_literal(a.mk_ge(mod, zero)); + // q >= 0 or p = (p mod q) + q * (p div q) + // q <= 0 or p = (p mod q) + q * (p div q) + // q >= 0 or (p mod q) >= 0 + // q <= 0 or (p mod q) >= 0 + // q <= 0 or (p mod q) < q + // q >= 0 or (p mod q) < -q + mk_axiom(q_ge_0, eq); + mk_axiom(q_le_0, eq); + mk_axiom(q_ge_0, mod_ge_0); + mk_axiom(q_le_0, mod_ge_0); + mk_axiom(q_le_0, ~mk_literal(a.mk_ge(a.mk_sub(mod, q), zero))); + mk_axiom(q_ge_0, ~mk_literal(a.mk_ge(a.mk_add(mod, q), zero))); + rational k; + if (m_arith_params.m_arith_enum_const_mod && a.is_numeral(q, k) && + k.is_pos() && k < rational(8)) { + unsigned _k = k.get_unsigned(); + literal_buffer lits; + for (unsigned j = 0; j < _k; ++j) { + literal mod_j = th.mk_eq(mod, a.mk_int(j), false); + lits.push_back(mod_j); + ctx().mark_as_relevant(mod_j); + } + ctx().mk_th_axiom(get_id(), lits.size(), lits.begin()); + } + } + + void mk_axiom(literal l) { + ctx().mk_th_axiom(get_id(), false_literal, l); + if (ctx().relevancy()) { + ctx().mark_as_relevant(l); + } + } + + void mk_axiom(literal l1, literal l2) { + if (l1 == false_literal) { + mk_axiom(l2); + return; + } + ctx().mk_th_axiom(get_id(), l1, l2); + if (ctx().relevancy()) { + ctx().mark_as_relevant(l1); + expr_ref e(m); + ctx().literal2expr(l2, e); + ctx().add_rel_watch(~l1, e); + } + } + + void mk_axiom(literal l1, literal l2, literal l3) { + ctx().mk_th_axiom(get_id(), l1, l2, l3); + if (ctx().relevancy()) { + expr_ref e(m); + ctx().mark_as_relevant(l1); + ctx().literal2expr(l2, e); + ctx().add_rel_watch(~l1, e); + ctx().literal2expr(l3, e); + ctx().add_rel_watch(~l2, e); + } + } + + literal mk_literal(expr* e) { + expr_ref pinned(e, m); + if (!ctx().e_internalized(e)) { + ctx().internalize(e, false); + } + return ctx().get_literal(e); + } + + + void init_search_eh() { + m_arith_eq_adapter.init_search_eh(); + m_num_conflicts = 0; + } + + bool can_get_value(theory_var v) const { + return + (v != null_theory_var) && + (v < static_cast(m_theory_var2var_index.size())) && + (UINT_MAX != m_theory_var2var_index[v]) && + (m_solver->is_term(m_theory_var2var_index[v]) || m_variable_values.count(m_theory_var2var_index[v]) > 0); + } + + + bool can_get_ivalue(theory_var v) const { + if (v == null_theory_var || (v >= static_cast(m_theory_var2var_index.size()))) + return false; + return m_solver->var_is_registered(m_theory_var2var_index[v]); + } + + lean::impq get_ivalue(theory_var v) const { + lean_assert(can_get_ivalue(v)); + lean::var_index vi = m_theory_var2var_index[v]; + if (!m_solver->is_term(vi)) + return m_solver->get_value(vi); + + const lean::lar_term& term = m_solver->get_term(vi); + lean::impq result(term.m_v); + for (const auto & i: term.m_coeffs) { + result += m_solver->get_value(i.first) * i.second; + } + return result; + } + + + rational get_value(theory_var v) const { + if (!can_get_value(v)) return rational::zero(); + lean::var_index vi = m_theory_var2var_index[v]; + if (m_variable_values.count(vi) > 0) { + return m_variable_values[vi]; + } + if (m_solver->is_term(vi)) { + const lean::lar_term& term = m_solver->get_term(vi); + rational result = term.m_v; + for (auto i = term.m_coeffs.begin(); i != term.m_coeffs.end(); ++i) { + result += m_variable_values[i->first] * i->second; + } + m_variable_values[vi] = result; + return result; + } + UNREACHABLE(); + return m_variable_values[vi]; + } + + void init_variable_values() { + if (m_solver.get() && th.get_num_vars() > 0) { + m_solver->get_model(m_variable_values); + } + } + + void reset_variable_values() { + m_variable_values.clear(); + } + + bool assume_eqs() { + svector vars; + theory_var sz = static_cast(th.get_num_vars()); + for (theory_var v = 0; v < sz; ++v) { + if (th.is_relevant_and_shared(get_enode(v))) { + vars.push_back(m_theory_var2var_index[v]); + } + } + if (vars.empty()) { + return false; + } + TRACE("arith", + for (theory_var v = 0; v < sz; ++v) { + if (th.is_relevant_and_shared(get_enode(v))) { + tout << "v" << v << " " << m_theory_var2var_index[v] << " "; + } + } + tout << "\n"; + ); + m_solver->random_update(vars.size(), vars.c_ptr()); + m_model_eqs.reset(); + TRACE("arith", display(tout);); + + unsigned old_sz = m_assume_eq_candidates.size(); + bool result = false; + int start = ctx().get_random_value(); + for (theory_var i = 0; i < sz; ++i) { + theory_var v = (i + start) % sz; + enode* n1 = get_enode(v); + if (!th.is_relevant_and_shared(n1)) { + continue; + } + if (!can_get_ivalue(v)) { + continue; + } + theory_var other = m_model_eqs.insert_if_not_there(v); + if (other == v) { + continue; + } + enode* n2 = get_enode(other); + if (n1->get_root() != n2->get_root()) { + TRACE("arith", tout << mk_pp(n1->get_owner(), m) << " = " << mk_pp(n2->get_owner(), m) << "\n"; + tout << mk_pp(n1->get_owner(), m) << " = " << mk_pp(n2->get_owner(), m) << "\n"; + tout << "v" << v << " = " << "v" << other << "\n";); + m_assume_eq_candidates.push_back(std::make_pair(v, other)); + result = true; + } + } + + if (result) { + ctx().push_trail(restore_size_trail, false>(m_assume_eq_candidates, old_sz)); + } + + return delayed_assume_eqs(); + } + + bool delayed_assume_eqs() { + if (m_assume_eq_head == m_assume_eq_candidates.size()) + return false; + + ctx().push_trail(value_trail(m_assume_eq_head)); + while (m_assume_eq_head < m_assume_eq_candidates.size()) { + std::pair const & p = m_assume_eq_candidates[m_assume_eq_head]; + theory_var v1 = p.first; + theory_var v2 = p.second; + enode* n1 = get_enode(v1); + enode* n2 = get_enode(v2); + m_assume_eq_head++; + CTRACE("arith", + get_ivalue(v1) == get_ivalue(v2) && n1->get_root() != n2->get_root(), + tout << "assuming eq: v" << v1 << " = v" << v2 << "\n";); + if (get_ivalue(v1) == get_ivalue(v2) && n1->get_root() != n2->get_root() && th.assume_eq(n1, n2)) { + return true; + } + } + return false; + } + + bool has_delayed_constraints() const { + return !(m_asserted_atoms.empty() && m_delayed_terms.empty() && m_delayed_equalities.empty()); + } + + final_check_status final_check_eh() { + lbool is_sat = l_true; + if (m_delay_constraints) { + init_solver(); + for (unsigned i = 0; i < m_asserted_atoms.size(); ++i) { + bool_var bv = m_asserted_atoms[i].m_bv; + assert_bound(bv, m_asserted_atoms[i].m_is_true, *m_bool_var2bound.find(bv)); + } + for (unsigned i = 0; i < m_delayed_terms.size(); ++i) { + internalize_def(m_delayed_terms[i].get()); + } + for (unsigned i = 0; i < m_delayed_defs.size(); ++i) { + internalize_eq(m_delayed_defs[i]); + } + for (unsigned i = 0; i < m_delayed_equalities.size(); ++i) { + std::pair const& eq = m_delayed_equalities[i]; + internalize_eq(eq.first, eq.second); + } + is_sat = make_feasible(); + } + else if (m_solver->get_status() != lean::lp_status::OPTIMAL) { + is_sat = make_feasible(); + } + switch (is_sat) { + case l_true: + if (delayed_assume_eqs()) { + return FC_CONTINUE; + } + if (assume_eqs()) { + return FC_CONTINUE; + } + if (m_not_handled != 0) { + return FC_GIVEUP; + } + return FC_DONE; + case l_false: + set_conflict(); + return FC_CONTINUE; + case l_undef: + return m.canceled() ? FC_CONTINUE : FC_GIVEUP; + default: + UNREACHABLE(); + break; + } + return FC_GIVEUP; + } + + + /** + \brief We must redefine this method, because theory of arithmetic contains + underspecified operators such as division by 0. + (/ a b) is essentially an uninterpreted function when b = 0. + Thus, 'a' must be considered a shared var if it is the child of an underspecified operator. + + if merge(a / b, x + y) and a / b is root, then x + y become shared and all z + u in equivalence class of x + y. + + + TBD: when the set of underspecified subterms is small, compute the shared variables below it. + Recompute the set if there are merges that invalidate it. + Use the set to determine if a variable is shared. + */ + bool is_shared(theory_var v) const { + if (m_underspecified.empty()) { + return false; + } + enode * n = get_enode(v); + enode * r = n->get_root(); + unsigned usz = m_underspecified.size(); + TRACE("shared", tout << ctx().get_scope_level() << " " << v << " " << r->get_num_parents() << "\n";); + if (r->get_num_parents() > 2*usz) { + for (unsigned i = 0; i < usz; ++i) { + app* u = m_underspecified[i]; + unsigned sz = u->get_num_args(); + for (unsigned j = 0; j < sz; ++j) { + if (ctx().get_enode(u->get_arg(j))->get_root() == r) { + return true; + } + } + } + } + else { + enode_vector::const_iterator it = r->begin_parents(); + enode_vector::const_iterator end = r->end_parents(); + for (; it != end; ++it) { + enode * parent = *it; + if (is_underspecified(parent->get_owner())) { + return true; + } + } + } + return false; + } + + bool can_propagate() { +#if 0 + if (ctx().at_base_level() && has_delayed_constraints()) { + // we could add the delayed constraints here directly to the tableau instead of using bounds variables. + } +#endif + return m_asserted_atoms.size() > m_asserted_qhead; + } + + void propagate() { + flush_bound_axioms(); + if (!can_propagate()) { + return; + } + while (m_asserted_qhead < m_asserted_atoms.size() && !ctx().inconsistent()) { + bool_var bv = m_asserted_atoms[m_asserted_qhead].m_bv; + bool is_true = m_asserted_atoms[m_asserted_qhead].m_is_true; + +#if 1 + m_to_check.push_back(bv); +#else + propagate_bound(bv, is_true, b); +#endif + if (!m_delay_constraints) { + lp::bound& b = *m_bool_var2bound.find(bv); + assert_bound(bv, is_true, b); + } + + ++m_asserted_qhead; + } + if (m_delay_constraints || ctx().inconsistent()) { + m_to_check.reset(); + return; + } + /*for (; qhead < m_asserted_atoms.size() && !ctx().inconsistent(); ++qhead) { + bool_var bv = m_asserted_atoms[qhead].m_bv; + bool is_true = m_asserted_atoms[qhead].m_is_true; + lp::bound& b = *m_bool_var2bound.find(bv); + propagate_bound_compound(bv, is_true, b); + }*/ + + lbool lbl = make_feasible(); + + switch(lbl) { + case l_false: + TRACE("arith", tout << "propagation conflict\n";); + set_conflict(); + break; + case l_true: + propagate_basic_bounds(); + propagate_bounds_with_lp_solver(); + break; + case l_undef: + break; + } + + } + + void propagate_bounds_with_lp_solver() { + if (BP_NONE == propagation_mode()) { + return; + } + int num_of_p = m_solver->settings().st().m_num_of_implied_bounds; + local_bound_propagator bp(*this); + m_solver->propagate_bounds_for_touched_rows(bp); + if (m.canceled()) { + return; + } + int new_num_of_p = m_solver->settings().st().m_num_of_implied_bounds; + CTRACE("arith", new_num_of_p > num_of_p, tout << "found " << new_num_of_p << " implied bounds\n";); + if (m_solver->get_status() == lean::lp_status::INFEASIBLE) { + set_conflict(); + } + else { + for (unsigned i = 0; !m.canceled() && !ctx().inconsistent() && i < bp.m_ibounds.size(); ++i) { + propagate_lp_solver_bound(bp.m_ibounds[i]); + } + } + } + + bool bound_is_interesting(unsigned vi, lean::lconstraint_kind kind, const rational & bval) const { + theory_var v; + if (m_solver->is_term(vi)) { + v = m_term_index2theory_var.get(m_solver->adjust_term_index(vi), null_theory_var); + } + else { + v = m_var_index2theory_var.get(vi, null_theory_var); + } + + if (v == null_theory_var) return false; + + if (m_unassigned_bounds[v] == 0 || m_bounds.size() <= static_cast(v)) { + TRACE("arith", tout << "return\n";); + return false; + } + lp_bounds const& bounds = m_bounds[v]; + for (unsigned i = 0; i < bounds.size(); ++i) { + lp::bound* b = bounds[i]; + if (ctx().get_assignment(b->get_bv()) != l_undef) { + continue; + } + literal lit = is_bound_implied(kind, bval, *b); + if (lit == null_literal) { + continue; + } + return true; + } + return false; + } + + struct local_bound_propagator: public lean::bound_propagator { + imp & m_imp; + local_bound_propagator(imp& i) : bound_propagator(*i.m_solver), m_imp(i) {} + + bool bound_is_interesting(unsigned j, lean::lconstraint_kind kind, const rational & v) { + return m_imp.bound_is_interesting(j, kind, v); + } + + virtual void consume(rational const& v, unsigned j) { + m_imp.set_evidence(j); + } + }; + + + void propagate_lp_solver_bound(lean::implied_bound& be) { + + theory_var v; + lean::var_index vi = be.m_j; + if (m_solver->is_term(vi)) { + v = m_term_index2theory_var.get(m_solver->adjust_term_index(vi), null_theory_var); + } + else { + v = m_var_index2theory_var.get(vi, null_theory_var); + } + + if (v == null_theory_var) return; + TRACE("arith", tout << "v" << v << " " << be.kind() << " " << be.m_bound << "\n"; + // if (m_unassigned_bounds[v] == 0) m_solver->print_bound_evidence(be, tout); + ); + + + if (m_unassigned_bounds[v] == 0 || m_bounds.size() <= static_cast(v)) { + TRACE("arith", tout << "return\n";); + return; + } + lp_bounds const& bounds = m_bounds[v]; + bool first = true; + for (unsigned i = 0; i < bounds.size(); ++i) { + lp::bound* b = bounds[i]; + if (ctx().get_assignment(b->get_bv()) != l_undef) { + continue; + } + literal lit = is_bound_implied(be.kind(), be.m_bound, *b); + if (lit == null_literal) { + continue; + } + + m_solver->settings().st().m_num_of_implied_bounds ++; + if (first) { + first = false; + m_core.reset(); + m_eqs.reset(); + m_params.reset(); + local_bound_propagator bp(*this); + m_solver->explain_implied_bound(be, bp); + } + CTRACE("arith", m_unassigned_bounds[v] == 0, tout << "missed bound\n";); + updt_unassigned_bounds(v, -1); + TRACE("arith", + ctx().display_literals_verbose(tout, m_core); + tout << "\n --> "; + ctx().display_literal_verbose(tout, lit); + tout << "\n"; + // display_evidence(tout, m_explanation); + m_solver->print_implied_bound(be, tout); + ); + DEBUG_CODE( + for (auto& lit : m_core) { + SASSERT(ctx().get_assignment(lit) == l_true); + }); + ++m_stats.m_bound_propagations1; + assign(lit); + } + } + + literal_vector m_core2; + + void assign(literal lit) { + SASSERT(validate_assign(lit)); + if (m_core.size() < small_lemma_size() && m_eqs.empty()) { + m_core2.reset(); + for (unsigned i = 0; i < m_core.size(); ++i) { + m_core2.push_back(~m_core[i]); + } + m_core2.push_back(lit); + justification * js = 0; + if (proofs_enabled()) { + js = alloc(theory_lemma_justification, get_id(), ctx(), m_core2.size(), m_core2.c_ptr(), + m_params.size(), m_params.c_ptr()); + } + ctx().mk_clause(m_core2.size(), m_core2.c_ptr(), js, CLS_AUX_LEMMA, 0); + } + else { + ctx().assign( + lit, ctx().mk_justification( + ext_theory_propagation_justification( + get_id(), ctx().get_region(), m_core.size(), m_core.c_ptr(), + m_eqs.size(), m_eqs.c_ptr(), lit, m_params.size(), m_params.c_ptr()))); + } + } + + literal is_bound_implied(lean::lconstraint_kind k, rational const& value, lp::bound const& b) const { + if ((k == lean::LE || k == lean::LT) && b.get_bound_kind() == lp::upper_t && value <= b.get_value()) { + // v <= value <= b.get_value() => v <= b.get_value() + return literal(b.get_bv(), false); + } + if ((k == lean::GE || k == lean::GT) && b.get_bound_kind() == lp::lower_t && b.get_value() <= value) { + // b.get_value() <= value <= v => b.get_value() <= v + return literal(b.get_bv(), false); + } + if (k == lean::LE && b.get_bound_kind() == lp::lower_t && value < b.get_value()) { + // v <= value < b.get_value() => v < b.get_value() + return literal(b.get_bv(), true); + } + if (k == lean::LT && b.get_bound_kind() == lp::lower_t && value <= b.get_value()) { + // v < value <= b.get_value() => v < b.get_value() + return literal(b.get_bv(), true); + } + if (k == lean::GE && b.get_bound_kind() == lp::upper_t && b.get_value() < value) { + // b.get_value() < value <= v => b.get_value() < v + return literal(b.get_bv(), true); + } + if (k == lean::GT && b.get_bound_kind() == lp::upper_t && b.get_value() <= value) { + // b.get_value() <= value < v => b.get_value() < v + return literal(b.get_bv(), true); + } + + return null_literal; + } + + void mk_bound_axioms(lp::bound& b) { + if (!ctx().is_searching()) { + // + // NB. We make an assumption that user push calls propagation + // before internal scopes are pushed. This flushes all newly + // asserted atoms into the right context. + // + m_new_bounds.push_back(&b); + return; + } + theory_var v = b.get_var(); + lp::bound_kind kind1 = b.get_bound_kind(); + rational const& k1 = b.get_value(); + lp_bounds & bounds = m_bounds[v]; + + lp::bound* end = 0; + lp::bound* lo_inf = end, *lo_sup = end; + lp::bound* hi_inf = end, *hi_sup = end; + + for (unsigned i = 0; i < bounds.size(); ++i) { + lp::bound& other = *bounds[i]; + if (&other == &b) continue; + if (b.get_bv() == other.get_bv()) continue; + lp::bound_kind kind2 = other.get_bound_kind(); + rational const& k2 = other.get_value(); + if (k1 == k2 && kind1 == kind2) { + // the bounds are equivalent. + continue; + } + + SASSERT(k1 != k2 || kind1 != kind2); + if (kind2 == lp::lower_t) { + if (k2 < k1) { + if (lo_inf == end || k2 > lo_inf->get_value()) { + lo_inf = &other; + } + } + else if (lo_sup == end || k2 < lo_sup->get_value()) { + lo_sup = &other; + } + } + else if (k2 < k1) { + if (hi_inf == end || k2 > hi_inf->get_value()) { + hi_inf = &other; + } + } + else if (hi_sup == end || k2 < hi_sup->get_value()) { + hi_sup = &other; + } + } + if (lo_inf != end) mk_bound_axiom(b, *lo_inf); + if (lo_sup != end) mk_bound_axiom(b, *lo_sup); + if (hi_inf != end) mk_bound_axiom(b, *hi_inf); + if (hi_sup != end) mk_bound_axiom(b, *hi_sup); + } + + + void mk_bound_axiom(lp::bound& b1, lp::bound& b2) { + theory_var v = b1.get_var(); + literal l1(b1.get_bv()); + literal l2(b2.get_bv()); + rational const& k1 = b1.get_value(); + rational const& k2 = b2.get_value(); + lp::bound_kind kind1 = b1.get_bound_kind(); + lp::bound_kind kind2 = b2.get_bound_kind(); + bool v_is_int = is_int(v); + SASSERT(v == b2.get_var()); + if (k1 == k2 && kind1 == kind2) return; + SASSERT(k1 != k2 || kind1 != kind2); + parameter coeffs[3] = { parameter(symbol("farkas")), + parameter(rational(1)), parameter(rational(1)) }; + + if (kind1 == lp::lower_t) { + if (kind2 == lp::lower_t) { + if (k2 <= k1) { + mk_clause(~l1, l2, 3, coeffs); + } + else { + mk_clause(l1, ~l2, 3, coeffs); + } + } + else if (k1 <= k2) { + // k1 <= k2, k1 <= x or x <= k2 + mk_clause(l1, l2, 3, coeffs); + } + else { + // k1 > hi_inf, k1 <= x => ~(x <= hi_inf) + mk_clause(~l1, ~l2, 3, coeffs); + if (v_is_int && k1 == k2 + rational(1)) { + // k1 <= x or x <= k1-1 + mk_clause(l1, l2, 3, coeffs); + } + } + } + else if (kind2 == lp::lower_t) { + if (k1 >= k2) { + // k1 >= lo_inf, k1 >= x or lo_inf <= x + mk_clause(l1, l2, 3, coeffs); + } + else { + // k1 < k2, k2 <= x => ~(x <= k1) + mk_clause(~l1, ~l2, 3, coeffs); + if (v_is_int && k1 == k2 - rational(1)) { + // x <= k1 or k1+l <= x + mk_clause(l1, l2, 3, coeffs); + } + + } + } + else { + // kind1 == A_UPPER, kind2 == A_UPPER + if (k1 >= k2) { + // k1 >= k2, x <= k2 => x <= k1 + mk_clause(l1, ~l2, 3, coeffs); + } + else { + // k1 <= hi_sup , x <= k1 => x <= hi_sup + mk_clause(~l1, l2, 3, coeffs); + } + } + } + + typedef lp_bounds::iterator iterator; + + void flush_bound_axioms() { + CTRACE("arith", !m_new_bounds.empty(), tout << "flush bound axioms\n";); + + while (!m_new_bounds.empty()) { + lp_bounds atoms; + atoms.push_back(m_new_bounds.back()); + m_new_bounds.pop_back(); + theory_var v = atoms.back()->get_var(); + for (unsigned i = 0; i < m_new_bounds.size(); ++i) { + if (m_new_bounds[i]->get_var() == v) { + atoms.push_back(m_new_bounds[i]); + m_new_bounds[i] = m_new_bounds.back(); + m_new_bounds.pop_back(); + --i; + } + } + CTRACE("arith_verbose", !atoms.empty(), + for (unsigned i = 0; i < atoms.size(); ++i) { + atoms[i]->display(tout); tout << "\n"; + }); + lp_bounds occs(m_bounds[v]); + + std::sort(atoms.begin(), atoms.end(), compare_bounds()); + std::sort(occs.begin(), occs.end(), compare_bounds()); + + iterator begin1 = occs.begin(); + iterator begin2 = occs.begin(); + iterator end = occs.end(); + begin1 = first(lp::lower_t, begin1, end); + begin2 = first(lp::upper_t, begin2, end); + + iterator lo_inf = begin1, lo_sup = begin1; + iterator hi_inf = begin2, hi_sup = begin2; + iterator lo_inf1 = begin1, lo_sup1 = begin1; + iterator hi_inf1 = begin2, hi_sup1 = begin2; + bool flo_inf, fhi_inf, flo_sup, fhi_sup; + ptr_addr_hashtable visited; + for (unsigned i = 0; i < atoms.size(); ++i) { + lp::bound* a1 = atoms[i]; + lo_inf1 = next_inf(a1, lp::lower_t, lo_inf, end, flo_inf); + hi_inf1 = next_inf(a1, lp::upper_t, hi_inf, end, fhi_inf); + lo_sup1 = next_sup(a1, lp::lower_t, lo_sup, end, flo_sup); + hi_sup1 = next_sup(a1, lp::upper_t, hi_sup, end, fhi_sup); + if (lo_inf1 != end) lo_inf = lo_inf1; + if (lo_sup1 != end) lo_sup = lo_sup1; + if (hi_inf1 != end) hi_inf = hi_inf1; + if (hi_sup1 != end) hi_sup = hi_sup1; + if (!flo_inf) lo_inf = end; + if (!fhi_inf) hi_inf = end; + if (!flo_sup) lo_sup = end; + if (!fhi_sup) hi_sup = end; + visited.insert(a1); + if (lo_inf1 != end && lo_inf != end && !visited.contains(*lo_inf)) mk_bound_axiom(*a1, **lo_inf); + if (lo_sup1 != end && lo_sup != end && !visited.contains(*lo_sup)) mk_bound_axiom(*a1, **lo_sup); + if (hi_inf1 != end && hi_inf != end && !visited.contains(*hi_inf)) mk_bound_axiom(*a1, **hi_inf); + if (hi_sup1 != end && hi_sup != end && !visited.contains(*hi_sup)) mk_bound_axiom(*a1, **hi_sup); + } + } + } + + struct compare_bounds { + bool operator()(lp::bound* a1, lp::bound* a2) const { return a1->get_value() < a2->get_value(); } + }; + + + lp_bounds::iterator first( + lp::bound_kind kind, + iterator it, + iterator end) { + for (; it != end; ++it) { + lp::bound* a = *it; + if (a->get_bound_kind() == kind) return it; + } + return end; + } + + lp_bounds::iterator next_inf( + lp::bound* a1, + lp::bound_kind kind, + iterator it, + iterator end, + bool& found_compatible) { + rational const & k1(a1->get_value()); + iterator result = end; + found_compatible = false; + for (; it != end; ++it) { + lp::bound * a2 = *it; + if (a1 == a2) continue; + if (a2->get_bound_kind() != kind) continue; + rational const & k2(a2->get_value()); + found_compatible = true; + if (k2 <= k1) { + result = it; + } + else { + break; + } + } + return result; + } + + lp_bounds::iterator next_sup( + lp::bound* a1, + lp::bound_kind kind, + iterator it, + iterator end, + bool& found_compatible) { + rational const & k1(a1->get_value()); + found_compatible = false; + for (; it != end; ++it) { + lp::bound * a2 = *it; + if (a1 == a2) continue; + if (a2->get_bound_kind() != kind) continue; + rational const & k2(a2->get_value()); + found_compatible = true; + if (k1 < k2) { + return it; + } + } + return end; + } + + void propagate_basic_bounds() { + for (auto const& bv : m_to_check) { + lp::bound& b = *m_bool_var2bound.find(bv); + propagate_bound(bv, ctx().get_assignment(bv) == l_true, b); + if (ctx().inconsistent()) break; + + } + m_to_check.reset(); + } + + // for glb lo': lo' < lo: + // lo <= x -> lo' <= x + // lo <= x -> ~(x <= lo') + // for lub hi': hi' > hi + // x <= hi -> x <= hi' + // x <= hi -> ~(x >= hi') + + void propagate_bound(bool_var bv, bool is_true, lp::bound& b) { + if (BP_NONE == propagation_mode()) { + return; + } + lp::bound_kind k = b.get_bound_kind(); + theory_var v = b.get_var(); + inf_rational val = b.get_value(is_true); + lp_bounds const& bounds = m_bounds[v]; + SASSERT(!bounds.empty()); + if (bounds.size() == 1) return; + if (m_unassigned_bounds[v] == 0) return; + + literal lit1(bv, !is_true); + literal lit2 = null_literal; + bool find_glb = (is_true == (k == lp::lower_t)); + if (find_glb) { + rational glb; + lp::bound* lb = 0; + for (unsigned i = 0; i < bounds.size(); ++i) { + lp::bound* b2 = bounds[i]; + if (b2 == &b) continue; + rational const& val2 = b2->get_value(); + if ((is_true ? val2 < val : val2 <= val) && (!lb || glb < val2)) { + lb = b2; + glb = val2; + } + } + if (!lb) return; + bool sign = lb->get_bound_kind() != lp::lower_t; + lit2 = literal(lb->get_bv(), sign); + } + else { + rational lub; + lp::bound* ub = 0; + for (unsigned i = 0; i < bounds.size(); ++i) { + lp::bound* b2 = bounds[i]; + if (b2 == &b) continue; + rational const& val2 = b2->get_value(); + if ((is_true ? val < val2 : val <= val2) && (!ub || val2 < lub)) { + ub = b2; + lub = val2; + } + } + if (!ub) return; + bool sign = ub->get_bound_kind() != lp::upper_t; + lit2 = literal(ub->get_bv(), sign); + } + TRACE("arith", + ctx().display_literal_verbose(tout, lit1); + ctx().display_literal_verbose(tout << " => ", lit2); + tout << "\n";); + updt_unassigned_bounds(v, -1); + ++m_stats.m_bound_propagations2; + m_params.reset(); + m_core.reset(); + m_eqs.reset(); + m_core.push_back(lit2); + m_params.push_back(parameter(symbol("farkas"))); + m_params.push_back(parameter(rational(1))); + m_params.push_back(parameter(rational(1))); + assign(lit2); + ++m_stats.m_bounds_propagations; + } + + void add_use_lists(lp::bound* b) { + theory_var v = b->get_var(); + lean::var_index vi = get_var_index(v); + if (m_solver->is_term(vi)) { + lean::lar_term const& term = m_solver->get_term(vi); + for (auto i = term.m_coeffs.begin(); i != term.m_coeffs.end(); ++i) { + lean::var_index wi = i->first; + unsigned w = m_var_index2theory_var[wi]; + m_use_list.reserve(w + 1, ptr_vector()); + m_use_list[w].push_back(b); + } + } + } + + void del_use_lists(lp::bound* b) { + theory_var v = b->get_var(); + lean::var_index vi = m_theory_var2var_index[v]; + if (m_solver->is_term(vi)) { + lean::lar_term const& term = m_solver->get_term(vi); + for (auto i = term.m_coeffs.begin(); i != term.m_coeffs.end(); ++i) { + lean::var_index wi = i->first; + unsigned w = m_var_index2theory_var[wi]; + SASSERT(m_use_list[w].back() == b); + m_use_list[w].pop_back(); + } + } + } + + // + // propagate bounds to compound terms + // The idea is that if bounds on all variables in an inequality ax + by + cz >= k + // have been assigned we may know the truth value of the inequality by using simple + // bounds propagation. + // + void propagate_bound_compound(bool_var bv, bool is_true, lp::bound& b) { + theory_var v = b.get_var(); + TRACE("arith", tout << mk_pp(get_owner(v), m) << "\n";); + if (static_cast(v) >= m_use_list.size()) { + return; + } + for (auto const& vb : m_use_list[v]) { + if (ctx().get_assignment(vb->get_bv()) != l_undef) { + TRACE("arith_verbose", display_bound(tout << "assigned ", *vb) << "\n";); + continue; + } + inf_rational r; + // x + y + // x >= 0, y >= 1 -> x + y >= 1 + // x <= 0, y <= 2 -> x + y <= 2 + literal lit = null_literal; + if (lp::lower_t == vb->get_bound_kind()) { + if (get_glb(*vb, r) && r >= vb->get_value()) { // vb is assigned true + lit = literal(vb->get_bv(), false); + } + else if (get_lub(*vb, r) && r < vb->get_value()) { // vb is assigned false + lit = literal(vb->get_bv(), true); + } + } + else { + if (get_glb(*vb, r) && r > vb->get_value()) { // VB <= value < val(VB) + lit = literal(vb->get_bv(), true); + } + else if (get_lub(*vb, r) && r <= vb->get_value()) { // val(VB) <= value + lit = literal(vb->get_bv(), false); + } + } + + if (lit != null_literal) { + TRACE("arith", + ctx().display_literals_verbose(tout, m_core); + tout << "\n --> "; + ctx().display_literal_verbose(tout, lit); + tout << "\n"; + ); + + + assign(lit); + } + else { + TRACE("arith_verbose", display_bound(tout << "skip ", *vb) << "\n";); + } + } + } + + bool get_lub(lp::bound const& b, inf_rational& lub) { + return get_bound(b, lub, true); + } + + bool get_glb(lp::bound const& b, inf_rational& glb) { + return get_bound(b, glb, false); + } + + std::ostream& display_bound(std::ostream& out, lp::bound const& b) { + return out << mk_pp(ctx().bool_var2expr(b.get_bv()), m); + } + + bool get_bound(lp::bound const& b, inf_rational& r, bool is_lub) { + m_core.reset(); + m_eqs.reset(); + m_params.reset(); + r.reset(); + theory_var v = b.get_var(); + lean::var_index vi = m_theory_var2var_index[v]; + SASSERT(m_solver->is_term(vi)); + lean::lar_term const& term = m_solver->get_term(vi); + for (auto const coeff : term.m_coeffs) { + lean::var_index wi = coeff.first; + lean::constraint_index ci; + rational value; + bool is_strict; + if (coeff.second.is_neg() == is_lub) { + // -3*x ... <= lub based on lower bound for x. + if (!m_solver->has_lower_bound(wi, ci, value, is_strict)) { + return false; + } + if (is_strict) { + r += inf_rational(rational::zero(), coeff.second.is_pos()); + } + } + else { + if (!m_solver->has_upper_bound(wi, ci, value, is_strict)) { + return false; + } + if (is_strict) { + r += inf_rational(rational::zero(), coeff.second.is_pos()); + } + } + r += value * coeff.second; + set_evidence(ci); + } + TRACE("arith_verbose", tout << (is_lub?"lub":"glb") << " is " << r << "\n";); + return true; + } + + void assert_bound(bool_var bv, bool is_true, lp::bound& b) { + if (m_solver->get_status() == lean::lp_status::INFEASIBLE) { + return; + } + scoped_internalize_state st(*this); + st.vars().push_back(b.get_var()); + st.coeffs().push_back(rational::one()); + init_left_side(st); + lean::lconstraint_kind k = lean::EQ; + switch (b.get_bound_kind()) { + case lp::lower_t: + k = is_true ? lean::GE : lean::LT; + break; + case lp::upper_t: + k = is_true ? lean::LE : lean::GT; + break; + } + if (k == lean::LT || k == lean::LE) { + ++m_stats.m_assert_lower; + } + else { + ++m_stats.m_assert_upper; + } + auto vi = get_var_index(b.get_var()); + auto ci = m_solver->add_var_bound(vi, k, b.get_value()); + TRACE("arith", tout << "v" << b.get_var() << "\n";); + add_ineq_constraint(ci, literal(bv, !is_true)); + + propagate_eqs(vi, ci, k, b); + } + + // + // fixed equalities. + // A fixed equality is inferred if there are two variables v1, v2 whose + // upper and lower bounds coincide. + // Then the equality v1 == v2 is propagated to the core. + // + + typedef std::pair constraint_bound; + vector m_lower_terms; + vector m_upper_terms; + typedef std::pair value_sort_pair; + typedef pair_hash, bool_hash> value_sort_pair_hash; + typedef map > value2var; + value2var m_fixed_var_table; + + void propagate_eqs(lean::var_index vi, lean::constraint_index ci, lean::lconstraint_kind k, lp::bound& b) { + if (propagate_eqs()) { + rational const& value = b.get_value(); + if (k == lean::GE) { + set_lower_bound(vi, ci, value); + if (has_upper_bound(vi, ci, value)) { + fixed_var_eh(b.get_var(), value); + } + } + else if (k == lean::LE) { + set_upper_bound(vi, ci, value); + if (has_lower_bound(vi, ci, value)) { + fixed_var_eh(b.get_var(), value); + } + } + } + } + + bool dump_lemmas() const { return m_arith_params.m_arith_dump_lemmas; } + + bool propagate_eqs() const { return m_arith_params.m_arith_propagate_eqs && m_num_conflicts < m_arith_params.m_arith_propagation_threshold; } + + bound_prop_mode propagation_mode() const { return m_num_conflicts < m_arith_params.m_arith_propagation_threshold ? m_arith_params.m_arith_bound_prop : BP_NONE; } + + unsigned small_lemma_size() const { return m_arith_params.m_arith_small_lemma_size; } + + bool proofs_enabled() const { return m.proofs_enabled(); } + + bool use_tableau() const { return lp_params(ctx().get_params()).simplex_strategy() < 2; } + + void set_upper_bound(lean::var_index vi, lean::constraint_index ci, rational const& v) { set_bound(vi, ci, v, false); } + + void set_lower_bound(lean::var_index vi, lean::constraint_index ci, rational const& v) { set_bound(vi, ci, v, true); } + + void set_bound(lean::var_index vi, lean::constraint_index ci, rational const& v, bool is_lower) { + if (!m_solver->is_term(vi)) { + // m_solver already tracks bounds on proper variables, but not on terms. + return; + } + lean::var_index ti = m_solver->adjust_term_index(vi); + auto& vec = is_lower ? m_lower_terms : m_upper_terms; + if (vec.size() <= ti) { + vec.resize(ti + 1, constraint_bound(UINT_MAX, rational())); + } + constraint_bound& b = vec[ti]; + if (b.first == UINT_MAX || (is_lower? b.second < v : b.second > v)) { + ctx().push_trail(vector_value_trail(vec, ti)); + b.first = ci; + b.second = v; + } + } + + bool has_upper_bound(lean::var_index vi, lean::constraint_index& ci, rational const& bound) { return has_bound(vi, ci, bound, false); } + + bool has_lower_bound(lean::var_index vi, lean::constraint_index& ci, rational const& bound) { return has_bound(vi, ci, bound, true); } + + bool has_bound(lean::var_index vi, lean::constraint_index& ci, rational const& bound, bool is_lower) { + + if (m_solver->is_term(vi)) { + + lean::var_index ti = m_solver->adjust_term_index(vi); + theory_var v = m_term_index2theory_var.get(ti, null_theory_var); + rational val; + TRACE("arith", tout << vi << " " << v << "\n";); + if (v != null_theory_var && a.is_numeral(get_owner(v), val) && bound == val) { + ci = UINT_MAX; + return bound == val; + } + + auto& vec = is_lower ? m_lower_terms : m_upper_terms; + if (vec.size() > ti) { + constraint_bound& b = vec[ti]; + ci = b.first; + return ci != UINT_MAX && bound == b.second; + } + else { + return false; + + } + } + else { + bool is_strict = false; + rational b; + if (is_lower) { + return m_solver->has_lower_bound(vi, ci, b, is_strict) && b == bound && !is_strict; + } + else { + return m_solver->has_upper_bound(vi, ci, b, is_strict) && b == bound && !is_strict; + } + } + } + + + bool is_equal(theory_var x, theory_var y) const { return get_enode(x)->get_root() == get_enode(y)->get_root(); } + + + void fixed_var_eh(theory_var v1, rational const& bound) { + theory_var v2; + value_sort_pair key(bound, is_int(v1)); + if (m_fixed_var_table.find(key, v2)) { + if (static_cast(v2) < th.get_num_vars() && !is_equal(v1, v2)) { + auto vi1 = get_var_index(v1); + auto vi2 = get_var_index(v2); + lean::constraint_index ci1, ci2, ci3, ci4; + TRACE("arith", tout << "fixed: " << mk_pp(get_owner(v1), m) << " " << mk_pp(get_owner(v2), m) << " " << bound << " " << has_lower_bound(vi2, ci3, bound) << "\n";); + if (has_lower_bound(vi2, ci3, bound) && has_upper_bound(vi2, ci4, bound)) { + VERIFY (has_lower_bound(vi1, ci1, bound)); + VERIFY (has_upper_bound(vi1, ci2, bound)); + ++m_stats.m_fixed_eqs; + m_core.reset(); + m_eqs.reset(); + set_evidence(ci1); + set_evidence(ci2); + set_evidence(ci3); + set_evidence(ci4); + enode* x = get_enode(v1); + enode* y = get_enode(v2); + justification* js = + ctx().mk_justification( + ext_theory_eq_propagation_justification( + get_id(), ctx().get_region(), m_core.size(), m_core.c_ptr(), m_eqs.size(), m_eqs.c_ptr(), x, y, 0, 0)); + + TRACE("arith", + for (unsigned i = 0; i < m_core.size(); ++i) { + ctx().display_detailed_literal(tout, m_core[i]); + tout << "\n"; + } + for (unsigned i = 0; i < m_eqs.size(); ++i) { + tout << mk_pp(m_eqs[i].first->get_owner(), m) << " = " << mk_pp(m_eqs[i].second->get_owner(), m) << "\n"; + } + tout << " ==> "; + tout << mk_pp(x->get_owner(), m) << " = " << mk_pp(y->get_owner(), m) << "\n"; + ); + + // parameters are TBD. + SASSERT(validate_eq(x, y)); + ctx().assign_eq(x, y, eq_justification(js)); + } + } + else { + // bounds on v2 were changed. + m_fixed_var_table.insert(key, v1); + } + } + else { + m_fixed_var_table.insert(key, v1); + } + } + + lbool make_feasible() { + reset_variable_values(); + ++m_stats.m_make_feasible; + if (m_solver->A_r().column_count() > m_stats.m_max_cols) + m_stats.m_max_cols = m_solver->A_r().column_count(); + if (m_solver->A_r().row_count() > m_stats.m_max_rows) + m_stats.m_max_rows = m_solver->A_r().row_count(); + TRACE("arith_verbose", display(tout);); + lean::lp_status status = m_solver->find_feasible_solution(); + m_stats.m_num_iterations = m_solver->settings().st().m_total_iterations; + m_stats.m_num_factorizations = m_solver->settings().st().m_num_factorizations; + m_stats.m_need_to_solve_inf = m_solver->settings().st().m_need_to_solve_inf; + + switch (status) { + case lean::lp_status::INFEASIBLE: + return l_false; + case lean::lp_status::FEASIBLE: + case lean::lp_status::OPTIMAL: + // SASSERT(m_solver->all_constraints_hold()); + return l_true; + case lean::lp_status::TIME_EXHAUSTED: + + default: + TRACE("arith", tout << "status treated as inconclusive: " << status << "\n";); + // TENTATIVE_UNBOUNDED, UNBOUNDED, TENTATIVE_DUAL_UNBOUNDED, DUAL_UNBOUNDED, + // FLOATING_POINT_ERROR, TIME_EXAUSTED, ITERATIONS_EXHAUSTED, EMPTY, UNSTABLE + return l_undef; + } + } + + vector> m_explanation; + literal_vector m_core; + svector m_eqs; + vector m_params; + + // lean::constraint_index const null_constraint_index = UINT_MAX; // not sure what a correct fix is + + void set_evidence(lean::constraint_index idx) { + if (idx == UINT_MAX) { + return; + } + switch (m_constraint_sources[idx]) { + case inequality_source: { + literal lit = m_inequalities[idx]; + SASSERT(lit != null_literal); + m_core.push_back(lit); + break; + } + case equality_source: { + SASSERT(m_equalities[idx].first != nullptr); + SASSERT(m_equalities[idx].second != nullptr); + m_eqs.push_back(m_equalities[idx]); + break; + } + case definition_source: { + // skip definitions (these are treated as hard constraints) + break; + } + default: + UNREACHABLE(); + break; + } + } + + void set_conflict() { + m_eqs.reset(); + m_core.reset(); + m_params.reset(); + m_explanation.clear(); + m_solver->get_infeasibility_explanation(m_explanation); + // m_solver->shrink_explanation_to_minimum(m_explanation); // todo, enable when perf is fixed + /* + static unsigned cn = 0; + static unsigned num_l = 0; + num_l+=m_explanation.size(); + std::cout << num_l / (++cn) << "\n"; + */ + ++m_num_conflicts; + ++m_stats.m_conflicts; + TRACE("arith", tout << "scope: " << ctx().get_scope_level() << "\n"; display_evidence(tout, m_explanation); ); + TRACE("arith", display(tout);); + for (auto const& ev : m_explanation) { + if (!ev.first.is_zero()) { + set_evidence(ev.second); + } + } + SASSERT(validate_conflict()); + ctx().set_conflict( + ctx().mk_justification( + ext_theory_conflict_justification( + get_id(), ctx().get_region(), + m_core.size(), m_core.c_ptr(), + m_eqs.size(), m_eqs.c_ptr(), m_params.size(), m_params.c_ptr()))); + } + + justification * why_is_diseq(theory_var v1, theory_var v2) { + return 0; + } + + void reset_eh() { + m_arith_eq_adapter.reset_eh(); + m_solver = 0; + m_not_handled = nullptr; + del_bounds(0); + m_unassigned_bounds.reset(); + m_asserted_qhead = 0; + m_scopes.reset(); + m_stats.reset(); + m_to_check.reset(); + } + + void init_model(model_generator & mg) { + init_variable_values(); + m_factory = alloc(arith_factory, m); + mg.register_factory(m_factory); + TRACE("arith", display(tout);); + } + + model_value_proc * mk_value(enode * n, model_generator & mg) { + theory_var v = n->get_th_var(get_id()); + return alloc(expr_wrapper_proc, m_factory->mk_value(get_value(v), m.get_sort(n->get_owner()))); + } + + bool get_value(enode* n, expr_ref& r) { + theory_var v = n->get_th_var(get_id()); + if (can_get_value(v)) { + r = a.mk_numeral(get_value(v), is_int(n)); + return true; + } + else { + return false; + } + } + + bool validate_eq_in_model(theory_var v1, theory_var v2, bool is_true) const { + SASSERT(v1 != null_theory_var); + SASSERT(v2 != null_theory_var); + return (get_value(v1) == get_value(v2)) == is_true; + } + + // Auxiliary verification utilities. + + bool validate_conflict() { + if (dump_lemmas()) { + ctx().display_lemma_as_smt_problem(m_core.size(), m_core.c_ptr(), m_eqs.size(), m_eqs.c_ptr(), false_literal); + } + context nctx(m, ctx().get_fparams(), ctx().get_params()); + add_background(nctx); + bool result = l_true != nctx.check(); + CTRACE("arith", !result, ctx().display_lemma_as_smt_problem(tout, m_core.size(), m_core.c_ptr(), m_eqs.size(), m_eqs.c_ptr(), false_literal); + display(tout);); + return result; + } + + bool validate_assign(literal lit) { + if (dump_lemmas()) { + ctx().display_lemma_as_smt_problem(m_core.size(), m_core.c_ptr(), m_eqs.size(), m_eqs.c_ptr(), lit); + } + context nctx(m, ctx().get_fparams(), ctx().get_params()); + m_core.push_back(~lit); + add_background(nctx); + m_core.pop_back(); + bool result = l_true != nctx.check(); + CTRACE("arith", !result, ctx().display_lemma_as_smt_problem(tout, m_core.size(), m_core.c_ptr(), m_eqs.size(), m_eqs.c_ptr(), lit); + display(tout);); + return result; + } + + bool validate_eq(enode* x, enode* y) { + context nctx(m, ctx().get_fparams(), ctx().get_params()); + add_background(nctx); + nctx.assert_expr(m.mk_not(m.mk_eq(x->get_owner(), y->get_owner()))); + return l_true != nctx.check(); + } + + void add_background(context& nctx) { + for (unsigned i = 0; i < m_core.size(); ++i) { + expr_ref tmp(m); + ctx().literal2expr(m_core[i], tmp); + nctx.assert_expr(tmp); + } + for (unsigned i = 0; i < m_eqs.size(); ++i) { + nctx.assert_expr(m.mk_eq(m_eqs[i].first->get_owner(), m_eqs[i].second->get_owner())); + } + } + + theory_lra::inf_eps value(theory_var v) { + lean::impq ival = get_ivalue(v); + return inf_eps(0, inf_rational(ival.x, ival.y)); + } + + theory_lra::inf_eps maximize(theory_var v, expr_ref& blocker, bool& has_shared) { + lean::var_index vi = m_theory_var2var_index.get(v, UINT_MAX); + vector > coeffs; + rational coeff; + if (m_solver->is_term(vi)) { + const lean::lar_term& term = m_solver->get_term(vi); + for (auto & ti : term.m_coeffs) { + coeffs.push_back(std::make_pair(ti.second, ti.first)); + } + coeff = term.m_v; + } + else { + coeffs.push_back(std::make_pair(rational::one(), vi)); + coeff = rational::zero(); + } + lean::impq term_max; + if (m_solver->maximize_term(coeffs, term_max)) { + blocker = mk_gt(v); + inf_rational val(term_max.x + coeff, term_max.y); + return inf_eps(rational::zero(), val); + } + else { + TRACE("arith", tout << "Unbounded " << v << "\n";); + has_shared = false; + blocker = m.mk_false(); + return inf_eps(rational::one(), inf_rational()); + } + } + + expr_ref mk_gt(theory_var v) { + lean::impq val = get_ivalue(v); + expr* obj = get_enode(v)->get_owner(); + rational r = val.x; + expr_ref e(m); + if (a.is_int(m.get_sort(obj))) { + if (r.is_int()) { + r += rational::one(); + } + else { + r = ceil(r); + } + e = a.mk_numeral(r, m.get_sort(obj)); + e = a.mk_ge(obj, e); + } + else { + e = a.mk_numeral(r, m.get_sort(obj)); + if (val.y.is_neg()) { + e = a.mk_ge(obj, e); + } + else { + e = a.mk_gt(obj, e); + } + } + TRACE("opt", tout << "v" << v << " " << val << " " << r << " " << e << "\n";); + return e; + } + + theory_var add_objective(app* term) { + return internalize_def(term); + } + + app_ref mk_obj(theory_var v) { + lean::var_index vi = m_theory_var2var_index[v]; + if (m_solver->is_term(vi)) { + expr_ref_vector args(m); + const lean::lar_term& term = m_solver->get_term(vi); + for (auto & ti : term.m_coeffs) { + theory_var w = m_var_index2theory_var[ti.first]; + expr* o = get_enode(w)->get_owner(); + args.push_back(a.mk_mul(a.mk_numeral(ti.second, a.is_int(o)), o)); + } + rational r = term.m_v; + args.push_back(a.mk_numeral(r, r.is_int())); + return app_ref(a.mk_add(args.size(), args.c_ptr()), m); + } + else { + theory_var w = m_var_index2theory_var[vi]; + return app_ref(get_enode(w)->get_owner(), m); + } + } + + expr_ref mk_ge(filter_model_converter& fm, theory_var v, inf_rational const& val) { + rational r = val.get_rational(); + bool is_strict = val.get_infinitesimal().is_pos(); + app_ref b(m); + if (is_strict) { + b = a.mk_le(mk_obj(v), a.mk_numeral(r, r.is_int())); + } + else { + b = a.mk_ge(mk_obj(v), a.mk_numeral(r, r.is_int())); + } + if (!ctx().b_internalized(b)) { + fm.insert(b->get_decl()); + bool_var bv = ctx().mk_bool_var(b); + ctx().set_var_theory(bv, get_id()); + // ctx().set_enode_flag(bv, true); + lp::bound_kind bkind = lp::bound_kind::lower_t; + if (is_strict) bkind = lp::bound_kind::upper_t; + lp::bound* a = alloc(lp::bound, bv, v, r, bkind); + mk_bound_axioms(*a); + updt_unassigned_bounds(v, +1); + m_bounds[v].push_back(a); + m_bounds_trail.push_back(v); + m_bool_var2bound.insert(bv, a); + TRACE("arith", tout << mk_pp(b, m) << "\n";); + } + if (is_strict) { + b = m.mk_not(b); + } + TRACE("arith", tout << b << "\n";); + return expr_ref(b, m); + + } + + + void display(std::ostream & out) const { + if (m_solver) { + m_solver->print_constraints(out); + m_solver->print_terms(out); + } + unsigned nv = th.get_num_vars(); + for (unsigned v = 0; v < nv; ++v) { + out << "v" << v; + if (can_get_value(v)) out << ", value: " << get_value(v); + out << ", shared: " << ctx().is_shared(get_enode(v)) + << ", rel: " << ctx().is_relevant(get_enode(v)) + << ", def: "; th.display_var_flat_def(out, v) << "\n"; + } + } + + void display_evidence(std::ostream& out, vector> const& evidence) { + for (auto const& ev : evidence) { + expr_ref e(m); + SASSERT(!ev.first.is_zero()); + if (ev.first.is_zero()) { + continue; + } + unsigned idx = ev.second; + switch (m_constraint_sources.get(idx, null_source)) { + case inequality_source: { + literal lit = m_inequalities[idx]; + ctx().literal2expr(lit, e); + out << e << " " << ctx().get_assignment(lit) << "\n"; + break; + } + case equality_source: + out << mk_pp(m_equalities[idx].first->get_owner(), m) << " = " + << mk_pp(m_equalities[idx].second->get_owner(), m) << "\n"; + break; + case definition_source: { + theory_var v = m_definitions[idx]; + out << "def: v" << v << " := " << mk_pp(th.get_enode(v)->get_owner(), m) << "\n"; + break; + } + case null_source: + default: + UNREACHABLE(); + break; + } + } + for (auto const& ev : evidence) { + m_solver->print_constraint(ev.second, out << ev.first << ": "); + } + } + + void collect_statistics(::statistics & st) const { + m_arith_eq_adapter.collect_statistics(st); + st.update("arith-lower", m_stats.m_assert_lower); + st.update("arith-upper", m_stats.m_assert_upper); + st.update("arith-rows", m_stats.m_add_rows); + st.update("arith-propagations", m_stats.m_bounds_propagations); + st.update("arith-iterations", m_stats.m_num_iterations); + st.update("arith-factorizations", m_stats.m_num_factorizations); + st.update("arith-pivots", m_stats.m_need_to_solve_inf); + st.update("arith-plateau-iterations", m_stats.m_num_iterations_with_no_progress); + st.update("arith-fixed-eqs", m_stats.m_fixed_eqs); + st.update("arith-conflicts", m_stats.m_conflicts); + st.update("arith-bound-propagations-lp", m_stats.m_bound_propagations1); + st.update("arith-bound-propagations-cheap", m_stats.m_bound_propagations2); + st.update("arith-diseq", m_stats.m_assert_diseq); + st.update("arith-make-feasible", m_stats.m_make_feasible); + st.update("arith-max-columns", m_stats.m_max_cols); + st.update("arith-max-rows", m_stats.m_max_rows); + } + }; + + theory_lra::theory_lra(ast_manager& m, theory_arith_params& ap): + theory(m.get_family_id("arith")) { + m_imp = alloc(imp, *this, m, ap); + } + theory_lra::~theory_lra() { + dealloc(m_imp); + } + theory* theory_lra::mk_fresh(context* new_ctx) { + return alloc(theory_lra, new_ctx->get_manager(), new_ctx->get_fparams()); + } + void theory_lra::init(context * ctx) { + theory::init(ctx); + m_imp->init(ctx); + } + bool theory_lra::internalize_atom(app * atom, bool gate_ctx) { + return m_imp->internalize_atom(atom, gate_ctx); + } + bool theory_lra::internalize_term(app * term) { + return m_imp->internalize_term(term); + } + void theory_lra::internalize_eq_eh(app * atom, bool_var v) { + m_imp->internalize_eq_eh(atom, v); + } + void theory_lra::assign_eh(bool_var v, bool is_true) { + m_imp->assign_eh(v, is_true); + } + void theory_lra::new_eq_eh(theory_var v1, theory_var v2) { + m_imp->new_eq_eh(v1, v2); + } + bool theory_lra::use_diseqs() const { + return m_imp->use_diseqs(); + } + void theory_lra::new_diseq_eh(theory_var v1, theory_var v2) { + m_imp->new_diseq_eh(v1, v2); + } + void theory_lra::push_scope_eh() { + theory::push_scope_eh(); + m_imp->push_scope_eh(); + } + void theory_lra::pop_scope_eh(unsigned num_scopes) { + m_imp->pop_scope_eh(num_scopes); + theory::pop_scope_eh(num_scopes); + } + void theory_lra::restart_eh() { + m_imp->restart_eh(); + } + void theory_lra::relevant_eh(app* e) { + m_imp->relevant_eh(e); + } + void theory_lra::init_search_eh() { + m_imp->init_search_eh(); + } + final_check_status theory_lra::final_check_eh() { + return m_imp->final_check_eh(); + } + bool theory_lra::is_shared(theory_var v) const { + return m_imp->is_shared(v); + } + bool theory_lra::can_propagate() { + return m_imp->can_propagate(); + } + void theory_lra::propagate() { + m_imp->propagate(); + } + justification * theory_lra::why_is_diseq(theory_var v1, theory_var v2) { + return m_imp->why_is_diseq(v1, v2); + } + void theory_lra::reset_eh() { + m_imp->reset_eh(); + } + void theory_lra::init_model(model_generator & m) { + m_imp->init_model(m); + } + model_value_proc * theory_lra::mk_value(enode * n, model_generator & mg) { + return m_imp->mk_value(n, mg); + } + bool theory_lra::get_value(enode* n, expr_ref& r) { + return m_imp->get_value(n, r); + } + bool theory_lra::validate_eq_in_model(theory_var v1, theory_var v2, bool is_true) const { + return m_imp->validate_eq_in_model(v1, v2, is_true); + } + void theory_lra::display(std::ostream & out) const { + m_imp->display(out); + } + void theory_lra::collect_statistics(::statistics & st) const { + m_imp->collect_statistics(st); + } + theory_lra::inf_eps theory_lra::value(theory_var v) { + return m_imp->value(v); + } + theory_lra::inf_eps theory_lra::maximize(theory_var v, expr_ref& blocker, bool& has_shared) { + return m_imp->maximize(v, blocker, has_shared); + } + theory_var theory_lra::add_objective(app* term) { + return m_imp->add_objective(term); + } + expr_ref theory_lra::mk_ge(filter_model_converter& fm, theory_var v, inf_rational const& val) { + return m_imp->mk_ge(fm, v, val); + } + + + +} diff --git a/src/smt/theory_lra.h b/src/smt/theory_lra.h new file mode 100644 index 000000000..beb5c8387 --- /dev/null +++ b/src/smt/theory_lra.h @@ -0,0 +1,97 @@ +/*++ +Copyright (c) 2016 Microsoft Corporation + +Module Name: + + theory_lra.h + +Abstract: + + + +Author: + + Lev Nachmanson (levnach) 2016-25-3 + Nikolaj Bjorner (nbjorner) + +Revision History: + + +--*/ +#pragma once + +#include "theory_opt.h" + +namespace smt { + class theory_lra : public theory, public theory_opt { + class imp; + imp* m_imp; + + public: + theory_lra(ast_manager& m, theory_arith_params& ap); + virtual ~theory_lra(); + virtual theory* mk_fresh(context* new_ctx); + virtual char const* get_name() const { return "lra"; } + + virtual void init(context * ctx); + + virtual bool internalize_atom(app * atom, bool gate_ctx); + + virtual bool internalize_term(app * term); + + virtual void internalize_eq_eh(app * atom, bool_var v); + + virtual void assign_eh(bool_var v, bool is_true); + + virtual void new_eq_eh(theory_var v1, theory_var v2); + + virtual bool use_diseqs() const; + + virtual void new_diseq_eh(theory_var v1, theory_var v2); + + virtual void push_scope_eh(); + + virtual void pop_scope_eh(unsigned num_scopes); + + virtual void restart_eh(); + + virtual void relevant_eh(app* e); + + virtual void init_search_eh(); + + virtual final_check_status final_check_eh(); + + virtual bool is_shared(theory_var v) const; + + virtual bool can_propagate(); + + virtual void propagate(); + + virtual justification * why_is_diseq(theory_var v1, theory_var v2); + + // virtual void flush_eh(); + + virtual void reset_eh(); + + virtual void init_model(model_generator & m); + + virtual model_value_proc * mk_value(enode * n, model_generator & mg); + + virtual bool get_value(enode* n, expr_ref& r); + + virtual bool validate_eq_in_model(theory_var v1, theory_var v2, bool is_true) const; + + virtual void display(std::ostream & out) const; + + virtual void collect_statistics(::statistics & st) const; + + // optimization + virtual inf_eps value(theory_var); + virtual inf_eps maximize(theory_var v, expr_ref& blocker, bool& has_shared); + virtual theory_var add_objective(app* term); + virtual expr_ref mk_ge(filter_model_converter& fm, theory_var v, inf_rational const& val); + + + }; + +} diff --git a/src/smt/theory_seq.cpp b/src/smt/theory_seq.cpp index 7a9369fd3..3f8845546 100644 --- a/src/smt/theory_seq.cpp +++ b/src/smt/theory_seq.cpp @@ -2176,6 +2176,8 @@ bool theory_seq::simplify_and_solve_eqs() { return m_new_propagation || ctx.inconsistent(); } +void theory_seq::internalize_eq_eh(app * atom, bool_var v) {} + bool theory_seq::internalize_term(app* term) { context & ctx = get_context(); diff --git a/src/smt/theory_seq.h b/src/smt/theory_seq.h index 2b8fb2fd7..e145d3077 100644 --- a/src/smt/theory_seq.h +++ b/src/smt/theory_seq.h @@ -343,7 +343,7 @@ namespace smt { virtual final_check_status final_check_eh(); virtual bool internalize_atom(app* atom, bool) { return internalize_term(atom); } virtual bool internalize_term(app*); - virtual void internalize_eq_eh(app * atom, bool_var v) {} + virtual void internalize_eq_eh(app * atom, bool_var v); virtual void new_eq_eh(theory_var, theory_var); virtual void new_diseq_eh(theory_var, theory_var); virtual void assign_eh(bool_var v, bool is_true); diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 818aca29a..62846e709 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -60,6 +60,7 @@ namespace smt { totalCacheAccessCount(0), cacheHitCount(0), cacheMissCount(0), + m_fresh_id(0), m_find(*this), m_trail_stack(*this) { @@ -441,7 +442,12 @@ namespace smt { } app * theory_str::mk_fresh_const(char const* name, sort* s) { - return u.mk_skolem(symbol(name), 0, 0, s); + string_buffer<64> buffer; + buffer << name; + buffer << "!tmp"; + buffer << m_fresh_id; + m_fresh_id++; + return u.mk_skolem(symbol(buffer.c_str()), 0, 0, s); } @@ -5552,8 +5558,8 @@ namespace smt { for (; arg1_grdItor != groundedMap[arg1DeAlias].end(); arg1_grdItor++) { std::vector ndVec; ndVec.insert(ndVec.end(), arg0_grdItor->first.begin(), arg0_grdItor->first.end()); - int arg0VecSize = arg0_grdItor->first.size(); - int arg1VecSize = arg1_grdItor->first.size(); + size_t arg0VecSize = arg0_grdItor->first.size(); + size_t arg1VecSize = arg1_grdItor->first.size(); if (arg0VecSize > 0 && arg1VecSize > 0 && u.str.is_string(arg0_grdItor->first[arg0VecSize - 1]) && u.str.is_string(arg1_grdItor->first[0])) { ndVec.pop_back(); ndVec.push_back(mk_concat(arg0_grdItor->first[arg0VecSize - 1], arg1_grdItor->first[0])); @@ -5645,8 +5651,8 @@ namespace smt { } bool theory_str::is_partial_in_grounded_concat(const std::vector & strVec, const std::vector & subStrVec) { - int strCnt = strVec.size(); - int subStrCnt = subStrVec.size(); + size_t strCnt = strVec.size(); + size_t subStrCnt = subStrVec.size(); if (strCnt == 0 || subStrCnt == 0) { return false; @@ -5717,7 +5723,7 @@ namespace smt { } // tail nodes - int tailIdx = i + subStrCnt - 1; + size_t tailIdx = i + subStrCnt - 1; zstring subStrTailVal; if (u.str.is_string(subStrVec[subStrCnt - 1], subStrTailVal)) { zstring strTailVal; @@ -5908,8 +5914,14 @@ namespace smt { app * n2_curr = to_app(n2); // case 0: n1_curr is const string, n2_curr is const string - if (u.str.is_string(n1_curr) && u.str.is_string(n2_curr)) { - if (n1_curr != n2_curr) { + zstring n1_curr_str, n2_curr_str; + if (u.str.is_string(n1_curr, n1_curr_str) && u.str.is_string(n2_curr, n2_curr_str)) { + TRACE("str", tout << "checking string constants: n1=" << n1_curr_str << ", n2=" << n2_curr_str << std::endl;); + if (n1_curr_str == n2_curr_str) { + // TODO(mtrberzi) potential correction: if n1_curr != n2_curr, + // assert that these two terms are in fact equal, because they ought to be + return true; + } else { return false; } } @@ -6864,7 +6876,7 @@ namespace smt { // easiest case. we will search within these bounds } else if (upper_bound_exists && !lower_bound_exists) { // search between 0 and the upper bound - v_lower_bound == rational::zero(); + v_lower_bound = rational::zero(); } else if (lower_bound_exists && !upper_bound_exists) { // check some finite portion of the search space v_upper_bound = v_lower_bound + rational(10); @@ -8251,7 +8263,7 @@ namespace smt { } } if (lId == -1) - lId = mLMap.size(); + lId = static_cast(mLMap.size()); for (std::set::iterator itor2 = nSet.begin(); itor2 != nSet.end(); itor2++) { bool itorHasEqcValue = false; get_eqc_value(*itor2, itorHasEqcValue); @@ -8290,7 +8302,7 @@ namespace smt { } } if (rId == -1) - rId = mRMap.size(); + rId = static_cast(mRMap.size()); for (itor2 = nSet.begin(); itor2 != nSet.end(); itor2++) { bool rHasEqcValue = false; get_eqc_value(*itor2, rHasEqcValue); @@ -9182,7 +9194,7 @@ namespace smt { // ---------------------------------------------------------------------------------------- int len = atoi(lenStr.encode().c_str()); bool coverAll = false; - svector options; + vector options; int_vector base; TRACE("str", tout @@ -9227,16 +9239,16 @@ namespace smt { ); // ---------------------------------------------------------------------------------------- - + ptr_vector orList; ptr_vector andList; for (long long i = l; i < h; i++) { orList.push_back(m.mk_eq(val_indicator, mk_string(longlong_to_string(i).c_str()) )); if (m_params.m_AggressiveValueTesting) { - literal l = mk_eq(val_indicator, mk_string(longlong_to_string(i).c_str()), false); - ctx.mark_as_relevant(l); - ctx.force_phase(l); + literal lit = mk_eq(val_indicator, mk_string(longlong_to_string(i).c_str()), false); + ctx.mark_as_relevant(lit); + ctx.force_phase(lit); } zstring aStr = gen_val_string(len, options[i - l]); @@ -10059,7 +10071,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/smt/theory_str.h b/src/smt/theory_str.h index 0403b0623..4752e8a1b 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -350,6 +350,8 @@ protected: unsigned long cacheHitCount; unsigned long cacheMissCount; + unsigned m_fresh_id; + // cache mapping each string S to Length(S) obj_map length_ast_map; diff --git a/src/tactic/portfolio/pb2bv_solver.cpp b/src/tactic/portfolio/pb2bv_solver.cpp index 10acc0bb6..c8aa82e97 100644 --- a/src/tactic/portfolio/pb2bv_solver.cpp +++ b/src/tactic/portfolio/pb2bv_solver.cpp @@ -20,6 +20,7 @@ Notes: #include "solver_na2as.h" #include "tactic.h" #include "pb2bv_rewriter.h" +#include "th_rewriter.h" #include "filter_model_converter.h" #include "ast_pp.h" #include "model_smt2_pp.h" @@ -29,6 +30,7 @@ class pb2bv_solver : public solver_na2as { params_ref m_params; mutable expr_ref_vector m_assertions; mutable ref m_solver; + mutable th_rewriter m_th_rewriter; mutable pb2bv_rewriter m_rewriter; public: @@ -39,6 +41,7 @@ public: m_params(p), m_assertions(m), m_solver(s), + m_th_rewriter(m, p), m_rewriter(m, p) { } @@ -121,10 +124,11 @@ public: private: void flush_assertions() const { proof_ref proof(m); - expr_ref fml(m); + expr_ref fml1(m), fml(m); expr_ref_vector fmls(m); for (unsigned i = 0; i < m_assertions.size(); ++i) { - m_rewriter(m_assertions[i].get(), fml, proof); + m_th_rewriter(m_assertions[i].get(), fml1, proof); + m_rewriter(fml1, fml, proof); m_solver->assert_expr(fml); } m_rewriter.flush_side_constraints(fmls); diff --git a/src/tactic/smtlogics/qflra_tactic.cpp b/src/tactic/smtlogics/qflra_tactic.cpp index 41f0e16f3..0865f3b47 100644 --- a/src/tactic/smtlogics/qflra_tactic.cpp +++ b/src/tactic/smtlogics/qflra_tactic.cpp @@ -22,7 +22,6 @@ Notes: #include"solve_eqs_tactic.h" #include"elim_uncnstr_tactic.h" #include"smt_tactic.h" -// include"mip_tactic.h" #include"recover_01_tactic.h" #include"ctx_simplify_tactic.h" #include"probe_arith.h" @@ -72,5 +71,18 @@ tactic * mk_qflra_tactic(ast_manager & m, params_ref const & p) { // using_params(mk_smt_tactic(), pivot_p)), // p); +#if 0 + + params_ref simplex_0, simplex_1, simplex_2; + simplex_0.set_uint("lp.simplex_strategy", 0); + simplex_1.set_uint("lp.simplex_strategy", 1); + simplex_2.set_uint("lp.simplex_strategy", 2); + + return par(using_params(mk_smt_tactic(), simplex_0), + using_params(mk_smt_tactic(), simplex_1), + using_params(mk_smt_tactic(), simplex_2)); +#else return using_params(using_params(mk_smt_tactic(), pivot_p), p); +#endif + } diff --git a/src/test/argument_parser.h b/src/test/argument_parser.h new file mode 100644 index 000000000..706167f49 --- /dev/null +++ b/src/test/argument_parser.h @@ -0,0 +1,144 @@ +/* +Copyright (c) 2013 Microsoft Corporation. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. + +Author: Lev Nachmanson +*/ + +#include +#include +#include +#include +#include + +namespace lean { +class argument_parser { + std::unordered_map m_options; + std::unordered_map m_options_with_after_string; + std::set m_used_options; + std::unordered_map m_used_options_with_after_string; + std::vector m_free_args; + std::vector m_args; + +public: + std::string m_error_message; + argument_parser(unsigned argn, char * const* args) { + for (unsigned i = 0; i < argn; i++) { + m_args.push_back(std::string(args[i])); + } + } + + void add_option(std::string s) { + add_option_with_help_string(s, ""); + } + + void add_option_with_help_string(std::string s, std::string help_string) { + m_options[s]=help_string; + } + + void add_option_with_after_string(std::string s) { + add_option_with_after_string_with_help(s, ""); + } + + void add_option_with_after_string_with_help(std::string s, std::string help_string) { + m_options_with_after_string[s]=help_string; + } + + + bool parse() { + bool status_is_ok = true; + for (unsigned i = 0; i < m_args.size(); i++) { + std::string ar = m_args[i]; + if (m_options.find(ar) != m_options.end() ) + m_used_options.insert(ar); + else if (m_options_with_after_string.find(ar) != m_options_with_after_string.end()) { + if (i == m_args.size() - 1) { + m_error_message = "Argument is missing after "+ar; + return false; + } + i++; + m_used_options_with_after_string[ar] = m_args[i]; + } else { + if (starts_with(ar, "-") || starts_with(ar, "//")) + status_is_ok = false; + + m_free_args.push_back(ar); + } + } + return status_is_ok; + } + + bool contains(std::unordered_map & m, std::string s) { + return m.find(s) != m.end(); + } + + bool contains(std::set & m, std::string s) { + return m.find(s) != m.end(); + } + + bool option_is_used(std::string option) { + return contains(m_used_options, option) || contains(m_used_options_with_after_string, option); + } + + std::string get_option_value(std::string option) { + auto t = m_used_options_with_after_string.find(option); + if (t != m_used_options_with_after_string.end()){ + return t->second; + } + return std::string(); + } + + bool starts_with(std::string s, char const * prefix) { + return starts_with(s, std::string(prefix)); + } + + bool starts_with(std::string s, std::string prefix) { + return s.substr(0, prefix.size()) == prefix; + } + + std::string usage_string() { + std::string ret = ""; + std::vector unknown_options; + for (auto t : m_free_args) { + if (starts_with(t, "-") || starts_with(t, "\\")) { + unknown_options.push_back(t); + } + } + if (unknown_options.size()) { + ret = "Unknown options:"; + } + for (auto unknownOption : unknown_options) { + ret += unknownOption; + ret += ","; + } + ret += "\n"; + ret += "Usage:\n"; + for (auto allowed_option : m_options) + ret += allowed_option.first + " " + (allowed_option.second.size() == 0 ? std::string("") : std::string("/") + allowed_option.second) + std::string("\n"); + for (auto s : m_options_with_after_string) { + ret += s.first + " " + (s.second.size() == 0? " \"option value\"":("\""+ s.second+"\"")) + "\n"; + } + return ret; + } + + void print() { + if (m_used_options.size() == 0 && m_used_options_with_after_string.size() == 0 && m_free_args.size() == 0) { + std::cout << "no options are given" << std::endl; + return; + } + std::cout << "options are: " << std::endl; + for (std::string s : m_used_options) { + std::cout << s << std::endl; + } + for (auto & t : m_used_options_with_after_string) { + std::cout << t.first << " " << t.second << std::endl; + } + if (m_free_args.size() > 0) { + std::cout << "free arguments are: " << std::endl; + for (auto & t : m_free_args) { + std::cout << t << " " << std::endl; + } + } + } +}; +} diff --git a/src/test/lp.cpp b/src/test/lp.cpp new file mode 100644 index 000000000..235dab960 --- /dev/null +++ b/src/test/lp.cpp @@ -0,0 +1,3236 @@ +/* +Copyright (c) 2017 Microsoft Corporation. All rights reserved. +Author: Lev Nachmanson +*/ +#include +#if _LINUX_ +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "util/lp/lp_utils.h" +#include "util/lp/lp_primal_simplex.h" +#include "util/lp/mps_reader.h" +#include "smt_reader.h" +#include "util/lp/binary_heap_priority_queue.h" +#include "argument_parser.h" +#include "test_file_reader.h" +#include "util/lp/indexed_value.h" +#include "util/lp/lar_solver.h" +#include "util/lp/numeric_pair.h" +#include "util/lp/binary_heap_upair_queue.h" +#include "util/lp/stacked_value.h" +#include "util/lp/stacked_unordered_set.h" +#include "util/lp/int_set.h" +#include "util/stopwatch.h" +namespace lean { +unsigned seed = 1; + + random_gen g_rand; + static unsigned my_random() { + return g_rand(); + } +struct simple_column_namer:public column_namer +{ + std::string get_column_name(unsigned j) const override { + return std::string("x") + T_to_string(j); + } +}; + + +template +void test_matrix(sparse_matrix & a) { + auto m = a.dimension(); + +// copy a to b in the reversed order + sparse_matrix b(m); + std::cout << "copy b to a"<< std::endl; + for (int row = m - 1; row >= 0; row--) + for (int col = m - 1; col >= 0; col --) { + b(row, col) = (T const&) a(row, col); + } + + + std::cout << "zeroing b in the reverse order"<< std::endl; + for (int row = m - 1; row >= 0; row--) + for (int col = m - 1; col >= 0; col --) + b.set(row, col, T(0)); + + + + for (unsigned row = 0; row < m; row ++) + for (unsigned col = 0; col < m; col ++) + a.set(row, col, T(0)); + + + unsigned i = my_random() % m; + unsigned j = my_random() % m; + + auto t = T(1); + + a.set(i, j, t); + + lean_assert(a.get(i, j) == t); + + unsigned j1; + if (j < m - 1) { + j1 = m - 1; + a.set(i, j1, T(2)); + } +} + +void tst1() { + std::cout << "testing the minimial matrix with 1 row and 1 column" << std::endl; + sparse_matrix m0(1); + m0.set(0, 0, 1); + // print_matrix(m0); + m0.set(0, 0, 0); + // print_matrix(m0); + test_matrix(m0); + + unsigned rows = 2; + sparse_matrix m(rows); + std::cout << "setting m(0,1)=" << std::endl; + + m.set(0, 1, 11); + m.set(0, 0, 12); + + // print_matrix(m); + + test_matrix(m); + + sparse_matrix m1(2); + m1.set(0, 0, 2); + m1.set(1, 0, 3); + // print_matrix(m1); + std::cout << " zeroing matrix 2 by 2" << std::endl; + m1.set(0, 0, 0); + m1.set(1, 0, 0); + // print_matrix(m1); + + test_matrix(m1); + + + std::cout << "printing zero matrix 3 by 1" << std::endl; + sparse_matrix m2(3); + // print_matrix(m2); + + m2.set(0, 0, 1); + m2.set(2, 0, 2); + std::cout << "printing matrix 3 by 1 with a gap" << std::endl; + // print_matrix(m2); + + test_matrix(m2); + + sparse_matrix m10by9(10); + m10by9.set(0, 1, 1); + + m10by9(0, 1) = 4; + + double test = m10by9(0, 1); + + std::cout << "got " << test << std::endl; + + + m10by9.set(0, 8, 8); + m10by9.set(3, 4, 7); + m10by9.set(3, 2, 5); + m10by9.set(3, 8, 99); + m10by9.set(3, 2, 6); + m10by9.set(1, 8, 9); + m10by9.set(4, 0, 40); + m10by9.set(0, 0, 10); + + std::cout << "printing matrix 10 by 9" << std::endl; + // print_matrix(m10by9); + + + test_matrix(m10by9); + std::cout <<"zeroing m10by9\n"; +#ifdef LEAN_DEBUG + for (unsigned int i = 0; i < m10by9.dimension(); i++) + for (unsigned int j = 0; j < m10by9.column_count(); j++) + m10by9.set(i, j, 0); +#endif + // print_matrix(m10by9); +} + +vector allocate_basis_heading(unsigned count) { // the rest of initilization will be handled by lu_QR + vector basis_heading(count, -1); + return basis_heading; +} + + +void init_basic_part_of_basis_heading(vector & basis, vector & basis_heading) { + lean_assert(basis_heading.size() >= basis.size()); + unsigned m = basis.size(); + for (unsigned i = 0; i < m; i++) { + unsigned column = basis[i]; + basis_heading[column] = i; + } +} + +void init_non_basic_part_of_basis_heading(vector & basis_heading, vector & non_basic_columns) { + non_basic_columns.clear(); + for (int j = basis_heading.size(); j--;){ + if (basis_heading[j] < 0) { + non_basic_columns.push_back(j); + // the index of column j in m_nbasis is (- basis_heading[j] - 1) + basis_heading[j] = - static_cast(non_basic_columns.size()); + } + } +} +void init_basis_heading_and_non_basic_columns_vector(vector & basis, + vector & basis_heading, + vector & non_basic_columns) { + init_basic_part_of_basis_heading(basis, basis_heading); + init_non_basic_part_of_basis_heading(basis_heading, non_basic_columns); +} + +void change_basis(unsigned entering, unsigned leaving, vector& basis, vector& nbasis, vector & basis_heading) { + int place_in_basis = basis_heading[leaving]; + int place_in_non_basis = - basis_heading[entering] - 1; + basis_heading[entering] = place_in_basis; + basis_heading[leaving] = -place_in_non_basis - 1; + basis[place_in_basis] = entering; + nbasis[place_in_non_basis] = leaving; +} + + +#ifdef LEAN_DEBUG +void test_small_lu(lp_settings & settings) { + std::cout << " test_small_lu" << std::endl; + static_matrix m(3, 6); + vector basis(3); + basis[0] = 0; + basis[1] = 1; + basis[2] = 3; + + m(0, 0) = 1; m(0, 2)= 3.9; m(2, 3) = 11; m(0, 5) = -3; + m(1, 1) = 4; m(1, 4) = 7; + m(2, 0) = 1.8; m(2, 2) = 5; m(2, 4) = 2; m(2, 5) = 8; + +#ifdef LEAN_DEBUG + print_matrix(m, std::cout); +#endif + vector heading = allocate_basis_heading(m.column_count()); + vector non_basic_columns; + init_basis_heading_and_non_basic_columns_vector(basis, heading, non_basic_columns); + lu l(m, basis, settings); + lean_assert(l.is_correct(basis)); + indexed_vector w(m.row_count()); + std::cout << "entering 2, leaving 0" << std::endl; + l.prepare_entering(2, w); // to init vector w + l.replace_column(0, w, heading[0]); + change_basis(2, 0, basis, non_basic_columns, heading); + // #ifdef LEAN_DEBUG + // std::cout << "we were factoring " << std::endl; + // print_matrix(get_B(l)); + // #endif + lean_assert(l.is_correct(basis)); + std::cout << "entering 4, leaving 3" << std::endl; + l.prepare_entering(4, w); // to init vector w + l.replace_column(0, w, heading[3]); + change_basis(4, 3, basis, non_basic_columns, heading); + std::cout << "we were factoring " << std::endl; +#ifdef LEAN_DEBUG + { + auto bl = get_B(l, basis); + print_matrix(&bl, std::cout); + } +#endif + lean_assert(l.is_correct(basis)); + + std::cout << "entering 5, leaving 1" << std::endl; + l.prepare_entering(5, w); // to init vector w + l.replace_column(0, w, heading[1]); + change_basis(5, 1, basis, non_basic_columns, heading); + std::cout << "we were factoring " << std::endl; +#ifdef LEAN_DEBUG + { + auto bl = get_B(l, basis); + print_matrix(&bl, std::cout); + } +#endif + lean_assert(l.is_correct(basis)); + std::cout << "entering 3, leaving 2" << std::endl; + l.prepare_entering(3, w); // to init vector w + l.replace_column(0, w, heading[2]); + change_basis(3, 2, basis, non_basic_columns, heading); + std::cout << "we were factoring " << std::endl; +#ifdef LEAN_DEBUG + { + auto bl = get_B(l, basis); + print_matrix(&bl, std::cout); + } +#endif + lean_assert(l.is_correct(basis)); + + m.add_row(); + m.add_column(); + m.add_row(); + m.add_column(); + for (unsigned i = 0; i < m.column_count(); i++) { + m(3, i) = i; + m(4, i) = i * i; // to make the rows linearly independent + } + unsigned j = m.column_count() ; + basis.push_back(j-2); + heading.push_back(basis.size() - 1); + basis.push_back(j-1); + heading.push_back(basis.size() - 1); + + auto columns_to_replace = l.get_set_of_columns_to_replace_for_add_last_rows(heading); + l.add_last_rows_to_B(heading, columns_to_replace); + std::cout << "here" << std::endl; + lean_assert(l.is_correct(basis)); +} + +#endif + +void fill_long_row(sparse_matrix &m, int i) { + int n = m.dimension(); + for (int j = 0; j < n; j ++) { + m (i, (j + i) % n) = j * j; + } +} + +void fill_long_row(static_matrix &m, int i) { + int n = m.column_count(); + for (int j = 0; j < n; j ++) { + m (i, (j + i) % n) = j * j; + } +} + + +void fill_long_row_exp(sparse_matrix &m, int i) { + int n = m.dimension(); + + for (int j = 0; j < n; j ++) { + m(i, j) = my_random() % 20; + } +} + +void fill_long_row_exp(static_matrix &m, int i) { + int n = m.column_count(); + + for (int j = 0; j < n; j ++) { + m(i, j) = my_random() % 20; + } +} + +void fill_larger_sparse_matrix_exp(sparse_matrix & m){ + for ( unsigned i = 0; i < m.dimension(); i++ ) + fill_long_row_exp(m, i); +} + +void fill_larger_sparse_matrix_exp(static_matrix & m){ + for ( unsigned i = 0; i < m.row_count(); i++ ) + fill_long_row_exp(m, i); +} + + +void fill_larger_sparse_matrix(sparse_matrix & m){ + for ( unsigned i = 0; i < m.dimension(); i++ ) + fill_long_row(m, i); +} + +void fill_larger_sparse_matrix(static_matrix & m){ + for ( unsigned i = 0; i < m.row_count(); i++ ) + fill_long_row(m, i); +} + + +int perm_id = 0; + +#ifdef LEAN_DEBUG +void test_larger_lu_exp(lp_settings & settings) { + std::cout << " test_larger_lu_exp" << std::endl; + static_matrix m(6, 12); + vector basis(6); + basis[0] = 1; + basis[1] = 3; + basis[2] = 0; + basis[3] = 4; + basis[4] = 5; + basis[5] = 6; + + + fill_larger_sparse_matrix_exp(m); + // print_matrix(m); + vector heading = allocate_basis_heading(m.column_count()); + vector non_basic_columns; + init_basis_heading_and_non_basic_columns_vector(basis, heading, non_basic_columns); + lu l(m, basis, settings); + + dense_matrix left_side = l.get_left_side(basis); + dense_matrix right_side = l.get_right_side(); + lean_assert(left_side == right_side); + int leaving = 3; + int entering = 8; + for (unsigned i = 0; i < m.row_count(); i++) { + std::cout << static_cast(m(i, entering)) << std::endl; + } + + indexed_vector w(m.row_count()); + + l.prepare_entering(entering, w); + l.replace_column(0, w, heading[leaving]); + change_basis(entering, leaving, basis, non_basic_columns, heading); + lean_assert(l.is_correct(basis)); + + l.prepare_entering(11, w); // to init vector w + l.replace_column(0, w, heading[0]); + change_basis(11, 0, basis, non_basic_columns, heading); + lean_assert(l.is_correct(basis)); +} + +void test_larger_lu_with_holes(lp_settings & settings) { + std::cout << " test_larger_lu_with_holes" << std::endl; + static_matrix m(8, 9); + vector basis(8); + for (unsigned i = 0; i < m.row_count(); i++) { + basis[i] = i; + } + m(0, 0) = 1; m(0, 1) = 2; m(0, 2) = 3; m(0, 3) = 4; m(0, 4) = 5; m(0, 8) = 99; + /* */ m(1, 1) =- 6; m(1, 2) = 7; m(1, 3) = 8; m(1, 4) = 9; + /* */ m(2, 2) = 10; + /* */ m(3, 2) = 11; m(3, 3) = -12; + /* */ m(4, 2) = 13; m(4, 3) = 14; m(4, 4) = 15; + // the rest of the matrix is denser + m(5, 4) = 28; m(5, 5) = -18; m(5, 6) = 19; m(5, 7) = 25; + /* */ m(6, 5) = 20; m(6, 6) = -21; + /* */ m(7, 5) = 22; m(7, 6) = 23; m(7, 7) = 24; m(7, 8) = 88; + print_matrix(m, std::cout); + vector heading = allocate_basis_heading(m.column_count()); + vector non_basic_columns; + init_basis_heading_and_non_basic_columns_vector(basis, heading, non_basic_columns); + lu l(m, basis, settings); + std::cout << "printing factorization" << std::endl; + for (int i = l.tail_size() - 1; i >=0; i--) { + auto lp = l.get_lp_matrix(i); + lp->set_number_of_columns(m.row_count()); + lp->set_number_of_rows(m.row_count()); + print_matrix( lp, std::cout); + } + + dense_matrix left_side = l.get_left_side(basis); + dense_matrix right_side = l.get_right_side(); + if (!(left_side == right_side)) { + std::cout << "different sides" << std::endl; + } + + indexed_vector w(m.row_count()); + l.prepare_entering(8, w); // to init vector w + l.replace_column(0, w, heading[0]); + change_basis(8, 0, basis, non_basic_columns, heading); + lean_assert(l.is_correct(basis)); +} + + +void test_larger_lu(lp_settings& settings) { + std::cout << " test_larger_lu" << std::endl; + static_matrix m(6, 12); + vector basis(6); + basis[0] = 1; + basis[1] = 3; + basis[2] = 0; + basis[3] = 4; + basis[4] = 5; + basis[5] = 6; + + + fill_larger_sparse_matrix(m); + print_matrix(m, std::cout); + + vector heading = allocate_basis_heading(m.column_count()); + vector non_basic_columns; + init_basis_heading_and_non_basic_columns_vector(basis, heading, non_basic_columns); + auto l = lu (m, basis, settings); + // std::cout << "printing factorization" << std::endl; + // for (int i = lu.tail_size() - 1; i >=0; i--) { + // auto lp = lu.get_lp_matrix(i); + // lp->set_number_of_columns(m.row_count()); + // lp->set_number_of_rows(m.row_count()); + // print_matrix(* lp); + // } + + dense_matrix left_side = l.get_left_side(basis); + dense_matrix right_side = l.get_right_side(); + if (!(left_side == right_side)) { + std::cout << "left side" << std::endl; + print_matrix(&left_side, std::cout); + std::cout << "right side" << std::endl; + print_matrix(&right_side, std::cout); + + std::cout << "different sides" << std::endl; + std::cout << "initial factorization is incorrect" << std::endl; + exit(1); + } + indexed_vector w(m.row_count()); + l.prepare_entering(9, w); // to init vector w + l.replace_column(0, w, heading[0]); + change_basis(9, 0, basis, non_basic_columns, heading); + lean_assert(l.is_correct(basis)); +} + + +void test_lu(lp_settings & settings) { + test_small_lu(settings); + test_larger_lu(settings); + test_larger_lu_with_holes(settings); + test_larger_lu_exp(settings); +} +#endif + + + + + + +void init_b(vector & b, sparse_matrix & m, vector& x) { + for (unsigned i = 0; i < m.dimension(); i++) { + b.push_back(m.dot_product_with_row(i, x)); + } +} + +void init_b(vector & b, static_matrix & m, vector & x) { + for (unsigned i = 0; i < m.row_count(); i++) { + b.push_back(m.dot_product_with_row(i, x)); + } +} + + +void test_lp_0() { + std::cout << " test_lp_0 " << std::endl; + static_matrix m_(3, 7); + m_(0, 0) = 3; m_(0, 1) = 2; m_(0, 2) = 1; m_(0, 3) = 2; m_(0, 4) = 1; + m_(1, 0) = 1; m_(1, 1) = 1; m_(1, 2) = 1; m_(1, 3) = 1; m_(1, 5) = 1; + m_(2, 0) = 4; m_(2, 1) = 3; m_(2, 2) = 3; m_(2, 3) = 4; m_(2, 6) = 1; + vector x_star(7); + x_star[0] = 225; x_star[1] = 117; x_star[2] = 420; + x_star[3] = x_star[4] = x_star[5] = x_star[6] = 0; + vector b; + init_b(b, m_, x_star); + vector basis(3); + basis[0] = 0; basis[1] = 1; basis[2] = 2; + vector costs(7); + costs[0] = 19; + costs[1] = 13; + costs[2] = 12; + costs[3] = 17; + costs[4] = 0; + costs[5] = 0; + costs[6] = 0; + + vector column_types(7, column_type::low_bound); + vector upper_bound_values; + lp_settings settings; + simple_column_namer cn; + vector nbasis; + vector heading; + + lp_primal_core_solver lpsolver(m_, b, x_star, basis, nbasis, heading, costs, column_types, upper_bound_values, settings, cn); + + lpsolver.solve(); +} + +void test_lp_1() { + std::cout << " test_lp_1 " << std::endl; + static_matrix m(4, 7); + m(0, 0) = 1; m(0, 1) = 3; m(0, 2) = 1; m(0, 3) = 1; + m(1, 0) = -1; m(1, 2) = 3; m(1, 4) = 1; + m(2, 0) = 2; m(2, 1) = -1; m(2, 2) = 2; m(2, 5) = 1; + m(3, 0) = 2; m(3, 1) = 3; m(3, 2) = -1; m(3, 6) = 1; +#ifdef LEAN_DEBUG + print_matrix(m, std::cout); +#endif + vector x_star(7); + x_star[0] = 0; x_star[1] = 0; x_star[2] = 0; + x_star[3] = 3; x_star[4] = 2; x_star[5] = 4; x_star[6] = 2; + + vector basis(4); + basis[0] = 3; basis[1] = 4; basis[2] = 5; basis[3] = 6; + + vector b; + b.push_back(3); + b.push_back(2); + b.push_back(4); + b.push_back(2); + + vector costs(7); + costs[0] = 5; + costs[1] = 5; + costs[2] = 3; + costs[3] = 0; + costs[4] = 0; + costs[5] = 0; + costs[6] = 0; + + + + vector column_types(7, column_type::low_bound); + vector upper_bound_values; + + std::cout << "calling lp\n"; + lp_settings settings; + simple_column_namer cn; + + vector nbasis; + vector heading; + + lp_primal_core_solver lpsolver(m, b, + x_star, + basis, + nbasis, heading, + costs, + column_types, upper_bound_values, settings, cn); + + lpsolver.solve(); +} + + +void test_lp_primal_core_solver() { + test_lp_0(); + test_lp_1(); +} + + +#ifdef LEAN_DEBUG +template +void test_swap_rows_with_permutation(sparse_matrix& m){ + std::cout << "testing swaps" << std::endl; + unsigned dim = m.row_count(); + dense_matrix original(&m); + permutation_matrix q(dim); + print_matrix(m, std::cout); + lean_assert(original == q * m); + for (int i = 0; i < 100; i++) { + unsigned row1 = my_random() % dim; + unsigned row2 = my_random() % dim; + if (row1 == row2) continue; + std::cout << "swap " << row1 << " " << row2 << std::endl; + m.swap_rows(row1, row2); + q.transpose_from_left(row1, row2); + lean_assert(original == q * m); + print_matrix(m, std::cout); + std::cout << std::endl; + } +} +#endif +template +void fill_matrix(sparse_matrix& m); // forward definition +#ifdef LEAN_DEBUG +template +void test_swap_cols_with_permutation(sparse_matrix& m){ + std::cout << "testing swaps" << std::endl; + unsigned dim = m.row_count(); + dense_matrix original(&m); + permutation_matrix q(dim); + print_matrix(m, std::cout); + lean_assert(original == q * m); + for (int i = 0; i < 100; i++) { + unsigned row1 = my_random() % dim; + unsigned row2 = my_random() % dim; + if (row1 == row2) continue; + std::cout << "swap " << row1 << " " << row2 << std::endl; + m.swap_rows(row1, row2); + q.transpose_from_right(row1, row2); + lean_assert(original == q * m); + print_matrix(m, std::cout); + std::cout << std::endl; + } +} + + +template +void test_swap_rows(sparse_matrix& m, unsigned i0, unsigned i1){ + std::cout << "test_swap_rows(" << i0 << "," << i1 << ")" << std::endl; + sparse_matrix mcopy(m.dimension()); + for (unsigned i = 0; i < m.dimension(); i++) + for (unsigned j = 0; j < m.dimension(); j++) { + mcopy(i, j)= m(i, j); + } + std::cout << "swapping rows "<< i0 << "," << i1 << std::endl; + m.swap_rows(i0, i1); + + for (unsigned j = 0; j < m.dimension(); j++) { + lean_assert(mcopy(i0, j) == m(i1, j)); + lean_assert(mcopy(i1, j) == m(i0, j)); + } +} +template +void test_swap_columns(sparse_matrix& m, unsigned i0, unsigned i1){ + std::cout << "test_swap_columns(" << i0 << "," << i1 << ")" << std::endl; + sparse_matrix mcopy(m.dimension()); + for (unsigned i = 0; i < m.dimension(); i++) + for (unsigned j = 0; j < m.dimension(); j++) { + mcopy(i, j)= m(i, j); + } + m.swap_columns(i0, i1); + + for (unsigned j = 0; j < m.dimension(); j++) { + lean_assert(mcopy(j, i0) == m(j, i1)); + lean_assert(mcopy(j, i1) == m(j, i0)); + } + + for (unsigned i = 0; i < m.dimension(); i++) { + if (i == i0 || i == i1) + continue; + for (unsigned j = 0; j < m.dimension(); j++) { + lean_assert(mcopy(j, i)== m(j, i)); + } + } +} +#endif + +template +void fill_matrix(sparse_matrix& m){ + int v = 0; + for (int i = m.dimension() - 1; i >= 0; i--) { + for (int j = m.dimension() - 1; j >=0; j--){ + m(i, j) = v++; + } + } +} + +void test_pivot_like_swaps_and_pivot(){ + sparse_matrix m(10); + fill_matrix(m); + // print_matrix(m); +// pivot at 2,7 + m.swap_columns(0, 7); + // print_matrix(m); + m.swap_rows(2, 0); + // print_matrix(m); + for (unsigned i = 1; i < m.dimension(); i++) { + m(i, 0) = 0; + } + // print_matrix(m); + +// say pivot at 3,4 + m.swap_columns(1, 4); + // print_matrix(m); + m.swap_rows(1, 3); + // print_matrix(m); + + vector row; + double alpha = 2.33; + unsigned pivot_row = 1; + unsigned target_row = 2; + unsigned pivot_row_0 = 3; + double beta = 3.1; + m(target_row, 3) = 0; + m(target_row, 5) = 0; + m(pivot_row, 6) = 0; +#ifdef LEAN_DEBUG + print_matrix(m, std::cout); +#endif + + for (unsigned j = 0; j < m.dimension(); j++) { + row.push_back(m(target_row, j) + alpha * m(pivot_row, j) + beta * m(pivot_row_0, j)); + } + + for (auto & t : row) { + std::cout << t << ","; + } + std::cout << std::endl; + lp_settings settings; + m.pivot_row_to_row(pivot_row, alpha, target_row, settings); + m.pivot_row_to_row(pivot_row_0, beta, target_row, settings); + // print_matrix(m); + for (unsigned j = 0; j < m.dimension(); j++) { + lean_assert(abs(row[j] - m(target_row, j)) < 0.00000001); + } +} + +#ifdef LEAN_DEBUG +void test_swap_rows() { + sparse_matrix m(10); + fill_matrix(m); + // print_matrix(m); + test_swap_rows(m, 3, 5); + + test_swap_rows(m, 1, 3); + + + test_swap_rows(m, 1, 3); + + test_swap_rows(m, 1, 7); + + test_swap_rows(m, 3, 7); + + test_swap_rows(m, 0, 7); + + m(0, 4) = 1; + // print_matrix(m); + test_swap_rows(m, 0, 7); + +// go over some corner cases + sparse_matrix m0(2); + test_swap_rows(m0, 0, 1); + m0(0, 0) = 3; + test_swap_rows(m0, 0, 1); + m0(1, 0) = 3; + test_swap_rows(m0, 0, 1); + + + sparse_matrix m1(10); + test_swap_rows(m1, 0, 1); + m1(0, 0) = 3; + test_swap_rows(m1, 0, 1); + m1(1, 0) = 3; + m1(0, 3) = 5; + m1(1, 3) = 4; + m1(1, 8) = 8; + m1(1, 9) = 8; + + test_swap_rows(m1, 0, 1); + + sparse_matrix m2(3); + test_swap_rows(m2, 0, 1); + m2(0, 0) = 3; + test_swap_rows(m2, 0, 1); + m2(2, 0) = 3; + test_swap_rows(m2, 0, 2); +} + +void fill_uniformly(sparse_matrix & m, unsigned dim) { + int v = 0; + for (unsigned i = 0; i < dim; i++) { + for (unsigned j = 0; j < dim; j++) { + m(i, j) = v++; + } + } +} + +void fill_uniformly(dense_matrix & m, unsigned dim) { + int v = 0; + for (unsigned i = 0; i < dim; i++) { + for (unsigned j = 0; j < dim; j++) { + m.set_elem(i, j, v++); + } + } +} + +void sparse_matrix_with_permutaions_test() { + unsigned dim = 4; + sparse_matrix m(dim); + fill_uniformly(m, dim); + dense_matrix dm(dim, dim); + fill_uniformly(dm, dim); + dense_matrix dm0(dim, dim); + fill_uniformly(dm0, dim); + permutation_matrix q0(dim); + q0[0] = 1; + q0[1] = 0; + q0[2] = 3; + q0[3] = 2; + permutation_matrix q1(dim); + q1[0] = 1; + q1[1] = 2; + q1[2] = 3; + q1[3] = 0; + permutation_matrix p0(dim); + p0[0] = 1; + p0[1] = 0; + p0[2] = 3; + p0[3] = 2; + permutation_matrix p1(dim); + p1[0] = 1; + p1[1] = 2; + p1[2] = 3; + p1[3] = 0; + + m.multiply_from_left(q0); + for (unsigned i = 0; i < dim; i++) { + for (unsigned j = 0; j < dim; j++) { + lean_assert(m(i, j) == dm0.get_elem(q0[i], j)); + } + } + + auto q0_dm = q0 * dm; + lean_assert(m == q0_dm); + + m.multiply_from_left(q1); + for (unsigned i = 0; i < dim; i++) { + for (unsigned j = 0; j < dim; j++) { + lean_assert(m(i, j) == dm0.get_elem(q0[q1[i]], j)); + } + } + + + auto q1_q0_dm = q1 * q0_dm; + + lean_assert(m == q1_q0_dm); + + m.multiply_from_right(p0); + + for (unsigned i = 0; i < dim; i++) { + for (unsigned j = 0; j < dim; j++) { + lean_assert(m(i, j) == dm0.get_elem(q0[q1[i]], p0[j])); + } + } + + auto q1_q0_dm_p0 = q1_q0_dm * p0; + + lean_assert(m == q1_q0_dm_p0); + + m.multiply_from_right(p1); + + for (unsigned i = 0; i < dim; i++) { + for (unsigned j = 0; j < dim; j++) { + lean_assert(m(i, j) == dm0.get_elem(q0[q1[i]], p1[p0[j]])); + } + } + + auto q1_q0_dm_p0_p1 = q1_q0_dm_p0 * p1; + lean_assert(m == q1_q0_dm_p0_p1); + + m.multiply_from_right(p1); + for (unsigned i = 0; i < dim; i++) { + for (unsigned j = 0; j < dim; j++) { + lean_assert(m(i, j) == dm0.get_elem(q0[q1[i]], p1[p1[p0[j]]])); + } + } + auto q1_q0_dm_p0_p1_p1 = q1_q0_dm_p0_p1 * p1; + + lean_assert(m == q1_q0_dm_p0_p1_p1); +} + +void test_swap_columns() { + sparse_matrix m(10); + fill_matrix(m); + // print_matrix(m); + + test_swap_columns(m, 3, 5); + + test_swap_columns(m, 1, 3); + + test_swap_columns(m, 1, 3); + + // print_matrix(m); + test_swap_columns(m, 1, 7); + + test_swap_columns(m, 3, 7); + + test_swap_columns(m, 0, 7); + + test_swap_columns(m, 0, 7); + +// go over some corner cases + sparse_matrix m0(2); + test_swap_columns(m0, 0, 1); + m0(0, 0) = 3; + test_swap_columns(m0, 0, 1); + m0(0, 1) = 3; + test_swap_columns(m0, 0, 1); + + + sparse_matrix m1(10); + test_swap_columns(m1, 0, 1); + m1(0, 0) = 3; + test_swap_columns(m1, 0, 1); + m1(0, 1) = 3; + m1(3, 0) = 5; + m1(3, 1) = 4; + m1(8, 1) = 8; + m1(9, 1) = 8; + + test_swap_columns(m1, 0, 1); + + sparse_matrix m2(3); + test_swap_columns(m2, 0, 1); + m2(0, 0) = 3; + test_swap_columns(m2, 0, 1); + m2(0, 2) = 3; + test_swap_columns(m2, 0, 2); +} + + + +void test_swap_operations() { + test_swap_rows(); + test_swap_columns(); +} + +void test_dense_matrix() { + dense_matrix d(3, 2); + d.set_elem(0, 0, 1); + d.set_elem(1, 1, 2); + d.set_elem(2, 0, 3); + // print_matrix(d); + + dense_matrix unit(2, 2); + d.set_elem(0, 0, 1); + d.set_elem(1, 1, 1); + + dense_matrix c = d * unit; + + // print_matrix(d); + + dense_matrix perm(3, 3); + perm.set_elem(0, 1, 1); + perm.set_elem(1, 0, 1); + perm.set_elem(2, 2, 1); + auto c1 = perm * d; + // print_matrix(c1); + + + dense_matrix p2(2, 2); + p2.set_elem(0, 1, 1); + p2.set_elem(1, 0, 1); + auto c2 = d * p2; +} +#endif + + + +vector> vector_of_permutaions() { + vector> ret; + { + permutation_matrix p0(5); + p0[0] = 1; p0[1] = 2; p0[2] = 3; p0[3] = 4; + p0[4] = 0; + ret.push_back(p0); + } + { + permutation_matrix p0(5); + p0[0] = 2; p0[1] = 0; p0[2] = 1; p0[3] = 4; + p0[4] = 3; + ret.push_back(p0); + } + return ret; +} + +void test_apply_reverse_from_right_to_perm(permutation_matrix & l) { + permutation_matrix p(5); + p[0] = 4; p[1] = 2; p[2] = 0; p[3] = 3; + p[4] = 1; + + permutation_matrix pclone(5); + pclone[0] = 4; pclone[1] = 2; pclone[2] = 0; pclone[3] = 3; + pclone[4] = 1; + + p.multiply_by_reverse_from_right(l); +#ifdef LEAN_DEBUG + auto rev = l.get_inverse(); + auto rs = pclone * rev; + lean_assert(p == rs) +#endif +} + +void test_apply_reverse_from_right() { + auto vec = vector_of_permutaions(); + for (unsigned i = 0; i < vec.size(); i++) { + test_apply_reverse_from_right_to_perm(vec[i]); + } +} + +void test_permutations() { + std::cout << "test permutations" << std::endl; + test_apply_reverse_from_right(); + vector v; v.resize(5, 0); + v[1] = 1; + v[3] = 3; + permutation_matrix p(5); + p[0] = 4; p[1] = 2; p[2] = 0; p[3] = 3; + p[4] = 1; + + indexed_vector vi(5); + vi.set_value(1, 1); + vi.set_value(3, 3); + + p.apply_reverse_from_right_to_T(v); + p.apply_reverse_from_right_to_T(vi); + lean_assert(vectors_are_equal(v, vi.m_data)); + lean_assert(vi.is_OK()); +} + +void lp_solver_test() { + // lp_revised_solver lp_revised; + // lp_revised.get_minimal_solution(); +} + +bool get_int_from_args_parser(const char * option, argument_parser & args_parser, unsigned & n) { + std::string s = args_parser.get_option_value(option); + if (s.size() > 0) { + n = atoi(s.c_str()); + return true; + } + return false; +} + +bool get_double_from_args_parser(const char * option, argument_parser & args_parser, double & n) { + std::string s = args_parser.get_option_value(option); + if (s.size() > 0) { + n = atof(s.c_str()); + return true; + } + return false; +} + + +void update_settings(argument_parser & args_parser, lp_settings& settings) { + unsigned n; + settings.m_simplex_strategy = simplex_strategy_enum::lu; + if (get_int_from_args_parser("--rep_frq", args_parser, n)) + settings.report_frequency = n; + else + settings.report_frequency = args_parser.option_is_used("--mpq")? 80: 1000; + + settings.print_statistics = true; + + if (get_int_from_args_parser("--percent_for_enter", args_parser, n)) + settings.percent_of_entering_to_check = n; + if (get_int_from_args_parser("--partial_pivot", args_parser, n)) { + std::cout << "setting partial pivot constant to " << n << std::endl; + settings.c_partial_pivoting = n; + } + if (get_int_from_args_parser("--density", args_parser, n)) { + double density = static_cast(n) / 100.0; + std::cout << "setting density to " << density << std::endl; + settings.density_threshold = density; + } + if (get_int_from_args_parser("--maxng", args_parser, n)) + settings.max_number_of_iterations_with_no_improvements = n; + double d; + if (get_double_from_args_parser("--harris_toler", args_parser, d)) { + std::cout << "setting harris_feasibility_tolerance to " << d << std::endl; + settings.harris_feasibility_tolerance = d; + } + if (get_int_from_args_parser("--random_seed", args_parser, n)) { + settings.random_seed(n); + } + if (get_int_from_args_parser("--simplex_strategy", args_parser, n)) { + settings.simplex_strategy() = static_cast(n); + } +} + +template +void setup_solver(unsigned max_iterations, unsigned time_limit, bool look_for_min, argument_parser & args_parser, lp_solver * solver) { + if (max_iterations > 0) + solver->set_max_iterations_per_stage(max_iterations); + + if (time_limit > 0) + solver->set_time_limit(time_limit); + + if (look_for_min) + solver->flip_costs(); + + update_settings(args_parser, solver->settings()); +} + +bool values_are_one_percent_close(double a, double b); + +void print_x(mps_reader & reader, lp_solver * solver) { + for (auto name : reader.column_names()) { + std::cout << name << "=" << solver->get_column_value_by_name(name) << ' '; + } + std::cout << std::endl; +} + +void compare_solutions(mps_reader & reader, lp_solver * solver, lp_solver * solver0) { + for (auto name : reader.column_names()) { + double a = solver->get_column_value_by_name(name); + double b = solver0->get_column_value_by_name(name); + if (!values_are_one_percent_close(a, b)) { + std::cout << "different values for " << name << ":" << a << " and " << b << std::endl; + } + } +} + + +void solve_mps_double(std::string file_name, bool look_for_min, unsigned max_iterations, unsigned time_limit, bool dual, bool compare_with_primal, argument_parser & args_parser) { + mps_reader reader(file_name); + reader.read(); + if (!reader.is_ok()) { + std::cout << "cannot process " << file_name << std::endl; + return; + } + + lp_solver * solver = reader.create_solver(dual); + setup_solver(max_iterations, time_limit, look_for_min, args_parser, solver); + int begin = get_millisecond_count(); + if (dual) { + std::cout << "solving for dual" << std::endl; + } + solver->find_maximal_solution(); + int span = get_millisecond_span(begin); + std::cout << "Status: " << lp_status_to_string(solver->get_status()) << std::endl; + if (solver->get_status() == lp_status::OPTIMAL) { + if (reader.column_names().size() < 20) { + print_x(reader, solver); + } + double cost = solver->get_current_cost(); + if (look_for_min) { + cost = -cost; + } + std::cout << "cost = " << cost << std::endl; + } + std::cout << "processed in " << span / 1000.0 << " seconds, running for " << solver->m_total_iterations << " iterations" << " one iter for " << (double)span/solver->m_total_iterations << " ms" << std::endl; + if (compare_with_primal) { + auto * primal_solver = reader.create_solver(false); + setup_solver(max_iterations, time_limit, look_for_min, args_parser, primal_solver); + primal_solver->find_maximal_solution(); + if (solver->get_status() != primal_solver->get_status()) { + std::cout << "statuses are different: dual " << lp_status_to_string(solver->get_status()) << " primal = " << lp_status_to_string(primal_solver->get_status()) << std::endl; + } else { + if (solver->get_status() == lp_status::OPTIMAL) { + double cost = solver->get_current_cost(); + if (look_for_min) { + cost = -cost; + } + double primal_cost = primal_solver->get_current_cost(); + if (look_for_min) { + primal_cost = -primal_cost; + } + std::cout << "primal cost = " << primal_cost << std::endl; + if (!values_are_one_percent_close(cost, primal_cost)) { + compare_solutions(reader, primal_solver, solver); + print_x(reader, primal_solver); + std::cout << "dual cost is " << cost << ", but primal cost is " << primal_cost << std::endl; + lean_assert(false); + } + } + } + delete primal_solver; + } + delete solver; +} + +void solve_mps_rational(std::string file_name, bool look_for_min, unsigned max_iterations, unsigned time_limit, bool dual, argument_parser & args_parser) { + mps_reader reader(file_name); + reader.read(); + if (reader.is_ok()) { + auto * solver = reader.create_solver(dual); + setup_solver(max_iterations, time_limit, look_for_min, args_parser, solver); + int begin = get_millisecond_count(); + solver->find_maximal_solution(); + std::cout << "Status: " << lp_status_to_string(solver->get_status()) << std::endl; + + if (solver->get_status() == lp_status::OPTIMAL) { + // for (auto name: reader.column_names()) { + // std::cout << name << "=" << solver->get_column_value_by_name(name) << ' '; + // } + lean::mpq cost = solver->get_current_cost(); + if (look_for_min) { + cost = -cost; + } + std::cout << "cost = " << cost.get_double() << std::endl; + } + std::cout << "processed in " << get_millisecond_span(begin) / 1000.0 << " seconds, running for " << solver->m_total_iterations << " iterations" << std::endl; + delete solver; + } else { + std::cout << "cannot process " << file_name << std::endl; + } +} +void get_time_limit_and_max_iters_from_parser(argument_parser & args_parser, unsigned & time_limit, unsigned & max_iters); // forward definition + +void solve_mps(std::string file_name, bool look_for_min, unsigned max_iterations, unsigned time_limit, bool solve_for_rational, bool dual, bool compare_with_primal, argument_parser & args_parser) { + if (!solve_for_rational) { + std::cout << "solving " << file_name << std::endl; + solve_mps_double(file_name, look_for_min, max_iterations, time_limit, dual, compare_with_primal, args_parser); + } + else { + std::cout << "solving " << file_name << " in rationals " << std::endl; + solve_mps_rational(file_name, look_for_min, max_iterations, time_limit, dual, args_parser); + } +} + +void solve_mps(std::string file_name, argument_parser & args_parser) { + bool look_for_min = args_parser.option_is_used("--min"); + unsigned max_iterations, time_limit; + bool solve_for_rational = args_parser.option_is_used("--mpq"); + bool dual = args_parser.option_is_used("--dual"); + bool compare_with_primal = args_parser.option_is_used("--compare_with_primal"); + get_time_limit_and_max_iters_from_parser(args_parser, time_limit, max_iterations); + solve_mps(file_name, look_for_min, max_iterations, time_limit, solve_for_rational, dual, compare_with_primal, args_parser); +} + +void solve_mps_in_rational(std::string file_name, bool dual, argument_parser & /*args_parser*/) { + std::cout << "solving " << file_name << std::endl; + + mps_reader reader(file_name); + reader.read(); + if (reader.is_ok()) { + auto * solver = reader.create_solver(dual); + solver->find_maximal_solution(); + std::cout << "status is " << lp_status_to_string(solver->get_status()) << std::endl; + if (solver->get_status() == lp_status::OPTIMAL) { + if (reader.column_names().size() < 20) { + for (auto name : reader.column_names()) { + std::cout << name << "=" << solver->get_column_value_by_name(name).get_double() << ' '; + } + } + std::cout << std::endl << "cost = " << numeric_traits::get_double(solver->get_current_cost()) << std::endl; + } + delete solver; + } else { + std::cout << "cannot process " << file_name << std::endl; + } +} + +void test_upair_queue() { + int n = 10; + binary_heap_upair_queue q(2); + std::unordered_map m; + for (int k = 0; k < 100; k++) { + int i = my_random()%n; + int j = my_random()%n; + q.enqueue(i, j, my_random()%n); + } + + q.remove(5, 5); + + while (!q.is_empty()) { + unsigned i, j; + q.dequeue(i, j); + } +} + +void test_binary_priority_queue() { + std::cout << "testing binary_heap_priority_queue..."; + auto q = binary_heap_priority_queue(10); + q.enqueue(2, 2); + q.enqueue(1, 1); + q.enqueue(9, 9); + q.enqueue(8, 8); + q.enqueue(5, 25); + q.enqueue(3, 3); + q.enqueue(4, 4); + q.enqueue(7, 30); + q.enqueue(6, 6); + q.enqueue(0, 0); + q.enqueue(5, 5); + q.enqueue(7, 7); + + for (unsigned i = 0; i < 10; i++) { + unsigned de = q.dequeue(); + lean_assert(i == de); + std::cout << de << std::endl; + } + q.enqueue(2, 2); + q.enqueue(1, 1); + q.enqueue(9, 9); + q.enqueue(8, 8); + q.enqueue(5, 5); + q.enqueue(3, 3); + q.enqueue(4, 4); + q.enqueue(7, 2); + q.enqueue(0, 1); + q.enqueue(6, 6); + q.enqueue(7, 7); + q.enqueue(33, 1000); + q.enqueue(20, 0); + q.dequeue(); + q.remove(33); + q.enqueue(0, 0); +#ifdef LEAN_DEBUG + unsigned t = 0; + while (q.size() > 0) { + unsigned d =q.dequeue(); + lean_assert(t++ == d); + std::cout << d << std::endl; + } +#endif + test_upair_queue(); + std::cout << " done" << std::endl; +} + +bool solution_is_feasible(std::string file_name, const std::unordered_map & solution) { + mps_reader reader(file_name); + reader.read(); + if (reader.is_ok()) { + lp_primal_simplex * solver = static_cast *>(reader.create_solver(false)); + return solver->solution_is_feasible(solution); + } + return false; +} + + +void solve_mps_with_known_solution(std::string file_name, std::unordered_map * solution, lp_status status, bool dual) { + std::cout << "solving " << file_name << std::endl; + mps_reader reader(file_name); + reader.read(); + if (reader.is_ok()) { + auto * solver = reader.create_solver(dual); + solver->find_maximal_solution(); + std::cout << "status is " << lp_status_to_string(solver->get_status()) << std::endl; + if (status != solver->get_status()){ + std::cout << "status should be " << lp_status_to_string(status) << std::endl; + lean_assert(status == solver->get_status()); + throw "status is wrong"; + } + if (solver->get_status() == lp_status::OPTIMAL) { + std::cout << "cost = " << solver->get_current_cost() << std::endl; + if (solution != nullptr) { + for (auto it : *solution) { + if (fabs(it.second - solver->get_column_value_by_name(it.first)) >= 0.000001) { + std::cout << "expected:" << it.first << "=" << + it.second <<", got " << solver->get_column_value_by_name(it.first) << std::endl; + } + lean_assert(fabs(it.second - solver->get_column_value_by_name(it.first)) < 0.000001); + } + } + if (reader.column_names().size() < 20) { + for (auto name : reader.column_names()) { + std::cout << name << "=" << solver->get_column_value_by_name(name) << ' '; + } + std::cout << std::endl; + } + } + delete solver; + } else { + std::cout << "cannot process " << file_name << std::endl; + } +} + +int get_random_rows() { + return 5 + my_random() % 2; +} + +int get_random_columns() { + return 5 + my_random() % 3; +} + +int get_random_int() { + return -1 + my_random() % 2; // (1.0 + RAND_MAX); +} + +void add_random_row(lp_primal_simplex * solver, int cols, int row) { + solver->add_constraint(lp_relation::Greater_or_equal, 1, row); + for (int i = 0; i < cols; i++) { + solver->set_row_column_coefficient(row, i, get_random_int()); + } +} + +void add_random_cost(lp_primal_simplex * solver, int cols) { + for (int i = 0; i < cols; i++) { + solver->set_cost_for_column(i, get_random_int()); + } +} + +lp_primal_simplex * generate_random_solver() { + int rows = get_random_rows(); + int cols = get_random_columns(); + auto * solver = new lp_primal_simplex(); + for (int i = 0; i < rows; i++) { + add_random_row(solver, cols, i); + } + add_random_cost(solver, cols); + return solver; +} + + + +void random_test_on_i(unsigned i) { + if (i % 1000 == 0) { + std::cout << "."; + } + srand(i); + auto *solver = generate_random_solver(); + solver->find_maximal_solution(); + // std::cout << lp_status_to_string(solver->get_status()) << std::endl; + delete solver; +} + +void random_test() { + for (unsigned i = 0; i < std::numeric_limits::max(); i++) { + try { + random_test_on_i(i); + } + catch (const char * error) { + std::cout << "i = " << i << ", throwing at ' " << error << "'" << std::endl; + break; + } + } +} + +#if _LINUX_ +void fill_file_names(vector &file_names, std::set & minimums) { + char *home_dir = getenv("HOME"); + if (home_dir == nullptr) { + std::cout << "cannot find home directory, don't know how to find the files"; + return; + } + std::string home_dir_str(home_dir); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/l0redund.mps"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/l1.mps"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/l2.mps"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/l3.mps"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/l4.mps"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/l4fix.mps"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/plan.mps"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/samp2.mps"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/murtagh.mps"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/l0.mps"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/AFIRO.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/SC50B.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/SC50A.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/KB2.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/SC105.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/STOCFOR1.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/ADLITTLE.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/BLEND.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/SCAGR7.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/SC205.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/SHARE2B.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/RECIPELP.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/LOTFI.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/VTP-BASE.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/SHARE1B.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/BOEING2.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/BORE3D.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/SCORPION.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/CAPRI.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/BRANDY.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/SCAGR25.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/SCTAP1.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/ISRAEL.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/SCFXM1.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/BANDM.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/E226.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/AGG.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/GROW7.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/ETAMACRO.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/FINNIS.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/SCSD1.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/STANDATA.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/STANDGUB.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/BEACONFD.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/STAIR.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/STANDMPS.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/GFRD-PNC.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/SCRS8.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/BOEING1.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/MODSZK1.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/DEGEN2.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/FORPLAN.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/AGG2.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/AGG3.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/SCFXM2.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/SHELL.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/PILOT4.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/SCSD6.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/SHIP04S.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/SEBA.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/GROW15.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/FFFFF800.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/BNL1.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/PEROLD.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/QAP8.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/SCFXM3.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/SHIP04L.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/GANGES.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/SCTAP2.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/GROW22.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/SHIP08S.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/PILOT-WE.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/MAROS.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/STOCFOR2.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/25FV47.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/SHIP12S.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/SCSD8.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/FIT1P.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/SCTAP3.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/SIERRA.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/PILOTNOV.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/CZPROB.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/FIT1D.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/PILOT-JA.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/SHIP08L.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/BNL2.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/NESM.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/CYCLE.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/acc-tight5.mps"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/SHIP12L.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/DEGEN3.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/GREENBEA.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/GREENBEB.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/80BAU3B.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/TRUSS.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/D2Q06C.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/WOODW.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/QAP12.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/D6CUBE.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/PILOT.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/DFL001.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/WOOD1P.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/FIT2P.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/PILOT87.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/STOCFOR3.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/QAP15.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/FIT2D.SIF"); + file_names.push_back(home_dir_str + "/projects/lean/src/tests/util/lp/test_files/netlib/MAROS-R7.SIF"); + minimums.insert("/projects/lean/src/tests/util/lp/test_files/netlib/FIT2P.SIF"); + minimums.insert("/projects/lean/src/tests/util/lp/test_files/netlib/DFL001.SIF"); + minimums.insert("/projects/lean/src/tests/util/lp/test_files/netlib/D2Q06C.SIF"); + minimums.insert("/projects/lean/src/tests/util/lp/test_files/netlib/80BAU3B.SIF"); + minimums.insert("/projects/lean/src/tests/util/lp/test_files/netlib/GREENBEB.SIF"); + minimums.insert("/projects/lean/src/tests/util/lp/test_files/netlib/GREENBEA.SIF"); + minimums.insert("/projects/lean/src/tests/util/lp/test_files/netlib/BNL2.SIF"); + minimums.insert("/projects/lean/src/tests/util/lp/test_files/netlib/SHIP08L.SIF"); + minimums.insert("/projects/lean/src/tests/util/lp/test_files/netlib/FIT1D.SIF"); + minimums.insert("/projects/lean/src/tests/util/lp/test_files/netlib/SCTAP3.SIF"); + minimums.insert("/projects/lean/src/tests/util/lp/test_files/netlib/SCSD8.SIF"); + minimums.insert("/projects/lean/src/tests/util/lp/test_files/netlib/SCSD6.SIF"); + minimums.insert("/projects/lean/src/tests/util/lp/test_files/netlib/MAROS-R7.SIF"); +} + +void test_out_dir(std::string out_dir) { + auto *out_dir_p = opendir(out_dir.c_str()); + if (out_dir_p == nullptr) { + std::cout << "creating directory " << out_dir << std::endl; +#ifdef LEAN_WINDOWS + int res = mkdir(out_dir.c_str()); +#else + int res = mkdir(out_dir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); +#endif + if (res) { + std::cout << "Cannot open output directory \"" << out_dir << "\"" << std::endl; + } + return; + } + closedir(out_dir_p); +} + +void find_dir_and_file_name(std::string a, std::string & dir, std::string& fn) { + // todo: make it system independent + size_t last_slash_pos = a.find_last_of("/"); + if (last_slash_pos >= a.size()) { + std::cout << "cannot find file name in " << a << std::endl; + throw; + } + dir = a.substr(0, last_slash_pos); + // std::cout << "dir = " << dir << std::endl; + fn = a.substr(last_slash_pos + 1); + // std::cout << "fn = " << fn << std::endl; +} + +void process_test_file(std::string test_dir, std::string test_file_name, argument_parser & args_parser, std::string out_dir, unsigned max_iters, unsigned time_limit, unsigned & successes, unsigned & failures, unsigned & inconclusives); + +void solve_some_mps(argument_parser & args_parser) { + unsigned max_iters, time_limit; + get_time_limit_and_max_iters_from_parser(args_parser, time_limit, max_iters); + unsigned successes = 0; + unsigned failures = 0; + unsigned inconclusives = 0; + std::set minimums; + vector file_names; + fill_file_names(file_names, minimums); + bool solve_for_rational = args_parser.option_is_used("--mpq"); + bool dual = args_parser.option_is_used("--dual"); + bool compare_with_primal = args_parser.option_is_used("--compare_with_primal"); + bool compare_with_glpk = args_parser.option_is_used("--compare_with_glpk"); + if (compare_with_glpk) { + std::string out_dir = args_parser.get_option_value("--out_dir"); + if (out_dir.size() == 0) { + out_dir = "/tmp/test"; + } + test_out_dir(out_dir); + for (auto& a : file_names) { + try { + std::string file_dir; + std::string file_name; + find_dir_and_file_name(a, file_dir, file_name); + process_test_file(file_dir, file_name, args_parser, out_dir, max_iters, time_limit, successes, failures, inconclusives); + } + catch(const char *s){ + std::cout<< "exception: "<< s << std::endl; + } + } + std::cout << "comparing with glpk: successes " << successes << ", failures " << failures << ", inconclusives " << inconclusives << std::endl; + return; + } + if (!solve_for_rational) { + solve_mps(file_names[6], false, 0, time_limit, false, dual, compare_with_primal, args_parser); + solve_mps_with_known_solution(file_names[3], nullptr, INFEASIBLE, dual); // chvatal: 135(d) + std::unordered_map sol; + sol["X1"] = 0; + sol["X2"] = 6; + sol["X3"] = 0; + sol["X4"] = 15; + sol["X5"] = 2; + sol["X6"] = 1; + sol["X7"] = 1; + sol["X8"] = 0; + solve_mps_with_known_solution(file_names[9], &sol, OPTIMAL, dual); + solve_mps_with_known_solution(file_names[0], &sol, OPTIMAL, dual); + sol.clear(); + sol["X1"] = 25.0/14.0; + // sol["X2"] = 0; + // sol["X3"] = 0; + // sol["X4"] = 0; + // sol["X5"] = 0; + // sol["X6"] = 0; + // sol["X7"] = 9.0/14.0; + solve_mps_with_known_solution(file_names[5], &sol, OPTIMAL, dual); // chvatal: 135(e) + solve_mps_with_known_solution(file_names[4], &sol, OPTIMAL, dual); // chvatal: 135(e) + solve_mps_with_known_solution(file_names[2], nullptr, UNBOUNDED, dual); // chvatal: 135(c) + solve_mps_with_known_solution(file_names[1], nullptr, UNBOUNDED, dual); // chvatal: 135(b) + solve_mps(file_names[8], false, 0, time_limit, false, dual, compare_with_primal, args_parser); + // return; + for (auto& s : file_names) { + try { + solve_mps(s, minimums.find(s) != minimums.end(), max_iters, time_limit, false, dual, compare_with_primal, args_parser); + } + catch(const char *s){ + std::cout<< "exception: "<< s << std::endl; + } + } + } else { + // unsigned i = 0; + for (auto& s : file_names) { + // if (i++ > 9) return; + try { + solve_mps_in_rational(s, dual, args_parser); + } + catch(const char *s){ + std::cout<< "exception: "<< s << std::endl; + } + } + } +} +#endif + +void solve_rational() { + lp_primal_simplex solver; + solver.add_constraint(lp_relation::Equal, lean::mpq(7), 0); + solver.add_constraint(lp_relation::Equal, lean::mpq(-3), 1); + + // setting the cost + int cost[] = {-3, -1, -1, 2, -1, 1, 1, -4}; + std::string var_names[8] = {"x1", "x2", "x3", "x4", "x5", "x6", "x7", "x8"}; + + for (unsigned i = 0; i < 8; i++) { + solver.set_cost_for_column(i, lean::mpq(cost[i])); + solver.give_symbolic_name_to_column(var_names[i], i); + } + + int row0[] = {1, 0, 3, 1, -5, -2 , 4, -6}; + for (unsigned i = 0; i < 8; i++) { + solver.set_row_column_coefficient(0, i, lean::mpq(row0[i])); + } + + int row1[] = {0, 1, -2, -1, 4, 1, -3, 5}; + for (unsigned i = 0; i < 8; i++) { + solver.set_row_column_coefficient(1, i, lean::mpq(row1[i])); + } + + int bounds[] = {8, 6, 4, 15, 2, 10, 10, 3}; + for (unsigned i = 0; i < 8; i++) { + solver.set_low_bound(i, lean::mpq(0)); + solver.set_upper_bound(i, lean::mpq(bounds[i])); + } + + std::unordered_map expected_sol; + expected_sol["x1"] = lean::mpq(0); + expected_sol["x2"] = lean::mpq(6); + expected_sol["x3"] = lean::mpq(0); + expected_sol["x4"] = lean::mpq(15); + expected_sol["x5"] = lean::mpq(2); + expected_sol["x6"] = lean::mpq(1); + expected_sol["x7"] = lean::mpq(1); + expected_sol["x8"] = lean::mpq(0); + solver.find_maximal_solution(); + lean_assert(solver.get_status() == OPTIMAL); + for (auto it : expected_sol) { + lean_assert(it.second == solver.get_column_value_by_name(it.first)); + } +} + + +std::string read_line(bool & end, std::ifstream & file) { + std::string s; + if (!getline(file, s)) { + end = true; + return std::string(); + } + end = false; + return s; +} + +bool contains(std::string const & s, char const * pattern) { + return s.find(pattern) != std::string::npos; +} + + +std::unordered_map * get_solution_from_glpsol_output(std::string & file_name) { + std::ifstream file(file_name); + if (!file.is_open()){ + std::cerr << "cannot open " << file_name << std::endl; + return nullptr; + } + std::string s; + bool end; + do { + s = read_line(end, file); + if (end) { + std::cerr << "unexpected file end " << file_name << std::endl; + return nullptr; + } + if (contains(s, "Column name")){ + break; + } + } while (true); + + read_line(end, file); + if (end) { + std::cerr << "unexpected file end " << file_name << std::endl; + return nullptr; + } + + auto ret = new std::unordered_map(); + + do { + s = read_line(end, file); + if (end) { + std::cerr << "unexpected file end " << file_name << std::endl; + return nullptr; + } + auto split = string_split(s, " \t", false); + if (split.size() == 0) { + return ret; + } + + lean_assert(split.size() > 3); + (*ret)[split[1]] = atof(split[3].c_str()); + } while (true); +} + + + +void test_init_U() { + static_matrix m(3, 7); + m(0, 0) = 10; m(0, 1) = 11; m(0, 2) = 12; m(0, 3) = 13; m(0, 4) = 14; + m(1, 0) = 20; m(1, 1) = 21; m(1, 2) = 22; m(1, 3) = 23; m(1, 5) = 24; + m(2, 0) = 30; m(2, 1) = 31; m(2, 2) = 32; m(2, 3) = 33; m(2, 6) = 34; +#ifdef LEAN_DEBUG + print_matrix(m, std::cout); +#endif + vector basis(3); + basis[0] = 1; + basis[1] = 2; + basis[2] = 4; + + sparse_matrix u(m, basis); + + for (unsigned i = 0; i < 3; i++) { + for (unsigned j = 0; j < 3; j ++) { + lean_assert(m(i, basis[j]) == u(i, j)); + } + } + + // print_matrix(m); + // print_matrix(u); +} + +void test_replace_column() { + sparse_matrix m(10); + fill_matrix(m); + m.swap_columns(0, 7); + m.swap_columns(6, 3); + m.swap_rows(2, 0); + for (unsigned i = 1; i < m.dimension(); i++) { + m(i, 0) = 0; + } + + indexed_vector w(m.dimension()); + for (unsigned i = 0; i < m.dimension(); i++) { + w.set_value(i % 3, i); + } + + lp_settings settings; + + for (unsigned column_to_replace = 0; column_to_replace < m.dimension(); column_to_replace ++) { + m.replace_column(column_to_replace, w, settings); + for (unsigned i = 0; i < m.dimension(); i++) { + lean_assert(abs(w[i] - m(i, column_to_replace)) < 0.00000001); + } + } +} + + +void setup_args_parser(argument_parser & parser) { + parser.add_option_with_help_string("-xyz_sample", "run a small interactive scenario"); + parser.add_option_with_after_string_with_help("--density", "the percentage of non-zeroes in the matrix below which it is not dense"); + parser.add_option_with_after_string_with_help("--harris_toler", "harris tolerance"); + parser.add_option_with_help_string("--test_swaps", "test row swaps with a permutation"); + parser.add_option_with_help_string("--test_perm", "test permutaions"); + parser.add_option_with_after_string_with_help("--checklu", "the file name for lu checking"); + parser.add_option_with_after_string_with_help("--partial_pivot", "the partial pivot constant, a number somewhere between 10 and 100"); + parser.add_option_with_after_string_with_help("--percent_for_enter", "which percent of columns check for entering column"); + parser.add_option_with_help_string("--totalinf", "minimizes the total infeasibility instead of diminishin infeasibility of the rows"); + parser.add_option_with_after_string_with_help("--rep_frq", "the report frequency, in how many iterations print the cost and other info "); + parser.add_option_with_help_string("--smt", "smt file format"); + parser.add_option_with_after_string_with_help("--filelist", "the file containing the list of files"); + parser.add_option_with_after_string_with_help("--file", "the input file name"); + parser.add_option_with_after_string_with_help("--random_seed", "random seed"); + parser.add_option_with_help_string("--bp", "bound propogation"); + parser.add_option_with_help_string("--min", "will look for the minimum for the given file if --file is used; the default is looking for the max"); + parser.add_option_with_help_string("--max", "will look for the maximum for the given file if --file is used; it is the default behavior"); + parser.add_option_with_after_string_with_help("--max_iters", "maximum total iterations in a core solver stage"); + parser.add_option_with_after_string_with_help("--time_limit", "time limit in seconds"); + parser.add_option_with_help_string("--mpq", "solve for rational numbers"); + parser.add_option_with_after_string_with_help("--simplex_strategy", "sets simplex strategy for rational number"); + parser.add_option_with_help_string("--test_lu", "test the work of the factorization"); + parser.add_option_with_help_string("--test_small_lu", "test the work of the factorization on a smallish matrix"); + parser.add_option_with_help_string("--test_larger_lu", "test the work of the factorization"); + parser.add_option_with_help_string("--test_larger_lu_with_holes", "test the work of the factorization"); + parser.add_option_with_help_string("--test_lp_0", "solve a small lp"); + parser.add_option_with_help_string("--solve_some_mps", "solves a list of mps problems"); + parser.add_option_with_after_string_with_help("--test_file_directory", "loads files from the directory for testing"); + parser.add_option_with_help_string("--compare_with_glpk", "compares the results by running glpsol"); + parser.add_option_with_after_string_with_help("--out_dir", "setting the output directory for tests, if not set /tmp is used"); + parser.add_option_with_help_string("--dual", "using the dual simplex solver"); + parser.add_option_with_help_string("--compare_with_primal", "using the primal simplex solver for comparison"); + parser.add_option_with_help_string("--lar", "test lar_solver"); + parser.add_option_with_after_string_with_help("--maxng", "max iterations without progress"); + parser.add_option_with_help_string("-tbq", "test binary queue"); + parser.add_option_with_help_string("--randomize_lar", "test randomize funclionality"); + parser.add_option_with_help_string("--smap", "test stacked_map"); + parser.add_option_with_help_string("--term", "simple term test"); + parser.add_option_with_help_string("--eti"," run a small evidence test for total infeasibility scenario"); + parser.add_option_with_help_string("--row_inf", "forces row infeasibility search"); + parser.add_option_with_help_string("-pd", "presolve with double solver"); + parser.add_option_with_help_string("--test_int_set", "test int_set"); + parser.add_option_with_help_string("--test_mpq", "test rationals"); + parser.add_option_with_help_string("--test_mpq_np", "test rationals"); + parser.add_option_with_help_string("--test_mpq_np_plus", "test rationals using plus instead of +="); +} + +struct fff { int a; int b;}; + +void test_stacked_map_itself() { + vector v(3,0); + for(auto u : v) + std::cout << u << std::endl; + + std::unordered_map foo; + fff l; + l.a = 0; + l.b =1; + foo[1] = l; + int r = 1; + int k = foo[r].a; + std::cout << k << std::endl; + + stacked_map m; + m[0] = 3; + m[1] = 4; + m.push(); + m[1] = 5; + m[2] = 2; + m.pop(); + m.erase(2); + m[2] = 3; + m.erase(1); + m.push(); + m[3] = 100; + m[4] = 200; + m.erase(1); + m.push(); + m[5] = 300; + m[6] = 400; + m[5] = 301; + m.erase(5); + m[3] = 122; + + m.pop(2); + m.pop(); +} + +void test_stacked_unsigned() { + std::cout << "test stacked unsigned" << std::endl; + stacked_value v(0); + v = 1; + v = 2; + v.push(); + v = 3; + v = 4; + v.pop(); + lean_assert(v == 2); + v ++; + v++; + std::cout << "before push v=" << v << std::endl; + v.push(); + v++; + v.push(); + v+=1; + std::cout << "v = " << v << std::endl; + v.pop(2); + lean_assert(v == 4); + const unsigned & rr = v; + std::cout << rr << std:: endl; + +} + +void test_stacked_value() { + test_stacked_unsigned(); +} + +void test_stacked_vector() { + std::cout << "test_stacked_vector" << std::endl; + stacked_vector v; + v.push(); + v.push_back(0); + v.push_back(1); + v.push(); + v[0] = 3; + v[0] = 0; + v.push_back(2); + v.push_back(3); + v.push_back(34); + v.push(); + v[1]=3; + v[2] = 3; + v.push(); + v[0]= 7; + v[1] = 9; + v.pop(2); + if (v.size()) + v[v.size() -1 ] = 7; + v.push(); + v.push_back(33); + v[0] = 13; + v.pop(); + +} + +void test_stacked_set() { +#ifdef LEAN_DEBUG + std::cout << "test_stacked_set" << std::endl; + stacked_unordered_set s; + s.insert(1); + s.insert(2); + s.insert(3); + std::unordered_set scopy = s(); + s.push(); + s.insert(4); + s.pop(); + lean_assert(s() == scopy); + s.push(); + s.push(); + s.insert(4); + s.insert(5); + s.push(); + s.insert(4); + s.pop(3); + lean_assert(s() == scopy); +#endif +} + +void test_stacked() { + std::cout << "test_stacked_map()" << std::endl; + test_stacked_map_itself(); + test_stacked_value(); + test_stacked_vector(); + test_stacked_set(); + +} + +char * find_home_dir() { + #ifdef _WINDOWS + #else + char * home_dir = getenv("HOME"); + if (home_dir == nullptr) { + std::cout << "cannot find home directory" << std::endl; + return nullptr; + } + #endif + return nullptr; +} + + +template +void print_chunk(T * arr, unsigned len) { + for (unsigned i = 0; i < len; i++) { + std::cout << arr[i] << ", "; + } + std::cout << std::endl; +} + +struct mem_cpy_place_holder { + static void mem_copy_hook(int * destination, unsigned num) { + if (destination == nullptr || num == 0) { + throw "bad parameters"; + } + } +}; + +void finalize(unsigned ret) { + /* + finalize_util_module(); + finalize_numerics_module(); + */ + // return ret; +} + +void get_time_limit_and_max_iters_from_parser(argument_parser & args_parser, unsigned & time_limit, unsigned & max_iters) { + std::string s = args_parser.get_option_value("--max_iters"); + if (s.size() > 0) { + max_iters = atoi(s.c_str()); + } else { + max_iters = 0; + } + + std::string time_limit_string = args_parser.get_option_value("--time_limit"); + if (time_limit_string.size() > 0) { + time_limit = atoi(time_limit_string.c_str()); + } else { + time_limit = 0; + } +} + + +std::string create_output_file_name(bool minimize, std::string file_name, bool use_mpq) { + std::string ret = file_name + "_lp_tst_" + (minimize?"min":"max"); + if (use_mpq) return ret + "_mpq.out"; + return ret + ".out"; +} + +std::string create_output_file_name_for_glpsol(bool minimize, std::string file_name){ + return file_name + (minimize?"_min":"_max") + "_glpk_out"; +} + +int run_glpk(std::string file_name, std::string glpk_out_file_name, bool minimize, unsigned time_limit) { + std::string minmax(minimize?"--min":"--max"); + std::string tmlim = time_limit > 0 ? std::string(" --tmlim ") + std::to_string(time_limit)+ " ":std::string(); + std::string command_line = std::string("glpsol --nointopt --nomip ") + minmax + tmlim + + " -o " + glpk_out_file_name +" " + file_name + " > /dev/null"; + return system(command_line.c_str()); +} + +std::string get_status(std::string file_name) { + std::ifstream f(file_name); + if (!f.is_open()) { + std::cout << "cannot open " << file_name << std::endl; + throw 0; + } + std::string str; + while (getline(f, str)) { + if (str.find("Status") != std::string::npos) { + vector tokens = split_and_trim(str); + if (tokens.size() != 2) { + std::cout << "unexpected Status string " << str << std::endl; + throw 0; + } + return tokens[1]; + } + } + std::cout << "cannot find the status line in " << file_name << std::endl; + throw 0; +} + +// returns true if the costs should be compared too +bool compare_statuses(std::string glpk_out_file_name, std::string lp_out_file_name, unsigned & successes, unsigned & failures) { + std::string glpk_status = get_status(glpk_out_file_name); + std::string lp_tst_status = get_status(lp_out_file_name); + + if (glpk_status != lp_tst_status) { + if (glpk_status == "UNDEFINED" && (lp_tst_status == "UNBOUNDED" || lp_tst_status == "INFEASIBLE")) { + successes++; + return false; + } else { + std::cout << "glpsol and lp_tst disagree: glpsol status is " << glpk_status; + std::cout << " but lp_tst status is " << lp_tst_status << std::endl; + failures++; + return false; + } + } + return lp_tst_status == "OPTIMAL"; +} + +double get_glpk_cost(std::string file_name) { + std::ifstream f(file_name); + if (!f.is_open()) { + std::cout << "cannot open " << file_name << std::endl; + throw 0; + } + std::string str; + while (getline(f, str)) { + if (str.find("Objective") != std::string::npos) { + vector tokens = split_and_trim(str); + if (tokens.size() != 5) { + std::cout << "unexpected Objective std::string " << str << std::endl; + throw 0; + } + return atof(tokens[3].c_str()); + } + } + std::cout << "cannot find the Objective line in " << file_name << std::endl; + throw 0; +} + +double get_lp_tst_cost(std::string file_name) { + std::ifstream f(file_name); + if (!f.is_open()) { + std::cout << "cannot open " << file_name << std::endl; + throw 0; + } + std::string str; + std::string cost_string; + while (getline(f, str)) { + if (str.find("cost") != std::string::npos) { + cost_string = str; + } + } + if (cost_string.size() == 0) { + std::cout << "cannot find the cost line in " << file_name << std::endl; + throw 0; + } + + vector tokens = split_and_trim(cost_string); + if (tokens.size() != 3) { + std::cout << "unexpected cost string " << cost_string << std::endl; + throw 0; + } + return atof(tokens[2].c_str()); +} + +bool values_are_one_percent_close(double a, double b) { + double maxval = std::max(fabs(a), fabs(b)); + if (maxval < 0.000001) { + return true; + } + + double one_percent = maxval / 100; + return fabs(a - b) <= one_percent; +} + +// returns true if both are optimal +void compare_costs(std::string glpk_out_file_name, + std::string lp_out_file_name, + unsigned & successes, + unsigned & failures) { + double a = get_glpk_cost(glpk_out_file_name); + double b = get_lp_tst_cost(lp_out_file_name); + + if (values_are_one_percent_close(a, b)) { + successes++; + } else { + failures++; + std::cout << "glpsol cost is " << a << " lp_tst cost is " << b << std::endl; + } +} + + + +void compare_with_glpk(std::string glpk_out_file_name, std::string lp_out_file_name, unsigned & successes, unsigned & failures, std::string /*lp_file_name*/) { +#ifdef CHECK_GLPK_SOLUTION + std::unordered_map * solution_table = get_solution_from_glpsol_output(glpk_out_file_name); + if (solution_is_feasible(lp_file_name, *solution_table)) { + std::cout << "glpk solution is feasible" << std::endl; + } else { + std::cout << "glpk solution is infeasible" << std::endl; + } + delete solution_table; +#endif + if (compare_statuses(glpk_out_file_name, lp_out_file_name, successes, failures)) { + compare_costs(glpk_out_file_name, lp_out_file_name, successes, failures); + } +} +void test_lar_on_file(std::string file_name, argument_parser & args_parser); + +void process_test_file(std::string test_dir, std::string test_file_name, argument_parser & args_parser, std::string out_dir, unsigned max_iters, unsigned time_limit, unsigned & successes, unsigned & failures, unsigned & inconclusives) { + bool use_mpq = args_parser.option_is_used("--mpq"); + bool minimize = args_parser.option_is_used("--min"); + std::string full_lp_tst_out_name = out_dir + "/" + create_output_file_name(minimize, test_file_name, use_mpq); + + std::string input_file_name = test_dir + "/" + test_file_name; + if (input_file_name[input_file_name.size() - 1] == '~') { + // std::cout << "ignoring " << input_file_name << std::endl; + return; + } + std::cout <<"processing " << input_file_name << std::endl; + + std::ofstream out(full_lp_tst_out_name); + if (!out.is_open()) { + std::cout << "cannot open file " << full_lp_tst_out_name << std::endl; + throw 0; + } + std::streambuf *coutbuf = std::cout.rdbuf(); // save old buffer + std::cout.rdbuf(out.rdbuf()); // redirect std::cout to dir_entry->d_name! + bool dual = args_parser.option_is_used("--dual"); + try { + if (args_parser.option_is_used("--lar")) + test_lar_on_file(input_file_name, args_parser); + else + solve_mps(input_file_name, minimize, max_iters, time_limit, use_mpq, dual, false, args_parser); + } + catch(...) { + std::cout << "catching the failure" << std::endl; + failures++; + std::cout.rdbuf(coutbuf); // reset to standard output again + return; + } + std::cout.rdbuf(coutbuf); // reset to standard output again + + if (args_parser.option_is_used("--compare_with_glpk")) { + std::string glpk_out_file_name = out_dir + "/" + create_output_file_name_for_glpsol(minimize, std::string(test_file_name)); + int glpk_exit_code = run_glpk(input_file_name, glpk_out_file_name, minimize, time_limit); + if (glpk_exit_code != 0) { + std::cout << "glpk failed" << std::endl; + inconclusives++; + } else { + compare_with_glpk(glpk_out_file_name, full_lp_tst_out_name, successes, failures, input_file_name); + } + } +} +/* + int my_readdir(DIR *dirp, struct dirent * +#ifndef LEAN_WINDOWS + entry +#endif + , struct dirent **result) { +#ifdef LEAN_WINDOWS + *result = readdir(dirp); // NOLINT + return *result != nullptr? 0 : 1; +#else + return readdir_r(dirp, entry, result); +#endif +} +*/ +/* +vector> get_file_list_of_dir(std::string test_file_dir) { + DIR *dir; + if ((dir = opendir(test_file_dir.c_str())) == nullptr) { + std::cout << "Cannot open directory " << test_file_dir << std::endl; + throw 0; + } + vector> ret; + struct dirent entry; + struct dirent* result; + int return_code; + for (return_code = my_readdir(dir, &entry, &result); +#ifndef LEAN_WINDOWS + result != nullptr && +#endif + return_code == 0; + return_code = my_readdir(dir, &entry, &result)) { + DIR *tmp_dp = opendir(result->d_name); + struct stat file_record; + if (tmp_dp == nullptr) { + std::string s = test_file_dir+ "/" + result->d_name; + int stat_ret = stat(s.c_str(), & file_record); + if (stat_ret!= -1) { + ret.push_back(make_pair(result->d_name, file_record.st_size)); + } else { + perror("stat"); + exit(1); + } + } else { + closedir(tmp_dp); + } + } + closedir(dir); + return ret; +} +*/ +/* +struct file_size_comp { + unordered_map& m_file_sizes; + file_size_comp(unordered_map& fs) :m_file_sizes(fs) {} + int operator()(std::string a, std::string b) { + std::cout << m_file_sizes.size() << std::endl; + std::cout << a << std::endl; + std::cout << b << std::endl; + + auto ls = m_file_sizes.find(a); + std::cout << "fa" << std::endl; + auto rs = m_file_sizes.find(b); + std::cout << "fb" << std::endl; + if (ls != m_file_sizes.end() && rs != m_file_sizes.end()) { + std::cout << "fc " << std::endl; + int r = (*ls < *rs? -1: (*ls > *rs)? 1 : 0); + std::cout << "calc r " << std::endl; + return r; + } else { + std::cout << "sc " << std::endl; + return 0; + } + } +}; + +*/ +struct sort_pred { + bool operator()(const std::pair &left, const std::pair &right) { + return left.second < right.second; + } +}; + + +void test_files_from_directory(std::string test_file_dir, argument_parser & args_parser) { + /* + std::cout << "loading files from directory \"" << test_file_dir << "\"" << std::endl; + std::string out_dir = args_parser.get_option_value("--out_dir"); + if (out_dir.size() == 0) { + out_dir = "/tmp/test"; + } + DIR *out_dir_p = opendir(out_dir.c_str()); + if (out_dir_p == nullptr) { + std::cout << "Cannot open output directory \"" << out_dir << "\"" << std::endl; + return; + } + closedir(out_dir_p); + vector> files = get_file_list_of_dir(test_file_dir); + std::sort(files.begin(), files.end(), sort_pred()); + unsigned max_iters, time_limit; + get_time_limit_and_max_iters_from_parser(args_parser, time_limit, max_iters); + unsigned successes = 0, failures = 0, inconclusives = 0; + for (auto & t : files) { + process_test_file(test_file_dir, t.first, args_parser, out_dir, max_iters, time_limit, successes, failures, inconclusives); + } + std::cout << "comparing with glpk: successes " << successes << ", failures " << failures << ", inconclusives " << inconclusives << std::endl; + */ +} + + +std::unordered_map get_solution_map(lp_solver * lps, mps_reader & reader) { + std::unordered_map ret; + for (auto it : reader.column_names()) { + ret[it] = lps->get_column_value_by_name(it); + } + return ret; +} + +void run_lar_solver(argument_parser & args_parser, lar_solver * solver, mps_reader * reader) { + std::string maxng = args_parser.get_option_value("--maxng"); + if (maxng.size() > 0) { + solver->settings().max_number_of_iterations_with_no_improvements = atoi(maxng.c_str()); + } + if (args_parser.option_is_used("-pd")){ + solver->settings().presolve_with_double_solver_for_lar = true; + } + + std::string iter = args_parser.get_option_value("--max_iters"); + if (iter.size() > 0) { + solver->settings().max_total_number_of_iterations = atoi(iter.c_str()); + } + if (args_parser.option_is_used("--compare_with_primal")){ + if (reader == nullptr) { + std::cout << "cannot compare with primal, the reader is null " << std::endl; + return; + } + auto * lps = reader->create_solver(false); + lps->find_maximal_solution(); + std::unordered_map sol = get_solution_map(lps, *reader); + std::cout << "status = " << lp_status_to_string(solver->get_status()) << std::endl; + return; + } + int begin = get_millisecond_count(); + lp_status status = solver->solve(); + std::cout << "status is " << lp_status_to_string(status) << ", processed for " << get_millisecond_span(begin) / 1000.0 <<" seconds, and " << solver->get_total_iterations() << " iterations" << std::endl; + if (solver->get_status() == INFEASIBLE) { + vector> evidence; + solver->get_infeasibility_explanation(evidence); + } + if (args_parser.option_is_used("--randomize_lar")) { + if (solver->get_status() != OPTIMAL) { + std::cout << "cannot check randomize on an infeazible problem" << std::endl; + return; + } + std::cout << "checking randomize" << std::endl; + vector all_vars = solver->get_list_of_all_var_indices(); + unsigned m = all_vars.size(); + if (m > 100) + m = 100; + + var_index *vars = new var_index[m]; + for (unsigned i = 0; i < m; i++) + vars[i]=all_vars[i]; + + solver->random_update(m, vars); + delete []vars; + } +} + +lar_solver * create_lar_solver_from_file(std::string file_name, argument_parser & args_parser) { + if (args_parser.option_is_used("--smt")) { + smt_reader reader(file_name); + reader.read(); + if (!reader.is_ok()){ + std::cout << "cannot process " << file_name << std::endl; + return nullptr; + } + return reader.create_lar_solver(); + } + mps_reader reader(file_name); + reader.read(); + if (!reader.is_ok()) { + std::cout << "cannot process " << file_name << std::endl; + return nullptr; + } + return reader.create_lar_solver(); +} + +void test_lar_on_file(std::string file_name, argument_parser & args_parser) { + lar_solver * solver = create_lar_solver_from_file(file_name, args_parser); + mps_reader reader(file_name); + mps_reader * mps_reader = nullptr; + reader.read(); + if (reader.is_ok()) { + mps_reader = & reader; + run_lar_solver(args_parser, solver, mps_reader); + } + delete solver; +} + +vector get_file_names_from_file_list(std::string filelist) { + std::ifstream file(filelist); + if (!file.is_open()) { + std::cout << "cannot open " << filelist << std::endl; + return vector(); + } + vector ret; + bool end; + do { + std::string s = read_line(end, file); + if (end) + break; + if (s.size() == 0) + break; + ret.push_back(s); + } while (true); + return ret; +} + +void test_lar_solver(argument_parser & args_parser) { + + std::string file_name = args_parser.get_option_value("--file"); + if (file_name.size() > 0) { + test_lar_on_file(file_name, args_parser); + return; + } + + std::string file_list = args_parser.get_option_value("--filelist"); + if (file_list.size() > 0) { + for (std::string fn : get_file_names_from_file_list(file_list)) + test_lar_on_file(fn, args_parser); + return; + } +} + +void test_numeric_pair() { + numeric_pair a; + numeric_pair b(2, lean::mpq(6, 2)); + a = b; + numeric_pair c(0.1, 0.5); + a += 2*c; + a -= c; + lean_assert (a == b + c); + numeric_pair d = a * 2; + std::cout << a << std::endl; + lean_assert(b == b); + lean_assert(b < a); + lean_assert(b <= a); + lean_assert(a > b); + lean_assert(a != b); + lean_assert(a >= b); + lean_assert(-a < b); + lean_assert(a < 2 * b); + lean_assert(b + b > a); + lean_assert(lean::mpq(2.1) * b + b > a); + lean_assert(-b * lean::mpq(2.1) - b < lean::mpq(0.99) * a); + std::cout << - b * lean::mpq(2.1) - b << std::endl; + lean_assert(-b *(lean::mpq(2.1) + 1) == - b * lean::mpq(2.1) - b); +} + +void get_matrix_dimensions(std::ifstream & f, unsigned & m, unsigned & n) { + std::string line; + getline(f, line); + getline(f, line); + vector r = split_and_trim(line); + m = atoi(r[1].c_str()); + getline(f, line); + r = split_and_trim(line); + n = atoi(r[1].c_str()); +} + +void read_row_cols(unsigned i, static_matrix& A, std::ifstream & f) { + do { + std::string line; + getline(f, line); + if (line== "row_end") + break; + auto r = split_and_trim(line); + lean_assert(r.size() == 4); + unsigned j = atoi(r[1].c_str()); + double v = atof(r[3].c_str()); + A.set(i, j, v); + } while (true); +} + +bool read_row(static_matrix & A, std::ifstream & f) { + std::string line; + getline(f, line); + if (static_cast(line.find("row")) == -1) + return false; + auto r = split_and_trim(line); + if (r[0] != "row") + std::cout << "wrong row line" << line << std::endl; + unsigned i = atoi(r[1].c_str()); + read_row_cols(i, A, f); + return true; +} + +void read_rows(static_matrix& A, std::ifstream & f) { + while (read_row(A, f)) {} +} + +void read_basis(vector & basis, std::ifstream & f) { + std::cout << "reading basis" << std::endl; + std::string line; + getline(f, line); + lean_assert(line == "basis_start"); + do { + getline(f, line); + if (line == "basis_end") + break; + unsigned j = atoi(line.c_str()); + basis.push_back(j); + } while (true); +} + +void read_indexed_vector(indexed_vector & v, std::ifstream & f) { + std::string line; + getline(f, line); + lean_assert(line == "vector_start"); + do { + getline(f, line); + if (line == "vector_end") break; + auto r = split_and_trim(line); + unsigned i = atoi(r[0].c_str()); + double val = atof(r[1].c_str()); + v.set_value(val, i); + std::cout << "setting value " << i << " = " << val << std::endl; + } while (true); +} + +void check_lu_from_file(std::string lufile_name) { + std::ifstream f(lufile_name); + if (!f.is_open()) { + std::cout << "cannot open file " << lufile_name << std::endl; + } + unsigned m, n; + get_matrix_dimensions(f, m, n); + std::cout << "init matrix " << m << " by " << n << std::endl; + static_matrix A(m, n); + read_rows(A, f); + vector basis; + read_basis(basis, f); + indexed_vector v(m); + // read_indexed_vector(v, f); + f.close(); + vector basis_heading; + lp_settings settings; + vector non_basic_columns; + lu lsuhl(A, basis, settings); + indexed_vector d(A.row_count()); + unsigned entering = 26; + lsuhl.solve_Bd(entering, d, v); +#ifdef LEAN_DEBUG + auto B = get_B(lsuhl, basis); + vector a(m); + A.copy_column_to_vector(entering, a); + indexed_vector cd(d); + B.apply_from_left(cd.m_data, settings); + lean_assert(vectors_are_equal(cd.m_data , a)); +#endif +} + +void test_square_dense_submatrix() { + std::cout << "testing square_dense_submatrix" << std::endl; + unsigned parent_dim = 7; + sparse_matrix parent(parent_dim); + fill_matrix(parent); + unsigned index_start = 3; + square_dense_submatrix d; + d.init(&parent, index_start); + for (unsigned i = index_start; i < parent_dim; i++) + for (unsigned j = index_start; j < parent_dim; j++) + d[i][j] = i*3+j*2; +#ifdef LEAN_DEBUG + unsigned dim = parent_dim - index_start; + dense_matrix m(dim, dim); + for (unsigned i = index_start; i < parent_dim; i++) + for (unsigned j = index_start; j < parent_dim; j++) + m[i-index_start][j-index_start] = d[i][j]; + print_matrix(&m, std::cout); +#endif + for (unsigned i = index_start; i < parent_dim; i++) + for (unsigned j = index_start; j < parent_dim; j++) + d[i][j] = d[j][i]; +#ifdef LEAN_DEBUG + for (unsigned i = index_start; i < parent_dim; i++) + for (unsigned j = index_start; j < parent_dim; j++) + m[i-index_start][j-index_start] = d[i][j]; + + print_matrix(&m, std::cout); + std::cout << std::endl; +#endif +} + + + +void print_st(lp_status status) { + std::cout << lp_status_to_string(status) << std::endl; +} + + + +void test_term() { + lar_solver solver; + unsigned _x = 0; + unsigned _y = 1; + var_index x = solver.add_var(_x); + var_index y = solver.add_var(_y); + + vector> term_ls; + term_ls.push_back(std::pair((int)1, x)); + term_ls.push_back(std::pair((int)1, y)); + var_index z = solver.add_term(term_ls, mpq(3)); + + vector> ls; + ls.push_back(std::pair((int)1, x)); + ls.push_back(std::pair((int)1, y)); + ls.push_back(std::pair((int)1, z)); + + solver.add_constraint(ls, lconstraint_kind::EQ, mpq(0)); + auto status = solver.solve(); + std::cout << lp_status_to_string(status) << std::endl; + std::unordered_map model; + solver.get_model(model); + + for (auto & t : model) { + std::cout << solver.get_variable_name(t.first) << " = " << t.second.get_double() << ","; + } + std::cout << std::endl; + +} + +void test_evidence_for_total_inf_simple(argument_parser & args_parser) { + lar_solver solver; + var_index x = solver.add_var(0); + var_index y = solver.add_var(1); + solver.add_var_bound(x, LE, -mpq(1)); + solver.add_var_bound(y, GE, mpq(0)); + vector> ls; + + ls.push_back(std::pair((int)1, x)); + ls.push_back(std::pair((int)1, y)); + solver.add_constraint(ls, GE, mpq(1)); + ls.pop_back(); + ls.push_back(std::pair(-(int)1, y)); + solver.add_constraint(ls, lconstraint_kind::GE, mpq(0)); + auto status = solver.solve(); + std::cout << lp_status_to_string(status) << std::endl; + std::unordered_map model; + lean_assert(solver.get_status() == INFEASIBLE); +} +void test_bound_propagation_one_small_sample1() { + /* +(<= (+ a (* (- 1.0) b)) 0.0) +(<= (+ b (* (- 1.0) x_13)) 0.0) +--> (<= (+ a (* (- 1.0) c)) 0.0) + +the inequality on (<= a c) is obtained from a triangle inequality (<= a b) (<= b c). +If b becomes basic variable, then it is likely the old solver ends up with a row that implies (<= a c). + a - b <= 0.0 + b - c <= 0.0 + + got to get a <= c + */ + std::function bound_is_relevant = + [&](unsigned j, bool is_low_bound, bool strict, const rational& bound_val) { + return true; + }; + lar_solver ls; + unsigned a = ls.add_var(0); + unsigned b = ls.add_var(1); + unsigned c = ls.add_var(2); + vector> coeffs; + coeffs.push_back(std::pair(1, a)); + coeffs.push_back(std::pair(-1, c)); + ls.add_term(coeffs, zero_of_type()); + coeffs.pop_back(); + coeffs.push_back(std::pair(-1, b)); + ls.add_term(coeffs, zero_of_type()); + coeffs.clear(); + coeffs.push_back(std::pair(1, a)); + coeffs.push_back(std::pair(-1, b)); + ls.add_constraint(coeffs, LE, zero_of_type()); + coeffs.clear(); + coeffs.push_back(std::pair(1, b)); + coeffs.push_back(std::pair(-1, c)); + ls.add_constraint(coeffs, LE, zero_of_type()); + vector ev; + ls.add_var_bound(a, LE, mpq(1)); + ls.solve(); + bound_propagator bp(ls); + ls.propagate_bounds_for_touched_rows(bp); + std::cout << " bound ev from test_bound_propagation_one_small_sample1" << std::endl; + for (auto & be : bp.m_ibounds) { + std::cout << "bound\n"; + ls.print_implied_bound(be, std::cout); + } +} + +void test_bound_propagation_one_small_samples() { + test_bound_propagation_one_small_sample1(); + /* + (>= x_46 0.0) +(<= x_29 0.0) +(not (<= x_68 0.0)) +(<= (+ (* (/ 1001.0 1998.0) x_10) (* (- 1.0) x_151) x_68) (- (/ 1001.0 999.0))) +(<= (+ (* (/ 1001.0 999.0) x_9) + (* (- 1.0) x_152) + (* (/ 1001.0 999.0) x_151) + (* (/ 1001.0 999.0) x_68)) + (- (/ 1502501.0 999000.0))) +(not (<= (+ (* (/ 999.0 2.0) x_10) (* (- 1.0) x_152) (* (- (/ 999.0 2.0)) x_151)) + (/ 1001.0 2.0))) +(not (<= x_153 0.0))z +(>= (+ x_9 (* (- (/ 1001.0 999.0)) x_10) (* (- 1.0) x_153) (* (- 1.0) x_68)) + (/ 5003.0 1998.0)) +--> (not (<= (+ x_10 x_46 (* (- 1.0) x_29)) 0.0)) + +and + +(<= (+ a (* (- 1.0) b)) 0.0) +(<= (+ b (* (- 1.0) x_13)) 0.0) +--> (<= (+ a (* (- 1.0) x_13)) 0.0) + +In the first case, there typically are no atomic formulas for bounding x_10. So there is never some +basic lemma of the form (>= x46 0), (<= x29 0), (>= x10 0) -> (not (<= (+ x10 x46 (- x29)) 0)). +Instead the bound on x_10 falls out from a bigger blob of constraints. + +In the second case, the inequality on (<= x19 x13) is obtained from a triangle inequality (<= x19 x9) (<= x9 x13). +If x9 becomes basic variable, then it is likely the old solver ends up with a row that implies (<= x19 x13). + */ +} +void test_bound_propagation_one_row() { + lar_solver ls; + unsigned x0 = ls.add_var(0); + unsigned x1 = ls.add_var(1); + vector> c; + c.push_back(std::pair(1, x0)); + c.push_back(std::pair(-1, x1)); + ls.add_constraint(c, EQ, one_of_type()); + vector ev; + ls.add_var_bound(x0, LE, mpq(1)); + ls.solve(); + bound_propagator bp(ls); + ls.propagate_bounds_for_touched_rows(bp); +} +void test_bound_propagation_one_row_with_bounded_vars() { + lar_solver ls; + unsigned x0 = ls.add_var(0); + unsigned x1 = ls.add_var(1); + vector> c; + c.push_back(std::pair(1, x0)); + c.push_back(std::pair(-1, x1)); + ls.add_constraint(c, EQ, one_of_type()); + vector ev; + ls.add_var_bound(x0, GE, mpq(-3)); + ls.add_var_bound(x0, LE, mpq(3)); + ls.add_var_bound(x0, LE, mpq(1)); + ls.solve(); + bound_propagator bp(ls); + ls.propagate_bounds_for_touched_rows(bp); +} +void test_bound_propagation_one_row_mixed() { + lar_solver ls; + unsigned x0 = ls.add_var(0); + unsigned x1 = ls.add_var(1); + vector> c; + c.push_back(std::pair(1, x0)); + c.push_back(std::pair(-1, x1)); + ls.add_constraint(c, EQ, one_of_type()); + vector ev; + ls.add_var_bound(x1, LE, mpq(1)); + ls.solve(); + bound_propagator bp(ls); + ls.propagate_bounds_for_touched_rows(bp); +} + +void test_bound_propagation_two_rows() { + lar_solver ls; + unsigned x = ls.add_var(0); + unsigned y = ls.add_var(1); + unsigned z = ls.add_var(2); + vector> c; + c.push_back(std::pair(1, x)); + c.push_back(std::pair(2, y)); + c.push_back(std::pair(3, z)); + ls.add_constraint(c, GE, one_of_type()); + c.clear(); + c.push_back(std::pair(3, x)); + c.push_back(std::pair(2, y)); + c.push_back(std::pair(1, z)); + ls.add_constraint(c, GE, one_of_type()); + ls.add_var_bound(x, LE, mpq(2)); + vector ev; + ls.add_var_bound(y, LE, mpq(1)); + ls.solve(); + bound_propagator bp(ls); + ls.propagate_bounds_for_touched_rows(bp); +} + +void test_total_case_u() { + std::cout << "test_total_case_u\n"; + lar_solver ls; + unsigned x = ls.add_var(0); + unsigned y = ls.add_var(1); + unsigned z = ls.add_var(2); + vector> c; + c.push_back(std::pair(1, x)); + c.push_back(std::pair(2, y)); + c.push_back(std::pair(3, z)); + ls.add_constraint(c, LE, one_of_type()); + ls.add_var_bound(x, GE, zero_of_type()); + ls.add_var_bound(y, GE, zero_of_type()); + vector ev; + ls.add_var_bound(z, GE, zero_of_type()); + ls.solve(); + bound_propagator bp(ls); + ls.propagate_bounds_for_touched_rows(bp); +} +bool contains_j_kind(unsigned j, lconstraint_kind kind, const mpq & rs, const vector & ev) { + for (auto & e : ev) { + if (e.m_j == j && e.m_bound == rs && e.kind() == kind) + return true; + } + return false; +} +void test_total_case_l(){ + std::cout << "test_total_case_l\n"; + lar_solver ls; + unsigned x = ls.add_var(0); + unsigned y = ls.add_var(1); + unsigned z = ls.add_var(2); + vector> c; + c.push_back(std::pair(1, x)); + c.push_back(std::pair(2, y)); + c.push_back(std::pair(3, z)); + ls.add_constraint(c, GE, one_of_type()); + ls.add_var_bound(x, LE, one_of_type()); + ls.add_var_bound(y, LE, one_of_type()); + ls.settings().presolve_with_double_solver_for_lar = true; + vector ev; + ls.add_var_bound(z, LE, zero_of_type()); + ls.solve(); + bound_propagator bp(ls); + ls.propagate_bounds_for_touched_rows(bp); + lean_assert(ev.size() == 4); + lean_assert(contains_j_kind(x, GE, - one_of_type(), ev)); +} +void test_bound_propagation() { + test_total_case_u(); + test_bound_propagation_one_small_samples(); + test_bound_propagation_one_row(); + test_bound_propagation_one_row_with_bounded_vars(); + test_bound_propagation_two_rows(); + test_bound_propagation_one_row_mixed(); + test_total_case_l(); + +} + +void test_int_set() { + int_set s(4); + s.insert(2); + s.print(std::cout); + s.insert(1); + s.insert(2); + s.print(std::cout); + lean_assert(s.contains(2)); + lean_assert(s.size() == 2); + s.erase(2); + lean_assert(s.size() == 1); + s.erase(2); + lean_assert(s.size() == 1); + s.print(std::cout); + s.insert(3); + s.insert(2); + s.clear(); + lean_assert(s.size() == 0); + + +} + +void test_rationals_no_numeric_pairs() { + stopwatch sw; + + vector c; + for (unsigned j = 0; j < 10; j ++) + c.push_back(mpq(my_random()%100, 1 + my_random()%100 )); + + vector x; + for (unsigned j = 0; j < 10; j ++) + x.push_back(mpq(my_random()%100, 1 + my_random()%100 )); + + unsigned k = 500000; + mpq r=zero_of_type(); + sw.start(); + + for (unsigned j = 0; j < k; j++){ + mpq val = zero_of_type(); + for (unsigned j=0;j< c.size(); j++){ + val += c[j]*x[j]; + } + + r += val; + } + + sw.stop(); + std::cout << "operation with rationals no pairs " << sw.get_seconds() << std::endl; + std::cout << T_to_string(r) << std::endl; +} + +void test_rationals_no_numeric_pairs_plus() { + stopwatch sw; + + vector c; + for (unsigned j = 0; j < 10; j ++) + c.push_back(mpq(my_random()%100, 1 + my_random()%100 )); + + vector x; + for (unsigned j = 0; j < 10; j ++) + x.push_back(mpq(my_random()%100, 1 + my_random()%100 )); + + unsigned k = 500000; + mpq r=zero_of_type(); + sw.start(); + + for (unsigned j = 0; j < k; j++){ + mpq val = zero_of_type(); + for (unsigned j=0;j< c.size(); j++){ + val = val + c[j]*x[j]; + } + + r = r + val; + } + + sw.stop(); + std::cout << "operation with rationals no pairs " << sw.get_seconds() << std::endl; + std::cout << T_to_string(r) << std::endl; +} + + + +void test_rationals() { + stopwatch sw; + + vector c; + for (unsigned j = 0; j < 10; j ++) + c.push_back(mpq(my_random()%100, 1 + my_random()%100)); + + + + vector> x; + for (unsigned j = 0; j < 10; j ++) + x.push_back(mpq(my_random()%100, 1 + my_random()%100 )); + + std::cout << "x = "; + print_vector(x, std::cout); + + unsigned k = 1000000; + numeric_pair r=zero_of_type>(); + sw.start(); + + for (unsigned j = 0; j < k; j++) { + for (unsigned i = 0; i < c.size(); i++) { + r+= c[i] * x[i]; + } + } + sw.stop(); + std::cout << "operation with rationals " << sw.get_seconds() << std::endl; + std::cout << T_to_string(r) << std::endl; +} + +void test_lp_local(int argn, char**argv) { + std::cout << "resize\n"; + vector r; + r.resize(1); + + // initialize_util_module(); + // initialize_numerics_module(); + int ret; + argument_parser args_parser(argn, argv); + setup_args_parser(args_parser); + if (!args_parser.parse()) { + std::cout << args_parser.m_error_message << std::endl; + std::cout << args_parser.usage_string(); + ret = 1; + return finalize(ret); + } + + args_parser.print(); + + if (args_parser.option_is_used("--test_mpq")) { + test_rationals(); + return finalize(0); + } + + if (args_parser.option_is_used("--test_mpq_np")) { + test_rationals_no_numeric_pairs(); + return finalize(0); + } + + if (args_parser.option_is_used("--test_mpq_np_plus")) { + test_rationals_no_numeric_pairs_plus(); + return finalize(0); + } + + + + if (args_parser.option_is_used("--test_int_set")) { + test_int_set(); + return finalize(0); + } + if (args_parser.option_is_used("--bp")) { + test_bound_propagation(); + return finalize(0); + } + + + std::string lufile = args_parser.get_option_value("--checklu"); + if (lufile.size()) { + check_lu_from_file(lufile); + return finalize(0); + } + +#ifdef LEAN_DEBUG + if (args_parser.option_is_used("--test_swaps")) { + sparse_matrix m(10); + fill_matrix(m); + test_swap_rows_with_permutation(m); + test_swap_cols_with_permutation(m); + return finalize(0); + } +#endif + if (args_parser.option_is_used("--test_perm")) { + test_permutations(); + return finalize(0); + } + if (args_parser.option_is_used("--test_file_directory")) { + test_files_from_directory(args_parser.get_option_value("--test_file_directory"), args_parser); + return finalize(0); + } + std::string file_list = args_parser.get_option_value("--filelist"); + if (file_list.size() > 0) { + for (std::string fn : get_file_names_from_file_list(file_list)) + solve_mps(fn, args_parser); + return finalize(0); + } + + if (args_parser.option_is_used("-tbq")) { + test_binary_priority_queue(); + ret = 0; + return finalize(ret); + } + +#ifdef LEAN_DEBUG + lp_settings settings; + update_settings(args_parser, settings); + if (args_parser.option_is_used("--test_lu")) { + test_lu(settings); + ret = 0; + return finalize(ret); + } + + if (args_parser.option_is_used("--test_small_lu")) { + test_small_lu(settings); + ret = 0; + return finalize(ret); + } + + if (args_parser.option_is_used("--lar")){ + std::cout <<"calling test_lar_solver" << std::endl; + test_lar_solver(args_parser); + return finalize(0); + } + + + + if (args_parser.option_is_used("--test_larger_lu")) { + test_larger_lu(settings); + ret = 0; + return finalize(ret); + } + + if (args_parser.option_is_used("--test_larger_lu_with_holes")) { + test_larger_lu_with_holes(settings); + ret = 0; + return finalize(ret); + } +#endif + if (args_parser.option_is_used("--eti")) { + test_evidence_for_total_inf_simple(args_parser); + ret = 0; + return finalize(ret); + } + + + if (args_parser.option_is_used("--test_lp_0")) { + test_lp_0(); + ret = 0; + return finalize(ret); + } + + if (args_parser.option_is_used("--smap")) { + test_stacked(); + ret = 0; + return finalize(ret); + } + if (args_parser.option_is_used("--term")) { + test_term(); + ret = 0; + return finalize(ret); + } + unsigned max_iters; + unsigned time_limit; + get_time_limit_and_max_iters_from_parser(args_parser, time_limit, max_iters); + bool dual = args_parser.option_is_used("--dual"); + bool solve_for_rational = args_parser.option_is_used("--mpq"); + std::string file_name = args_parser.get_option_value("--file"); + if (file_name.size() > 0) { + solve_mps(file_name, args_parser.option_is_used("--min"), max_iters, time_limit, solve_for_rational, dual, args_parser.option_is_used("--compare_with_primal"), args_parser); + ret = 0; + return finalize(ret); + } + + if (args_parser.option_is_used("--solve_some_mps")) { +#if _LINUX_ + solve_some_mps(args_parser); +#endif + ret = 0; + return finalize(ret); + } + // lean::ccc = 0; + return finalize(0); + test_init_U(); + test_replace_column(); +#ifdef LEAN_DEBUG + sparse_matrix_with_permutaions_test(); + test_dense_matrix(); + test_swap_operations(); + test_permutations(); + test_pivot_like_swaps_and_pivot(); +#endif + tst1(); + std::cout << "done with LP tests\n"; + return finalize(0); // has_violations() ? 1 : 0); +} +} +void tst_lp(char ** argv, int argc, int& i) { + lean::test_lp_local(argc - 2, argv + 2); +} diff --git a/src/test/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/test/smt_reader.h b/src/test/smt_reader.h new file mode 100644 index 000000000..38e3f4157 --- /dev/null +++ b/src/test/smt_reader.h @@ -0,0 +1,396 @@ +/* + Copyright (c) 2013 Microsoft Corporation. All rights reserved. + Released under Apache 2.0 license as described in the file LICENSE. + + Author: Lev Nachmanson +*/ + +#pragma once + +// reads an MPS file reperesenting a Mixed Integer Program +#include +#include +#include +#include "util/lp/lp_primal_simplex.h" +#include "util/lp/lp_dual_simplex.h" +#include "util/lp/lar_solver.h" +#include +#include +#include +#include +#include "util/lp/mps_reader.h" +#include "util/lp/ul_pair.h" +#include "util/lp/lar_constraints.h" +#include +#include +namespace lean { + + template + T from_string(const std::string& str) { + std::istringstream ss(str); + T ret; + ss >> ret; + return ret; + } + + class smt_reader { + public: + struct lisp_elem { + std::string m_head; + std::vector m_elems; + void print() { + if (m_elems.size()) { + std::cout << '('; + std::cout << m_head << ' '; + for (auto & el : m_elems) + el.print(); + + std::cout << ')'; + } else { + std::cout << " " << m_head; + } + } + unsigned size() const { return static_cast(m_elems.size()); } + bool is_simple() const { return size() == 0; } + }; + struct formula_constraint { + lconstraint_kind m_kind; + std::vector> m_coeffs; + mpq m_right_side; + void add_pair(mpq c, std::string name) { + m_coeffs.push_back(make_pair(c, name)); + } + formula_constraint() : m_right_side(numeric_traits::zero()) {} + }; + + lisp_elem m_formula_lisp_elem; + + std::unordered_map m_name_to_var_index; + std::vector m_constraints; + bool m_is_OK; + unsigned m_line_number; + std::string m_file_name; + std::ifstream m_file_stream; + std::string m_line; + smt_reader(std::string file_name): + m_is_OK(true), + m_line_number(0), + m_file_name(file_name), + m_file_stream(file_name) { + } + + void set_error() { + std::cout << "setting error" << std::endl; + m_is_OK = false; + } + + bool is_ok() { + return m_is_OK; + } + + bool prefix(const char * pr) { + return m_line.find(pr) == 0; + } + + int first_separator() { + unsigned blank_pos = static_cast(m_line.find(' ')); + unsigned br_pos = static_cast(m_line.find('(')); + unsigned reverse_br_pos = static_cast(m_line.find(')')); + return std::min(blank_pos, std::min(br_pos, reverse_br_pos)); + } + + void fill_lisp_elem(lisp_elem & lm) { + if (m_line[0] == '(') + fill_nested_elem(lm); + else + fill_simple_elem(lm); + } + + void fill_simple_elem(lisp_elem & lm) { + int separator = first_separator(); + lean_assert(-1 != separator && separator != 0); + lm.m_head = m_line.substr(0, separator); + m_line = m_line.substr(separator); + } + + void fill_nested_elem(lisp_elem & lm) { + lean_assert(m_line[0] == '('); + m_line = m_line.substr(1); + int separator = first_separator(); + lm.m_head = m_line.substr(0, separator); + m_line = m_line.substr(lm.m_head.size()); + eat_blanks(); + while (m_line.size()) { + if (m_line[0] == '(') { + lisp_elem el; + fill_nested_elem(el); + lm.m_elems.push_back(el); + } else { + if (m_line[0] == ')') { + m_line = m_line.substr(1); + break; + } + lisp_elem el; + fill_simple_elem(el); + lm.m_elems.push_back(el); + } + eat_blanks(); + } + } + + void eat_blanks() { + while (m_line.size()) { + if (m_line[0] == ' ') + m_line = m_line.substr(1); + else + break; + } + } + + void fill_formula_elem() { + fill_lisp_elem(m_formula_lisp_elem); + } + + void parse_line() { + if (m_line.find(":formula") == 0) { + int first_br = static_cast(m_line.find('(')); + if (first_br == -1) { + std::cout << "empty formula" << std::endl; + return; + } + m_line = m_line.substr(first_br); + fill_formula_elem(); + } + } + + void set_constraint_kind(formula_constraint & c, lisp_elem & el) { + if (el.m_head == "=") { + c.m_kind = EQ; + } else if (el.m_head == ">=") { + c.m_kind = GE; + } else if (el.m_head == "<=") { + c.m_kind = LE; + } else if (el.m_head == ">") { + c.m_kind = GT; + } else if (el.m_head == "<") { + c.m_kind = LT; + } else { + std::cout << "kind " << el.m_head << " is not supported " << std::endl; + set_error(); + } + } + + void adjust_rigth_side(formula_constraint & /* c*/, lisp_elem & /*el*/) { + // lean_assert(el.m_head == "0"); // do nothing for the time being + } + + void set_constraint_coeffs(formula_constraint & c, lisp_elem & el) { + lean_assert(el.m_elems.size() == 2); + set_constraint_coeffs_on_coeff_element(c, el.m_elems[0]); + adjust_rigth_side(c, el.m_elems[1]); + } + + + bool is_integer(std::string & s) { + if (s.size() == 0) return false; + return atoi(s.c_str()) != 0 || isdigit(s.c_str()[0]); + } + + void add_complex_sum_elem(formula_constraint & c, lisp_elem & el) { + if (el.m_head == "*") { + add_mult_elem(c, el.m_elems); + } else if (el.m_head == "~") { + lisp_elem & minel = el.m_elems[0]; + lean_assert(minel.is_simple()); + c.m_right_side += mpq(str_to_int(minel.m_head)); + } else { + std::cout << "unexpected input " << el.m_head << std::endl; + set_error(); + return; + } + } + + std::string get_name(lisp_elem & name) { + lean_assert(name.is_simple()); + lean_assert(!is_integer(name.m_head)); + return name.m_head; + } + + + void add_mult_elem(formula_constraint & c, std::vector & els) { + lean_assert(els.size() == 2); + mpq coeff = get_coeff(els[0]); + std::string col_name = get_name(els[1]); + c.add_pair(coeff, col_name); + } + + mpq get_coeff(lisp_elem & le) { + if (le.is_simple()) { + return mpq(str_to_int(le.m_head)); + } else { + lean_assert(le.m_head == "~"); + lean_assert(le.size() == 1); + lisp_elem & el = le.m_elems[0]; + lean_assert(el.is_simple()); + return -mpq(str_to_int(el.m_head)); + } + } + + int str_to_int(std::string & s) { + lean_assert(is_integer(s)); + return atoi(s.c_str()); + } + + void add_sum_elem(formula_constraint & c, lisp_elem & el) { + if (el.size()) { + add_complex_sum_elem(c, el); + } else { + lean_assert(is_integer(el.m_head)); + int v = atoi(el.m_head.c_str()); + mpq vr(v); + c.m_right_side -= vr; + } + } + + void add_sum(formula_constraint & c, std::vector & sum_els) { + for (auto & el : sum_els) + add_sum_elem(c, el); + } + + void set_constraint_coeffs_on_coeff_element(formula_constraint & c, lisp_elem & el) { + if (el.m_head == "*") { + add_mult_elem(c, el.m_elems); + } else if (el.m_head == "+") { + add_sum(c, el.m_elems); + } else { + lean_assert(false); // unexpected input + } + } + + void create_constraint(lisp_elem & el) { + formula_constraint c; + set_constraint_kind(c, el); + set_constraint_coeffs(c, el); + m_constraints.push_back(c); + } + + void fill_constraints() { + if (m_formula_lisp_elem.m_head != "and") { + std::cout << "unexpected top element " << m_formula_lisp_elem.m_head << std::endl; + set_error(); + return; + } + for (auto & el : m_formula_lisp_elem.m_elems) + create_constraint(el); + } + + void read() { + if (!m_file_stream.is_open()){ + std::cout << "cannot open file " << m_file_name << std::endl; + set_error(); + return; + } + while (m_is_OK && getline(m_file_stream, m_line)) { + parse_line(); + m_line_number++; + } + + m_file_stream.close(); + fill_constraints(); + } + + /* + void fill_lar_solver_on_row(row * row, lar_solver *solver) { + if (row->m_name != m_cost_row_name) { + lar_constraint c(get_lar_relation_from_row(row->m_type), row->m_right_side); + for (auto s : row->m_row_columns) { + var_index i = solver->add_var(s.first); + c.add_variable_to_constraint(i, s.second); + } + solver->add_constraint(&c); + } else { + // ignore the cost row + } + } + + + void fill_lar_solver_on_rows(lar_solver * solver) { + for (auto row_it : m_rows) { + fill_lar_solver_on_row(row_it.second, solver); + } + } + + void create_low_constraint_for_var(column* col, bound * b, lar_solver *solver) { + lar_constraint c(GE, b->m_low); + var_index i = solver->add_var(col->m_name); + c.add_variable_to_constraint(i, numeric_traits::one()); + solver->add_constraint(&c); + } + + void create_upper_constraint_for_var(column* col, bound * b, lar_solver *solver) { + lar_constraint c(LE, b->m_upper); + var_index i = solver->add_var(col->m_name); + c.add_variable_to_constraint(i, numeric_traits::one()); + solver->add_constraint(&c); + } + + void create_equality_contraint_for_var(column* col, bound * b, lar_solver *solver) { + lar_constraint c(EQ, b->m_fixed_value); + var_index i = solver->add_var(col->m_name); + c.add_variable_to_constraint(i, numeric_traits::one()); + solver->add_constraint(&c); + } + + void fill_lar_solver_on_columns(lar_solver * solver) { + for (auto s : m_columns) { + mps_reader::column * col = s.second; + solver->add_var(col->m_name); + auto b = col->m_bound; + if (b == nullptr) return; + + if (b->m_free) continue; + + if (b->m_low_is_set) { + create_low_constraint_for_var(col, b, solver); + } + if (b->m_upper_is_set) { + create_upper_constraint_for_var(col, b, solver); + } + if (b->m_value_is_fixed) { + create_equality_contraint_for_var(col, b, solver); + } + } + } + */ + + unsigned register_name(std::string s) { + auto it = m_name_to_var_index.find(s); + if (it!= m_name_to_var_index.end()) + return it->second; + + unsigned ret = static_cast(m_name_to_var_index.size()); + m_name_to_var_index[s] = ret; + return ret; + } + + void add_constraint_to_solver(lar_solver * solver, formula_constraint & fc) { + vector> ls; + for (auto & it : fc.m_coeffs) { + ls.push_back(std::make_pair(it.first, solver->add_var(register_name(it.second)))); + } + solver->add_constraint(ls, fc.m_kind, fc.m_right_side); + } + + void fill_lar_solver(lar_solver * solver) { + for (formula_constraint & fc : m_constraints) + add_constraint_to_solver(solver, fc); + } + + + lar_solver * create_lar_solver() { + lar_solver * ls = new lar_solver(); + fill_lar_solver(ls); + return ls; + } + }; +} diff --git a/src/test/test_file_reader.h b/src/test/test_file_reader.h new file mode 100644 index 000000000..c7a9e3b8b --- /dev/null +++ b/src/test/test_file_reader.h @@ -0,0 +1,73 @@ +/* +Copyright (c) 2013 Microsoft Corporation. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. + +Author: Lev Nachmanson +*/ +#pragma once + +// reads a text file +#include +#include +#include +#include +#include +#include "util/lp/lp_utils.h" +#include "util/lp/lp_solver.h" + +namespace lean { + +template +struct test_result { + lp_status m_status; + T m_cost; + std::unordered_map column_values; +}; + +template +class test_file_reader { + struct raw_blob { + std::vector m_unparsed_strings; + std::vector m_blobs; + }; + + struct test_file_blob { + std::string m_name; + std::string m_content; + std::unordered_map m_table; + std::unordered_map m_blobs; + + test_result * get_test_result() { + test_result * tr = new test_result(); + throw "not impl"; + return tr; + } + }; + std::ifstream m_file_stream; +public: + // constructor + test_file_reader(std::string file_name) : m_file_stream(file_name) { + if (!m_file_stream.is_open()) { + std::cout << "cannot open file " << "\'" << file_name << "\'" << std::endl; + } + } + + raw_blob scan_to_row_blob() { + } + + test_file_blob scan_row_blob_to_test_file_blob(raw_blob /* rblob */) { + } + + test_result * get_test_result() { + if (!m_file_stream.is_open()) { + return nullptr; + } + + raw_blob rblob = scan_to_row_blob(); + + test_file_blob tblob = scan_row_blob_to_test_file_blob(rblob); + + return tblob.get_test_result(); + } +}; +} diff --git a/src/util/hash.cpp b/src/util/hash.cpp index 42ba3f4da..a54f993af 100644 --- a/src/util/hash.cpp +++ b/src/util/hash.cpp @@ -83,8 +83,8 @@ unsigned string_hash(const char * str, unsigned length, unsigned init_value) { Z3_fallthrough; case 1 : a+=str[0]; - Z3_fallthrough; /* case 0: nothing left to add */ + break; } mix(a,b,c); /*-------------------------------------------- report the result */ diff --git a/src/util/lp/binary_heap_priority_queue.h b/src/util/lp/binary_heap_priority_queue.h new file mode 100644 index 000000000..a6206948c --- /dev/null +++ b/src/util/lp/binary_heap_priority_queue.h @@ -0,0 +1,70 @@ + +/* + 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; + // 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), m_heap_size(0) {} // 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..440b45b02 --- /dev/null +++ b/src/util/lp/binary_heap_priority_queue.hpp @@ -0,0 +1,194 @@ +/* + 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), + m_heap_size(0) +{ } + + +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..26cfd5532 --- /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; + svector m_pairs; // inverse to index + svector 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..508692e5a --- /dev/null +++ b/src/util/lp/bound_analyzer_on_row.h @@ -0,0 +1,335 @@ +/* + 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; + bound_propagator & m_bp; + unsigned m_row_or_term_index; + int m_column_of_u; // 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; // index of an unlimited from below monoid + impq m_rs; + +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_bp(bp), + m_row_or_term_index(row_or_term_index), + m_column_of_u(-1), + m_column_of_l(-1), + m_rs(rs) + {} + + + 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..56e75a1fb --- /dev/null +++ b/src/util/lp/column_info.h @@ -0,0 +1,250 @@ +/* + 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; + bool m_low_bound_is_strict; + bool m_upper_bound_is_set; + bool m_upper_bound_is_strict; + T m_low_bound; + T m_upper_bound; + T m_fixed_value; + bool m_is_fixed; + T m_cost; + unsigned m_column_index; +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(): + m_low_bound_is_set(false), + m_low_bound_is_strict(false), + m_upper_bound_is_set (false), + m_upper_bound_is_strict (false), + m_is_fixed(false), + m_cost(numeric_traits::zero()), + m_column_index(static_cast(-1)) + {} + + column_info(unsigned column_index) : + m_low_bound_is_set(false), + m_low_bound_is_strict(false), + m_upper_bound_is_set (false), + m_upper_bound_is_strict (false), + m_is_fixed(false), + m_cost(numeric_traits::zero()), + 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/conversion_helper.h b/src/util/lp/conversion_helper.h new file mode 100644 index 000000000..bff2ad563 --- /dev/null +++ b/src/util/lp/conversion_helper.h @@ -0,0 +1,43 @@ +/* + Copyright (c) 2013 Microsoft Corporation. All rights reserved. + + Author: Lev Nachmanson +*/ +#pragma once +namespace lean { +template +struct conversion_helper { + static V get_low_bound(const column_info & ci) { + return V(ci.get_low_bound(), ci.low_bound_is_strict()? 1 : 0); + } + + static V get_upper_bound(const column_info & ci) { + return V(ci.get_upper_bound(), ci.upper_bound_is_strict()? -1 : 0); + } +}; + +template<> +struct conversion_helper { + static double get_upper_bound(const column_info & ci) { + if (!ci.upper_bound_is_strict()) + return ci.get_upper_bound().get_double(); + double eps = 0.00001; + if (!ci.low_bound_is_set()) + return ci.get_upper_bound().get_double() - eps; + eps = std::min((ci.get_upper_bound() - ci.get_low_bound()).get_double() / 1000, eps); + return ci.get_upper_bound().get_double() - eps; + } + + static double get_low_bound(const column_info & ci) { + if (!ci.low_bound_is_strict()) + return ci.get_low_bound().get_double(); + double eps = 0.00001; + if (!ci.upper_bound_is_set()) + return ci.get_low_bound().get_double() + eps; + eps = std::min((ci.get_upper_bound() - ci.get_low_bound()).get_double() / 1000, eps); + return ci.get_low_bound().get_double() + eps; + } + +}; + +} 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..2a3a14b31 --- /dev/null +++ b/src/util/lp/core_solver_pretty_printer.h @@ -0,0 +1,117 @@ +/* + 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; + 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; + std::string m_upp_bounds_title; + std::string m_exact_norm_title; + std::string m_approx_norm_title; + + + unsigned ncols() { return m_core_solver.m_A.column_count(); } + unsigned nrows() { return m_core_solver.m_A.row_count(); } + unsigned m_artificial_start; + 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..786b8b3a1 --- /dev/null +++ b/src/util/lp/core_solver_pretty_printer.hpp @@ -0,0 +1,383 @@ +/* + 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_low_bounds_title = "low"; + m_upp_bounds_title = "upp"; + m_exact_norm_title = "exact cn"; + m_approx_norm_title = "approx cn"; + m_artificial_start = std::numeric_limits::max(); + + 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/init_lar_solver.h b/src/util/lp/init_lar_solver.h new file mode 100644 index 000000000..3fc29f25b --- /dev/null +++ b/src/util/lp/init_lar_solver.h @@ -0,0 +1,576 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ + +// here we are inside lean::lar_solver class + +bool strategy_is_undecided() const { + return m_settings.simplex_strategy() == simplex_strategy_enum::undecided; +} + +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 way 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))); + add_non_basic_var_to_core_fields(ext_j); + lean_assert(sizes_are_correct()); + return i; +} + +void register_new_ext_var_index(unsigned ext_v) { + lean_assert(!contains(m_ext_vars_to_columns, ext_v)); + unsigned j = static_cast(m_ext_vars_to_columns.size()); + m_ext_vars_to_columns[ext_v] = j; + lean_assert(m_columns_to_ext_vars_or_term_indices.size() == j); + m_columns_to_ext_vars_or_term_indices.push_back(ext_v); +} + +void add_non_basic_var_to_core_fields(unsigned ext_j) { + register_new_ext_var_index(ext_j); + 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 (use_lu()) + add_new_var_to_core_fields_for_doubles(false); +} + +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_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); + } +} + + +var_index add_term_undecided(const vector> & coeffs, + const mpq &m_v) { + m_terms.push_back(new lar_term(coeffs, m_v)); + m_orig_terms.push_back(new lar_term(coeffs, m_v)); + return m_terms_start_index + m_terms.size() - 1; +} + +// terms +var_index add_term(const vector> & coeffs, + const mpq &m_v) { + if (strategy_is_undecided()) + return add_term_undecided(coeffs, m_v); + + 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; + var_index ret = m_terms_start_index + adjusted_term_index; + if (use_tableau() && !coeffs.empty()) { + add_row_for_term(m_orig_terms.back(), ret); + 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()); + return ret; +} + +void add_row_for_term(const lar_term * term, unsigned term_ext_index) { + lean_assert(sizes_are_correct()); + add_row_from_term_no_constraint(term, term_ext_index); + lean_assert(sizes_are_correct()); +} + +void add_row_from_term_no_constraint(const lar_term * term, unsigned term_ext_index) { + register_new_ext_var_index(term_ext_index); + // 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 (use_lu()) + fill_last_row_of_A_d(A_d(), term); +} + +void add_basic_var_to_core_fields() { + bool use_lu = m_mpq_lar_core_solver.need_to_presolve_with_double_solver(); + lean_assert(!use_lu || 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 (use_lu) + add_new_var_to_core_fields_for_doubles(true); +} + +constraint_index add_var_bound(var_index j, lconstraint_kind kind, const mpq & right_side) { + 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 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 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_constraint_from_term_and_create_new_column_row(unsigned term_j, const lar_term* term, + lconstraint_kind kind, const mpq & right_side) { + + add_row_from_term_no_constraint(term, term_j); + unsigned j = A_r().column_count() - 1; + 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 decide_on_strategy_and_adjust_initial_state() { + lean_assert(strategy_is_undecided()); + if (m_vars_to_ul_pairs.size() > m_settings.column_number_threshold_for_using_lu_in_lar_solver) { + m_settings.simplex_strategy() = simplex_strategy_enum::lu; + } else { + m_settings.simplex_strategy() = simplex_strategy_enum::tableau_rows; // todo: when to switch to tableau_costs? + } + adjust_initial_state(); +} + +void adjust_initial_state() { + switch (m_settings.simplex_strategy()) { + case simplex_strategy_enum::lu: + adjust_initial_state_for_lu(); + break; + case simplex_strategy_enum::tableau_rows: + adjust_initial_state_for_tableau_rows(); + break; + case simplex_strategy_enum::tableau_costs: + lean_assert(false); // not implemented + case simplex_strategy_enum::undecided: + adjust_initial_state_for_tableau_rows(); + break; + } +} + +void adjust_initial_state_for_lu() { + copy_from_mpq_matrix(A_d()); + unsigned n = A_d().column_count(); + m_mpq_lar_core_solver.m_d_x.resize(n); + m_mpq_lar_core_solver.m_d_low_bounds.resize(n); + m_mpq_lar_core_solver.m_d_upper_bounds.resize(n); + m_mpq_lar_core_solver.m_d_heading = m_mpq_lar_core_solver.m_r_heading; + m_mpq_lar_core_solver.m_d_basis = m_mpq_lar_core_solver.m_r_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 adjust_initial_state_for_tableau_rows() { + for (unsigned j = 0; j < m_terms.size(); j++) { + if (contains(m_ext_vars_to_columns, j + m_terms_start_index)) + continue; + add_row_from_term_no_constraint(m_terms[j], j + m_terms_start_index); + } +} + +// 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 ); +} + +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(); + + } +} + 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..215514b39 --- /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; // 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..532b62617 --- /dev/null +++ b/src/util/lp/iterator_on_indexed_vector.h @@ -0,0 +1,38 @@ +/* + 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; + iterator_on_indexed_vector(const indexed_vector & v) : + m_v(v), + m_offset(0) + {} + 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..1a9381a70 --- /dev/null +++ b/src/util/lp/iterator_on_pivot_row.h @@ -0,0 +1,44 @@ +/* + 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; + 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_basis_returned(false), + 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..96a1a8cf3 --- /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; // offset + iterator_on_row(const vector> & row) : m_row(row), m_i(0) + {} + 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..3dd217103 --- /dev/null +++ b/src/util/lp/iterator_on_term_with_basis_var.h @@ -0,0 +1,57 @@ +/* + 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 { + const lar_term & m_term; + std::unordered_map::const_iterator m_i; // the offset in term coeffs + bool m_term_j_returned; + 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_term(t), + m_i(t.m_coeffs.begin()), + m_term_j_returned(false), + 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..71d69c3a4 --- /dev/null +++ b/src/util/lp/lar_core_solver.h @@ -0,0 +1,801 @@ +/* + 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; // 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) { + // 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(!settings().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); + m_stacked_simplex_strategy.pop(k); + settings().simplex_strategy() = m_stacked_simplex_strategy; + 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().simplex_strategy() == simplex_strategy_enum::lu; + } + + 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 (settings().random_next() % 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 & initial_delta) const{ + mpq delta = initial_delta; + 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..a6dd7e3e0 --- /dev/null +++ b/src/util/lp/lar_core_solver.hpp @@ -0,0 +1,293 @@ +/* + 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_infeasible_sum_sign(0), + 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..b74515566 --- /dev/null +++ b/src/util/lp/lar_solver.h @@ -0,0 +1,1549 @@ +/* + 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" +#include "util/lp/conversion_helper.h" +namespace lean { + +class lar_solver : public column_namer { + //////////////////// fields ////////////////////////// + lp_settings m_settings; + stacked_value m_status; + stacked_value m_simplex_strategy; + 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; + // 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; // such can be found at the initialization step + stacked_value m_term_count; + vector m_terms; + vector m_orig_terms; + const var_index m_terms_start_index; + indexed_vector m_column_buffer; +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_status(OPTIMAL), + m_infeasible_column_index(-1), + m_terms_start_index(1000000), + 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; + } + +#include "util/lp/init_lar_solver.h" + + 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 use_lu() const { return m_settings.simplex_strategy() == simplex_strategy_enum::lu; } + + bool sizes_are_correct() const { + lean_assert(strategy_is_undecided() || !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; + } + + + 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() { + if (strategy_is_undecided()) + decide_on_strategy_and_adjust_initial_state(); + + 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() { + m_simplex_strategy = m_settings.simplex_strategy(); + m_simplex_strategy.push(); + 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(); + } + + 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) { + 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(m_settings.simplex_strategy() == simplex_strategy_enum::undecided || + (!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); + m_simplex_strategy.pop(k); + m_settings.simplex_strategy() = m_simplex_strategy; + 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) { + if (settings().simplex_strategy() == simplex_strategy_enum::undecided) + decide_on_strategy_and_adjust_initial_state(); + + 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().simplex_strategy()) { + case simplex_strategy_enum::tableau_rows: + prepare_costs_for_r_solver(term); + settings().simplex_strategy() = simplex_strategy_enum::tableau_costs; + { + bool ret = maximize_term_on_tableau(term, term_max); + settings().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::lu: + 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); + } + + + + 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 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 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)); + } + + 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) { + matr.m_rows.resize(A_r().row_count()); + matr.m_columns.resize(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; + } + + 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 { + mpq delta = mpq(1, 2); // start from 0.5 to have less clashes + lean_assert(m_status == OPTIMAL); + unsigned i; + do { + + // different pairs have to produce different singleton values + std::unordered_set set_of_different_pairs; + std::unordered_set set_of_different_singles; + delta = m_mpq_lar_core_solver.find_delta_for_strict_bounds(delta); + for (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]; + set_of_different_pairs.insert(rp); + mpq x = rp.x + delta * rp.y; + set_of_different_singles.insert(x); + if (set_of_different_pairs.size() + != set_of_different_singles.size()) { + delta /= mpq(2); + break; + } + + variable_values[i] = x; + } + } while (i != m_mpq_lar_core_solver.m_r_x.size()); + } + + + 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..634accfd4 --- /dev/null +++ b/src/util/lp/linear_combination_iterator.h @@ -0,0 +1,50 @@ +/* + 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; + 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), + m_offset(0) + {} + 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..a12b7b5d2 --- /dev/null +++ b/src/util/lp/lp_core_solver_base.h @@ -0,0 +1,690 @@ +/* + 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; + unsigned m_iters_with_no_cost_growing; + 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; + + + 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; + 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 + 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; + 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; + int_set* m_pivoted_rows; + bool m_look_for_feasible_solution_only; + 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 (m_settings.random_next() % 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; + } + unsigned & iters_with_no_cost_growing() { + return m_iters_with_no_cost_growing; + } + + const unsigned & iters_with_no_cost_growing() const { + return m_iters_with_no_cost_growing; + } +}; +} 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..a0dba9de7 --- /dev/null +++ b/src/util/lp/lp_core_solver_base.hpp @@ -0,0 +1,1019 @@ +/* + 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_total_iterations(0), + m_iters_with_no_cost_growing(0), + m_status(FEASIBLE), + m_inf_set(A.column_count()), + m_using_infeas_costs(false), + 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_basis_sort_counter(0), + m_steepest_edge_coefficients(A.column_count()), + m_tracing_basis_changes(false), + m_pivoted_rows(nullptr), + m_look_for_feasible_solution_only(false) { + 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() { + allocate_basis_heading(); + if (m_settings.use_lu()) + 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); + if(A_mult_x_is_off()) { + m_status = FLOATING_POINT_ERROR; + m_iters_with_no_cost_growing++; + return false; + } + + 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(); + m_status = FLOATING_POINT_ERROR; + return false; + } + 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..6565331b3 --- /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->iters_with_no_cost_growing()++; + return false; + } + } + + if (!problem_is_dual_feasible()) { + // todo : shift the costs!!!! + revert_to_previous_basis(); + this->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 this->m_settings.random_next() % 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 = this->m_settings.random_next() % 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->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->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..4dff2a4f1 --- /dev/null +++ b/src/util/lp/lp_dual_simplex.h @@ -0,0 +1,80 @@ +/* + 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; + 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; + } + } + + lp_dual_simplex() : m_core_solver(nullptr) {} + + + void decide_on_status_after_stage1(); + + void fix_logical_for_stage2(unsigned j); + + void fix_structural_for_stage2(unsigned j); + + void unmark_boxed_and_fixed_columns_and_fix_structural_costs(); + + void restore_right_sides(); + + void solve_for_stage2(); + + void fill_x_with_zeros(); + + void stage1(); + + void stage2(); + + void fill_first_stage_solver_fields(); + + column_type get_column_type(unsigned j); + + void fill_costs_bounds_types_and_can_enter_basis_for_the_first_stage_solver_structural_column(unsigned j); + + void fill_costs_bounds_types_and_can_enter_basis_for_the_first_stage_solver_logical_column(unsigned j); + + void fill_costs_and_bounds_and_column_types_for_the_first_stage_solver(); + + void set_type_for_logical(unsigned j, column_type col_type) { + this->m_column_types_of_logicals[j - this->number_of_core_structurals()] = col_type; + } + + void fill_first_stage_solver_fields_for_row_slack_and_artificial(unsigned row, + unsigned & slack_var, + unsigned & artificial); + + void augment_matrix_A_and_fill_x_and_allocate_some_fields(); + + + + void copy_m_b_aside_and_set_it_to_zeros(); + + void find_maximal_solution(); + + virtual T get_column_value(unsigned column) const { + return this->get_column_value_with_core_solver(column, m_core_solver); + } + + T get_current_cost() const; +}; +} diff --git a/src/util/lp/lp_dual_simplex.hpp b/src/util/lp/lp_dual_simplex.hpp new file mode 100644 index 000000000..5047e117f --- /dev/null +++ b/src/util/lp/lp_dual_simplex.hpp @@ -0,0 +1,362 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include "util/lp/lp_dual_simplex.h" +namespace lean{ + +template void lp_dual_simplex::decide_on_status_after_stage1() { + switch (m_core_solver->get_status()) { + case OPTIMAL: + if (this->m_settings.abs_val_is_smaller_than_artificial_tolerance(m_core_solver->get_cost())) { + this->m_status = FEASIBLE; + } else { + this->m_status = UNBOUNDED; + } + break; + case DUAL_UNBOUNDED: + lean_unreachable(); + case ITERATIONS_EXHAUSTED: + this->m_status = ITERATIONS_EXHAUSTED; + break; + case TIME_EXHAUSTED: + this->m_status = TIME_EXHAUSTED; + break; + case FLOATING_POINT_ERROR: + this->m_status = FLOATING_POINT_ERROR; + break; + default: + lean_unreachable(); + } +} + +template void lp_dual_simplex::fix_logical_for_stage2(unsigned j) { + lean_assert(j >= this->number_of_core_structurals()); + switch (m_column_types_of_logicals[j - this->number_of_core_structurals()]) { + case column_type::low_bound: + m_low_bounds[j] = numeric_traits::zero(); + m_column_types_of_core_solver[j] = column_type::low_bound; + m_can_enter_basis[j] = true; + break; + case column_type::fixed: + this->m_upper_bounds[j] = m_low_bounds[j] = numeric_traits::zero(); + m_column_types_of_core_solver[j] = column_type::fixed; + m_can_enter_basis[j] = false; + break; + default: + lean_unreachable(); + } +} + +template void lp_dual_simplex::fix_structural_for_stage2(unsigned j) { + column_info * ci = this->m_map_from_var_index_to_column_info[this->m_core_solver_columns_to_external_columns[j]]; + switch (ci->get_column_type()) { + case column_type::low_bound: + m_low_bounds[j] = numeric_traits::zero(); + m_column_types_of_core_solver[j] = column_type::low_bound; + m_can_enter_basis[j] = true; + break; + case column_type::fixed: + case column_type::upper_bound: + lean_unreachable(); + case column_type::boxed: + this->m_upper_bounds[j] = ci->get_adjusted_upper_bound() / this->m_column_scale[j]; + m_low_bounds[j] = numeric_traits::zero(); + m_column_types_of_core_solver[j] = column_type::boxed; + m_can_enter_basis[j] = true; + break; + case column_type::free_column: + m_can_enter_basis[j] = true; + m_column_types_of_core_solver[j] = column_type::free_column; + break; + default: + lean_unreachable(); + } + // T cost_was = this->m_costs[j]; + this->set_scaled_cost(j); +} + +template void lp_dual_simplex::unmark_boxed_and_fixed_columns_and_fix_structural_costs() { + unsigned j = this->m_A->column_count(); + while (j-- > this->number_of_core_structurals()) { + fix_logical_for_stage2(j); + } + j = this->number_of_core_structurals(); + while (j--) { + fix_structural_for_stage2(j); + } +} + +template void lp_dual_simplex::restore_right_sides() { + unsigned i = this->m_A->row_count(); + while (i--) { + this->m_b[i] = m_b_copy[i]; + } +} + +template void lp_dual_simplex::solve_for_stage2() { + m_core_solver->restore_non_basis(); + m_core_solver->solve_yB(m_core_solver->m_y); + m_core_solver->fill_reduced_costs_from_m_y_by_rows(); + m_core_solver->start_with_initial_basis_and_make_it_dual_feasible(); + m_core_solver->set_status(FEASIBLE); + m_core_solver->solve(); + switch (m_core_solver->get_status()) { + case OPTIMAL: + this->m_status = OPTIMAL; + break; + case DUAL_UNBOUNDED: + this->m_status = INFEASIBLE; + break; + case TIME_EXHAUSTED: + this->m_status = TIME_EXHAUSTED; + break; + case FLOATING_POINT_ERROR: + this->m_status = FLOATING_POINT_ERROR; + break; + default: + lean_unreachable(); + } + this->m_second_stage_iterations = m_core_solver->total_iterations(); + this->m_total_iterations = (this->m_first_stage_iterations + this->m_second_stage_iterations); +} + +template void lp_dual_simplex::fill_x_with_zeros() { + unsigned j = this->m_A->column_count(); + while (j--) { + this->m_x[j] = numeric_traits::zero(); + } +} + +template void lp_dual_simplex::stage1() { + lean_assert(m_core_solver == nullptr); + this->m_x.resize(this->m_A->column_count(), numeric_traits::zero()); + if (this->m_settings.get_message_ostream() != nullptr) + this->print_statistics_on_A(*this->m_settings.get_message_ostream()); + m_core_solver = new lp_dual_core_solver( + *this->m_A, + m_can_enter_basis, + this->m_b, // the right side vector + this->m_x, + this->m_basis, + this->m_nbasis, + this->m_heading, + this->m_costs, + this->m_column_types_of_core_solver, + this->m_low_bounds, + this->m_upper_bounds, + this->m_settings, + *this); + m_core_solver->fill_reduced_costs_from_m_y_by_rows(); + m_core_solver->start_with_initial_basis_and_make_it_dual_feasible(); + if (this->m_settings.abs_val_is_smaller_than_artificial_tolerance(m_core_solver->get_cost())) { + // skipping stage 1 + m_core_solver->set_status(OPTIMAL); + m_core_solver->set_total_iterations(0); + } else { + m_core_solver->solve(); + } + decide_on_status_after_stage1(); + this->m_first_stage_iterations = m_core_solver->total_iterations(); +} + +template void lp_dual_simplex::stage2() { + unmark_boxed_and_fixed_columns_and_fix_structural_costs(); + restore_right_sides(); + solve_for_stage2(); +} + +template void lp_dual_simplex::fill_first_stage_solver_fields() { + unsigned slack_var = this->number_of_core_structurals(); + unsigned artificial = this->number_of_core_structurals() + this->m_slacks; + + for (unsigned row = 0; row < this->row_count(); row++) { + fill_first_stage_solver_fields_for_row_slack_and_artificial(row, slack_var, artificial); + } + fill_costs_and_bounds_and_column_types_for_the_first_stage_solver(); +} + +template column_type lp_dual_simplex::get_column_type(unsigned j) { + lean_assert(j < this->m_A->column_count()); + if (j >= this->number_of_core_structurals()) { + return m_column_types_of_logicals[j - this->number_of_core_structurals()]; + } + return this->m_map_from_var_index_to_column_info[this->m_core_solver_columns_to_external_columns[j]]->get_column_type(); +} + +template void lp_dual_simplex::fill_costs_bounds_types_and_can_enter_basis_for_the_first_stage_solver_structural_column(unsigned j) { + // see 4.7 in the dissertation of Achim Koberstein + lean_assert(this->m_core_solver_columns_to_external_columns.find(j) != + this->m_core_solver_columns_to_external_columns.end()); + + T free_bound = T(1e4); // see 4.8 + unsigned jj = this->m_core_solver_columns_to_external_columns[j]; + lean_assert(this->m_map_from_var_index_to_column_info.find(jj) != this->m_map_from_var_index_to_column_info.end()); + column_info * ci = this->m_map_from_var_index_to_column_info[jj]; + switch (ci->get_column_type()) { + case column_type::upper_bound: { + std::stringstream s; + s << "unexpected bound type " << j << " " + << column_type_to_string(get_column_type(j)); + throw_exception(s.str()); + break; + } + case column_type::low_bound: { + m_can_enter_basis[j] = true; + this->set_scaled_cost(j); + this->m_low_bounds[j] = numeric_traits::zero(); + this->m_upper_bounds[j] =numeric_traits::one(); + break; + } + case column_type::free_column: { + m_can_enter_basis[j] = true; + this->set_scaled_cost(j); + this->m_upper_bounds[j] = free_bound; + this->m_low_bounds[j] = -free_bound; + break; + } + case column_type::boxed: + m_can_enter_basis[j] = false; + this->m_costs[j] = numeric_traits::zero(); + this->m_upper_bounds[j] = this->m_low_bounds[j] = numeric_traits::zero(); // is it needed? + break; + default: + lean_unreachable(); + } + m_column_types_of_core_solver[j] = column_type::boxed; +} + +template void lp_dual_simplex::fill_costs_bounds_types_and_can_enter_basis_for_the_first_stage_solver_logical_column(unsigned j) { + this->m_costs[j] = 0; + lean_assert(get_column_type(j) != column_type::upper_bound); + if ((m_can_enter_basis[j] = (get_column_type(j) == column_type::low_bound))) { + m_column_types_of_core_solver[j] = column_type::boxed; + this->m_low_bounds[j] = numeric_traits::zero(); + this->m_upper_bounds[j] = numeric_traits::one(); + } else { + m_column_types_of_core_solver[j] = column_type::fixed; + this->m_low_bounds[j] = numeric_traits::zero(); + this->m_upper_bounds[j] = numeric_traits::zero(); + } +} + +template void lp_dual_simplex::fill_costs_and_bounds_and_column_types_for_the_first_stage_solver() { + unsigned j = this->m_A->column_count(); + while (j-- > this->number_of_core_structurals()) { // go over logicals here + fill_costs_bounds_types_and_can_enter_basis_for_the_first_stage_solver_logical_column(j); + } + j = this->number_of_core_structurals(); + while (j--) { + fill_costs_bounds_types_and_can_enter_basis_for_the_first_stage_solver_structural_column(j); + } +} + +template void lp_dual_simplex::fill_first_stage_solver_fields_for_row_slack_and_artificial(unsigned row, + unsigned & slack_var, + unsigned & artificial) { + lean_assert(row < this->row_count()); + auto & constraint = this->m_constraints[this->m_core_solver_rows_to_external_rows[row]]; + // we need to bring the program to the form Ax = b + T rs = this->m_b[row]; + switch (constraint.m_relation) { + case Equal: // no slack variable here + set_type_for_logical(artificial, column_type::fixed); + this->m_basis[row] = artificial; + this->m_costs[artificial] = numeric_traits::zero(); + (*this->m_A)(row, artificial) = numeric_traits::one(); + artificial++; + break; + + case Greater_or_equal: + set_type_for_logical(slack_var, column_type::low_bound); + (*this->m_A)(row, slack_var) = - numeric_traits::one(); + if (rs > 0) { + // adding one artificial + set_type_for_logical(artificial, column_type::fixed); + (*this->m_A)(row, artificial) = numeric_traits::one(); + this->m_basis[row] = artificial; + this->m_costs[artificial] = numeric_traits::zero(); + artificial++; + } else { + // we can put a slack_var into the basis, and avoid adding an artificial variable + this->m_basis[row] = slack_var; + this->m_costs[slack_var] = numeric_traits::zero(); + } + slack_var++; + break; + case Less_or_equal: + // introduce a non-negative slack variable + set_type_for_logical(slack_var, column_type::low_bound); + (*this->m_A)(row, slack_var) = numeric_traits::one(); + if (rs < 0) { + // adding one artificial + set_type_for_logical(artificial, column_type::fixed); + (*this->m_A)(row, artificial) = - numeric_traits::one(); + this->m_basis[row] = artificial; + this->m_costs[artificial] = numeric_traits::zero(); + artificial++; + } else { + // we can put slack_var into the basis, and avoid adding an artificial variable + this->m_basis[row] = slack_var; + this->m_costs[slack_var] = numeric_traits::zero(); + } + slack_var++; + break; + } +} + +template void lp_dual_simplex::augment_matrix_A_and_fill_x_and_allocate_some_fields() { + this->count_slacks_and_artificials(); + this->m_A->add_columns_at_the_end(this->m_slacks + this->m_artificials); + unsigned n = this->m_A->column_count(); + this->m_column_types_of_core_solver.resize(n); + m_column_types_of_logicals.resize(this->m_slacks + this->m_artificials); + this->m_costs.resize(n); + this->m_upper_bounds.resize(n); + this->m_low_bounds.resize(n); + m_can_enter_basis.resize(n); + this->m_basis.resize(this->m_A->row_count()); +} + + + +template void lp_dual_simplex::copy_m_b_aside_and_set_it_to_zeros() { + for (unsigned i = 0; i < this->m_b.size(); i++) { + m_b_copy.push_back(this->m_b[i]); + this->m_b[i] = numeric_traits::zero(); // preparing for the first stage + } +} + +template void lp_dual_simplex::find_maximal_solution(){ + if (this->problem_is_empty()) { + this->m_status = lp_status::EMPTY; + return; + } + + this->flip_costs(); // do it for now, todo ( remove the flipping) + + this->cleanup(); + if (this->m_status == INFEASIBLE) { + return; + } + this->fill_matrix_A_and_init_right_side(); + this->fill_m_b(); + this->scale(); + augment_matrix_A_and_fill_x_and_allocate_some_fields(); + fill_first_stage_solver_fields(); + copy_m_b_aside_and_set_it_to_zeros(); + stage1(); + if (this->m_status == FEASIBLE) { + stage2(); + } +} + + +template T lp_dual_simplex::get_current_cost() const { + T ret = numeric_traits::zero(); + for (auto it : this->m_map_from_var_index_to_column_info) { + ret += this->get_column_cost_value(it.first, it.second); + } + return -ret; // we flip costs for now +} +} diff --git a/src/util/lp/lp_dual_simplex_instances.cpp b/src/util/lp/lp_dual_simplex_instances.cpp new file mode 100644 index 000000000..6610814d8 --- /dev/null +++ b/src/util/lp/lp_dual_simplex_instances.cpp @@ -0,0 +1,9 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include "util/lp/lp_dual_simplex.hpp" +template lean::mpq lean::lp_dual_simplex::get_current_cost() const; +template void lean::lp_dual_simplex::find_maximal_solution(); +template double lean::lp_dual_simplex::get_current_cost() const; +template void lean::lp_dual_simplex::find_maximal_solution(); diff --git a/src/util/lp/lp_params.pyg b/src/util/lp/lp_params.pyg new file mode 100644 index 000000000..7731536f0 --- /dev/null +++ b/src/util/lp/lp_params.pyg @@ -0,0 +1,12 @@ +def_module_params('lp', + export=True, + params=( + ('rep_freq', UINT, 0, 'the report frequency, in how many iterations print the cost and other info '), + ('min', BOOL, False, 'minimize cost'), + ('print_stats', BOOL, False, 'print statistic'), + ('simplex_strategy', UINT, 0, 'simplex strategy for the solver'), + ('bprop_on_pivoted_rows', BOOL, True, 'propagate bounds on rows changed by the pivot operation') + )) + + + diff --git a/src/util/lp/lp_primal_core_solver.h b/src/util/lp/lp_primal_core_solver.h new file mode 100644 index 000000000..3fada1e5d --- /dev/null +++ b/src/util/lp/lp_primal_core_solver.h @@ -0,0 +1,988 @@ +/* + 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; + 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; + 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 (m_settings.random_next() % 10) break; + // } else if (row_len == len) { + // choices.push_back(i); + // if (m_settings.random_next() % 10) break; + // } + // } + + // if (choices.size() == 0) + // return -1; + + // if (choices.size() == 1) + // return choices[0]; + + // unsigned k = this->m_settings.random_next() % 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 && (this->m_settings.random_next() % 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[this->m_settings.random_next() % 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()), + m_epsilon_of_reduced_cost(T(1)/T(10000000)), + m_bland_mode_threshold(1000) { + + 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..47eec468a --- /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 && this->m_settings.random_next() % 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) && this->m_settings.random_next() % 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 = this->m_settings.random_next() % 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 = this->m_settings.random_next() % 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 = this->m_settings.random_next() % 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->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->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->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->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->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->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->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(this->m_settings.random_next() % 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->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(this->m_settings.random_next() % 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..ca231fd34 --- /dev/null +++ b/src/util/lp/lp_primal_core_solver_instances.cpp @@ -0,0 +1,27 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include +#include +#include +#include "util/vector.h" +#include +#include "util/lp/lar_solver.h" +#include "util/lp/lp_primal_core_solver.hpp" +#include "util/lp/lp_primal_core_solver_tableau.hpp" +namespace lean { + +template void lp_primal_core_solver::find_feasible_solution(); +template void lean::lp_primal_core_solver >::find_feasible_solution(); + +template unsigned lp_primal_core_solver::solve(); +template unsigned lp_primal_core_solver::solve_with_tableau(); +template unsigned lp_primal_core_solver::solve(); +template unsigned lp_primal_core_solver >::solve(); +template void lean::lp_primal_core_solver::clear_breakpoints(); +template bool lean::lp_primal_core_solver::update_basis_and_x_tableau(int, int, lean::mpq const&); +template bool lean::lp_primal_core_solver::update_basis_and_x_tableau(int, int, double const&); +template bool lean::lp_primal_core_solver >::update_basis_and_x_tableau(int, int, lean::numeric_pair const&); + +} diff --git a/src/util/lp/lp_primal_core_solver_tableau.hpp b/src/util/lp/lp_primal_core_solver_tableau.hpp new file mode 100644 index 000000000..0c09c22c9 --- /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 && this->m_settings.random_next() % 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->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->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->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 = this->m_settings.random_next() % 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->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.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..fcddb4eb1 --- /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; + 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(): m_core_solver(nullptr) {} + + 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..6b6aba2ad --- /dev/null +++ b/src/util/lp/lp_settings.h @@ -0,0 +1,390 @@ +/* + 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 { + undecided = 3, + tableau_rows = 0, + tableau_costs = 1, + lu = 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); + + +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; + // used for messages, for example, the computation progress messages + std::ostream* m_message_out; + + stats m_stats; + random_gen m_rand; + +public: + unsigned reps_in_scaler; + // when the absolute value of an element is less than pivot_epsilon + // in pivoting, we treat it as a zero + double pivot_epsilon; + // see Chatal, page 115 + double positive_price_epsilon; + // 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; + int c_partial_pivoting; // this is the constant c from page 410 + unsigned depth_of_rook_search; + bool using_partial_pivoting; + // dissertation of Achim Koberstein + // if Bx - b is different at any component more that refactor_epsilon then we refactor + double refactor_tolerance; + double pivot_tolerance; + double zero_tolerance; + double drop_tolerance; + double tolerance_for_artificials; + double can_be_taken_to_basis_tolerance; + + unsigned percent_of_entering_to_check; // we try to find a profitable column in a percentage of the columns + bool use_scaling; + double scaling_maximum; + double scaling_minimum; + double harris_feasibility_tolerance; // page 179 of Istvan Maros + double ignore_epsilon_of_harris; + unsigned max_number_of_iterations_with_no_improvements; + unsigned max_total_number_of_iterations; + double time_limit; // the maximum time limit of the total run time in seconds + // dual section + double dual_feasibility_tolerance; // // page 71 of the PhD thesis of Achim Koberstein + double primal_feasibility_tolerance; // page 71 of the PhD thesis of Achim Koberstein + double relative_primal_feasibility_tolerance; // page 71 of the PhD thesis of Achim Koberstein + + bool m_bound_propagation; + + 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), + m_debug_out( &std::cout), + m_message_out(&std::cout), + reps_in_scaler(20), + pivot_epsilon(0.00000001), + positive_price_epsilon(1e-7), + entering_diag_epsilon ( 1e-8), + c_partial_pivoting ( 10), // this is the constant c from page 410 + depth_of_rook_search ( 4), + using_partial_pivoting ( true), + // dissertation of Achim Koberstein + // if Bx - b is different at any component more that refactor_epsilon then we refactor + refactor_tolerance ( 1e-4), + pivot_tolerance ( 1e-6), + zero_tolerance ( 1e-12), + drop_tolerance ( 1e-14), + tolerance_for_artificials ( 1e-4), + can_be_taken_to_basis_tolerance ( 0.00001), + + percent_of_entering_to_check ( 5),// we try to find a profitable column in a percentage of the columns + use_scaling ( true), + scaling_maximum ( 1), + scaling_minimum ( 0.5), + harris_feasibility_tolerance ( 1e-7), // page 179 of Istvan Maros + ignore_epsilon_of_harris ( 10e-5), + max_number_of_iterations_with_no_improvements ( 2000000), + max_total_number_of_iterations ( 20000000), + time_limit ( std::numeric_limits::max()), // the maximum time limit of the total run time in seconds + // dual section + dual_feasibility_tolerance ( 1e-7), // // page 71 of the PhD thesis of Achim Koberstein + primal_feasibility_tolerance ( 1e-7), // page 71 of the PhD thesis of Achim Koberstein + relative_primal_feasibility_tolerance ( 1e-9), // page 71 of the PhD thesis of Achim Koberstein + m_bound_propagation ( true), + presolve_with_double_solver_for_lar(true), + m_simplex_strategy(simplex_strategy_enum::tableau_rows), + report_frequency(1000), + print_statistics(false), + column_norms_update_frequency(12000), + scale_with_ratio(true), + density_threshold(0.7), + use_breakpoints_in_feasibility_search(false), + max_row_length_for_bound_propagation(300), + backup_costs(true), + column_number_threshold_for_using_lu_in_lar_solver(4000) + {} + + 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; + simplex_strategy_enum m_simplex_strategy; + simplex_strategy_enum simplex_strategy() const { + return m_simplex_strategy; + } + + simplex_strategy_enum & simplex_strategy() { + return m_simplex_strategy; + } + + bool use_lu() const { + return m_simplex_strategy == simplex_strategy_enum::lu; + } + + bool use_tableau() const { + return m_simplex_strategy == simplex_strategy_enum::tableau_rows || + m_simplex_strategy == simplex_strategy_enum::tableau_costs; + } + + bool use_tableau_rows() const { + return m_simplex_strategy == simplex_strategy_enum::tableau_rows; + } + + int report_frequency; + bool print_statistics; + unsigned column_norms_update_frequency; + bool scale_with_ratio; + double density_threshold; // need to tune it up, todo +#ifdef LEAN_DEBUG + static unsigned ddd; // used for debugging +#endif + bool use_breakpoints_in_feasibility_search; + unsigned random_next() { return m_rand(); } + void random_seed(unsigned s) { m_rand.set_seed(s); } + unsigned max_row_length_for_bound_propagation; + bool backup_costs; + unsigned column_number_threshold_for_using_lu_in_lar_solver; +}; // 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..b57a3acda --- /dev/null +++ b/src/util/lp/lp_settings.hpp @@ -0,0 +1,123 @@ +/* + 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; +} + + + +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; +} +#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..1bfe7dcdc --- /dev/null +++ b/src/util/lp/lp_solver.h @@ -0,0 +1,252 @@ +/* + 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; // this is the matrix of constraints + vector m_b; // the right side vector + unsigned m_first_stage_iterations; + unsigned m_second_stage_iterations; + 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; + unsigned m_slacks; + 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_settings m_settings; + lp_solver(): + m_A(nullptr), // this is the matrix of constraints + m_first_stage_iterations (0), + m_second_stage_iterations (0), + m_artificials (0), + m_slacks (0), + m_status(lp_status::UNKNOWN) + {} + + 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; + } + +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..0d8163a14 --- /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; +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; + 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; + // 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..2d2c7c7c4 --- /dev/null +++ b/src/util/lp/lu.hpp @@ -0,0 +1,943 @@ +/* + 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_status(LU_status::OK), + 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_failure(false), + m_row_eta_work_vector(A.row_count()), + m_refactor_counter(0) { + 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..4c793d56e --- /dev/null +++ b/src/util/lp/mps_reader.h @@ -0,0 +1,883 @@ +/* + 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 { + T m_low; + T m_upper; + bool m_low_is_set; + bool m_upper_is_set; + bool m_value_is_fixed; + T m_fixed_value; + bool m_free; + // constructor + bound() : m_low(numeric_traits::zero()), + m_low_is_set(true), + m_upper_is_set(false), + m_value_is_fixed(false), + m_free(false) {} // 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; + unsigned m_index; + column(std::string name, unsigned index): m_name(name), + m_bound(nullptr), + m_index(index) { + } + }; + + struct row { + row_type m_type; + std::string m_name; + std::unordered_map m_row_columns; + unsigned m_index; + T m_right_side; + T m_range; + row(row_type type, std::string name, unsigned index) : + m_type(type), + m_name(name), + m_index(index), + m_right_side(zero_of_type()), + m_range(zero_of_type()) + { + } + }; + + bool m_is_OK; + std::string m_file_name; + 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; + unsigned m_line_number; + std::ostream * m_message_stream; + + 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_is_OK(true), + m_file_name(file_name), + m_file_stream(file_name), + m_cost_line_count(0), + m_line_number(0), + m_message_stream(& std::cout) {} + 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 = static_cast(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..84c99b3b1 --- /dev/null +++ b/src/util/lp/numeric_pair.h @@ -0,0 +1,327 @@ +/* + 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) { + } + + numeric_pair(const numeric_pair & n) : x(n.x), y(n.y) {} + + template + numeric_pair(X xp, Y yp) : x(convert_struct::convert(xp)), y(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..4bdd57f25 --- /dev/null +++ b/src/util/lp/permutation_matrix.h @@ -0,0 +1,135 @@ +/* + 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 + +} diff --git a/src/util/lp/permutation_matrix.hpp b/src/util/lp/permutation_matrix.hpp new file mode 100644 index 000000000..ec9af5a50 --- /dev/null +++ b/src/util/lp/permutation_matrix.hpp @@ -0,0 +1,323 @@ +/* + 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; +} + + +} diff --git a/src/util/lp/permutation_matrix_instances.cpp b/src/util/lp/permutation_matrix_instances.cpp new file mode 100644 index 000000000..91473fabc --- /dev/null +++ b/src/util/lp/permutation_matrix_instances.cpp @@ -0,0 +1,55 @@ +/* + 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&); +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..8cb9740ea --- /dev/null +++ b/src/util/lp/random_updater.h @@ -0,0 +1,80 @@ +/* +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 { + struct interval { + bool upper_bound_is_set; + numeric_pair upper_bound; + bool low_bound_is_set; + numeric_pair low_bound; + interval() : upper_bound_is_set(false), + low_bound_is_set(false) {} + + 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; + unsigned range; + 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..7c6a0539f --- /dev/null +++ b/src/util/lp/random_updater.hpp @@ -0,0 +1,207 @@ +/* + 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), + range(100000) { + 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 = m_core_solver.settings().random_next(); + if ((!r.low_bound_is_set) && (!r.upper_bound_is_set)) + return numeric_pair(rand % range, 0); + if (r.low_bound_is_set && (!r.upper_bound_is_set)) + return r.low_bound + numeric_pair(rand % range, 0); + if ((!r.low_bound_is_set) && r.upper_bound_is_set) + return r.upper_bound - numeric_pair(rand % range, 0); + lean_assert(r.low_bound_is_set && r.upper_bound_is_set); + return r.low_bound + (rand % range) * (r.upper_bound - r.low_bound)/ range; +} + +void random_updater::random_shift_var(unsigned j) { + m_column_j = m_core_solver.get_column_iterator(j); + if (m_column_j->size() >= 50) { + delete m_column_j; + return; + } + interval interv = find_shift_interval(j); + if (interv.is_empty()) { + delete m_column_j; + return; + } + + shift_var(j, interv); + delete m_column_j; +} + +void random_updater::update() { + for (auto j : m_var_set) { + if (m_var_set.size() <= m_values.size()) { + break; // we are done + } + random_shift_var(j); + } +} + +void random_updater::add_value(numeric_pair& v) { + auto it = m_values.find(v); + if (it == m_values.end()) { + m_values[v] = 1; + } else { + it->second++; + } +} + +void random_updater::remove_value(numeric_pair& v) { + std::unordered_map, unsigned>::iterator it = m_values.find(v); + lean_assert(it != m_values.end()); + it->second--; + if (it->second == 0) + m_values.erase((std::unordered_map, unsigned>::const_iterator)it); +} + +void random_updater::add_column_to_sets(unsigned j) { + if (m_core_solver.m_r_heading[j] < 0) { + m_var_set.insert(j); + add_value(m_core_solver.m_r_x[j]); + } else { + unsigned row = m_core_solver.m_r_heading[j]; + for (auto row_c : m_core_solver.m_r_A.m_rows[row]) { + unsigned cj = row_c.m_j; + if (m_core_solver.m_r_heading[cj] < 0) { + m_var_set.insert(cj); + add_value(m_core_solver.m_r_x[cj]); + } + } + } +} +} diff --git a/src/util/lp/random_updater_instances.cpp b/src/util/lp/random_updater_instances.cpp new file mode 100644 index 000000000..5b4c89bd5 --- /dev/null +++ b/src/util/lp/random_updater_instances.cpp @@ -0,0 +1,5 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include "util/lp/random_updater.hpp" diff --git a/src/util/lp/row_eta_matrix.h b/src/util/lp/row_eta_matrix.h new file mode 100644 index 000000000..90acb89f3 --- /dev/null +++ b/src/util/lp/row_eta_matrix.h @@ -0,0 +1,74 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ + +#pragma once +#include "util/vector.h" +#include "util/debug.h" +#include +#include "util/lp/sparse_vector.h" +#include "util/lp/indexed_vector.h" +#include "util/lp/permutation_matrix.h" +namespace lean { + // This is the sum of a unit matrix and a lower triangular matrix + // with non-zero elements only in one row +template +class row_eta_matrix + : public tail_matrix { +#ifdef LEAN_DEBUG + unsigned m_dimension; +#endif + unsigned m_row_start; + unsigned m_row; + sparse_vector m_row_vector; +public: +#ifdef LEAN_DEBUG + row_eta_matrix(unsigned row_start, unsigned row, unsigned dim): +#else + row_eta_matrix(unsigned row_start, unsigned row): +#endif + +#ifdef LEAN_DEBUG + m_dimension(dim), +#endif + m_row_start(row_start), m_row(row) { + } + + bool is_dense() const { return false; } + + void print(std::ostream & out) { + print_matrix(*this, out); + } + + const T & get_diagonal_element() const { + return m_row_vector.m_data[m_row]; + } + + void apply_from_left(vector & w, lp_settings &); + + void apply_from_left_local_to_T(indexed_vector & w, lp_settings & settings); + void apply_from_left_local_to_X(indexed_vector & w, lp_settings & settings); + + void apply_from_left_to_T(indexed_vector & w, lp_settings & settings) { + apply_from_left_local_to_T(w, settings); + } + + void push_back(unsigned row_index, T val ) { + lean_assert(row_index != m_row); + m_row_vector.push_back(row_index, val); + } + + void apply_from_right(vector & w); + void apply_from_right(indexed_vector & w); + + void conjugate_by_permutation(permutation_matrix & p); +#ifdef LEAN_DEBUG + T get_elem(unsigned row, unsigned col) const; + unsigned row_count() const { return m_dimension; } + unsigned column_count() const { return m_dimension; } + void set_number_of_rows(unsigned m) { m_dimension = m; } + void set_number_of_columns(unsigned n) { m_dimension = n; } +#endif +}; // end of row_eta_matrix +} diff --git a/src/util/lp/row_eta_matrix.hpp b/src/util/lp/row_eta_matrix.hpp new file mode 100644 index 000000000..5758abeb8 --- /dev/null +++ b/src/util/lp/row_eta_matrix.hpp @@ -0,0 +1,171 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include "util/vector.h" +#include "util/lp/row_eta_matrix.h" +namespace lean { +template +void row_eta_matrix::apply_from_left(vector & w, lp_settings &) { + // #ifdef LEAN_DEBUG + // dense_matrix deb(*this); + // auto clone_w = clone_vector(w, m_dimension); + // deb.apply_from_left(clone_w, settings); + // #endif + + auto & w_at_row = w[m_row]; + for (auto & it : m_row_vector.m_data) { + w_at_row += w[it.first] * it.second; + } + // w[m_row] = w_at_row; + // #ifdef LEAN_DEBUG + // lean_assert(vectors_are_equal(clone_w, w, m_dimension)); + // delete [] clone_w; + // #endif +} + +template +void row_eta_matrix::apply_from_left_local_to_T(indexed_vector & w, lp_settings & settings) { + auto w_at_row = w[m_row]; + bool was_zero_at_m_row = is_zero(w_at_row); + + for (auto & it : m_row_vector.m_data) { + w_at_row += w[it.first] * it.second; + } + + if (!settings.abs_val_is_smaller_than_drop_tolerance(w_at_row)){ + if (was_zero_at_m_row) { + w.m_index.push_back(m_row); + } + w[m_row] = w_at_row; + } else if (!was_zero_at_m_row){ + w[m_row] = zero_of_type(); + auto it = std::find(w.m_index.begin(), w.m_index.end(), m_row); + w.m_index.erase(it); + } + // TBD: lean_assert(check_vector_for_small_values(w, settings)); +} + +template +void row_eta_matrix::apply_from_left_local_to_X(indexed_vector & w, lp_settings & settings) { + auto w_at_row = w[m_row]; + bool was_zero_at_m_row = is_zero(w_at_row); + + for (auto & it : m_row_vector.m_data) { + w_at_row += w[it.first] * it.second; + } + + if (!settings.abs_val_is_smaller_than_drop_tolerance(w_at_row)){ + if (was_zero_at_m_row) { + w.m_index.push_back(m_row); + } + w[m_row] = w_at_row; + } else if (!was_zero_at_m_row){ + w[m_row] = zero_of_type(); + auto it = std::find(w.m_index.begin(), w.m_index.end(), m_row); + w.m_index.erase(it); + } + // TBD: does not compile lean_assert(check_vector_for_small_values(w, settings)); +} + +template +void row_eta_matrix::apply_from_right(vector & w) { + const T & w_row = w[m_row]; + if (numeric_traits::is_zero(w_row)) return; +#ifdef LEAN_DEBUG + // dense_matrix deb(*this); + // auto clone_w = clone_vector(w, m_dimension); + // deb.apply_from_right(clone_w); +#endif + for (auto & it : m_row_vector.m_data) { + w[it.first] += w_row * it.second; + } +#ifdef LEAN_DEBUG + // lean_assert(vectors_are_equal(clone_w, w, m_dimension)); + // delete clone_w; +#endif +} + +template +void row_eta_matrix::apply_from_right(indexed_vector & w) { + lean_assert(w.is_OK()); + const T & w_row = w[m_row]; + if (numeric_traits::is_zero(w_row)) return; +#ifdef LEAN_DEBUG + // vector wcopy(w.m_data); + // apply_from_right(wcopy); +#endif + if (numeric_traits::precise()) { + for (auto & it : m_row_vector.m_data) { + unsigned j = it.first; + bool was_zero = numeric_traits::is_zero(w[j]); + const T & v = w[j] += w_row * it.second; + + if (was_zero) { + if (!numeric_traits::is_zero(v)) + w.m_index.push_back(j); + } else { + if (numeric_traits::is_zero(v)) + w.erase_from_index(j); + } + } + } else { // the non precise version + const double drop_eps = 1e-14; + for (auto & it : m_row_vector.m_data) { + unsigned j = it.first; + bool was_zero = numeric_traits::is_zero(w[j]); + T & v = w[j] += w_row * it.second; + + if (was_zero) { + if (!lp_settings::is_eps_small_general(v, drop_eps)) + w.m_index.push_back(j); + else + v = zero_of_type(); + } else { + if (lp_settings::is_eps_small_general(v, drop_eps)) { + w.erase_from_index(j); + v = zero_of_type(); + } + } + } + } +#ifdef LEAN_DEBUG + // lean_assert(vectors_are_equal(wcopy, w.m_data)); + +#endif +} + +template +void row_eta_matrix::conjugate_by_permutation(permutation_matrix & p) { + // this = p * this * p(-1) +#ifdef LEAN_DEBUG + // auto rev = p.get_reverse(); + // auto deb = ((*this) * rev); + // deb = p * deb; +#endif + m_row = p.apply_reverse(m_row); + // copy aside the column indices + vector columns; + for (auto & it : m_row_vector.m_data) + columns.push_back(it.first); + for (unsigned i = static_cast(columns.size()); i-- > 0;) + m_row_vector.m_data[i].first = p.get_rev(columns[i]); +#ifdef LEAN_DEBUG + // lean_assert(deb == *this); +#endif +} +#ifdef LEAN_DEBUG +template +T row_eta_matrix::get_elem(unsigned row, unsigned col) const { + if (row == m_row){ + if (col == row) { + return numeric_traits::one(); + } + return m_row_vector[col]; + } + + return col == row ? numeric_traits::one() : numeric_traits::zero(); +} +#endif +} + diff --git a/src/util/lp/row_eta_matrix_instances.cpp b/src/util/lp/row_eta_matrix_instances.cpp new file mode 100644 index 000000000..c32023164 --- /dev/null +++ b/src/util/lp/row_eta_matrix_instances.cpp @@ -0,0 +1,33 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include "util/vector.h" +#include +#include "util/lp/row_eta_matrix.hpp" +#include "util/lp/lu.h" +namespace lean { +template void row_eta_matrix::conjugate_by_permutation(permutation_matrix&); +template void row_eta_matrix >::conjugate_by_permutation(permutation_matrix >&); +template void row_eta_matrix::conjugate_by_permutation(permutation_matrix&); +#ifdef LEAN_DEBUG +template mpq row_eta_matrix::get_elem(unsigned int, unsigned int) const; +template mpq row_eta_matrix >::get_elem(unsigned int, unsigned int) const; +template double row_eta_matrix::get_elem(unsigned int, unsigned int) const; +#endif +template void row_eta_matrix::apply_from_left(vector&, lp_settings&); +template void row_eta_matrix::apply_from_right(vector&); +template void row_eta_matrix::apply_from_right(indexed_vector&); +template void row_eta_matrix >::apply_from_left(vector>&, lp_settings&); +template void row_eta_matrix >::apply_from_right(vector&); +template void row_eta_matrix >::apply_from_right(indexed_vector&); +template void row_eta_matrix::apply_from_left(vector&, lp_settings&); +template void row_eta_matrix::apply_from_right(vector&); +template void row_eta_matrix::apply_from_right(indexed_vector&); +template void row_eta_matrix::apply_from_left_to_T(indexed_vector&, lp_settings&); +template void row_eta_matrix::apply_from_left_local_to_T(indexed_vector&, lp_settings&); +template void row_eta_matrix >::apply_from_left_to_T(indexed_vector&, lp_settings&); +template void row_eta_matrix >::apply_from_left_local_to_T(indexed_vector&, lp_settings&); +template void row_eta_matrix::apply_from_left_to_T(indexed_vector&, lp_settings&); +template void row_eta_matrix::apply_from_left_local_to_T(indexed_vector&, lp_settings&); +} diff --git a/src/util/lp/scaler.h b/src/util/lp/scaler.h new file mode 100644 index 000000000..33c5a6cc4 --- /dev/null +++ b/src/util/lp/scaler.h @@ -0,0 +1,79 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ + +#pragma once +#include "util/vector.h" +#include +#include +#include /* printf, fopen */ +#include /* exit, EXIT_FAILURE */ +#include "util/lp/lp_utils.h" +#include "util/lp/static_matrix.h" +namespace lean { +// for scaling an LP +template +class scaler { + vector & m_b; // right side + static_matrix &m_A; // the constraint matrix + const T & m_scaling_minimum; + const T & m_scaling_maximum; + vector& m_column_scale; + lp_settings & m_settings; +public: + // constructor + scaler(vector & b, static_matrix &A, const T & scaling_minimum, const T & scaling_maximum, vector & column_scale, + lp_settings & settings): + m_b(b), + m_A(A), + m_scaling_minimum(scaling_minimum), + m_scaling_maximum(scaling_maximum), + m_column_scale(column_scale), + m_settings(settings) { + lean_assert(m_column_scale.size() == 0); + m_column_scale.resize(m_A.column_count(), numeric_traits::one()); + } + + T right_side_balance(); + + T get_balance() { return m_A.get_balance(); } + + T A_min() const; + + T A_max() const; + + T get_A_ratio() const; + + T get_max_ratio_on_rows() const; + + T get_max_ratio_on_columns() const; + + void scale_rows_with_geometric_mean(); + + void scale_columns_with_geometric_mean(); + + void scale_once_for_ratio(); + + bool scale_with_ratio(); + + void bring_row_maximums_to_one(); + + void bring_column_maximums_to_one(); + + void bring_rows_and_columns_maximums_to_one(); + + bool scale_with_log_balance(); + // Returns true if and only if the scaling was successful. + // It is the caller responsibility to restore the matrix + bool scale(); + + void scale_rows(); + + void scale_row(unsigned i); + + void scale_column(unsigned i); + + void scale_columns(); +}; +} diff --git a/src/util/lp/scaler.hpp b/src/util/lp/scaler.hpp new file mode 100644 index 000000000..69427eea0 --- /dev/null +++ b/src/util/lp/scaler.hpp @@ -0,0 +1,254 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include +#include "util/lp/scaler.h" +#include "util/lp/numeric_pair.h" +namespace lean { +// for scaling an LP +template T scaler::right_side_balance() { + T ret = zero_of_type(); + unsigned i = m_A.row_count(); + while (i--) { + T rs = abs(convert_struct::convert(m_b[i])); + if (!is_zero(rs)) { + numeric_traits::log(rs); + ret += rs * rs; + } + } + return ret; +} + +template T scaler::A_min() const { + T min = zero_of_type(); + for (unsigned i = 0; i < m_A.row_count(); i++) { + T t = m_A.get_min_abs_in_row(i); + min = i == 0 ? t : std::min(t, min); + } + return min; +} + +template T scaler::A_max() const { + T max = zero_of_type(); + for (unsigned i = 0; i < m_A.row_count(); i++) { + T t = m_A.get_max_abs_in_row(i); + max = i == 0? t : std::max(t, max); + } + return max; +} + +template T scaler::get_A_ratio() const { + T min = A_min(); + T max = A_max(); + lean_assert(!m_settings.abs_val_is_smaller_than_zero_tolerance(min)); + T ratio = max / min; + return ratio; +} + +template T scaler::get_max_ratio_on_rows() const { + T ret = T(1); + unsigned i = m_A.row_count(); + while (i--) { + T den = m_A.get_min_abs_in_row(i); + lean_assert(!m_settings.abs_val_is_smaller_than_zero_tolerance(den)); + T t = m_A.get_max_abs_in_row(i)/ den; + if (t > ret) + ret = t; + } + return ret; +} + +template T scaler::get_max_ratio_on_columns() const { + T ret = T(1); + unsigned i = m_A.column_count(); + while (i--) { + T den = m_A.get_min_abs_in_column(i); + if (m_settings.abs_val_is_smaller_than_zero_tolerance(den)) + continue; // got a zero column + T t = m_A.get_max_abs_in_column(i)/den; + if (t > ret) + ret = t; + } + return ret; +} + +template void scaler::scale_rows_with_geometric_mean() { + unsigned i = m_A.row_count(); + while (i--) { + T max = m_A.get_max_abs_in_row(i); + T min = m_A.get_min_abs_in_row(i); + lean_assert(max > zero_of_type() && min > zero_of_type()); + if (is_zero(max) || is_zero(min)) + continue; + T gm = T(sqrt(numeric_traits::get_double(max*min))); + if (m_settings.is_eps_small_general(gm, 0.01)) { + continue; + } + m_A.multiply_row(i, one_of_type() / gm); + m_b[i] /= gm; + } +} + +template void scaler::scale_columns_with_geometric_mean() { + unsigned i = m_A.column_count(); + while (i--) { + T max = m_A.get_max_abs_in_column(i); + T min = m_A.get_min_abs_in_column(i); + T den = T(sqrt(numeric_traits::get_double(max*min))); + if (m_settings.is_eps_small_general(den, 0.01)) + continue; // got a zero column + T gm = T(1)/ den; + T cs = m_column_scale[i] * gm; + if (m_settings.is_eps_small_general(cs, 0.1)) + continue; + m_A.multiply_column(i, gm); + m_column_scale[i] = cs; + } +} + +template void scaler::scale_once_for_ratio() { + T max_ratio_on_rows = get_max_ratio_on_rows(); + T max_ratio_on_columns = get_max_ratio_on_columns(); + bool scale_rows_first = max_ratio_on_rows > max_ratio_on_columns; + // if max_ratio_on_columns is the largerst then the rows are in worser shape then columns + if (scale_rows_first) { + scale_rows_with_geometric_mean(); + scale_columns_with_geometric_mean(); + } else { + scale_columns_with_geometric_mean(); + scale_rows_with_geometric_mean(); + } +} + +template bool scaler::scale_with_ratio() { + T ratio = get_A_ratio(); + // The ratio is greater than or equal to one. We would like to diminish it and bring it as close to 1 as possible + unsigned reps = m_settings.reps_in_scaler; + do { + scale_once_for_ratio(); + T new_r = get_A_ratio(); + if (new_r >= T(0.9) * ratio) + break; + } while (reps--); + + bring_rows_and_columns_maximums_to_one(); + return true; +} + +template void scaler::bring_row_maximums_to_one() { + unsigned i = m_A.row_count(); + while (i--) { + T t = m_A.get_max_abs_in_row(i); + if (m_settings.abs_val_is_smaller_than_zero_tolerance(t)) continue; + m_A.multiply_row(i, one_of_type() / t); + m_b[i] /= t; + } +} + +template void scaler::bring_column_maximums_to_one() { + unsigned i = m_A.column_count(); + while (i--) { + T max = m_A.get_max_abs_in_column(i); + if (m_settings.abs_val_is_smaller_than_zero_tolerance(max)) continue; + T t = T(1) / max; + m_A.multiply_column(i, t); + m_column_scale[i] *= t; + } +} + +template void scaler::bring_rows_and_columns_maximums_to_one() { + if (get_max_ratio_on_rows() > get_max_ratio_on_columns()) { + bring_row_maximums_to_one(); + bring_column_maximums_to_one(); + } else { + bring_column_maximums_to_one(); + bring_row_maximums_to_one(); + } +} + +template bool scaler::scale_with_log_balance() { + T balance = get_balance(); + T balance_before_scaling = balance; + // todo : analyze the scale order : rows-columns, or columns-rows. Iterate if needed + for (int i = 0; i < 10; i++) { + scale_rows(); + scale_columns(); + T nb = get_balance(); + if (nb < T(0.9) * balance) { + balance = nb; + } else { + balance = nb; + break; + } + } + return balance <= balance_before_scaling; +} +// Returns true if and only if the scaling was successful. +// It is the caller responsibility to restore the matrix +template bool scaler::scale() { + if (numeric_traits::precise()) return true; + if (m_settings.scale_with_ratio) + return scale_with_ratio(); + return scale_with_log_balance(); +} + +template void scaler::scale_rows() { + for (unsigned i = 0; i < m_A.row_count(); i++) + scale_row(i); +} + +template void scaler::scale_row(unsigned i) { + T row_max = std::max(m_A.get_max_abs_in_row(i), abs(convert_struct::convert(m_b[i]))); + T alpha = numeric_traits::one(); + if (numeric_traits::is_zero(row_max)) { + return; + } + if (numeric_traits::get_double(row_max) < m_scaling_minimum) { + do { + alpha *= 2; + row_max *= 2; + } while (numeric_traits::get_double(row_max) < m_scaling_minimum); + m_A.multiply_row(i, alpha); + m_b[i] *= alpha; + } else if (numeric_traits::get_double(row_max) > m_scaling_maximum) { + do { + alpha /= 2; + row_max /= 2; + } while (numeric_traits::get_double(row_max) > m_scaling_maximum); + m_A.multiply_row(i, alpha); + m_b[i] *= alpha; + } +} + +template void scaler::scale_column(unsigned i) { + T column_max = m_A.get_max_abs_in_column(i); + T alpha = numeric_traits::one(); + + if (numeric_traits::is_zero(column_max)){ + return; // the column has zeros only + } + + if (numeric_traits::get_double(column_max) < m_scaling_minimum) { + do { + alpha *= 2; + column_max *= 2; + } while (numeric_traits::get_double(column_max) < m_scaling_minimum); + } else if (numeric_traits::get_double(column_max) > m_scaling_maximum) { + do { + alpha /= 2; + column_max /= 2; + } while (numeric_traits::get_double(column_max) > m_scaling_maximum); + } else { + return; + } + m_A.multiply_column(i, alpha); + m_column_scale[i] = alpha; +} + +template void scaler::scale_columns() { + for (unsigned i = 0; i < m_A.column_count(); i++) { + scale_column(i); + } +} +} diff --git a/src/util/lp/scaler_instances.cpp b/src/util/lp/scaler_instances.cpp new file mode 100644 index 000000000..f97e8098f --- /dev/null +++ b/src/util/lp/scaler_instances.cpp @@ -0,0 +1,7 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include "util/lp/scaler.hpp" +template bool lean::scaler::scale(); +template bool lean::scaler::scale(); diff --git a/src/util/lp/signature_bound_evidence.h b/src/util/lp/signature_bound_evidence.h new file mode 100644 index 000000000..a22c188b4 --- /dev/null +++ b/src/util/lp/signature_bound_evidence.h @@ -0,0 +1,23 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#pragma once +#include "util/lp/lp_settings.h" +#include "util/lp/lar_constraints.h" +namespace lean { +struct bound_signature { + unsigned m_i; + bool m_at_low; + bound_signature(unsigned i, bool at_low) :m_i(i), m_at_low(m_at_low) {} + bool at_upper_bound() const { return !m_at_low_bound;} + bool at_low_bound() const { return m_at_low;} +}; +template +struct signature_bound_evidence { + vector m_evidence; + unsigned m_j; // found new bound + bool m_low_bound; + X m_bound; +}; +} diff --git a/src/util/lp/sparse_matrix.h b/src/util/lp/sparse_matrix.h new file mode 100644 index 000000000..7256004da --- /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; + vector> m_values; // the actual column values + + col_header(): m_shortened_markovitz(0) {} + + void shorten_markovich_by_one() { + m_shortened_markovitz++; + } + + void zero_shortened_markovitz() { + m_shortened_markovitz = 0; + } + }; + + unsigned m_n_of_active_elems; + 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..ff6ac9997 --- /dev/null +++ b/src/util/lp/sparse_matrix.hpp @@ -0,0 +1,1256 @@ +/* + 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_n_of_active_elems(0), + 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..975cb7f28 --- /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..019497aa5 --- /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; + 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..fb12da8c4 --- /dev/null +++ b/src/util/lp/static_matrix.hpp @@ -0,0 +1,408 @@ +/* + 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) { + unsigned ii = c.m_i; + lean_assert(i < row_count() && ii < column_count()); + lean_assert(i != ii); + + m_became_zeros.reset(); + T alpha = -get_val(c); + lean_assert(!is_zero(alpha)); + auto & ii_row_vals = m_rows[ii]; + remove_element(ii_row_vals, ii_row_vals[c.m_offset]); + scan_row_ii_to_offset_vector(ii); + lean_assert(!is_zero(alpha)); + unsigned prev_size_ii = ii_row_vals.size(); + // run over the pivot row and update row ii + for (const auto & iv : m_rows[i]) { + unsigned j = iv.m_j; + if (j == pivot_col) continue; + T alv = alpha * iv.m_value; + lean_assert(!is_zero(iv.m_value)); + int j_offs = m_vector_of_row_offsets[j]; + if (j_offs == -1) { // it is a new element + add_new_element(ii, j, alv); + } + else { + auto & row_el_iv = ii_row_vals[j_offs]; + row_el_iv.m_value += alv; + if (is_zero(row_el_iv.m_value)) { + m_became_zeros.push_back(j_offs); + ensure_increasing(m_became_zeros); + } + } + } + + // clean the work vector + for (unsigned k = 0; k < prev_size_ii; k++) { + m_vector_of_row_offsets[ii_row_vals[k].m_j] = -1; + } + + for (unsigned k = m_became_zeros.size(); k-- > 0; ) { + unsigned j = m_became_zeros[k]; + remove_element(ii_row_vals, ii_row_vals[j]); + } + return !ii_row_vals.empty(); +} + + +// constructor that copies columns of the basis from A +template +static_matrix::static_matrix(static_matrix const &A, unsigned * /* basis */) : + m_vector_of_row_offsets(A.column_count(), numeric_traits::zero()) { + unsigned m = A.row_count(); + init_row_columns(m, m); + while (m--) { + for (auto & col : A.m_columns[m]){ + set(col.m_i, m, A.get_value_of_column_cell(col)); + } + } +} + +template void static_matrix::clear() { + m_vector_of_row_offsets.clear(); + m_rows.clear(); + m_columns.clear(); +} + +template void static_matrix::init_vector_of_row_offsets() { + m_vector_of_row_offsets.clear(); + m_vector_of_row_offsets.resize(column_count(), -1); +} + +template void static_matrix::init_empty_matrix(unsigned m, unsigned n) { + init_vector_of_row_offsets(); + init_row_columns(m, n); +} + +template unsigned static_matrix::lowest_row_in_column(unsigned col) { + lean_assert(col < column_count()); + column_strip & colstrip = m_columns[col]; + lean_assert(colstrip.size() > 0); + unsigned ret = 0; + for (auto & t : colstrip) { + if (t.m_i > ret) { + ret = t.m_i; + } + } + return ret; +} + +template void static_matrix::add_columns_at_the_end(unsigned delta) { + for (unsigned i = 0; i < delta; i++) + add_column(); +} + +template void static_matrix::forget_last_columns(unsigned how_many_to_forget) { + lean_assert(m_columns.size() >= how_many_to_forget); + unsigned j = column_count() - 1; + for (; how_many_to_forget > 0; how_many_to_forget--) { + remove_last_column(j --); + } +} + +template void static_matrix::remove_last_column(unsigned j) { + column_strip & col = m_columns.back(); + for (auto & it : col) { + auto & row = m_rows[it.m_i]; + unsigned offset = row.size() - 1; + for (auto row_it = row.rbegin(); row_it != row.rend(); row_it ++) { + if (row_it->m_j == j) { + row.erase(row.begin() + offset); + break; + } + offset--; + } + } + m_columns.pop_back(); + m_vector_of_row_offsets.pop_back(); +} + + + + +template void static_matrix::set(unsigned row, unsigned col, T const & val) { + if (numeric_traits::is_zero(val)) return; + lean_assert(row < row_count() && col < column_count()); + auto & r = m_rows[row]; + unsigned offs_in_cols = static_cast(m_columns[col].size()); + m_columns[col].push_back(make_column_cell(row, static_cast(r.size()))); + r.push_back(make_row_cell(col, offs_in_cols, val)); +} + +template +std::set> static_matrix::get_domain() { + std::set> ret; + for (unsigned i = 0; i < m_rows.size(); i++) { + for (auto it : m_rows[i]) { + ret.insert(std::make_pair(i, it.m_j)); + } + } + return ret; +} + + +template void static_matrix::copy_column_to_indexed_vector (unsigned j, indexed_vector & v) const { + lean_assert(j < m_columns.size()); + for (auto & it : m_columns[j]) { + const T& val = get_val(it); + if (!is_zero(val)) + v.set_value(val, it.m_i); + } +} + + + +template T static_matrix::get_max_abs_in_row(unsigned row) const { + T ret = numeric_traits::zero(); + for (auto & t : m_rows[row]) { + T a = abs(t.get_val()); + if (a > ret) { + ret = a; + } + } + return ret; +} + +template T static_matrix::get_min_abs_in_row(unsigned row) const { + bool first_time = true; + T ret = numeric_traits::zero(); + for (auto & t : m_rows[row]) { + T a = abs(t.get_val()); + if (first_time) { + ret = a; + first_time = false; + } else if (a < ret) { + ret = a; + } + } + return ret; +} + + +template T static_matrix::get_max_abs_in_column(unsigned column) const { + T ret = numeric_traits::zero(); + for (const auto & t : m_columns[column]) { + T a = abs(get_val(t)); + if (a > ret) { + ret = a; + } + } + return ret; +} + +template T static_matrix::get_min_abs_in_column(unsigned column) const { + bool first_time = true; + T ret = numeric_traits::zero(); + for (auto & t : m_columns[column]) { + T a = abs(get_val(t)); + if (first_time) { + first_time = false; + ret = a; + } else if (a < ret) { + ret = a; + } + } + return ret; +} + +#ifdef LEAN_DEBUG +template void static_matrix::check_consistency() { + std::unordered_map, T> by_rows; + for (int i = 0; i < m_rows.size(); i++){ + for (auto & t : m_rows[i]) { + std::pair p(i, t.m_j); + lean_assert(by_rows.find(p) == by_rows.end()); + by_rows[p] = t.get_val(); + } + } + std::unordered_map, T> by_cols; + for (int i = 0; i < m_columns.size(); i++){ + for (auto & t : m_columns[i]) { + std::pair p(t.m_i, i); + lean_assert(by_cols.find(p) == by_cols.end()); + by_cols[p] = get_val(t); + } + } + lean_assert(by_rows.size() == by_cols.size()); + + for (auto & t : by_rows) { + auto ic = by_cols.find(t.first); + if (ic == by_cols.end()){ + //std::cout << "rows have pair (" << t.first.first <<"," << t.first.second + // << "), but columns don't " << std::endl; + } + lean_assert(ic != by_cols.end()); + lean_assert(t.second == ic->second); + } +} +#endif + + +template void static_matrix::cross_out_row(unsigned k) { +#ifdef LEAN_DEBUG + check_consistency(); +#endif + cross_out_row_from_columns(k, m_rows[k]); + fix_row_indices_in_each_column_for_crossed_row(k); + m_rows.erase(m_rows.begin() + k); +#ifdef LEAN_DEBUG + regen_domain(); + check_consistency(); +#endif +} + + +template void static_matrix::fix_row_indices_in_each_column_for_crossed_row(unsigned k) { + for (unsigned j = 0; j < m_columns.size(); j++) { + auto & col = m_columns[j]; + for (int i = 0; i < col.size(); i++) { + if (col[i].m_i > k) { + col[i].m_i--; + } + } + } +} + +template void static_matrix::cross_out_row_from_columns(unsigned k, row_strip & row) { + for (auto & t : row) { + cross_out_row_from_column(t.m_j, k); + } +} + +template void static_matrix::cross_out_row_from_column(unsigned col, unsigned k) { + auto & s = m_columns[col]; + for (unsigned i = 0; i < s.size(); i++) { + if (s[i].m_i == k) { + s.erase(s.begin() + i); + break; + } + } +} + +template T static_matrix::get_elem(unsigned i, unsigned j) const { // should not be used in efficient code !!!! + for (auto & t : m_rows[i]) { + if (t.m_j == j) { + return t.get_val(); + } + } + return numeric_traits::zero(); +} + + +template T static_matrix::get_balance() const { + T ret = zero_of_type(); + for (unsigned i = 0; i < row_count(); i++) { + ret += get_row_balance(i); + } + return ret; +} + +template T static_matrix::get_row_balance(unsigned row) const { + T ret = zero_of_type(); + for (auto & t : m_rows[row]) { + if (numeric_traits::is_zero(t.get_val())) continue; + T a = abs(t.get_val()); + numeric_traits::log(a); + ret += a * a; + } + return ret; +} + +template bool static_matrix::is_correct() const { + for (auto & r : m_rows) { + std::unordered_set s; + for (auto & rc : r) { + if (s.find(rc.m_j) != s.end()) { + std::cout << "found column " << rc.m_j << " twice in a row" << std::endl; + return false; + } + s.insert(rc.m_j); + if (rc.m_j >= m_columns.size()) + return false; + if (rc.m_offset >= m_columns[rc.m_j].size()) + return false; + if (rc.get_val() != get_val(m_columns[rc.m_j][rc.m_offset])) + return false; + } + } + for (auto & c : m_columns) { + std::unordered_set s; + for (auto & cc : c) { + if (s.find(cc.m_i) != s.end()) { + std::cout << "found row " << cc.m_i << " twice in a column" << std::endl; + return false; + } + s.insert(cc.m_i); + if (cc.m_i >= m_rows.size()) + return false; + if (cc.m_offset >= m_rows[cc.m_i].size()) + return false; + if (get_val(cc) != m_rows[cc.m_i][cc.m_offset].get_val()) + return false; + } + } + + + + return true; +} + +template +void static_matrix::remove_element(vector> & row_vals, row_cell & row_el_iv) { + unsigned column_offset = row_el_iv.m_offset; + auto & column_vals = m_columns[row_el_iv.m_j]; + column_cell& cs = m_columns[row_el_iv.m_j][column_offset]; + unsigned row_offset = cs.m_offset; + if (column_offset != column_vals.size() - 1) { + auto & cc = column_vals[column_offset] = column_vals.back(); // copy from the tail + m_rows[cc.m_i][cc.m_offset].m_offset = column_offset; + } + + if (row_offset != row_vals.size() - 1) { + auto & rc = row_vals[row_offset] = row_vals.back(); // copy from the tail + m_columns[rc.m_j][rc.m_offset].m_offset = row_offset; + } + + column_vals.pop_back(); + row_vals.pop_back(); +} +template +void static_matrix::add_new_element(unsigned row, unsigned col, const T& val) { + auto & row_vals = m_rows[row]; + auto & col_vals = m_columns[col]; + unsigned row_el_offs = static_cast(row_vals.size()); + unsigned col_el_offs = static_cast(col_vals.size()); + row_vals.push_back(row_cell(col, col_el_offs, val)); + col_vals.push_back(column_cell(row, row_el_offs)); +} + +} diff --git a/src/util/lp/static_matrix_instances.cpp b/src/util/lp/static_matrix_instances.cpp new file mode 100644 index 000000000..d0e2045c0 --- /dev/null +++ b/src/util/lp/static_matrix_instances.cpp @@ -0,0 +1,69 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#include "util/vector.h" +#include +#include +#include +#include "util/lp/static_matrix.hpp" +#include "util/lp/lp_core_solver_base.h" +#include "util/lp/lp_dual_core_solver.h" +#include "util/lp/lp_dual_simplex.h" +#include "util/lp/lp_primal_core_solver.h" +#include "util/lp/scaler.h" +#include "util/lp/lar_solver.h" +namespace lean { +template void static_matrix::add_columns_at_the_end(unsigned int); +template void static_matrix::clear(); +#ifdef LEAN_DEBUG +template bool static_matrix::is_correct() const; +#endif +template void static_matrix::copy_column_to_indexed_vector(unsigned int, indexed_vector&) const; + +template double static_matrix::get_balance() const; +template std::set> static_matrix::get_domain(); +template std::set> lean::static_matrix::get_domain(); +template std::set> lean::static_matrix >::get_domain(); +template double static_matrix::get_elem(unsigned int, unsigned int) const; +template double static_matrix::get_max_abs_in_column(unsigned int) const; +template double static_matrix::get_min_abs_in_column(unsigned int) const; +template double static_matrix::get_min_abs_in_row(unsigned int) const; +template void static_matrix::init_empty_matrix(unsigned int, unsigned int); +template void static_matrix::init_row_columns(unsigned int, unsigned int); +template static_matrix::ref & static_matrix::ref::operator=(double const&); +template void static_matrix::set(unsigned int, unsigned int, double const&); +template static_matrix::static_matrix(unsigned int, unsigned int); +template void static_matrix::add_column_to_vector(mpq const&, unsigned int, mpq*) const; +template void static_matrix::add_columns_at_the_end(unsigned int); +template bool static_matrix::is_correct() const; +template void static_matrix::copy_column_to_indexed_vector(unsigned int, indexed_vector&) const; + +template mpq static_matrix::get_balance() const; +template mpq static_matrix::get_elem(unsigned int, unsigned int) const; +template mpq static_matrix::get_max_abs_in_column(unsigned int) const; +template mpq static_matrix::get_max_abs_in_row(unsigned int) const; +template double static_matrix::get_max_abs_in_row(unsigned int) const; +template mpq static_matrix::get_min_abs_in_column(unsigned int) const; +template mpq static_matrix::get_min_abs_in_row(unsigned int) const; +template void static_matrix::init_row_columns(unsigned int, unsigned int); +template static_matrix::ref& static_matrix::ref::operator=(mpq const&); +template void static_matrix::set(unsigned int, unsigned int, mpq const&); + +template static_matrix::static_matrix(unsigned int, unsigned int); +#ifdef LEAN_DEBUG +template bool static_matrix >::is_correct() const; +#endif +template void static_matrix >::copy_column_to_indexed_vector(unsigned int, indexed_vector&) const; +template mpq static_matrix >::get_elem(unsigned int, unsigned int) const; +template void static_matrix >::init_empty_matrix(unsigned int, unsigned int); +template void static_matrix >::set(unsigned int, unsigned int, mpq const&); + + +template bool lean::static_matrix::pivot_row_to_row_given_cell(unsigned int, column_cell &, unsigned int); +template bool lean::static_matrix::pivot_row_to_row_given_cell(unsigned int, column_cell& , unsigned int); +template bool lean::static_matrix >::pivot_row_to_row_given_cell(unsigned int, column_cell&, unsigned int); +template void lean::static_matrix >::remove_element(vector, true, unsigned int>&, lean::row_cell&); + +} + diff --git a/src/util/lp/tail_matrix.h b/src/util/lp/tail_matrix.h new file mode 100644 index 000000000..c337b0933 --- /dev/null +++ b/src/util/lp/tail_matrix.h @@ -0,0 +1,28 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ + +#pragma once +#include "util/vector.h" +#include "util/lp/indexed_vector.h" +#include "util/lp/matrix.h" +#include "util/lp/lp_settings.h" +// These matrices appear at the end of the list + +namespace lean { +template +class tail_matrix +#ifdef LEAN_DEBUG + : public matrix +#endif +{ +public: + virtual void apply_from_left_to_T(indexed_vector & w, lp_settings & settings) = 0; + virtual void apply_from_left(vector & w, lp_settings & settings) = 0; + virtual void apply_from_right(vector & w) = 0; + virtual void apply_from_right(indexed_vector & w) = 0; + virtual ~tail_matrix() {} + virtual bool is_dense() const = 0; +}; +} diff --git a/src/util/lp/test_bound_analyzer.h b/src/util/lp/test_bound_analyzer.h new file mode 100644 index 000000000..262c610c7 --- /dev/null +++ b/src/util/lp/test_bound_analyzer.h @@ -0,0 +1,260 @@ +/* + Copyright (c) 2017 Microsoft Corporation + Author: Lev Nachmanson +*/ +#pragma once +#include "util/vector.h" +#include "util/lp/linear_combination_iterator.h" +#include "util/lp/implied_bound.h" +#include "util/lp/lp_settings.h" +#include +// this class is for testing only + +// We have an equality : sum by j of row[j]*x[j] = rs +// We try to pin a var by pushing the total by using the variable bounds +// In a loop we drive the partial sum down, denoting the variables of this process by _u. +// In the same loop trying to pin variables by pushing the partial sum up, denoting the variable related to it by _l + +// here in addition we assume that all coefficient in the row are positive +namespace lean { + +class test_bound_analyzer { + linear_combination_iterator & m_it; + std::function& m_low_bounds; + std::function& m_upper_bounds; + std::function m_column_types; + vector & m_implied_bounds; + vector m_coeffs; + int m_coeff_sign; + vector m_index; + unsigned m_row_or_term_index; + std::function & m_try_get_found_bound; +public : + // constructor + test_bound_analyzer(linear_combination_iterator &it, + std::function & low_bounds, + std::function & upper_bounds, + std::function column_types, + vector & evidence_vector, + unsigned row_or_term_index, + std::function & try_get_found_bound) : + m_it(it), + m_low_bounds(low_bounds), + m_upper_bounds(upper_bounds), + m_column_types(column_types), + m_implied_bounds(evidence_vector), + m_row_or_term_index(row_or_term_index), + m_try_get_found_bound(try_get_found_bound) + { + m_it.reset(); + unsigned i; + mpq a; + while (m_it.next(a, i) ) { + m_coeffs.push_back(a); + m_index.push_back(i); + } + } + + static int sign (const mpq & t) { return is_pos(t) ? 1: -1;} + + void analyze() { + // We have the equality sum by j of row[j]*x[j] = m_rs + // We try to pin a var by pushing the total of the partial sum down, denoting the variable of this process by _u. + for (unsigned i = 0; i < m_index.size(); i++) { + analyze_i(i); + } + } + void analyze_i(unsigned i) { + // set the m_coeff_is_pos + m_coeff_sign = sign(m_coeffs[i]); + analyze_i_for_lower(i); + analyze_i_for_upper(i); + } + + void analyze_i_for_upper(unsigned i) { + mpq l; + bool strict = false; + lean_assert(is_zero(l)); + for (unsigned k = 0; k < m_index.size(); k++) { + if (k == i) + continue; + mpq lb; + bool str; + if (!upper_bound_of_monoid(k, lb, str)) { + return; + } + l += lb; + if (str) + strict = true; + } + l /= m_coeffs[i]; + unsigned j = m_index[i]; + switch(m_column_types(j)) { + case column_type::fixed: + case column_type::boxed: + case column_type::upper_bound: + { + const auto & lb = m_upper_bounds(j); + if (l > lb.x || (l == lb.x && !(is_zero(lb.y) && strict))) { + break; // no improvement on the existing upper bound + } + } + default: + // std::cout << " got an upper bound with " << T_to_string(l) << "\n"; + m_implied_bounds.push_back(implied_bound(l, j, false, is_pos(m_coeffs[i]), m_row_or_term_index, strict)); + } + } + + + bool low_bound_of_monoid(unsigned k, mpq & lb, bool &strict) const { + int s = - m_coeff_sign * sign(m_coeffs[k]); + unsigned j = m_index[k]; + if (s > 0) { + switch(m_column_types(j)) { + case column_type::fixed: + case column_type::boxed: + case column_type::low_bound: + lb = -m_coeffs[k] * m_low_bounds(j).x; + strict = !is_zero(m_low_bounds(j).y); + return true; + default: + return false; + } + } + + switch(m_column_types(j)) { + case column_type::fixed: + case column_type::boxed: + case column_type::upper_bound: + lb = -m_coeffs[k] * m_upper_bounds(j).x; + strict = !is_zero(m_upper_bounds(j).y); + return true; + default: + return false; + } + } + + bool upper_bound_of_monoid(unsigned k, mpq & lb, bool & strict) const { + int s = - m_coeff_sign * sign(m_coeffs[k]); + unsigned j = m_index[k]; + if (s > 0) { + switch(m_column_types(j)) { + case column_type::fixed: + case column_type::boxed: + case column_type::upper_bound: + lb = -m_coeffs[k] * m_upper_bounds(j).x; + strict = !is_zero(m_upper_bounds(j).y); + + return true; + default: + return false; + } + } + + switch(m_column_types(j)) { + case column_type::fixed: + case column_type::boxed: + case column_type::low_bound: + lb = -m_coeffs[k] * m_low_bounds(j).x; + strict = !is_zero(m_low_bounds(j).y); + return true; + default: + return false; + } + } + + void analyze_i_for_lower(unsigned i) { + mpq l; + lean_assert(is_zero(l)); + bool strict = false; + for (unsigned k = 0; k < m_index.size(); k++) { + if (k == i) + continue; + mpq lb; + bool str; + if (!low_bound_of_monoid(k, lb, str)) { + return; + } + if (str) + strict = true; + l += lb; + } + l /= m_coeffs[i]; + unsigned j = m_index[i]; + switch(m_column_types(j)) { + case column_type::fixed: + case column_type::boxed: + case column_type::low_bound: + { + const auto & lb = m_low_bounds(j); + if (l < lb.x || (l == lb.x && !(is_zero(lb.y) && strict))) { + break; // no improvement on the existing upper bound + } + } + default: + // std::cout << " got a lower bound with " << T_to_string(l) << "\n"; + m_implied_bounds.push_back(implied_bound(l, j, true, is_pos(m_coeffs[i]), m_row_or_term_index, strict)); + } + } + + bool low_bound_is_available(unsigned j) const { + switch(m_column_types(j)) { + case column_type::fixed: + case column_type::boxed: + case column_type::low_bound: + return true; + default: + return false; + } + } + + bool upper_bound_is_available(unsigned j) const { + switch(m_column_types(j)) { + case column_type::fixed: + case column_type::boxed: + case column_type::upper_bound: + return true; + default: + return false; + } + } + + bool try_get_best_avail_bound(unsigned j, bool is_lower_bound, mpq & best_bound, bool & strict_of_best_bound) const { + if (m_try_get_found_bound(j, is_lower_bound, best_bound, strict_of_best_bound)) { + return true; + } + if (is_lower_bound) { + if (low_bound_is_available(j)) { + best_bound = m_low_bounds(j).x; + strict_of_best_bound = !is_zero(m_low_bounds(j).y); + return true; + } + } else { + if (upper_bound_is_available(j)) { + best_bound = m_upper_bounds(j).x; + strict_of_best_bound = !is_zero(m_low_bounds(j).y); + return true; + } + } + return false; + } + + bool bound_is_new(unsigned j, const mpq& b, bool is_lower_bound, bool strict) const { + mpq best_bound; + bool strict_of_best_bound; + if (try_get_best_avail_bound(j, is_lower_bound, best_bound, strict_of_best_bound)) { + if (is_lower_bound) { + if (b > best_bound || ( b != best_bound && (strict && !strict_of_best_bound))) // the second clouse stands for strong inequality + return true; + } else { + if (b < best_bound || ( b == best_bound && (strict && !strict_of_best_bound))) + return true; + } + return false; + } + return true; + } + +}; + +} diff --git a/src/util/lp/ul_pair.h b/src/util/lp/ul_pair.h new file mode 100644 index 000000000..2e77a7db0 --- /dev/null +++ b/src/util/lp/ul_pair.h @@ -0,0 +1,65 @@ +/* + 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; + constraint_index m_upper_bound_witness; +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; + 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() : + m_low_bound_witness(static_cast(-1)), + m_upper_bound_witness(static_cast(-1)), + m_i(static_cast(-1)) +{} + ul_pair(row_index ri) : + m_low_bound_witness(static_cast(-1)), + m_upper_bound_witness(static_cast(-1)), + 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..67947b602 100644 --- a/src/util/mpz.h +++ b/src/util/mpz.h @@ -497,7 +497,9 @@ public: STRACE("mpz", tout << "[mpz] 0 - " << to_string(a) << " == ";); if (is_small(a) && a.m_val == INT_MIN) { // neg(INT_MIN) is not a small int + MPZ_BEGIN_CRITICAL(); set_big_i64(a, - static_cast(INT_MIN)); + MPZ_END_CRITICAL(); return; } #ifndef _MP_GMP @@ -518,7 +520,9 @@ public: if (a.m_val < 0) { if (a.m_val == INT_MIN) { // abs(INT_MIN) is not a small int + MPZ_BEGIN_CRITICAL(); set_big_i64(a, - static_cast(INT_MIN)); + MPZ_END_CRITICAL(); } else a.m_val = -a.m_val; @@ -593,6 +597,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/small_object_allocator.cpp b/src/util/small_object_allocator.cpp index 60c85b660..ac2f64482 100644 --- a/src/util/small_object_allocator.cpp +++ b/src/util/small_object_allocator.cpp @@ -68,6 +68,7 @@ void small_object_allocator::reset() { #define MASK ((1 << PTR_ALIGNMENT) - 1) + void small_object_allocator::deallocate(size_t size, void * p) { if (size == 0) return; @@ -92,6 +93,7 @@ void small_object_allocator::deallocate(size_t size, void * p) { m_free_list[slot_id] = p; } + void * small_object_allocator::allocate(size_t size) { if (size == 0) return 0; @@ -100,8 +102,9 @@ void * small_object_allocator::allocate(size_t size) { return memory::allocate(size); #endif m_alloc_size += size; - if (size >= SMALL_OBJ_SIZE - (1 << PTR_ALIGNMENT)) + if (size >= SMALL_OBJ_SIZE - (1 << PTR_ALIGNMENT)) { return memory::allocate(size); + } #ifdef Z3DEBUG size_t osize = size; #endif diff --git a/src/util/sorting_network.h b/src/util/sorting_network.h index 33514730f..2a0f8b37d 100644 --- a/src/util/sorting_network.h +++ b/src/util/sorting_network.h @@ -337,7 +337,7 @@ Notes: } literal mk_at_most_1(bool full, unsigned n, literal const* xs, literal_vector& ors, bool use_ors) { - TRACE("pb", tout << (full?"full":"partial") << " "; + TRACE("pb_verbose", tout << (full?"full":"partial") << " "; for (unsigned i = 0; i < n; ++i) tout << xs[i] << " "; tout << "\n";); @@ -513,7 +513,7 @@ Notes: for (unsigned i = 0; i < N; ++i) { in.push_back(ctx.mk_not(xs[i])); } - TRACE("pb", + TRACE("pb_verbose", pp(tout << N << ": ", in); tout << " ~ " << k << "\n";); return true; @@ -595,7 +595,7 @@ Notes: } void card(unsigned k, unsigned n, literal const* xs, literal_vector& out) { - TRACE("pb", tout << "card k: " << k << " n: " << n << "\n";); + TRACE("pb_verbose", tout << "card k: " << k << " n: " << n << "\n";); if (n <= k) { psort_nw::sorting(n, xs, out); } @@ -609,7 +609,7 @@ Notes: card(k, n-l, xs + l, out2); smerge(k, out1.size(), out1.c_ptr(), out2.size(), out2.c_ptr(), out); } - TRACE("pb", tout << "card k: " << k << " n: " << n << "\n"; + TRACE("pb_verbose", tout << "card k: " << k << " n: " << n << "\n"; pp(tout << "in:", n, xs) << "\n"; pp(tout << "out:", out) << "\n";); @@ -637,7 +637,7 @@ Notes: void merge(unsigned a, literal const* as, unsigned b, literal const* bs, literal_vector& out) { - TRACE("pb", tout << "merge a: " << a << " b: " << b << "\n";); + TRACE("pb_verbose", tout << "merge a: " << a << " b: " << b << "\n";); if (a == 1 && b == 1) { literal y1 = mk_max(as[0], bs[0]); literal y2 = mk_min(as[0], bs[0]); @@ -672,7 +672,7 @@ Notes: odd_b.size(), odd_b.c_ptr(), out2); interleave(out1, out2, out); } - TRACE("pb", tout << "merge a: " << a << " b: " << b << "\n"; + TRACE("pb_verbose", tout << "merge a: " << a << " b: " << b << "\n"; pp(tout << "a:", a, as) << "\n"; pp(tout << "b:", b, bs) << "\n"; pp(tout << "out:", out) << "\n";); @@ -709,7 +709,7 @@ Notes: void interleave(literal_vector const& as, literal_vector const& bs, literal_vector& out) { - TRACE("pb", tout << "interleave: " << as.size() << " " << bs.size() << "\n";); + TRACE("pb_verbose", tout << "interleave: " << as.size() << " " << bs.size() << "\n";); SASSERT(as.size() >= bs.size()); SASSERT(as.size() <= bs.size() + 2); SASSERT(!as.empty()); @@ -729,7 +729,7 @@ Notes: out.push_back(as[sz+1]); } SASSERT(out.size() == as.size() + bs.size()); - TRACE("pb", tout << "interleave: " << as.size() << " " << bs.size() << "\n"; + TRACE("pb_verbose", tout << "interleave: " << as.size() << " " << bs.size() << "\n"; pp(tout << "a: ", as) << "\n"; pp(tout << "b: ", bs) << "\n"; pp(tout << "out: ", out) << "\n";); @@ -741,7 +741,7 @@ Notes: public: void sorting(unsigned n, literal const* xs, literal_vector& out) { - TRACE("pb", tout << "sorting: " << n << "\n";); + TRACE("pb_verbose", tout << "sorting: " << n << "\n";); switch(n) { case 0: break; @@ -766,7 +766,7 @@ Notes: } break; } - TRACE("pb", tout << "sorting: " << n << "\n"; + TRACE("pb_verbose", tout << "sorting: " << n << "\n"; pp(tout << "in:", n, xs) << "\n"; pp(tout << "out:", out) << "\n";); } @@ -802,7 +802,7 @@ Notes: unsigned a, literal const* as, unsigned b, literal const* bs, literal_vector& out) { - TRACE("pb", tout << "smerge: c:" << c << " a:" << a << " b:" << b << "\n";); + TRACE("pb_verbose", tout << "smerge: c:" << c << " a:" << a << " b:" << b << "\n";); if (a == 1 && b == 1 && c == 1) { literal y = mk_max(as[0], bs[0]); if (m_t != GE) { @@ -876,7 +876,7 @@ Notes: out.push_back(y); } } - TRACE("pb", tout << "smerge: c:" << c << " a:" << a << " b:" << b << "\n"; + TRACE("pb_verbose", tout << "smerge: c:" << c << " a:" << a << " b:" << b << "\n"; pp(tout << "a:", a, as) << "\n"; pp(tout << "b:", b, bs) << "\n"; pp(tout << "out:", out) << "\n"; @@ -920,7 +920,7 @@ Notes: unsigned a, literal const* as, unsigned b, literal const* bs, literal_vector& out) { - TRACE("pb", tout << "dsmerge: c:" << c << " a:" << a << " b:" << b << "\n";); + TRACE("pb_verbose", tout << "dsmerge: c:" << c << " a:" << a << " b:" << b << "\n";); SASSERT(a <= c); SASSERT(b <= c); SASSERT(a + b >= c); @@ -979,7 +979,7 @@ Notes: void dsorting(unsigned m, unsigned n, literal const* xs, literal_vector& out) { - TRACE("pb", tout << "dsorting m: " << m << " n: " << n << "\n";); + TRACE("pb_verbose", tout << "dsorting m: " << m << " n: " << n << "\n";); SASSERT(m <= n); literal_vector lits; for (unsigned i = 0; i < m; ++i) { @@ -1014,7 +1014,7 @@ Notes: void add_subset(bool polarity, unsigned k, unsigned offset, literal_vector& lits, unsigned n, literal const* xs) { - TRACE("pb", tout << "k:" << k << " offset: " << offset << " n: " << n << " "; + TRACE("pb_verbose", tout << "k:" << k << " offset: " << offset << " n: " << n << " "; pp(tout, lits) << "\n";); SASSERT(k + offset <= n); if (k == 0) { diff --git a/src/util/sstream.h b/src/util/sstream.h new file mode 100644 index 000000000..23d5bdfbb --- /dev/null +++ b/src/util/sstream.h @@ -0,0 +1,21 @@ + + +/* +Copyright (c) 2013 Microsoft Corporation. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. + +Author: Leonardo de Moura +*/ +#pragma once +#include +#include + +namespace lean { +/** \brief Wrapper for std::ostringstream */ +class sstream { + std::ostringstream m_strm; +public: + std::string str() const { return m_strm.str(); } + template sstream & operator<<(T const & t) { m_strm << t; return *this; } +}; +} diff --git a/src/util/vector.h b/src/util/vector.h index 7ffee2535..7e89a773c 100644 --- a/src/util/vector.h +++ b/src/util/vector.h @@ -117,6 +117,10 @@ public: } vector(SZ s) { + if (s == 0) { + m_data = 0; + return; + } SZ * mem = reinterpret_cast(memory::allocate(sizeof(T) * s + sizeof(SZ) * 2)); *mem = s; mem++; @@ -184,6 +188,8 @@ public: } } + void clear() { reset(); } + bool empty() const { return m_data == 0 || reinterpret_cast(m_data)[SIZE_IDX] == 0; } @@ -218,6 +224,33 @@ public: return m_data + size(); } + class reverse_iterator { + T* v; + public: + reverse_iterator(T* v):v(v) {} + + T operator*() { return *v; } + reverse_iterator operator++(int) { + reverse_iterator tmp = *this; + --v; + return tmp; + } + reverse_iterator& operator++() { + --v; + return *this; + } + + bool operator==(reverse_iterator const& other) const { + return other.v == v; + } + bool operator!=(reverse_iterator const& other) const { + return other.v != v; + } + }; + + reverse_iterator rbegin() { return reverse_iterator(end() - 1); } + reverse_iterator rend() { return reverse_iterator(begin() - 1); } + void set_end(iterator it) { if (m_data) { SZ new_sz = static_cast(it - m_data); @@ -362,8 +395,8 @@ public: void reverse() { SZ sz = size(); for (SZ i = 0; i < sz/2; ++i) { - std::swap(m_data[i], m_data[sz-i-1]); - } + std::swap(m_data[i], m_data[sz-i-1]); + } } void fill(T const & elem) { @@ -461,16 +494,23 @@ struct vector_hash : public vector_hash_tpl > template struct svector_hash : public vector_hash_tpl > {}; - -// Specialize vector to be inaccessible. +#include +// Specialize vector to be an instance of std::vector instead. // This will catch any regression of issue #564 and #420. -// Use std::vector instead. + template <> -class vector { -private: - vector(); +class vector : public std::vector { +public: + vector(vector const& other): std::vector(other) {} + vector(size_t sz, char const* s): std::vector(sz, s) {} + vector() {} + + void reset() { clear(); } + + }; + #endif /* VECTOR_H_ */