From 73d160e4d60c696f61357aa1da3e3105de9f5bd4 Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 17 Nov 2025 03:26:02 -0800 Subject: [PATCH 1/3] code dump --- .clang-format | 44 + .github/cmake.yml | 27 + .github/formatting.yml | 38 + .github/sanitizer.yml | 33 + .github/valgrind.yml | 26 + .gitignore | 75 + CMakeLists.txt | 197 +++ README.md | 9 +- cmake/AddInstallRPATHSupport.cmake | 237 +++ cmake/AddUninstallTarget.cmake | 70 + cmake/InstallBasicPackageFiles.cmake | 677 +++++++++ cmake/PSLPConfig.cmake.in | 3 + include/PSLP/API.h | 119 ++ include/PSLP/PresolveStatus.h | 26 + include/PSLP/Sol.h | 42 + include/core/Activity.h | 165 +++ include/core/Bounds.h | 28 + include/core/Constraints.h | 92 ++ include/core/CoreTransformations.h | 126 ++ include/core/Debugger.h | 130 ++ include/core/Locks.h | 81 + include/core/Matrix.h | 125 ++ include/core/Numerics.h | 60 + include/core/Postsolver.h | 188 +++ include/core/PresolveStats.h | 50 + include/core/Problem.h | 60 + include/core/RowColViews.h | 136 ++ include/core/State.h | 64 + include/core/Tags.h | 60 + include/core/Workspace.h | 55 + include/core/debug_macros.h | 75 + include/core/glbopts.h | 49 + include/core/utils.h | 35 + include/data_structures/Memory_wrapper.h | 62 + include/data_structures/Vec_macros.h | 129 ++ include/data_structures/dVec.h | 26 + include/data_structures/iVec.h | 38 + include/data_structures/u16Vec.h | 31 + include/explorers/DTonsEq.h | 87 ++ include/explorers/Parallel_cols.h | 33 + include/explorers/Parallel_rows.h | 70 + include/explorers/Primal_propagation.h | 80 + include/explorers/SimpleReductions.h | 116 ++ include/explorers/Simple_dual_fix.h | 35 + include/explorers/StonCols.h | 69 + include/explorers/Timer.h | 43 + src/core/Activity.c | 834 +++++++++++ src/core/Constraints.c | 322 ++++ src/core/CoreTransformation.c | 533 +++++++ src/core/Debugger.c | 811 ++++++++++ src/core/Locks.c | 190 +++ src/core/Matrix.c | 611 ++++++++ src/core/Postsolver.c | 844 +++++++++++ src/core/Presolver.c | 668 +++++++++ src/core/Problem.c | 110 ++ src/core/State.c | 208 +++ src/core/Tags.c | 48 + src/core/Workspace.c | 70 + src/core/utils.c | 100 ++ src/explorers/DtonsEq.c | 780 ++++++++++ src/explorers/Parallel_cols.c | 343 +++++ src/explorers/Parallel_rows.c | 612 ++++++++ src/explorers/Primal_propagation.c | 886 +++++++++++ src/explorers/SimpleReductions.c | 703 +++++++++ src/explorers/Simple_dual_fix.c | 178 +++ src/explorers/StonCols.c | 716 +++++++++ tests/all_tests.c | 41 + tests/minunit.h | 34 + tests/test_Constraints.h | 364 +++++ tests/test_CoreTransformations.h | 86 ++ tests/test_Matrix.h | 810 ++++++++++ tests/test_Parallel_cols.h | 609 ++++++++ tests/test_Parallel_rows.h | 1287 ++++++++++++++++ tests/test_Presolver.h | 64 + tests/test_SimpleReductions.h | 495 +++++++ tests/test_domain_propagation.h | 685 +++++++++ tests/test_dton.h | 1731 ++++++++++++++++++++++ tests/test_iVec.h | 90 ++ tests/test_macros.h | 154 ++ tests/test_postsolve.h | 1002 +++++++++++++ tests/test_ston.h | 1104 ++++++++++++++ 81 files changed, 22141 insertions(+), 3 deletions(-) create mode 100644 .clang-format create mode 100644 .github/cmake.yml create mode 100644 .github/formatting.yml create mode 100644 .github/sanitizer.yml create mode 100644 .github/valgrind.yml create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 cmake/AddInstallRPATHSupport.cmake create mode 100644 cmake/AddUninstallTarget.cmake create mode 100644 cmake/InstallBasicPackageFiles.cmake create mode 100644 cmake/PSLPConfig.cmake.in create mode 100644 include/PSLP/API.h create mode 100644 include/PSLP/PresolveStatus.h create mode 100644 include/PSLP/Sol.h create mode 100644 include/core/Activity.h create mode 100644 include/core/Bounds.h create mode 100644 include/core/Constraints.h create mode 100644 include/core/CoreTransformations.h create mode 100644 include/core/Debugger.h create mode 100644 include/core/Locks.h create mode 100644 include/core/Matrix.h create mode 100644 include/core/Numerics.h create mode 100644 include/core/Postsolver.h create mode 100644 include/core/PresolveStats.h create mode 100644 include/core/Problem.h create mode 100644 include/core/RowColViews.h create mode 100644 include/core/State.h create mode 100644 include/core/Tags.h create mode 100644 include/core/Workspace.h create mode 100644 include/core/debug_macros.h create mode 100644 include/core/glbopts.h create mode 100644 include/core/utils.h create mode 100644 include/data_structures/Memory_wrapper.h create mode 100644 include/data_structures/Vec_macros.h create mode 100644 include/data_structures/dVec.h create mode 100644 include/data_structures/iVec.h create mode 100644 include/data_structures/u16Vec.h create mode 100644 include/explorers/DTonsEq.h create mode 100644 include/explorers/Parallel_cols.h create mode 100644 include/explorers/Parallel_rows.h create mode 100644 include/explorers/Primal_propagation.h create mode 100644 include/explorers/SimpleReductions.h create mode 100644 include/explorers/Simple_dual_fix.h create mode 100644 include/explorers/StonCols.h create mode 100644 include/explorers/Timer.h create mode 100644 src/core/Activity.c create mode 100644 src/core/Constraints.c create mode 100644 src/core/CoreTransformation.c create mode 100644 src/core/Debugger.c create mode 100644 src/core/Locks.c create mode 100644 src/core/Matrix.c create mode 100644 src/core/Postsolver.c create mode 100644 src/core/Presolver.c create mode 100644 src/core/Problem.c create mode 100644 src/core/State.c create mode 100644 src/core/Tags.c create mode 100644 src/core/Workspace.c create mode 100644 src/core/utils.c create mode 100644 src/explorers/DtonsEq.c create mode 100644 src/explorers/Parallel_cols.c create mode 100644 src/explorers/Parallel_rows.c create mode 100644 src/explorers/Primal_propagation.c create mode 100644 src/explorers/SimpleReductions.c create mode 100644 src/explorers/Simple_dual_fix.c create mode 100644 src/explorers/StonCols.c create mode 100644 tests/all_tests.c create mode 100644 tests/minunit.h create mode 100644 tests/test_Constraints.h create mode 100644 tests/test_CoreTransformations.h create mode 100644 tests/test_Matrix.h create mode 100644 tests/test_Parallel_cols.h create mode 100644 tests/test_Parallel_rows.h create mode 100644 tests/test_Presolver.h create mode 100644 tests/test_SimpleReductions.h create mode 100644 tests/test_domain_propagation.h create mode 100644 tests/test_dton.h create mode 100644 tests/test_iVec.h create mode 100644 tests/test_macros.h create mode 100644 tests/test_postsolve.h create mode 100644 tests/test_ston.h diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000..780c5366 --- /dev/null +++ b/.clang-format @@ -0,0 +1,44 @@ +# .clang-format for C project with Allman style braces +BasedOnStyle: LLVM + +# Indentation +IndentWidth: 4 +UseTab: Never +TabWidth: 4 + +# Line length +ColumnLimit: 85 +BinPackParameters: true +BinPackArguments: true + +# Braces style +BreakBeforeBraces: Allman # Allman style: brace on next line +BraceWrapping: + AfterFunction: true + AfterControlStatement: true + AfterStruct: true + AfterClass: true + AfterNamespace: false + IndentBraces: false + +# Pointers +PointerAlignment: Right + +# Spaces +SpaceAfterCStyleCast: true +SpaceAfterLogicalNot: false +SpacesInParentheses: false +SpacesInContainerLiterals: false +SpaceBeforeParens: ControlStatements + +# Allow single-line short statements if simple +AllowShortIfStatementsOnASingleLine: true +AllowShortLoopsOnASingleLine: true +AllowShortFunctionsOnASingleLine: Inline +AllowAllParametersOfDeclarationOnNextLine: true + + +# Other +ReflowComments: true +IndentCaseLabels: true +AlignOperands: true diff --git a/.github/cmake.yml b/.github/cmake.yml new file mode 100644 index 00000000..3b434251 --- /dev/null +++ b/.github/cmake.yml @@ -0,0 +1,27 @@ +name: CI + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest] + build_type: [Release, Debug] + + steps: + - uses: actions/checkout@v3 + + - name: Configure CMake + run: cmake -B build -S . -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} + + - name: Build + run: cmake --build build --config ${{ matrix.build_type }} + + - name: Run tests + run: cd build && ctest -C ${{ matrix.build_type }} --output-on-failure diff --git a/.github/formatting.yml b/.github/formatting.yml new file mode 100644 index 00000000..98ba1036 --- /dev/null +++ b/.github/formatting.yml @@ -0,0 +1,38 @@ +name: Code Formatting + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + format: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest] + + steps: + - uses: actions/checkout@v5 + + # Install clang-format if needed + - name: Install clang-format (Linux) + if: matrix.os == 'ubuntu-latest' + run: sudo apt-get update && sudo apt-get install -y clang-format + + - name: Install clang-format (macOS) + if: matrix.os == 'macos-latest' + run: brew install clang-format || true + + # Check formatting + - name: Check C/C++ formatting + run: | + misformatted=$(find . -name '*.c' -o -name '*.h' -print0 | xargs -0 clang-format -style=file -output-replacements-xml | grep " + $ + PRIVATE + $ + $ + $ +) + +# Common compile options +target_compile_options(PSLP PRIVATE + -Wall -Wextra -Wpedantic -Wno-sign-compare +) + +# Config-specific compile options +target_compile_options(PSLP PRIVATE + $<$:-g -O0> + $<$:-O3 -DNDEBUG> + $<$:-O2 -g -DNDEBUG> + $<$:-Os -DNDEBUG> +) + +if(ENABLE_DEBUGGER OR CMAKE_BUILD_TYPE STREQUAL "Debug") + target_compile_definitions(PSLP PRIVATE DEBUGGER_ENABLED) +else() + target_compile_definitions(PSLP PRIVATE NDEBUG) +endif() + +# Testing +option(BUILD_TESTING "Build tests" ON) +if(BUILD_TESTING) + include(CTest) + enable_testing() + message(STATUS "Testing enabled") + + add_executable(all_tests tests/all_tests.c) + + # Link PSLP library + target_link_libraries(all_tests PRIVATE PSLP) + target_compile_definitions(PSLP PRIVATE TESTING) + + # Add include directories explicitly for the test target + target_include_directories(all_tests PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/include/core + ${CMAKE_CURRENT_SOURCE_DIR}/include/data_structures + ${CMAKE_CURRENT_SOURCE_DIR}/include/explorers + ${CMAKE_CURRENT_SOURCE_DIR}/include/PSLP + ) + + target_compile_definitions(all_tests PRIVATE TESTING) + + # Link math library on UNIX + if(UNIX) + target_link_libraries(all_tests PRIVATE m) + endif() + + add_test(NAME all_tests COMMAND all_tests) +else() + message(STATUS "Testing disabled") +endif() + +# Installation and export +install(TARGETS PSLP + EXPORT PSLPTargets + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/PSLP +) + +install(FILES ${PUBLIC_HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/PSLP) + +# Export the targets file +install(EXPORT PSLPTargets + FILE PSLPTargets.cmake + NAMESPACE PSLP:: + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/PSLP +) + +# Create and install Config file +include(CMakePackageConfigHelpers) +configure_package_config_file( + ${CMAKE_CURRENT_SOURCE_DIR}/cmake/PSLPConfig.cmake.in + ${CMAKE_CURRENT_BINARY_DIR}/PSLPConfig.cmake + INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/PSLP +) + +write_basic_package_version_file( + ${CMAKE_CURRENT_BINARY_DIR}/PSLPConfigVersion.cmake + VERSION ${PROJECT_VERSION} + COMPATIBILITY AnyNewerVersion +) + +install(FILES + ${CMAKE_CURRENT_BINARY_DIR}/PSLPConfig.cmake + ${CMAKE_CURRENT_BINARY_DIR}/PSLPConfigVersion.cmake + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/PSLP +) \ No newline at end of file diff --git a/README.md b/README.md index 355503f2..0986003c 100644 --- a/README.md +++ b/README.md @@ -23,10 +23,12 @@ dependencies. Happy presolving! ## Installation --- ## API -The public C API is defined in the header file `PSLP_API.h`, which contains all public data structures and function declarations. +The public C API is defined in the header file `PSLP/API.h" +`, which contains all public data structures and function declarations. After installing PSLP, you can include it in your project with: ```c -#include +#include ``` The API consists of three main operations: @@ -42,7 +44,8 @@ The API consists of three main operations: (The exact convention we use for the dual variables can be found in our XXX.) Detailed descriptions of each function and all associated data structures are -documented in `PSLP_API.h`. A video tutorial is available at XXX. +documented in `PSLP_"API.h" +`. A video tutorial is available at XXX. --- ## Citation diff --git a/cmake/AddInstallRPATHSupport.cmake b/cmake/AddInstallRPATHSupport.cmake new file mode 100644 index 00000000..cdbb20bd --- /dev/null +++ b/cmake/AddInstallRPATHSupport.cmake @@ -0,0 +1,237 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +#[=======================================================================[.rst: +AddInstallRPATHSupport +---------------------- + +Add support to RPATH during installation to the project and the targets + +.. command:: add_install_rpath_support + + Add support to RPATH during installation to the project:: + + .. code-block:: cmake + + add_install_rpath_support([BIN_DIRS dir [dir]] + [LIB_DIRS dir [dir]] + [INSTALL_NAME_DIR [dir]] + [DEPENDS condition [condition]] + [USE_LINK_PATH]) + + Normally (depending on the platform) when you install a shared + library you can either specify its absolute path as the install name, + or leave just the library name itself. In the former case the library + will be correctly linked during run time by all executables and other + shared libraries, but it must not change its install location. This + is often the case for libraries installed in the system default + library directory (e.g. ``/usr/lib``). + In the latter case, instead, the library can be moved anywhere in the + file system but at run time the dynamic linker must be able to find + it. This is often accomplished by setting environmental variables + (i.e. ``LD_LIBRARY_PATH`` on Linux). + This procedure is usually not desirable for two main reasons: + + - by setting the variable you are changing the default behaviour + of the dynamic linker thus potentially breaking executables (not as + destructive as ``LD_PRELOAD``) + - the variable will be used only by applications spawned by the shell + and not by other processes. + + RPATH aims in solving the issues introduced by the second + installation method. Using run-path dependent libraries you can + create a directory structure containing executables and dependent + libraries that users can relocate without breaking it. + A run-path dependent library is a dependent library whose complete + install name is not known when the library is created. + Instead, the library specifies that the dynamic loader must resolve + the library’s install name when it loads the executable that depends + on the library. The executable or the other shared library will + hardcode in the binary itself the additional search directories + to be passed to the dynamic linker. This works great in conjunction + with relative paths. + This command will enable support to RPATH to your project. + It will enable the following things: + + - If the project builds shared libraries it will generate a run-path + enabled shared library, i.e. its install name will be resolved + only at run time. + - In all cases (building executables and/or shared libraries) + dependent shared libraries with RPATH support will have their name + resolved only at run time, by embedding the search path directly + into the built binary. + + The command has the following parameters: + + Options: + - ``USE_LINK_PATH``: if passed the command will automatically adds to + the RPATH the path to all the dependent libraries. + + Arguments: + - ``BIN_DIRS`` list of directories when the targets (executable and + plugins) will be installed. + - ``LIB_DIRS`` list of directories to be added to the RPATH. These + directories will be added "relative" w.r.t. the ``BIN_DIRS`` and + ``LIB_DIRS``. + - ``INSTALL_NAME_DIR`` directory where the libraries will be installed. + This variable will be used only if ``CMAKE_SKIP_RPATH`` or + ``CMAKE_SKIP_INSTALL_RPATH`` is set to ``TRUE`` as it will set the + ``INSTALL_NAME_DIR`` on all targets + - ``DEPENDS`` list of conditions that should be ``TRUE`` to enable + RPATH, for example ``FOO; NOT BAR``. + + Note: see https://gitlab.kitware.com/cmake/cmake/issues/16589 for further + details. + +.. command:: target_append_install_rpath + + Add extra paths to RPATH for a specific target:: + + .. code-block:: cmake + + target_append_install_rpath( + + [LIB_DIRS dir [dir]] + [DEPENDS condition [condition]]) + + Arguments: + - ``INSTALL_DESTINATION`` path where the target will be installed. + - ``LIB_DIRS`` list of directories to be added to the RPATH. These + directories will be added "relative" w.r.t. the ``INSTALL_DESTINATION``. + - ``DEPENDS`` list of conditions that should be ``TRUE`` to enable + RPATH, for example ``FOO; NOT BAR``. + +#]=======================================================================] + +include(CMakeParseArguments) + + +macro(__AddInstallRPATHSupport_GET_SYSTEM_LIB_DIRS _var) + # Find system implicit lib directories + set(${_var} ${CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES}) + if(EXISTS "/etc/debian_version") # is this a debian system ? + if(CMAKE_LIBRARY_ARCHITECTURE) + list(APPEND ${_var} "/lib/${CMAKE_LIBRARY_ARCHITECTURE}" + "/usr/lib/${CMAKE_LIBRARY_ARCHITECTURE}") + endif() + endif() +endmacro() + + +macro(__AddInstallRPATHSupport_APPEND_RELATIVE_RPATH _var _bin_dir _lib_dir) + file(RELATIVE_PATH _rel_path ${_bin_dir} ${_lib_dir}) + if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + list(APPEND ${_var} "@loader_path/${_rel_path}") + else() + list(APPEND ${_var} "\$ORIGIN/${_rel_path}") + endif() +endmacro() + + + +function(ADD_INSTALL_RPATH_SUPPORT) + + set(_options USE_LINK_PATH) + set(_oneValueArgs INSTALL_NAME_DIR) + set(_multiValueArgs BIN_DIRS + LIB_DIRS + DEPENDS) + + cmake_parse_arguments(_ARS "${_options}" + "${_oneValueArgs}" + "${_multiValueArgs}" + "${ARGN}") + + # if either RPATH or INSTALL_RPATH is disabled + # and the INSTALL_NAME_DIR variable is set, then hardcode the install name + if(CMAKE_SKIP_RPATH OR CMAKE_SKIP_INSTALL_RPATH) + if(DEFINED _ARS_INSTALL_NAME_DIR) + set(CMAKE_INSTALL_NAME_DIR ${_ARS_INSTALL_NAME_DIR} PARENT_SCOPE) + endif() + endif() + + if (CMAKE_SKIP_RPATH OR (CMAKE_SKIP_INSTALL_RPATH AND CMAKE_SKIP_BUILD_RPATH)) + return() + endif() + + + set(_rpath_available 1) + if(DEFINED _ARS_DEPENDS) + foreach(_dep ${_ARS_DEPENDS}) + string(REGEX REPLACE " +" ";" _dep "${_dep}") + if(NOT (${_dep})) + set(_rpath_available 0) + endif() + endforeach() + endif() + + if(_rpath_available) + + # Enable RPATH on OSX. + set(CMAKE_MACOSX_RPATH TRUE PARENT_SCOPE) + + __AddInstallRPATHSupport_get_system_lib_dirs(_system_lib_dirs) + + # This is relative RPATH for libraries built in the same project + foreach(lib_dir ${_ARS_LIB_DIRS}) + list(FIND _system_lib_dirs "${lib_dir}" isSystemDir) + if("${isSystemDir}" STREQUAL "-1") + foreach(bin_dir ${_ARS_LIB_DIRS} ${_ARS_BIN_DIRS}) + __AddInstallRPATHSupport_append_relative_rpath(CMAKE_INSTALL_RPATH ${bin_dir} ${lib_dir}) + endforeach() + endif() + endforeach() + if(NOT "${CMAKE_INSTALL_RPATH}" STREQUAL "") + list(REMOVE_DUPLICATES CMAKE_INSTALL_RPATH) + endif() + set(CMAKE_INSTALL_RPATH ${CMAKE_INSTALL_RPATH} PARENT_SCOPE) + + # add the automatically determined parts of the RPATH + # which point to directories outside the build tree to the install RPATH + set(CMAKE_INSTALL_RPATH_USE_LINK_PATH ${_ARS_USE_LINK_PATH} PARENT_SCOPE) + + endif() + +endfunction() + + +function(TARGET_APPEND_INSTALL_RPATH _target) + set(_options ) + set(_oneValueArgs INSTALL_DESTINATION) + set(_multiValueArgs LIB_DIRS + DEPENDS) + + if (CMAKE_SKIP_RPATH OR (CMAKE_SKIP_INSTALL_RPATH AND CMAKE_SKIP_BUILD_RPATH)) + return() + endif() + + cmake_parse_arguments(_ARS "${_options}" + "${_oneValueArgs}" + "${_multiValueArgs}" + "${ARGN}") + + set(_rpath_available 1) + if(DEFINED _ARS_DEPENDS) + foreach(_dep ${_ARS_DEPENDS}) + string(REGEX REPLACE " +" ";" _dep "${_dep}") + if(NOT (${_dep})) + set(_rpath_available 0) + endif() + endforeach() + endif() + + if(_rpath_available) + + __AddInstallRPATHSupport_get_system_lib_dirs(_system_lib_dirs) + + get_target_property(_current_rpath ${_target} INSTALL_RPATH) + foreach(lib_dir ${_ARS_LIB_DIRS}) + list(FIND _system_lib_dirs "${lib_dir}" isSystemDir) + if("${isSystemDir}" STREQUAL "-1") + __AddInstallRPATHSupport_append_relative_rpath(_current_rpath ${_ARS_INSTALL_DESTINATION} ${lib_dir}) + endif() + endforeach() + set_target_properties(${_target} PROPERTIES INSTALL_RPATH "${_current_rpath}") + endif() + +endfunction() diff --git a/cmake/AddUninstallTarget.cmake b/cmake/AddUninstallTarget.cmake new file mode 100644 index 00000000..fefef2d1 --- /dev/null +++ b/cmake/AddUninstallTarget.cmake @@ -0,0 +1,70 @@ +#.rst: +# AddUninstallTarget +# ------------------ +# +# Add the "uninstall" target for your project:: +# +# include(AddUninstallTarget) +# +# +# will create a file cmake_uninstall.cmake in the build directory and add a +# custom target uninstall that will remove the files installed by your package +# (using install_manifest.txt) + +#============================================================================= +# Copyright 2008-2013 Kitware, Inc. +# Copyright 2013 Istituto Italiano di Tecnologia (IIT) +# Authors: Daniele E. Domenichelli +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= +# (To distribute this file outside of CMake, substitute the full +# License text for the above reference.) + + +if(DEFINED __ADD_UNINSTALL_TARGET_INCLUDED) + return() +endif() +set(__ADD_UNINSTALL_TARGET_INCLUDED TRUE) + + +set(_filename ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake) + +file(WRITE ${_filename} +"if(NOT EXISTS \"${CMAKE_CURRENT_BINARY_DIR}/install_manifest.txt\") + message(WARNING \"Cannot find install manifest: \\\"${CMAKE_CURRENT_BINARY_DIR}/install_manifest.txt\\\"\") + return() +endif() + +file(READ \"${CMAKE_CURRENT_BINARY_DIR}/install_manifest.txt\" files) +string(STRIP \"\${files}\" files) +string(REGEX REPLACE \"\\n\" \";\" files \"\${files}\") +list(REVERSE files) +foreach(file \${files}) + message(STATUS \"Uninstalling: \$ENV{DESTDIR}\${file}\") + if(EXISTS \"\$ENV{DESTDIR}\${file}\") + execute_process( + COMMAND \${CMAKE_COMMAND} -E remove \"\$ENV{DESTDIR}\${file}\" + OUTPUT_VARIABLE rm_out + RESULT_VARIABLE rm_retval) + if(NOT \"\${rm_retval}\" EQUAL 0) + message(FATAL_ERROR \"Problem when removing \\\"\$ENV{DESTDIR}\${file}\\\"\") + endif() + else() + message(STATUS \"File \\\"\$ENV{DESTDIR}\${file}\\\" does not exist.\") + endif() +endforeach(file) +") + +if("${CMAKE_GENERATOR}" MATCHES "^(Visual Studio|Xcode)") + set(_uninstall "UNINSTALL") +else() + set(_uninstall "uninstall") +endif() +add_custom_target(${_uninstall} COMMAND "${CMAKE_COMMAND}" -P "${_filename}") +set_property(TARGET ${_uninstall} PROPERTY FOLDER "CMakePredefinedTargets") diff --git a/cmake/InstallBasicPackageFiles.cmake b/cmake/InstallBasicPackageFiles.cmake new file mode 100644 index 00000000..aa216a72 --- /dev/null +++ b/cmake/InstallBasicPackageFiles.cmake @@ -0,0 +1,677 @@ +#.rst: +# InstallBasicPackageFiles +# ------------------------ +# +# A helper module to make your package easier to be found by other +# projects. +# +# +# .. command:: install_basic_package_files +# +# Create and install a basic version of cmake config files for your +# project:: +# +# install_basic_package_files( +# VERSION +# COMPATIBILITY +# [EXPORT ] # (default = "") +# [FIRST_TARGET ] # (default = "") +# [TARGETS ...] +# [TARGETS_PROPERTY ] +# [TARGETS_PROPERTIES ...] +# [NO_SET_AND_CHECK_MACRO] +# [NO_CHECK_REQUIRED_COMPONENTS_MACRO] +# [VARS_PREFIX ] # (default = "") +# [EXPORT_DESTINATION ] +# [INSTALL_DESTINATION ] +# [NAMESPACE ] # (default = "::") +# [EXTRA_PATH_VARS_SUFFIX path1 [path2 ...]] +# [CONFIG_TEMPLATE ] +# [UPPERCASE_FILENAMES | LOWERCASE_FILENAMES] +# [DEPENDENCIES " [...]" ...] +# [PRIVATE_DEPENDENCIES " [...]" ...] +# [INCLUDE_FILE ] +# [COMPONENT ] # (default = "") +# [NO_COMPATIBILITY_VARS] +# ) +# +# Depending on UPPERCASE_FILENAMES and LOWERCASE_FILENAMES, this +# function generates 3 files: +# +# - ``ConfigVersion.cmake`` or ``-config-version.cmake`` +# - ``Config.cmake`` or ``-config.cmake`` +# - ``Targets.cmake`` or ``-targets.cmake`` +# +# If neither ``UPPERCASE_FILENAMES`` nor ``LOWERCASE_FILENAMES`` is +# set, a file ``ConfigVersion.cmake.in`` or +# ``-config-version.cmake.in`` is searched, and the convention +# is chosed according to the file found. If no file was found, the +# uppercase convention is used. +# +# The ``DEPENDENCIES`` argument can be used to set a list of dependencies +# that will be searched using the :command:`find_dependency` command +# from the :module:`CMakeFindDependencyMacro` module. +# Dependencies can be followed by any of the possible :command:`find_dependency` +# argument. +# In this case, all the arguments must be specified within double quotes (e.g. +# " 1.0.0 EXACT", " CONFIG"). +# The ``PRIVATE_DEPENDENCIES`` argument is similar to ``DEPENDENCIES``, but +# these dependencies are included only when libraries are built ``STATIC``, i.e. +# if ``BUILD_SHARED_LIBS`` is ``OFF`` or if the ``TYPE`` property for one or +# more of the targets is ``STATIC_LIBRARY``. +# When using a custom template file, the ``@PACKAGE_DEPENDENCIES@`` +# string is replaced with the code checking for the dependencies +# specified by these two argument. +# +# Each file is generated twice, one for the build directory and one for +# the installation directory. The ``INSTALL_DESTINATION`` argument can be +# passed to install the files in a location different from the default +# one (``CMake`` on Windows, ``${CMAKE_INSTALL_LIBDIR}/cmake/${Name}`` +# on other platforms. The ``EXPORT_DESTINATION`` argument can be passed to +# generate the files in the build tree in a location different from the default +# one (``CMAKE_BINARY_DIR``). If this is a relative path, it is considered +# relative to the ``CMAKE_BINARY_DIR`` directory. +# +# The ``ConfigVersion.cmake`` is generated using +# ``write_basic_package_version_file``. The ``VERSION``, +# ``COMPATIBILITY``, ``NO_SET_AND_CHECK_MACRO``, and +# ``NO_CHECK_REQUIRED_COMPONENTS_MACRO`` are passed to this function +# and are used internally by :module:`CMakePackageConfigHelpers` module. +# +# ``VERSION`` shall be in the form ``[.[.[.]]]]``. +# If no ``VERSION`` is given, the ``PROJECT_VERSION`` variable is used. +# If this hasn’t been set, it errors out. The ``VERSION`` argument is also used +# to replace the ``@PACKAGE_VERSION@`` string in the configuration file. +# +# ``COMPATIBILITY`` shall be any of ````. +# The ``COMPATIBILITY`` mode ``AnyNewerVersion`` means that the installed +# package version will be considered compatible if it is newer or exactly the +# same as the requested version. This mode should be used for packages which are +# fully backward compatible, also across major versions. +# If ``SameMajorVersion`` is used instead, then the behaviour differs from +# ``AnyNewerVersion`` in that the major version number must be the same as +# requested, e.g. version 2.0 will not be considered compatible if 1.0 is +# requested. This mode should be used for packages which guarantee backward +# compatibility within the same major version. If ``ExactVersion`` is used, then +# the package is only considered compatible if the requested version matches +# exactly its own version number (not considering the tweak version). For +# example, version 1.2.3 of a package is only considered compatible to requested +# version 1.2.3. This mode is for packages without compatibility guarantees. If +# your project has more elaborated version matching rules, you will need to +# write your own custom ConfigVersion.cmake file instead of using this macro. +# +# By default ``install_basic_package_files`` also generates the two helper +# macros ``set_and_check()`` and ``check_required_components()`` into the +# ``Config.cmake`` file. ``set_and_check()`` should be used instead of the +# normal set() command for setting directories and file locations. Additionally +# to setting the variable it also checks that the referenced file or directory +# actually exists and fails with a ``FATAL_ERROR`` otherwise. This makes sure +# that the created ``Config.cmake`` file does not contain wrong +# references. When using the ``NO_SET_AND_CHECK_MACRO, this macro is not +# generated into the ``Config.cmake`` file. +# +# By default, ``install_basic_package_files`` append a call to +# ``check_required_components()`` in Config.cmake file if the +# package supports components. This macro checks whether all requested, +# non-optional components have been found, and if this is not the case, sets the +# ``_FOUND`` variable to ``FALSE``, so that the package is considered to +# be not found. It does that by testing the ``__FOUND`` +# variables for all requested required components. When using the +# ``NO_CHECK_REQUIRED_COMPONENTS_MACRO`` option, this macro is not generated +# into the Config.cmake file. +# +# Finally, the files in the build and install directory are exactly the same. +# +# See the documentation of :module:`CMakePackageConfigHelpers` module for +# further information and references therein. +# +# +# The ``Config.cmake`` is generated using +# ``configure_package_config_file``. See the documentation for the +# :module:`CMakePackageConfigHelpers` module for further information. +# If the ``CONFIG_TEMPLATE`` argument is passed, the specified file +# is used as template for generating the configuration file, otherwise +# this module expects to find a ``Config.cmake.in`` or +# ``-config.cmake.in`` file either in the root directory of the +# project or in current source directory. +# If the file does not exist, a very basic file is created. +# +# A set of variables are checked and passed to +# ``configure_package_config_file`` as ``PATH_VARS``. For each of the +# ``SUFFIX`` considered, if one of the variables:: +# +# _(BUILD|INSTALL)_ +# (BUILD|INSTALL)__ +# +# is defined, the ``_`` variable will be defined +# before configuring the package. In order to use that variable in the +# config file, you have to add a line:: +# +# set_and_check(_ \"@PACKAGE__@\") +# +# if the path must exist or just:: +# +# set(_ \"@PACKAGE__@\") +# +# if the path could be missing. +# +# These variable will have different values whether you are using the +# package from the build tree or from the install directory. Also these +# files will contain only relative paths, meaning that you can move the +# whole installation and the CMake files will still work. +# +# Default ``PATH_VARS`` suffixes are:: +# +# BINDIR BIN_DIR +# SBINDIR SBIN_DIR +# LIBEXECDIR LIBEXEC_DIR +# SYSCONFDIR SYSCONF_DIR +# SHAREDSTATEDIR SHAREDSTATE_DIR +# LOCALSTATEDIR LOCALSTATE_DIR +# LIBDIR LIB_DIR +# INCLUDEDIR INCLUDE_DIR +# OLDINCLUDEDIR OLDINCLUDE_DIR +# DATAROOTDIR DATAROOT_DIR +# DATADIR DATA_DIR +# INFODIR INFO_DIR +# LOCALEDIR LOCALE_DIR +# MANDIR MAN_DIR +# DOCDIR DOC_DIR +# +# more suffixes can be added using the ``EXTRA_PATH_VARS_SUFFIX`` +# argument. +# +# +# The ``Targets.cmake`` is generated using +# :command:`export(TARGETS)` (if ``EXPORT`` or no options are used) or +# :command:`export(TARGETS)` (if `EXPORT` is not used and one between +# ``TARGETS``, ``TARGETS_PROPERTY``, or ``TARGETS_PROPERTIES`` is used) in the +# build tree and :command:`install(EXPORT)` in the installation directory. +# The targets are exported using the value for the ``NAMESPACE`` +# argument as namespace. +# The export can be passed using the `EXPORT` argument. +# The targets can be passed using the `TARGETS` argument or using one or more +# global properties, that can be passed to the function using the +# ``TARGETS_PROPERTY`` or ``TARGET_PROPERTIES`` arguments. +# +# If the ``NO_COMPATIBILITY_VARS`` argument is not set, the compatibility +# variables ``_LIBRARIES`` and ``_INCLUDE_DIRS`` +# are set, trying to guess their correct values from the variables set or +# from the arguments passed to this command. This argument is ignored if +# the template file is not generated by this command. +# +# If the ``INCLUDE_FILE`` argument is passed, the content of the specified file +# (which might be templated) is appended to the ``Config.cmake``. +# This allows to inject custom code to this file, useful e.g. to set additional +# variables which are loaded by downstream projects. +# +# If the ``COMPONENT`` argument is passed, it is forwarded to the +# :command:`install` commands, otherwise is used. + +#============================================================================= +# Copyright 2013 Istituto Italiano di Tecnologia (IIT) +# Authors: Daniele E. Domenichelli +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= +# (To distribute this file outside of CMake, substitute the full +# License text for the above reference.) + + +if(COMMAND install_basic_package_files) + return() +endif() + + +include(GNUInstallDirs) +include(CMakePackageConfigHelpers) +include(CMakeParseArguments) + + +function(INSTALL_BASIC_PACKAGE_FILES _Name) + + # TODO check that _Name does not contain "-" characters + + set(_options NO_SET_AND_CHECK_MACRO + NO_CHECK_REQUIRED_COMPONENTS_MACRO + UPPERCASE_FILENAMES + LOWERCASE_FILENAMES + NO_COMPATIBILITY_VARS) + set(_oneValueArgs VERSION + COMPATIBILITY + EXPORT + FIRST_TARGET + TARGETS_PROPERTY + VARS_PREFIX + EXPORT_DESTINATION + INSTALL_DESTINATION + DESTINATION + NAMESPACE + CONFIG_TEMPLATE + INCLUDE_FILE + COMPONENT) + set(_multiValueArgs EXTRA_PATH_VARS_SUFFIX + TARGETS + TARGETS_PROPERTIES + DEPENDENCIES + PRIVATE_DEPENDENCIES) + cmake_parse_arguments(_IBPF "${_options}" "${_oneValueArgs}" "${_multiValueArgs}" "${ARGN}") + + if(NOT DEFINED _IBPF_VARS_PREFIX) + set(_IBPF_VARS_PREFIX ${_Name}) + endif() + + if(NOT DEFINED _IBPF_VERSION) + message(FATAL_ERROR "VERSION argument is required") + endif() + + if(NOT DEFINED _IBPF_COMPATIBILITY) + message(FATAL_ERROR "COMPATIBILITY argument is required") + endif() + + if(_IBPF_UPPERCASE_FILENAMES AND _IBPF_LOWERCASE_FILENAMES) + message(FATAL_ERROR "UPPERCASE_FILENAMES and LOWERCASE_FILENAMES arguments cannot be used together") + endif() + + # Prepare install and export commands + set(_first_target ${_Name}) + set(_targets ${_Name}) + set(_install_cmd EXPORT ${_Name}) + set(_export_cmd EXPORT ${_Name}) + + if(DEFINED _IBPF_FIRST_TARGET) + if(DEFINED _IBPF_TARGETS OR DEFINED _IBPF_TARGETS_PROPERTIES OR DEFINED _IBPF_TARGETS_PROPERTIES) + message(FATAL_ERROR "EXPORT cannot be used with TARGETS, TARGETS_PROPERTY or TARGETS_PROPERTIES") + endif() + + set(_first_target ${_IBPF_FIRST_TARGET}) + set(_targets ${_IBPF_FIRST_TARGET}) + endif() + + if(DEFINED _IBPF_EXPORT) + if(DEFINED _IBPF_TARGETS OR DEFINED _IBPF_TARGETS_PROPERTIES OR DEFINED _IBPF_TARGETS_PROPERTIES) + message(FATAL_ERROR "EXPORT cannot be used with TARGETS, TARGETS_PROPERTY or TARGETS_PROPERTIES") + endif() + + set(_export_cmd EXPORT ${_IBPF_EXPORT}) + set(_install_cmd EXPORT ${_IBPF_EXPORT}) + + elseif(DEFINED _IBPF_TARGETS) + if(DEFINED _IBPF_TARGETS_PROPERTY OR DEFINED _IBPF_TARGETS_PROPERTIES) + message(FATAL_ERROR "TARGETS cannot be used with TARGETS_PROPERTY or TARGETS_PROPERTIES") + endif() + + set(_targets ${_IBPF_TARGETS}) + set(_export_cmd TARGETS ${_IBPF_TARGETS}) + list(GET _targets 0 _first_target) + + elseif(DEFINED _IBPF_TARGETS_PROPERTY) + if(DEFINED _IBPF_TARGETS_PROPERTIES) + message(FATAL_ERROR "TARGETS_PROPERTIES cannot be used with TARGETS_PROPERTIES") + endif() + + get_property(_targets GLOBAL PROPERTY ${_IBPF_TARGETS_PROPERTY}) + set(_export_cmd TARGETS ${_targets}) + list(GET _targets 0 _first_target) + + elseif(DEFINED _IBPF_TARGETS_PROPERTIES) + + unset(_targets) + foreach(_prop ${_IBPF_TARGETS_PROPERTIES}) + get_property(_prop_val GLOBAL PROPERTY ${_prop}) + list(APPEND _targets ${_prop_val}) + endforeach() + set(_export_cmd TARGETS ${_targets}) + list(GET _targets 0 _first_target) + + endif() + + # Path for installed cmake files + if(DEFINED _IBPF_DESTINATION) + message(DEPRECATION "DESTINATION is deprecated. Use INSTALL_DESTINATION instead") + if(NOT DEFINED _IBPF_INSTALL_DESTINATION) + set(_IBPF_INSTALL_DESTINATION ${_IBPF_DESTINATION}) + endif() + endif() + + if(NOT DEFINED _IBPF_INSTALL_DESTINATION) + if(WIN32 AND NOT CYGWIN) + set(_IBPF_INSTALL_DESTINATION CMake) + else() + set(_IBPF_INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${_Name}) + endif() + endif() + + if(NOT DEFINED _IBPF_EXPORT_DESTINATION) + set(_IBPF_EXPORT_DESTINATION "${CMAKE_BINARY_DIR}") + elseif(NOT IS_ABSOLUTE _IBPF_EXPORT_DESTINATION) + set(_IBPF_EXPORT_DESTINATION "${CMAKE_BINARY_DIR}/${_IBPF_EXPORT_DESTINATION}") + endif() + + if(NOT DEFINED _IBPF_NAMESPACE) + set(_IBPF_NAMESPACE "${_Name}::") + endif() + + if(NOT DEFINED _IBPF_COMPONENT) + set(_IBPF_COMPONENT "${_Name}") + endif() + + if(_IBPF_NO_SET_AND_CHECK_MACRO) + list(APPEND configure_package_config_file_extra_args NO_SET_AND_CHECK_MACRO) + endif() + + if(_IBPF_NO_CHECK_REQUIRED_COMPONENTS_MACRO) + list(APPEND configure_package_config_file_extra_args NO_CHECK_REQUIRED_COMPONENTS_MACRO) + endif() + + + + # Set input file for config, and ensure that _IBPF_UPPERCASE_FILENAMES + # and _IBPF_LOWERCASE_FILENAMES are set correctly + unset(_config_cmake_in) + set(_generate_file 0) + if(DEFINED _IBPF_CONFIG_TEMPLATE) + if(NOT EXISTS "${_IBPF_CONFIG_TEMPLATE}") + message(FATAL_ERROR "Config template file \"${_IBPF_CONFIG_TEMPLATE}\" not found") + endif() + set(_config_cmake_in "${_IBPF_CONFIG_TEMPLATE}") + if(NOT _IBPF_UPPERCASE_FILENAMES AND NOT _IBPF_LOWERCASE_FILENAMES) + if("${_IBPF_CONFIG_TEMPLATE}" MATCHES "${_Name}Config.cmake.in") + set(_IBPF_UPPERCASE_FILENAMES 1) + elseif("${_IBPF_CONFIG_TEMPLATE}" MATCHES "${_name}-config.cmake.in") + set(_IBPF_LOWERCASE_FILENAMES 1) + else() + set(_IBPF_UPPERCASE_FILENAMES 1) + endif() + endif() + else() + string(TOLOWER "${_Name}" _name) + if(EXISTS "${CMAKE_SOURCE_DIR}/${_Name}Config.cmake.in") + set(_config_cmake_in "${CMAKE_SOURCE_DIR}/${_Name}Config.cmake.in") + if(NOT _IBPF_UPPERCASE_FILENAMES AND NOT _IBPF_LOWERCASE_FILENAMES) + set(_IBPF_UPPERCASE_FILENAMES 1) + endif() + elseif(EXISTS "${CMAKE_SOURCE_DIR}/${_name}-config.cmake.in") + set(_config_cmake_in "${CMAKE_SOURCE_DIR}/${_name}-config.cmake.in") + if(NOT _IBPF_UPPERCASE_FILENAMES AND NOT _IBPF_LOWERCASE_FILENAMES) + set(_IBPF_LOWERCASE_FILENAMES 1) + endif() + elseif(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${_Name}Config.cmake.in") + set(_config_cmake_in "${CMAKE_CURRENT_SOURCE_DIR}/${_Name}Config.cmake.in") + if(NOT _IBPF_UPPERCASE_FILENAMES AND NOT _IBPF_LOWERCASE_FILENAMES) + set(_IBPF_UPPERCASE_FILENAMES 1) + endif() + elseif(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${_name}-config.cmake.in") + set(_config_cmake_in "${CMAKE_CURRENT_SOURCE_DIR}/${_name}-config.cmake.in") + if(NOT _IBPF_UPPERCASE_FILENAMES AND NOT _IBPF_LOWERCASE_FILENAMES) + set(_IBPF_LOWERCASE_FILENAMES 1) + endif() + else() + set(_generate_file 1) + if(_IBPF_LOWERCASE_FILENAMES) + set(_config_cmake_in "${CMAKE_CURRENT_BINARY_DIR}/${_name}-config.cmake") + else() + set(_config_cmake_in "${CMAKE_CURRENT_BINARY_DIR}/${_Name}Config.cmake.in") + set(_IBPF_UPPERCASE_FILENAMES 1) + endif() + endif() + endif() + + # Set input file containing user variables + if(DEFINED _IBPF_INCLUDE_FILE) + if(NOT IS_ABSOLUTE "${_IBPF_INCLUDE_FILE}") + if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${_IBPF_INCLUDE_FILE}") + set(_IBPF_INCLUDE_FILE "${CMAKE_CURRENT_SOURCE_DIR}/${_IBPF_INCLUDE_FILE}") + endif() + endif() + if(NOT EXISTS "${_IBPF_INCLUDE_FILE}") + message(FATAL_ERROR "File \"${_IBPF_INCLUDE_FILE}\" not found") + endif() + file(READ ${_IBPF_INCLUDE_FILE} _includedfile_user_content_in) + string(CONFIGURE ${_includedfile_user_content_in} _includedfile_user_content) + set(INCLUDED_FILE_CONTENT +"#### Expanded from INCLUDE_FILE by install_basic_package_files() ####") + set(INCLUDED_FILE_CONTENT "${INCLUDED_FILE_CONTENT}\n\n${_includedfile_user_content}") + set(INCLUDED_FILE_CONTENT +"${INCLUDED_FILE_CONTENT} +#####################################################################") + endif() + + # Select output file names + if(_IBPF_UPPERCASE_FILENAMES) + set(_config_filename ${_Name}Config.cmake) + set(_version_filename ${_Name}ConfigVersion.cmake) + set(_targets_filename ${_Name}Targets.cmake) + elseif(_IBPF_LOWERCASE_FILENAMES) + set(_config_filename ${_name}-config.cmake) + set(_version_filename ${_name}-config-version.cmake) + set(_targets_filename ${_name}-targets.cmake) + endif() + + + # If the template config file does not exist, write a basic one + if(_generate_file) + # Generate the compatibility code + unset(_compatibility_vars) + if(NOT _IBPF_NO_COMPATIBILITY_VARS) + unset(_get_include_dir_code) + unset(_set_include_dir_code) + unset(_target_list) + foreach(_target ${_targets}) + list(APPEND _target_list ${_IBPF_NAMESPACE}${_target}) + endforeach() + if(DEFINED ${_IBPF_VARS_PREFIX}_BUILD_INCLUDEDIR OR + DEFINED BUILD_${_IBPF_VARS_PREFIX}_INCLUDEDIR OR + DEFINED ${_IBPF_VARS_PREFIX}_INSTALL_INCLUDEDIR OR + DEFINED INSTALL_${_IBPF_VARS_PREFIX}_INCLUDEDIR) + set(_get_include_dir "set(${_IBPF_VARS_PREFIX}_INCLUDEDIR \"\@PACKAGE_${_IBPF_VARS_PREFIX}_INCLUDEDIR\@\")\n") + set(_set_include_dir "set(${_Name}_INCLUDE_DIRS \"\${${_IBPF_VARS_PREFIX}_INCLUDEDIR}\")") + elseif(DEFINED ${_IBPF_VARS_PREFIX}_BUILD_INCLUDE_DIR OR + DEFINED BUILD_${_IBPF_VARS_PREFIX}_INCLUDE_DIR OR + DEFINED ${_IBPF_VARS_PREFIX}_INSTALL_INCLUDE_DIR OR + DEFINED INSTALL_${_IBPF_VARS_PREFIX}_INCLUDE_DIR) + set(_get_include_dir "set(${_IBPF_VARS_PREFIX}_INCLUDE_DIR \"\@PACKAGE_${_IBPF_VARS_PREFIX}_INCLUDE_DIR\@\")\n") + set(_set_include_dir "set(${_Name}_INCLUDE_DIRS \"\${${_IBPF_VARS_PREFIX}_INCLUDE_DIR}\")") + else() + unset(_include_dir_list) + foreach(_target ${_targets}) + set(_get_include_dir "${_get_include_dir}get_property(${_IBPF_VARS_PREFIX}_${_target}_INCLUDE_DIR TARGET ${_IBPF_NAMESPACE}${_target} PROPERTY INTERFACE_INCLUDE_DIRECTORIES)\n") + list(APPEND _include_dir_list "\"\${${_IBPF_VARS_PREFIX}_${_target}_INCLUDE_DIR}\"") + endforeach() + string(REPLACE ";" " " _include_dir_list "${_include_dir_list}") + string(REPLACE ";" " " _target_list "${_target_list}") + set(_set_include_dir "set(${_Name}_INCLUDE_DIRS ${_include_dir_list})\nlist(REMOVE_DUPLICATES ${_Name}_INCLUDE_DIRS)") + endif() + set(_compatibility_vars "# Compatibility\n${_get_include_dir}\nset(${_Name}_LIBRARIES ${_target_list})\n${_set_include_dir}") + endif() + + # Write the file + file(WRITE "${_config_cmake_in}" +"set(${_IBPF_VARS_PREFIX}_VERSION \@PACKAGE_VERSION\@) + +\@PACKAGE_INIT\@ + +\@PACKAGE_DEPENDENCIES\@ + +if(NOT TARGET ${_IBPF_NAMESPACE}${_first_target}) + include(\"\${CMAKE_CURRENT_LIST_DIR}/${_targets_filename}\") +endif() + +${_compatibility_vars} + +\@INCLUDED_FILE_CONTENT\@ +") + endif() + + # Make relative paths absolute (needed later on) and append the + # defined variables to _(build|install)_path_vars_suffix + foreach(p BINDIR BIN_DIR + SBINDIR SBIN_DIR + LIBEXECDIR LIBEXEC_DIR + SYSCONFDIR SYSCONF_DIR + SHAREDSTATEDIR SHAREDSTATE_DIR + LOCALSTATEDIR LOCALSTATE_DIR + LIBDIR LIB_DIR + INCLUDEDIR INCLUDE_DIR + OLDINCLUDEDIR OLDINCLUDE_DIR + DATAROOTDIR DATAROOT_DIR + DATADIR DATA_DIR + INFODIR INFO_DIR + LOCALEDIR LOCALE_DIR + MANDIR MAN_DIR + DOCDIR DOC_DIR + ${_IBPF_EXTRA_PATH_VARS_SUFFIX}) + if(DEFINED ${_IBPF_VARS_PREFIX}_BUILD_${p}) + list(APPEND _build_path_vars_suffix ${p}) + list(APPEND _build_path_vars "${_IBPF_VARS_PREFIX}_${p}") + endif() + if(DEFINED BUILD_${_IBPF_VARS_PREFIX}_${p}) + list(APPEND _build_path_vars_suffix ${p}) + list(APPEND _build_path_vars "${_IBPF_VARS_PREFIX}_${p}") + endif() + if(DEFINED ${_IBPF_VARS_PREFIX}_INSTALL_${p}) + list(APPEND _install_path_vars_suffix ${p}) + list(APPEND _install_path_vars "${_IBPF_VARS_PREFIX}_${p}") + endif() + if(DEFINED INSTALL_${_IBPF_VARS_PREFIX}_${p}) + list(APPEND _install_path_vars_suffix ${p}) + list(APPEND _install_path_vars "${_IBPF_VARS_PREFIX}_${p}") + endif() + endforeach() + + + # ConfigVersion.cmake file (same for build tree and intall) + write_basic_package_version_file("${_IBPF_EXPORT_DESTINATION}/${_version_filename}" + VERSION ${_IBPF_VERSION} + COMPATIBILITY ${_IBPF_COMPATIBILITY}) + install(FILES "${_IBPF_EXPORT_DESTINATION}/${_version_filename}" + DESTINATION ${_IBPF_INSTALL_DESTINATION} + COMPONENT ${_IBPF_COMPONENT}) + + + # Prepare PACKAGE_DEPENDENCIES variable + set(_need_private_deps 0) + if(NOT BUILD_SHARED_LIBS) + set(_need_private_deps 1) + else() + foreach(_target ${_targets}) + get_property(_type TARGET ${_target} PROPERTY TYPE) + if("${_type}" STREQUAL "STATIC_LIBRARY") + set(_need_private_deps 1) + break() + endif() + endforeach() + endif() + + unset(PACKAGE_DEPENDENCIES) + if(DEFINED _IBPF_DEPENDENCIES) + set(PACKAGE_DEPENDENCIES "#### Expanded from @PACKAGE_DEPENDENCIES@ by install_basic_package_files() ####\n\ninclude(CMakeFindDependencyMacro)\n") + + # FIXME When CMake 3.9 or greater is required, remove this madness and just + # use find_dependency + if (CMAKE_VERSION VERSION_LESS 3.9) + string(APPEND PACKAGE_DEPENDENCIES " +set(_${_Name}_FIND_PARTS_REQUIRED) +if (${_Name}_FIND_REQUIRED) + set(_${_Name}_FIND_PARTS_REQUIRED REQUIRED) +endif() +set(_${_Name}_FIND_PARTS_QUIET) +if (${_Name}_FIND_QUIETLY) + set(_${_Name}_FIND_PARTS_QUIET QUIET) +endif() +") + + foreach(_dep ${_IBPF_DEPENDENCIES}) + if("${_dep}" MATCHES ".+ .+") + string(REPLACE " " ";" _dep_list "${_dep}") + list(INSERT _dep_list 1 \${_${_Name}_FIND_PARTS_QUIET} \${_${_Name}_FIND_PARTS_REQUIRED}) + string(REPLACE ";" " " _depx "${_dep_list}") + string(APPEND PACKAGE_DEPENDENCIES "find_package(${_depx})\n") + else() + string(APPEND PACKAGE_DEPENDENCIES "find_dependency(${_dep})\n") + endif() + endforeach() + if(_need_private_deps) + foreach(_dep ${_IBPF_PRIVATE_DEPENDENCIES}) + if("${_dep}" MATCHES ".+ .+") + string(REPLACE " " ";" _dep_list "${_dep}") + list(INSERT _dep_list 1 \${_${_Name}_FIND_PARTS_QUIET} \${_${_Name}_FIND_PARTS_REQUIRED}) + string(REPLACE ";" "\n " _depx "${_dep_list}") + string(APPEND PACKAGE_DEPENDENCIES "find_package(${_depx})\n") + else() + string(APPEND PACKAGE_DEPENDENCIES "find_dependency(${_dep})\n") + endif() + endforeach() + endif() + + else() + + foreach(_dep ${_IBPF_DEPENDENCIES}) + string(APPEND PACKAGE_DEPENDENCIES "find_dependency(${_dep})\n") + endforeach() + if(_need_private_deps) + foreach(_dep ${_IBPF_PRIVATE_DEPENDENCIES}) + string(APPEND PACKAGE_DEPENDENCIES "find_dependency(${_dep})\n") + endforeach() + endif() + + endif() + + set(PACKAGE_DEPENDENCIES "${PACKAGE_DEPENDENCIES}\n###############################################################################\n") + endif() + + # Prepare PACKAGE_VERSION variable + set(PACKAGE_VERSION ${_IBPF_VERSION}) + + # Config.cmake (build tree) + foreach(p ${_build_path_vars_suffix}) + if(DEFINED ${_IBPF_VARS_PREFIX}_BUILD_${p}) + set(${_IBPF_VARS_PREFIX}_${p} "${${_IBPF_VARS_PREFIX}_BUILD_${p}}") + elseif(DEFINED BUILD_${_IBPF_VARS_PREFIX}_${p}) + set(${_IBPF_VARS_PREFIX}_${p} "${BUILD_${_IBPF_VARS_PREFIX}_${p}}") + endif() + endforeach() + configure_package_config_file("${_config_cmake_in}" + "${_IBPF_EXPORT_DESTINATION}/${_config_filename}" + INSTALL_DESTINATION ${_IBPF_EXPORT_DESTINATION} + PATH_VARS ${_build_path_vars} + ${configure_package_config_file_extra_args} + INSTALL_PREFIX ${CMAKE_BINARY_DIR}) + + # Config.cmake (installed) + foreach(p ${_install_path_vars_suffix}) + if(DEFINED ${_IBPF_VARS_PREFIX}_INSTALL_${p}) + set(${_IBPF_VARS_PREFIX}_${p} "${${_IBPF_VARS_PREFIX}_INSTALL_${p}}") + elseif(DEFINED INSTALL_${_IBPF_VARS_PREFIX}_${p}) + set(${_IBPF_VARS_PREFIX}_${p} "${INSTALL_${_IBPF_VARS_PREFIX}_${p}}") + endif() + endforeach() + configure_package_config_file("${_config_cmake_in}" + "${CMAKE_CURRENT_BINARY_DIR}/${_config_filename}.install" + INSTALL_DESTINATION ${_IBPF_INSTALL_DESTINATION} + PATH_VARS ${_install_path_vars} + ${configure_package_config_file_extra_args}) + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${_config_filename}.install" + DESTINATION ${_IBPF_INSTALL_DESTINATION} + RENAME ${_config_filename} + COMPONENT ${_IBPF_COMPONENT}) + + + # Targets.cmake (build tree) + export(${_export_cmd} + NAMESPACE ${_IBPF_NAMESPACE} + FILE "${_IBPF_EXPORT_DESTINATION}/${_targets_filename}") + + # Targets.cmake (installed) + install(${_install_cmd} + NAMESPACE ${_IBPF_NAMESPACE} + DESTINATION ${_IBPF_INSTALL_DESTINATION} + FILE "${_targets_filename}" + COMPONENT ${_IBPF_COMPONENT}) +endfunction() diff --git a/cmake/PSLPConfig.cmake.in b/cmake/PSLPConfig.cmake.in new file mode 100644 index 00000000..0a90ae61 --- /dev/null +++ b/cmake/PSLPConfig.cmake.in @@ -0,0 +1,3 @@ +@PACKAGE_INIT@ +include("${CMAKE_CURRENT_LIST_DIR}/PSLPTargets.cmake") +check_required_components(PSLP) \ No newline at end of file diff --git a/include/PSLP/API.h b/include/PSLP/API.h new file mode 100644 index 00000000..17917dac --- /dev/null +++ b/include/PSLP/API.h @@ -0,0 +1,119 @@ +/* + * Copyright 2025 Daniel Cederberg + * + * This file is part of the PSLP project (LP Presolver). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* Public header containing the outward facing API. It includes all the + input/output data structs and the API functions. Make sure this file is + somewhere appropriate and then use `#include ` to access the + public API. */ +#ifndef PRESOLVER_H +#define PRESOLVER_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include "PresolveStatus.h" +#include "Sol.h" +#include + + /* forward declaration */ + struct Problem; + struct PresolveStats; + + typedef struct + { + bool ston_cols; + bool dton_eq; + bool parallel_rows; + bool parallel_cols; + bool primal_propagation; + bool clean_small_coeff; + bool finite_bound_tightening; + bool dual_fix; + bool relax_bounds; + int max_shift; + double max_time; + bool verbose; + } Settings; + + /* struct corresponding to the presolved problem */ + typedef struct + { + // CSR format of a matrix A of size m x n with nnz non-zeros + double *Ax; + int *Ai; + int *Ap; + int m; + int n; + int nnz; + + // lhs and rhs in the form lhs <= Ax <= rhs + double *lhs; + double *rhs; + double *c; + + // variable bounds bounds[k].lb <= x_k <= bounds[k].ub + // Bound *bounds; + double *lbs; + double *ubs; + } PresolvedProblem; + + /* struct corresponding to the presolver*/ + typedef struct + { + struct PresolveStats *stats; + const Settings *stgs; + struct Problem *prob; + Solution *sol; + } Presolver; + + /* The user is responsible for freeing the settings struct using standard free. + */ + Settings *default_settings(); + void set_settings_true(Settings *stgs); + void set_settings_false(Settings *stgs); + + /* Initialize presolver, allocate memory, and build internal data structures. + The presolver maintains internal deep copies of Ax, Ai, Ap, lhs, rhs, lbs, + ubs, and c. The user is responsible for freeing this memory using + 'free_presolver'. If the allocation fails, the function returns NULL. */ + Presolver *new_presolver(const double *Ax, const int *Ai, const int *Ap, int m, + int n, int nnz, const double *lhs, const double *rhs, + const double *lbs, const double *ubs, const double *c, + const Settings *stgs, bool CSR); + + /* Free the memory allocated for the presolver. */ + void free_presolver(Presolver *presolver); + + /* Runs the presolver. At completion, the 'problem' field of the presolver + contains the presolved problem. */ + PresolveStatus presolver_run(Presolver *presolver); + + /* Postsolve the problem given the primal-dual solution (x, y, z) of the + reduced problem. The optimal value of the reduced problem is 'obj'. + The function populates presolver->sol. */ + void postsolve(Presolver *presolver, const double *x, const double *y, + const double *z, double obj); + +#ifdef __cplusplus +} +#endif + +#endif // PRESOLVER_H \ No newline at end of file diff --git a/include/PSLP/PresolveStatus.h b/include/PSLP/PresolveStatus.h new file mode 100644 index 00000000..644af9c3 --- /dev/null +++ b/include/PSLP/PresolveStatus.h @@ -0,0 +1,26 @@ +#ifndef PSLP_PresolveStatus_H +#define PSLP_PresolveStatus_H + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + + typedef uint8_t PresolveStatus; + + enum PresolveStatus_ + { + UNCHANGED = 0, + REDUCED = 1 << 0, + UNBOUNDED = 1 << 1, + INFEASIBLE = 1 << 2, + UNBNDORINFEAS = UNBOUNDED | INFEASIBLE, + }; + +#ifdef __cplusplus +} +#endif + +#endif // PSLP_PresolveStatus_H \ No newline at end of file diff --git a/include/PSLP/Sol.h b/include/PSLP/Sol.h new file mode 100644 index 00000000..5f76a8d3 --- /dev/null +++ b/include/PSLP/Sol.h @@ -0,0 +1,42 @@ +/* + * Copyright 2025 Daniel Cederberg + * + * This file is part of the PSLP project (LP Presolver). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CORE_SOL_H +#define CORE_SOL_H + +#ifdef __cplusplus +extern "C" +{ +#endif + + typedef struct + { + double *x; + double *y; + double *z; + double obj; + int dim_x; + int dim_y; + int status; + } Solution; + +#ifdef __cplusplus +} +#endif + +#endif // CORE_SOL_H \ No newline at end of file diff --git a/include/core/Activity.h b/include/core/Activity.h new file mode 100644 index 00000000..54b539a2 --- /dev/null +++ b/include/core/Activity.h @@ -0,0 +1,165 @@ +/* + * Copyright 2025 Daniel Cederberg + * + * This file is part of the PSLP project (LP Presolver). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ACTIVITY_H +#define ACTIVITY_H + +#include + +#include "Tags.h" +#include "debug_macros.h" +#include "iVec.h" + +// Forward declarations. +struct Bound; +struct Matrix; +struct ConstColView; + +#define INVALID_ACT_DEBUG 2 * INF // only used in debug mode + +typedef enum +{ + NOT_ADDED = 0, + ADDED = 1, + PROPAGATED_THIS_ROUND = 2, + PROPAGATE_NEXT_ROUND = 3, +} Status; + +typedef struct Activity +{ + // minimal and maximal activities + double min; + double max; + + // number of variables contributing with an infinite bound to the lower + // activity of this row + int n_inf_min; + + // number of variables contributing with an infinite bound to the upper + // activity of this row + int n_inf_max; + + // if the constraint has already been propagated in the current round + uint8_t status; + +} Activity; + +typedef uint8_t Altered_Activity; + +enum Altered_Activity +{ + NO_RECOMPUTE = 1 << 0, + MAX_ALTERED = 1 << 1, + MIN_ALTERED = 1 << 2, + // define like this for easy bit operations + MAX_ALTERED_RECOMPUTE = (1 << 3) | MAX_ALTERED, + MIN_ALTERED_RECOMPUTE = (1 << 4) | MIN_ALTERED +}; + +/* Constructor. Returns an activity struct. If the activity is valid and + can be used for deduction of something useful, then min and max are + computed. Otherwise, only n_inf_min and n_inf_max are updated. Indices + of rows with potentially useful activities are appended to + updated_activities. */ +Activity *new_activities(const struct Matrix *A, const ColTag *col_tags, + const struct Bound *bounds); + +void free_activities(Activity *activities); + +/* Other type of constructor used for computing dual activities. Unlike + new_activities, this function does not allocate any memory and it does not + compute activities for inactive rows */ +void Activities_init(const struct Matrix *A, const ColTag *col_tags, + const RowTag *row_tags, const struct Bound *bounds, + Activity *activities); + +// Initializes a single activity. Used for dual propagation. +void Activity_init(Activity *act, const double *vals, const int *cols, int len, + const struct Bound *bounds, const ColTag *col_tags); + +/* Recomputes n_inf_min and n_inf_max of a single activity. */ +void recompute_n_infs(Activity *act, const double *vals, const int *cols, int len, + const ColTag *coltags); + +/* Computes minimal and maximal activities of a row, taking column tags into + * account*/ +double compute_min_act_tags(const double *vals, const int *cols, int len, + const struct Bound *bounds, const ColTag *col_tags); +double compute_max_act_tags(const double *vals, const int *cols, int len, + const struct Bound *bounds, const ColTag *col_tags); + +/* Computes minimal and maximal activities, not taking column tags into account. + These functions should only be called when you know that the n_inf_min = 0 + or n_inf_max = 0. */ +double compute_min_act_no_tags(const double *vals, const int *cols, int len, + const struct Bound *bounds); +double compute_max_act_no_tags(const double *vals, const int *cols, int len, + const struct Bound *bounds); + +/* Computes minimal and maximal activities, taking ONE infinite bound into + account. These functions should only be called when you know that + n_inf_min = 1 or n_inf_max = 1. */ +double compute_min_act_one_tag(const double *vals, const int *cols, int len, + const struct Bound *bounds, int col_to_avoid); +double compute_max_act_one_tag(const double *vals, const int *cols, int len, + const struct Bound *bounds, int col_to_avoid); + +/* (vals, rows, len) represent the column with a bound change. finite_bound is + true if the old bound was finite. lower is true if a lower bound has been + updated. The new bound must have been updated in the bounds struct before + calling this function. + + This function allows the new_bound to be huge. It is the responsibility of + the caller to make sure that this only happens when it is supposed to happen + (eg. when we update a bound because we have fixed a variable to a huge value, + or because of a bound update due to a singleton inequality row). +*/ +void update_activities_bound_change(Activity *activities, const struct Matrix *A, + const struct Bound *bounds, const double *vals, + const int *rows, int len, double old_bound, + double new_bound, double finite_bound, + bool lower, + iVec *updated_activities HUGE_BOUND_PARAM); + +void update_activities_fixed_col(Activity *activities, const struct Matrix *A, + const struct Bound *bounds, const double *vals, + const int *rows, int len, double old_ub, + double old_lb, double val, bool ub_update, + bool lb_update, bool is_ub_inf, bool is_lb_inf, + iVec *updated_activities); + +/* Updates the activity struct when a coefficient in the matrix has changed. + Depending on the return value, we might have to recompute a max/min + activity value from scratch outside this function */ +Altered_Activity update_activity_coeff_change(Activity *act, double lb, double ub, + double old_coeff, double new_coeff, + ColTag cTag); + +/* Updates the activity struct when a bound changes. */ +Altered_Activity update_act_bound_change(Activity *act, double coeff, + double old_bound, double new_bound, + bool finite_bound, bool lower, + const double *vals, const int *cols, + int len, const struct Bound *bounds); + +void remove_finite_lb_from_activities(const struct ConstColView *col, Activity *acts, + double bound); + +void remove_finite_ub_from_activities(const struct ConstColView *col, Activity *acts, + double bound); +#endif // ACTIVITY_H \ No newline at end of file diff --git a/include/core/Bounds.h b/include/core/Bounds.h new file mode 100644 index 00000000..f1890819 --- /dev/null +++ b/include/core/Bounds.h @@ -0,0 +1,28 @@ +/* + * Copyright 2025 Daniel Cederberg + * + * This file is part of the PSLP project (LP Presolver). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CORE_BOUNDS_H +#define CORE_BOUNDS_H + +typedef struct Bound +{ + double lb; + double ub; +} Bound; + +#endif // CORE_BOUNDS_H \ No newline at end of file diff --git a/include/core/Constraints.h b/include/core/Constraints.h new file mode 100644 index 00000000..207e4a0d --- /dev/null +++ b/include/core/Constraints.h @@ -0,0 +1,92 @@ +/* + * Copyright 2025 Daniel Cederberg + * + * This file is part of the PSLP project (LP Presolver). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CONSTRAINTS_H +#define CONSTRAINTS_H + +#include + +#include "Tags.h" + +// forward declarations +struct State; +struct Mapping; +struct Matrix; +struct Bound; + +typedef struct Constraints +{ + // left and right hand sides for each row + double *lhs; + double *rhs; + int m; // number of constraints + int n; // number of variables + RowTag *row_tags; + ColTag *col_tags; + + // CSR representations of A and A transpose. CSR representation of A + // transpose is basically CSC representation of A. + struct Matrix *A; + struct Matrix *AT; + + // bounds for each variable + struct Bound *bounds; + + // the constraints must update internal data structures + struct State *state; +} Constraints; + +/* Constructor and destructor */ +Constraints *constraints_new(struct Matrix *A, struct Matrix *AT, double *lhs, + double *rhs, struct Bound *bounds, struct State *state, + RowTag *row_tags, ColTag *col_tags); +void free_constraints(Constraints *constraints); + +/* Cleans the problem and removes free space from eg. rows or variables that + have been deleted. For example, after this function, the pointers lhs and rhs + only contain active rows, and the pointer bounds contain bounds only for + active variables. + + After this call, 'mappings' contains the mapping from the old indices to the + new indices for both rows and columns. For example, if mapping->rows[3] = 2, + then row 3 in the old problem is now row 2 in the new problem. If + mapping->rows[1] = -1, then row 1 in the old problem has been removed. */ +void constraints_clean(Constraints *constraints, struct Mapping *mappings, + bool remove_all); + +/* This function processes the list of deleted rows. + 1. It updates col sizes and the list of empty and singleton columns. + 2. The row size of a deleted row is set to SIZEINACTIVEROW. + 3. It updates locks. */ +void delete_inactive_rows(Constraints *constraints); + +/* This function processes the lists of fixed/substituted columns. + 1. It updates A and AT. + 1. It updates row sizes and the lists of empty/ston rows. + 2. The column size of a deleted column is set to SIZEINACTIVECOL. + + Note that it only updates interal data structures that are + immediately linked to A and AT. In other words, it does not update + the lhs or rhs, or activities etc. + + This function clears the lists of fixed and substituted columns to + delete. +*/ +void delete_inactive_cols_from_A_and_AT(Constraints *constraints); + +#endif // CONSTRAINTS_H \ No newline at end of file diff --git a/include/core/CoreTransformations.h b/include/core/CoreTransformations.h new file mode 100644 index 00000000..40996369 --- /dev/null +++ b/include/core/CoreTransformations.h @@ -0,0 +1,126 @@ +/* + * Copyright 2025 Daniel Cederberg + * + * This file is part of the PSLP project (LP Presolver). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CORE_TRANSFORMATIONS_H +#define CORE_TRANSFORMATIONS_H + +#include "Postsolver.h" +#include "PresolveStatus.h" +#include "Tags.h" +#include "debug_macros.h" +#include "glbopts.h" +#include "iVec.h" +#include + +struct Constraints; +struct Lock; +struct Activity; +struct RowView; +struct ConstColView; +struct ConstRowView; +struct Matrix; +struct Bound; + +/* Checks if fixing a column to 'val' is feasible with respect to its bounds. + If yes, it fixes it and updates the activities. When a variable is fixed we + set both its lb and ub to its value. */ +PresolveStatus fix_col(struct Constraints *constraints, int col, double val, + double ck); + +/* Fixes a variable to infinity and marks the constraints it appears in as + inactive. */ +void fix_col_to_negative_inf(struct Constraints *constraints, int col); +void fix_col_to_positive_inf(struct Constraints *constraints, int col); + +// These should not be called with large values of new_lb or new_ub. +// 'row' is the row that causes the bound change. +PresolveStatus update_lb(struct Constraints *constraints, int col, double new_lb, + int row HUGE_BOUND_PARAM); +PresolveStatus update_ub(struct Constraints *constraints, int col, double new_ub, + int row HUGE_BOUND_PARAM); + +/* Updates the rhs/lhs of a row. These functions check for infeasibility + and also if the row becomes an equality constraint after the bound change. + The locks are updated. It also appends to doubleton equality rows + if a doubleton inequality row becomes an equality row. +*/ +PresolveStatus change_rhs_of_ineq(struct RowView *row, double new_rhs, + struct Lock *locks, iVec *dton_rows, + struct PostsolveInfo *postsolve_info, + int other_row_idx, double ratio); + +PresolveStatus change_lhs_of_ineq(struct RowView *row, double new_lhs, + struct Lock *locks, iVec *dton_rows, + struct PostsolveInfo *postsolve_info, + int other_row_idx, double ratio); + +/* Uses activities of a row to deduce if a variable is implied free. */ +bool implied_free_from_above_by_row(double Aik, const struct ConstRowView *row, + double lb, double ub, ColTag col_tag, + const struct Activity *act, + const ColTag *col_tags, + const struct Bound *bounds); +bool implied_free_from_below_by_row(double Aik, const struct ConstRowView *row, + double lb, double ub, ColTag col_tag, + const struct Activity *act, + const ColTag *col_tags, + const struct Bound *bounds); + +/* Checks if the upper or lower bound of a column is implied free by any of the + constraints it appears in. */ +bool is_ub_implied_free(const struct ConstColView *col, struct Activity *activities, + const double *lhs, const double *rhs, const RowTag *row_tags, + const struct Matrix *A, const ColTag *col_tags, + const struct Bound *bounds); +bool is_lb_implied_free(const struct ConstColView *col, struct Activity *activities, + const double *lhs, const double *rhs, const RowTag *row_tags, + const struct Matrix *A, const ColTag *col_tags, + const struct Bound *bounds); + +static inline void set_col_to_fixed(int col, ColTag *col_tag, + iVec *fixed_cols_to_delete) +{ + assert(!HAS_TAG(*col_tag, C_TAG_INACTIVE)); + UPDATE_TAG(*col_tag, C_TAG_FIXED); + iVec_append(fixed_cols_to_delete, col); +} + +static inline void set_row_to_inactive(int row, RowTag *row_tag, + iVec *rows_to_delete, PostsolveInfo *info, + double val) +{ + assert(!HAS_TAG(*row_tag, R_TAG_INACTIVE)); + iVec_append(rows_to_delete, row); + + // very important that we use UPDATE_TAG here instead of RESET_TAG, + // since the old infinity flags are needed for updating the locks + // correctly when the row is deleted. + UPDATE_TAG(*row_tag, R_TAG_INACTIVE); + + save_retrieval_deleted_row(info, row, val); +} + +static inline void set_col_to_substituted(int col, ColTag *col_tag, + iVec *substituted_cols_to_delete) +{ + assert(!HAS_TAG(*col_tag, C_TAG_INACTIVE)); + UPDATE_TAG(*col_tag, C_TAG_SUBSTITUTED); + iVec_append(substituted_cols_to_delete, col); +} + +#endif // CORE_TRANSFORMATIONS_H diff --git a/include/core/Debugger.h b/include/core/Debugger.h new file mode 100644 index 00000000..3f277ea7 --- /dev/null +++ b/include/core/Debugger.h @@ -0,0 +1,130 @@ +/* + * Copyright 2025 Daniel Cederberg + * + * This file is part of the PSLP project (LP Presolver). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CORE_PRESOLVER_DEBUGGER_H +#define CORE_PRESOLVER_DEBUGGER_H + +// forward declarations +struct Constraints; +struct State; +struct Matrix; +struct Bound; +struct Lock; +struct iVec; +struct PresolveStats; + +#include +#include +#include + +#include "Activity.h" +#include "Tags.h" + +int ASSERT_NO_ZEROS_D(const double *x, size_t len); +int ASSERT_INCREASING_I(const int *x, size_t len); +void ASSERT_NO_ACTIVE_STON_ROWS(const struct Matrix *A, const RowTag *row_tags); +void print_int_array(const int *arr, size_t len); +void print_double_array(const double *arr, size_t len); + +// runs all functions below +void run_debugger(const struct Constraints *constraints, bool finished); + +void verify_problem_up_to_date(const struct Constraints *constraints); + +// write a verify activity function. The max and min should be zero unless it is +// valid. void verify_activity(const Constraints *constraints, int row); +// TODO: should maybe take large bounds into account here? +void verify_activities(const struct Constraints *constraints); +void verify_activity(const ColTag *col_tags, const struct Bound *bounds, + Activity activity, RowTag row_tag, const int *cols, + const double *vals, int len); + +// Verifies that the states of the activities in updated_activities are ADDED +// and that they have n_inf_min = 0 or n_inf_max = 0. +void verify_row_states(const Activity *acts, const iVec *updated_activities); + +/* Coefficients in the problem data that are too large may cause + numerical issues. This function logs a warning if any entry in + A, lb, ub, lhs, rhs, c has magnitude larger than 10^7. + void verifyMagnitudeOfEntries(); */ + +/* verifies that all empty rows have been appended to the list of + empty rows, and that all rows in the list are empty */ +void verify_empty_rows(const struct Constraints *constraints); + +/* verifies that all singleton rows have been appended to the list of + singleton rows, and that all rows in the list are singleton */ +void verify_ston_rows(const struct Constraints *constraints); + +/* verifies that all doubleton equality rows have been appended + to the list of doubleton rows, and that all rows in the list + are doubleton equality rows. */ +void verify_doubleton_rows(const struct Constraints *constraints); + +/* verify that all empty columns have been appended to the list of + empty columns, and that all columns in the list are empty */ +void verify_empty_cols(const struct Constraints *constraints); + +/* verify that all singleton columns have been appended to the list of + singleton columns, and that all columns in the list are singleton */ +void verify_ston_cols(const struct Constraints *constraints); + +// verify row and column sizes +void verify_row_and_col_sizes(const struct Constraints *constraints); + +/* verifies that there are no duplicates in the list of updated activities, + list of dton_rows and ston_rows */ +// void verify_no_duplicates(const iVec *vec); +void verify_no_duplicates_lists(const struct State *data); +// void verify_no_duplicates_ptr(const int *data, int len); +void verify_no_duplicates_sort(const struct iVec *vec); +void verify_no_duplicates_sort_ptr(const int *data, int len); + +/* verifies that the input is a valid CSR matrix, no explicit zeros are + allowed, nnz are counted (compressed equal to true means that there + is no extra space between rows) + Currently this is static inside debugger.c */ +// void verify_CSR_matrix(const Matrix *A, bool compressed); + +/* verifies that A and AT are consistent */ +bool verify_A_and_AT_consistency(const struct Matrix *A, const struct Matrix *AT); + +/* verifies that + 1. A and AT are consistent + 2. both are valid CSR matrices + 3. A does not store the coefficients of inactive columns + 4. AT does not store the coefficients of inactive rows +*/ +void verify_A_and_AT(const struct Constraints *constraints, bool compressed); + +// verifies that the locks are correct +void verify_locks(const struct Matrix *AT, const struct Lock *locks, + const ColTag *col_tags, const RowTag *row_tags); + +// verifies that the row_tags are correct +void verify_row_tags(const struct Constraints *constraints); + +/* verify that there are no empty rows, empty columns, inactive rows, or + inactive columns when finished */ +void verify_empty_when_finished(const struct Constraints *constraints); + +void run_debugger_stats_consistency_check(const struct PresolveStats *stats); + +void print_matrix(const struct Matrix *A); + +#endif // CORE_PRESOLVER_DEBUGGER_H \ No newline at end of file diff --git a/include/core/Locks.h b/include/core/Locks.h new file mode 100644 index 00000000..6e191443 --- /dev/null +++ b/include/core/Locks.h @@ -0,0 +1,81 @@ +/* + * Copyright 2025 Daniel Cederberg + * + * This file is part of the PSLP project (LP Presolver). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef LOCKS_H +#define LOCKS_H + +#include +#include + +#include "Tags.h" +struct Matrix; +struct RowView; + +typedef struct Lock +{ + int up; + int down; +} Lock; + +/* Contructor that counts locks by traversing the rows of A */ +Lock *new_locks(const struct Matrix *A, const RowTag *row_tags); +void free_locks(Lock *locks); + +/* Updates locks when an equality constraint is transformed to an + inequality constraint*/ +void update_locks_eq_to_ineq(Lock *locks, const double *vals, const int *cols, + int len, bool to_upper); + +/* Updates locks when an inequality constraint is transformed to an + equality constraint*/ +void update_locks_ineq_to_eq(Lock *locks, const double *vals, const int *cols, + int len, bool is_new_constraint_lhs); + +// col should be a row view corresponding to the column +void count_locks_one_column(const struct RowView *col, Lock *lock, + const RowTag *row_tags); + +static inline void update_lock_coeff_deletion(struct Lock *lock, double val, + int isRhsInf, int isLhsInf) +{ + if (val > 0) + { + if (!isRhsInf) + { + lock->up--; + } + if (!isLhsInf) + { + lock->down--; + } + } + else + { + assert(val != 0); + if (!isRhsInf) + { + lock->down--; + } + if (!isLhsInf) + { + lock->up--; + } + } +} + +#endif // LOCKS_H \ No newline at end of file diff --git a/include/core/Matrix.h b/include/core/Matrix.h new file mode 100644 index 00000000..696ba936 --- /dev/null +++ b/include/core/Matrix.h @@ -0,0 +1,125 @@ +/* + * Copyright 2025 Daniel Cederberg + * + * This file is part of the PSLP project (LP Presolver). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SPARSE_LA_MATRIX_H +#define SPARSE_LA_MATRIX_H + +#include + +#include "debug_macros.h" + +struct RowView; + +typedef struct +{ + int start; + int end; +} RowRange; + +// Represents a sparse matrix stored in a modified CSR format. +// The modification is that every row includes some extra space. +typedef struct Matrix +{ + int m; + int n; + int nnz; + int n_alloc; + + int *i; + RowRange *p; + double *x; +} Matrix; + +// Constructs a new matrix with the given values but extra space. +// Assumes that (Ax, Ai, Ap) are CSR. +Matrix *matrix_new(const double *Ax, const int *Ai, const int *Ap, int n_rows, + int n_cols, int nnz); + +Matrix *matrix_new_no_extra_space(const double *Ax, const int *Ai, const int *Ap, + int n_rows, int n_cols, int nnz); + +// Allocate a new matrix with the given dimensions and nnz. +// write matrix_alloc +Matrix *matrix_alloc(int n_rows, int n_cols, int nnz); + +// Returns the transpose of the given matrix +Matrix *transpose(const Matrix *A, int *work_n_cols); + +// Computes the number of entries allocated for a row with 'size' nnzs +int calc_memory_row(int size, int extra_row_space, double memory_ratio); + +// Computes the total number of entries allocated for A +int calc_memory(int nnz, int n_rows, int extra_row_space, double memory_ratio); + +// frees all allocated memory +void free_matrix(Matrix *A); + +// Shifts row obtain extra space. Returns true if the shift was +// successful, and false otherwise. +bool shift_row(Matrix *A, int row, int extra_space, int max_shift); + +/* Updates a coefficient of the matrix. If the coefficient does not exists, + it inserts it. This function assumes there is space. Returns the old + value. The function updates the row size but not any other internal + data structures. + + This function can handle the case when val is zero. In this case, + if the coefficient of 'col' is nonzero in the row, it will be removed. + + This function expects that if 'val' is zero, then 'col' exists in the + row. +*/ +double insert_or_update_coeff(Matrix *A, int row, int col, double val, + int *row_size); + +/* Removes 'col' from a row. Updates the length of the row, but not column + sizes. It is assumed that col exists in the row. */ +void remove_coeff(struct RowView *row, int col); + +void count_rows(const Matrix *A, int *row_sizes); + +/* +When the presolving is finished we want to remove all redundant space, +regardless of its nature. + +It may also be beneficial to remove redundant space associated with +inactive rows/inactive variables in the middle of the presolving process. +In this case we must keep track of inactive rows/columns that are deleted, +since the corresponding rows must also be removed from other data +structures (eg. rowSize, stonRows, rowActivities etc). + +This function will be called on both A and AT. An empty row of AT, ie. an +empty column of A, will be removed when this function is called on AT. +To have consistency between A and AT we must update the column indices of A. +This is done in the end of the function (columns[j] = colsmap[columns[j]]). +*/ +void remove_extra_space(Matrix *A, const int *row_sizes, const int *col_sizes, + bool remove_all, int *col_idxs_map); + +void print_row_starts(const RowRange *row_ranges, size_t len); + +#ifdef TESTING +Matrix *random_matrix_new(int n_rows, int n_cols, double density); + +// replace_row_A assumes the matrix has sufficient with space to shift +// rows; otherwise it throws an assertion +void replace_row_A(Matrix *A, int row, double ratio, double *new_vals, int *cols_new, + int new_len); +#endif + +#endif // SPARSE_LA_MATRIX_H \ No newline at end of file diff --git a/include/core/Numerics.h b/include/core/Numerics.h new file mode 100644 index 00000000..459d4c91 --- /dev/null +++ b/include/core/Numerics.h @@ -0,0 +1,60 @@ +/* + * Copyright 2025 Daniel Cederberg + * + * This file is part of the PSLP project (LP Presolver). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CORE_NUMERICS_H +#define CORE_NUMERICS_H + +#include + +#define FEAS_TOL 1e-6 +#define BOUND_MARGINAL 0.5 * FEAS_TOL +#define ZERO_TOL 1e-10 +#define ZERO_TOL_DUAL_POSTSOLVE 1e-6 +#define HUGE_VAL_PS 1e7 + +// used for model clean up according to Gurobi paper +#define CLEAN1 1e-3 +#define CLEAN2 1e-10 + +#ifndef MAX +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) +#endif + +#ifndef MIN +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#endif + +#ifndef ABS +#define ABS(x) (((x) < 0) ? -(x) : (x)) +#endif + +#define IS_INTEGRAL(x) ((x) == (int) (x)) + +#define IS_EQUAL_FEAS_TOL(x, y) (ABS((x) - (y)) <= FEAS_TOL) +#define IS_ZERO_FEAS_TOL(x) (ABS(x) <= FEAS_TOL) +#define IS_GT_FEAS_TOL(x, y) ((x) - (y) >= FEAS_TOL) +#define IS_LT_FEAS_TOL(x, y) ((y) - (x) >= FEAS_TOL) +#define IS_HUGE(x) (ABS(x) >= HUGE_VAL_PS) + +#define SET_ZERO_IF_SMALL_DUAL_POSTSOLVE(x) \ + do \ + { \ + if (fabs(x) < ZERO_TOL_DUAL_POSTSOLVE) x = 0.0; \ + } while (0) + +#endif // CORE_NUMERICS_H \ No newline at end of file diff --git a/include/core/Postsolver.h b/include/core/Postsolver.h new file mode 100644 index 00000000..3c2c12e5 --- /dev/null +++ b/include/core/Postsolver.h @@ -0,0 +1,188 @@ +/* + * Copyright 2025 Daniel Cederberg + * + * This file is part of the PSLP project (LP Presolver). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CORE_POSTSOLVER_H +#define CORE_POSTSOLVER_H + +#include +#include + +#include "Sol.h" +#include "Tags.h" + +struct u16Vec; +struct dVec; +struct iVec; +struct Constraints; + +typedef uint16_t ReductionType; + +enum ReductionTypes +{ + // required for primal postsolve + FIXED_COL = 0, + FIXED_COL_INF = 1 << 0, + SUB_COL = 1 << 1, + PARALLEL_COL = 1 << 2, + + // required for dual postsolve + DELETED_ROW = 1 << 3, + ADDED_ROW = 1 << 4, + ADDED_ROWS = 1 << 5, + LHS_CHANGE = 1 << 6, + RHS_CHANGE = 1 << 7, + EQ_TO_INEQ = 1 << 8, + BOUND_CHANGE_NO_ROW = 1 << 9, + BOUND_CHANGE_THE_ROW = 1 << 10, +}; + +typedef struct PostsolveInfo +{ + int n_cols_reduced; + int n_rows_reduced; + + // contains the type of reduction + struct u16Vec *type; + + // contains the start index of the information required to undo a + // reduction + struct iVec *starts; + + // indices and vals contain the information required to undo a reduction + struct iVec *indices; + struct dVec *vals; + + // maps from original problem to reduced problem. If col_map[4] = 2 it means + // that the 2nd column in the reduced problem corresponds to the 4th column + // in the original problem. If col_map[4] = -1 it means that the 4th column + // in the original problem was removed. + const int *col_map; + const int *row_map; +} PostsolveInfo; + +PostsolveInfo *postsolve_info_new(int n_rows, int n_cols); +void postsolve_info_free(PostsolveInfo *info); +void postsolver_update(PostsolveInfo *info, int n_cols_reduced, int n_rows_reduced, + const int *col_map, const int *row_map); +void postsolver_run(const PostsolveInfo *info, Solution *sol, const double *x, + const double *y, const double *z); + +/* Saves the information required to retrieve variable xk that was fixed + to val. To recover the dual variable we need zk = ck - ak^T y + * info->vals stores [val, ck, ak[0], ak[1], .., dots, ak[len - 1] + * info->indices stores [col, dummy, rows[0], rows[1], ... rows[len - 1]]. +*/ +void save_retrieval_fixed_col(PostsolveInfo *info, int col, double val, double ck, + const double *vals, const int *rows, int len); + +/* Saves the information required to retrieve variable xk that was fixed + to either +INF or -INF. + * info->indices stores [sign(xk), k, row_len1, cols1, row_len2, + cols2, ..., row_len_nrows, cols_nrows] + where sign(xk) = 1 if xk is fixed to +INF and -1 if xk is fixed to -INF. + + * info->vals stores [nrows, bound, rhs / lhs 1, coeffs1, rhs / lhs 2, + coeffs2, ..., rhs / lhs nrows, coeffs_nrows] +*/ +void save_retrieval_fixed_col_inf(PostsolveInfo *info, int col, int pos_inf, + const struct Constraints *constraints, + double bound); + +/* This function saves the information required to retrieve variable xk + that was substituted from the problem using equality constraint i: + aik xk + sum_{j != k} aij xj = rhs. + info->vals stores [rhs, vals[0], vals[1], ... vals[len - 1], ck]. + info->indices stores [k , cols[0], cols[1], ... cols[len - 1], i]. + */ +void save_retrieval_sub_col(PostsolveInfo *info, int col, int *cols, double *coeffs, + int len, double rhs, int i, double ck); + +/* This function saves the information required to retrieve variable xj + and xk that were replaced with a new variable x_new = xj + ratio * xk + due to parallel column reduction. + info->vals stores [lb_j, ub_j, lb_k, ub_k, ratio]. + info->indices stores [j, k, cTag_j, cTag_k, dummy_value]. +*/ +void save_retrieval_parallel_col(PostsolveInfo *info, double ub_j, double lb_j, + double lb_k, double ub_k, double ratio, int j, + int k, ColTag cTag_j, ColTag cTag_k); + +/* This function saves the value on yi when row i is deleted */ +void save_retrieval_deleted_row(PostsolveInfo *info, int row, double val); + +/* This function saves the information required to retrieve yi when + row i is added to row j so the new row j becomes aj = aj + ratio * ai +*/ +void save_retrieval_added_row(PostsolveInfo *info, int i, int j, double ratio); + +/* This function saves the information required to retrieve yi and yj + when the rhs or lhs of row i is changed because of row j. Here we assume that + ai = ratio * aj. + + * info->vals stores [new_side, ratio, vals[0], vals[1], ... vals[len - 1]]. + * info->indices stores [i, j, cols[0], cols[1], ... cols[len - 1]]. + + where vals and cols correspond to row i. +*/ +void save_retrieval_rhs_or_lhs_change(PostsolveInfo *info, int i, const double *vals, + const int *cols, int len, double new_side, + int j, double ratio, bool is_lhs_change); + +/* This function saves the information required to retrieve yi when + row i has been added to many other rows. In this case we have + yi = yi - \sum_{j \neq i} (ajk / aik) yj. + + * info->vals stores [aik, vals[0], vals[1], ... vals[len - 1]]. + * info->indices stores [i, rows[0], rows[1], ... rows[len - 1]]. +*/ +void save_retrieval_added_rows(PostsolveInfo *info, int i, const int *rows, + const double *vals, int len, double aik); + +/* This function saves the information required to undo the effect of a bound + change. Suppose we use row 'i' to update one bound on variable 'j'. + If the implied bound on variable 'j' is active at the optimal solution + of the reduced problem, we set yi = yi + zj / aij. For every variable k + appearing in row 'i', we update zk = zk - (aik / aij) * zj. + + * info->vals stores [implied_bound, original_bound]. + * info->indices stores [j, is_original_lower_bound] +*/ +void save_retrieval_bound_change_no_row(PostsolveInfo *info, int j, + double implied_bound, + double original_other_bound, + int is_original_other_bound_lower_bound); + +/* This function saves the actual row that was used to derive several bound changes. + info->vals stores [(double) num_of_bound_changes), vals] + info->indices stores [i, cols] +*/ +void save_retrieval_bound_change_the_row(PostsolveInfo *info, int i, const int *cols, + const double *vals, int len, + int num_of_bound_changes); + +/* This function saves the information required to retrieve yi when + equality row i has been transformed into an inequality by eliminating + column k. The postsolve is + yi = (ck / aik) + yi. + + * info->vals stores [ck / aik]. + * info->indices stores [i]. +*/ +void save_retrieval_eq_to_ineq(PostsolveInfo *info, int row, double val); + +#endif // CORE_POSTSOLVER_H \ No newline at end of file diff --git a/include/core/PresolveStats.h b/include/core/PresolveStats.h new file mode 100644 index 00000000..31c0f5e1 --- /dev/null +++ b/include/core/PresolveStats.h @@ -0,0 +1,50 @@ +/* + * Copyright 2025 Daniel Cederberg + * + * This file is part of the PSLP project (LP Presolver). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PRESOLVE_STATS_H +#define PRESOLVE_STATS_H + +typedef struct PresolveStats +{ + int n_rows_original; + int n_cols_original; + int nnz_original; + int n_rows_reduced; + int n_cols_reduced; + int nnz_reduced; + + /* reduction stats */ + int nnz_removed_trivial; + int nnz_removed_fast; + int nnz_removed_primal_propagation; + int nnz_removed_parallel_rows; + int nnz_removed_parallel_cols; + + /* time stats */ + double ps_time_init; + double ps_time_fast; + double ps_time_medium; + double ps_time_primal_propagation; + double ps_time_parallel_rows; + double ps_time_parallel_cols; + double presolve_total_time; + double ps_time_post_solve; + +} PresolveStats; + +#endif // PRESOLVE_STATS_H \ No newline at end of file diff --git a/include/core/Problem.h b/include/core/Problem.h new file mode 100644 index 00000000..adea14f5 --- /dev/null +++ b/include/core/Problem.h @@ -0,0 +1,60 @@ +/* + * Copyright 2025 Daniel Cederberg + * + * This file is part of the PSLP project (LP Presolver). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CORE_PROBLEM_H +#define CORE_PROBLEM_H + +#include +struct Constraints; + +typedef struct +{ + double *c; + double offset; +} Objective; + +typedef struct Problem +{ + struct Constraints *constraints; + Objective *obj; +} Problem; + +/* Constructor and destructor */ +Problem *new_problem(struct Constraints *constraints, Objective *obj); +void free_problem(Problem *problem); + +/* Cleans the problem in the sense that all data associated with + inactive rows and inactive columns is removed. Rows and columns + are also re-indexed to take the removal of rows/columns into + account. */ +void problem_clean(Problem *problem, bool remove_all); + +/* Constructor and destructor */ +Objective *objective_new(double *c); +void free_objective(Objective *obj); + +/* Updates the offset when a variable is fixed */ +void fix_var_in_obj(Objective *obj, int col, double value); + +/* Substitutes variable 'k' from the objective using row 'i' + (assumed to be an equality). Row 'i' is specified by + (vals, cols, len, rhs) */ +void sub_var_in_obj(Objective *obj, const double *vals, const int *cols, int len, + int k, double aik, double rhs); + +#endif // CORE_PROBLEM_H \ No newline at end of file diff --git a/include/core/RowColViews.h b/include/core/RowColViews.h new file mode 100644 index 00000000..8e9b8b19 --- /dev/null +++ b/include/core/RowColViews.h @@ -0,0 +1,136 @@ +/* + * Copyright 2025 Daniel Cederberg + * + * This file is part of the PSLP project (LP Presolver). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Matrix.h" +#include "Tags.h" + +typedef struct RowView +{ + double *vals; + int *cols; + int *len; + RowRange *range; + double *lhs; + double *rhs; + RowTag *tag; + int i; +} RowView; + +typedef struct ConstRowView +{ + const double *vals; + const int *cols; + const int *len; + const RowRange *range; + const double *lhs; + const double *rhs; + const RowTag *tag; + int i; +} ConstRowView; + +typedef struct ColView +{ + double *vals; + int *rows; + int *len; + RowRange *range; + double *lb; + double *ub; + ColTag *tag; + int k; +} ColView; + +typedef struct ConstColView +{ + const double *vals; + const int *rows; + const int *len; + const RowRange *range; + const double *lb; + const double *ub; + const ColTag *tag; + int k; +} ConstColView; + +static inline RowView new_rowview(double *vals, int *cols, int *len, + RowRange *row_range, double *lhs, double *rhs, + RowTag *tag, int i) +{ + RowView view; + view.vals = vals; + view.cols = cols; + view.len = len; + view.range = row_range; + view.lhs = lhs; + view.rhs = rhs; + view.tag = tag; + view.i = i; + return view; +} + +static inline ConstRowView new_const_rowview(const double *vals, const int *cols, + const int *len, + const RowRange *row_range, + const double *lhs, const double *rhs, + const RowTag *tag, int i) +{ + ConstRowView view; + view.vals = vals; + view.cols = cols; + view.len = len; + view.range = row_range; + view.lhs = lhs; + view.rhs = rhs; + view.tag = tag; + view.i = i; + return view; +} + +static inline ColView new_colview(double *vals, int *rows, int *len, + RowRange *col_range, double *lb, double *ub, + ColTag *tag, int k) +{ + ColView view; + view.vals = vals; + view.rows = rows; + view.len = len; + view.range = col_range; + view.lb = lb; + view.ub = ub; + view.tag = tag; + view.k = k; + return view; +} + +static inline ConstColView new_const_colview(const double *vals, const int *rows, + const int *len, + const RowRange *col_range, + const double *lb, const double *ub, + const ColTag *tag, int k) +{ + ConstColView view; + view.vals = vals; + view.rows = rows; + view.len = len; + view.range = col_range; + view.lb = lb; + view.ub = ub; + view.tag = tag; + view.k = k; + return view; +} \ No newline at end of file diff --git a/include/core/State.h b/include/core/State.h new file mode 100644 index 00000000..cc92c4d9 --- /dev/null +++ b/include/core/State.h @@ -0,0 +1,64 @@ +/* + * Copyright 2025 Daniel Cederberg + * + * This file is part of the PSLP project (LP Presolver). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CORE_INTERNALDATA_H +#define CORE_INTERNALDATA_H + +#include "Postsolver.h" +#include "Tags.h" +#include "iVec.h" + +// forward declarations +struct Mapping; +struct Activity; +struct Work; +struct Lock; + +typedef struct State +{ + iVec *ston_rows; + iVec *ston_cols; + iVec *dton_rows; + iVec *empty_cols; + iVec *empty_rows; + iVec *updated_activities; + struct Lock *col_locks; + struct Activity *activities; + struct Work *work; + PostsolveInfo *postsolve_info; + + // number of non-zeros within each row and column + int *row_sizes; + int *col_sizes; + + iVec *fixed_cols_to_delete; + iVec *sub_cols_to_delete; + iVec *rows_to_delete; +} State; + +/* Constructor and destructor */ +State *new_state(int *row_sizes, int *col_sizes, struct Lock *col_locks, int n_rows, + int n_cols, struct Activity *activities, struct Work *work, + const RowTag *row_tags); +void free_state(State *data); + +/* Maintains consistency of all internal data structures when columns and + removes are deleted */ +void clean_state(State *data, const struct Mapping *maps, int n_rows, int n_cols); + +#endif // CORE_INTERNALDATA_H \ No newline at end of file diff --git a/include/core/Tags.h b/include/core/Tags.h new file mode 100644 index 00000000..b8a5f3a9 --- /dev/null +++ b/include/core/Tags.h @@ -0,0 +1,60 @@ +/* + * Copyright 2025 Daniel Cederberg + * + * This file is part of the PSLP project (LP Presolver). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CORE_TAGS_H +#define CORE_TAGS_H +#include + +// Parallel cols assumes these are 8 bits +typedef uint8_t RowTag; +typedef uint8_t ColTag; + +enum RowTag +{ + R_TAG_NONE = 0, + R_TAG_LHS_INF = 1 << 0, + R_TAG_RHS_INF = 1 << 1, + R_TAG_EQ = 1 << 2, + R_TAG_INACTIVE = 1 << 3, + R_TAG_INFEAS = 1 << 4 +}; + +enum ColTag +{ + C_TAG_NONE = 0, + C_TAG_LB_INF = 1 << 0, + C_TAG_LB_HUGE = 1 << 1, + C_TAG_UB_INF = 1 << 2, + C_TAG_UB_HUGE = 1 << 3, + C_TAG_FIXED = 1 << 4, + C_TAG_SUBSTITUTED = 1 << 5, + // for parallel cols to work correctly it is important + // that C_TAG_INACTIVE contains R_TAG_INACTIVE + C_TAG_INACTIVE = (C_TAG_FIXED | C_TAG_SUBSTITUTED) +}; + +RowTag *new_rowtags(double *lhs, double *rhs, int n_rows); + +#define UPDATE_TAG(tag, new_tag) (tag |= new_tag) +#define REMOVE_TAG(tag, old_tag) (tag &= ~old_tag) +#define HAS_TAG(tag, check_tag) (tag & check_tag) +#define RESET_TAG(tag, new_tag) (tag = new_tag) +#define HAS_STATUS(status, check_status) (status & check_status) +#define HAS_INF_TAG(tag) HAS_TAG((tag), (C_TAG_LB_INF | C_TAG_UB_INF)) + +#endif // CORE_TAGS_H diff --git a/include/core/Workspace.h b/include/core/Workspace.h new file mode 100644 index 00000000..7aee187f --- /dev/null +++ b/include/core/Workspace.h @@ -0,0 +1,55 @@ +/* + * Copyright 2025 Daniel Cederberg + * + * This file is part of the PSLP project (LP Presolver). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CORE_WORKSPACE_H +#define CORE_WORKSPACE_H + +#include "Bounds.h" +#include "iVec.h" + +typedef struct Mapping +{ + int *rows; + int *cols; +} Mapping; + +/* The int_vec in the following struct is used within + 1. parallel_rows to store bin_starts +*/ +typedef struct Work +{ + int *iwork_n_cols; + + // iwork_n_rows are used within parallel_rows + int *iwork_n_rows; + + // used within parallel_rows and parallel_cols + // iwork1_max_nrows_ncols is also used within dual propagation + int *iwork1_max_nrows_ncols; + int *iwork2_max_nrows_ncols; + + // int_vec is used within parallel_rows and dual propagation + iVec *int_vec; + + Mapping *mappings; +} Work; + +Work *new_work(int n_rows, int n_col); +void free_work(Work *work); + +#endif // CORE_WORKSPACE_H \ No newline at end of file diff --git a/include/core/debug_macros.h b/include/core/debug_macros.h new file mode 100644 index 00000000..85cd48b6 --- /dev/null +++ b/include/core/debug_macros.h @@ -0,0 +1,75 @@ +/* + * Copyright 2025 Daniel Cederberg + * + * This file is part of the PSLP project (LP Presolver). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef DEBUG_MACROS_H +#define DEBUG_MACROS_H + +#include +#include + +#ifdef NDEBUG +#define HUGE_BOUND_IS_OK +#define HUGE_BOUND_IS_NOT_OK +#define HUGE_BOUND_PARAM +#define HUGE_BOUND_ARG +#else +#define HUGE_BOUND_IS_OK , true +#define HUGE_BOUND_IS_NOT_OK , false +#define HUGE_BOUND_PARAM , bool huge_bound_ok +#define HUGE_BOUND_ARG , huge_bound_ok +#endif + +// OBS: The macros should be wrapped inside a DEBUG when use + +#ifndef NDEBUG +#define DEBUG(code) \ + do \ + { \ + code; \ + } while (0) +#else +#define DEBUG(code) \ + do \ + { \ + } while (0) +#endif + +#define ARRAYS_EQUAL(arr1, arr2, size) \ + ({ \ + bool arrays_equal = true; \ + for (size_t i = 0; i < (size); i++) \ + { \ + if ((arr1)[i] != (arr2)[i]) \ + { \ + arrays_equal = false; \ + break; \ + } \ + } \ + arrays_equal; \ + }) + +#define ASSERT_NO_INACTIVE_ROWS(rows, row_tags, len) \ + do \ + { \ + for (int i = 0; i < (len); i++) \ + { \ + assert(!HAS_TAG(row_tags[rows[i]], R_TAG_INACTIVE)); \ + } \ + } while (0) + +#endif // DEBUG_MACROS_H \ No newline at end of file diff --git a/include/core/glbopts.h b/include/core/glbopts.h new file mode 100644 index 00000000..eaacda0f --- /dev/null +++ b/include/core/glbopts.h @@ -0,0 +1,49 @@ +/* + * Copyright 2025 Daniel Cederberg + * + * This file is part of the PSLP project (LP Presolver). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GLB_H_GUARD +#define GLB_H_GUARD + +#ifdef TESTING +#define EXTRA_ROW_SPACE 2 +#define EXTRA_MEMORY_RATIO 1 +#else +#define EXTRA_ROW_SPACE 4 +// this value on EXTRA_MEMORY_RATIO matters for neos-5093327-huahum +#define EXTRA_MEMORY_RATIO 2 +#endif + +#define RETURN_IF_INFEASIBLE(x) \ + if ((x) == INFEASIBLE) return INFEASIBLE +#define RETURN_IF_UNBNDORINFEAS(x) \ + if ((x) == UNBNDORINFEAS) return UNBNDORINFEAS +#define RETURN_IF_NOT_UNCHANGED(x) \ + if ((x) != UNCHANGED) return x + +#define SIZE_INACTIVE_ROW -1 +#define SIZE_INACTIVE_COL -1 +#define MAX_RATIO_PIVOT 1e3 + +#define INF 1e20 +#define IS_POS_INF(x) ((x) >= INF) +#define IS_NEG_INF(x) ((x) <= -INF) +#define IS_ABS_INF(x) (IS_POS_INF(x) || IS_NEG_INF(x)) + +#define CVX_PRESOLVE_VERSION "0.0.1" + +#endif \ No newline at end of file diff --git a/include/core/utils.h b/include/core/utils.h new file mode 100644 index 00000000..9f44d649 --- /dev/null +++ b/include/core/utils.h @@ -0,0 +1,35 @@ +/* + * Copyright 2025 Daniel Cederberg + * + * This file is part of the PSLP project (LP Presolver). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CORE_UTILS_H +#define CORE_UTILS_H +#include "Tags.h" +#include "iVec.h" + +/* Functions for shrinking (ie. removing data belonging to inactive + cols/rows) */ +void dPtr_shrink(double *ptr, const int *map, int len); +void iPtr_shrink(int *ptr, const int *map, int len); +void rowTagPtr_shrink(RowTag *ptr, const int *map, int len); +void colTagPtr_shrink(ColTag *ptr, const int *map, int len); +void shrink_idx_vector(iVec *vec, const int *map); + +/* Other utility functions */ +double get_max_abs(const double *vals, int len); + +#endif // CORE_UTILS_H \ No newline at end of file diff --git a/include/data_structures/Memory_wrapper.h b/include/data_structures/Memory_wrapper.h new file mode 100644 index 00000000..40918e75 --- /dev/null +++ b/include/data_structures/Memory_wrapper.h @@ -0,0 +1,62 @@ +/* + * Copyright 2025 Daniel Cederberg + * + * This file is part of the PSLP project (LP Presolver). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MEMORY_WRAPPER_H +#define MEMORY_WRAPPER_H + +#include +#include + +static inline void *ps_malloc(int n, size_t size) +{ + return malloc(n * size); +} + +static inline void *ps_calloc(int n, size_t size) +{ + return calloc(n, size); +} + +#define PS_FREE(p) \ + do \ + { \ + free(p); \ + p = NULL; \ + } while (0) + +static inline void *ps_realloc(void *p, int n, size_t size) +{ + return realloc(p, n * size); +} + +#define RETURN_PTR_IF_NULL(ptr, ret_val) \ + do \ + { \ + if ((ptr) == NULL) \ + { \ + return (ret_val); \ + } \ + } while (0) + +#define RETURN_IF_NULL(ptr) \ + do \ + { \ + if ((ptr) == NULL) return; \ + } while (0) + +#endif // MEMORY_WRAPPER_H diff --git a/include/data_structures/Vec_macros.h b/include/data_structures/Vec_macros.h new file mode 100644 index 00000000..7ca7fd9a --- /dev/null +++ b/include/data_structures/Vec_macros.h @@ -0,0 +1,129 @@ +/* + * Copyright 2025 Daniel Cederberg + * + * This file is part of the PSLP project (LP Presolver). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef VEC_MACROS_H +#define VEC_MACROS_H + +#include +#include +#include +#include + +#include "Memory_wrapper.h" + +// Macro to define a generic vector class +#define DEFINE_VECTOR(TYPE, TYPE_NAME) \ + typedef struct TYPE_NAME##Vec \ + { \ + TYPE *data; \ + size_t len; \ + size_t capacity; \ + } TYPE_NAME##Vec; \ + \ + __attribute__((unused)) static TYPE_NAME##Vec *TYPE_NAME##Vec_new( \ + size_t capacity) \ + { \ + assert(capacity > 0); \ + TYPE_NAME##Vec *vec = \ + (TYPE_NAME##Vec *) ps_malloc(1, sizeof(TYPE_NAME##Vec)); \ + RETURN_PTR_IF_NULL(vec, NULL); \ + vec->data = (TYPE *) ps_malloc(capacity, sizeof(TYPE)); \ + if (vec->data == NULL) \ + { \ + PS_FREE(vec); \ + return NULL; \ + } \ + \ + vec->len = 0; \ + vec->capacity = capacity; \ + return vec; \ + } \ + \ + static inline void TYPE_NAME##Vec_free(TYPE_NAME##Vec *vec) \ + { \ + PS_FREE(vec->data); \ + PS_FREE(vec); \ + } \ + \ + static inline void TYPE_NAME##Vec_clear_no_resize(TYPE_NAME##Vec *vec) \ + { \ + vec->len = 0; \ + } \ + \ + static inline void TYPE_NAME##Vec_append(TYPE_NAME##Vec *vec, TYPE value) \ + { \ + if (vec->len >= vec->capacity) \ + { \ + vec->capacity *= 2; \ + assert(vec->capacity > 0); \ + TYPE *temp = \ + (TYPE *) ps_realloc(vec->data, vec->capacity, sizeof(TYPE)); \ + if (temp == NULL) \ + { \ + TYPE_NAME##Vec_free(vec); \ + fprintf(stderr, "Error: realloc failed\n"); \ + exit(1); \ + } \ + \ + vec->data = temp; \ + } \ + \ + vec->data[vec->len++] = value; \ + } \ + \ + static inline void TYPE_NAME##Vec_append_array(TYPE_NAME##Vec *vec, \ + const TYPE *values, size_t n) \ + { \ + if (vec->len + n > vec->capacity) \ + { \ + size_t new_capacity = vec->capacity > 0 ? vec->capacity : 1; \ + while (vec->len + n > new_capacity) \ + { \ + new_capacity *= 2; \ + } \ + \ + TYPE *temp = \ + (TYPE *) ps_realloc(vec->data, new_capacity, sizeof(TYPE)); \ + if (temp == NULL) \ + { \ + TYPE_NAME##Vec_free(vec); \ + fprintf(stderr, "Error: realloc failed\n"); \ + exit(1); \ + } \ + \ + vec->data = temp; \ + vec->capacity = new_capacity; \ + } \ + \ + memcpy(vec->data + vec->len, values, n * sizeof(TYPE)); \ + vec->len += n; \ + } \ + __attribute__((unused)) static int TYPE_NAME##Vec_contains( \ + const TYPE_NAME##Vec *vec, TYPE value) \ + { \ + for (size_t i = 0; i < vec->len; ++i) \ + { \ + if (vec->data[i] == value) \ + { \ + return 1; /* Element found */ \ + } \ + } \ + return 0; /* Element not found */ \ + } + +#endif diff --git a/include/data_structures/dVec.h b/include/data_structures/dVec.h new file mode 100644 index 00000000..dd22e4c1 --- /dev/null +++ b/include/data_structures/dVec.h @@ -0,0 +1,26 @@ +/* + * Copyright 2025 Daniel Cederberg + * + * This file is part of the PSLP project (LP Presolver). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef DVEC_H +#define DVEC_H + +#include "Vec_macros.h" + +DEFINE_VECTOR(double, d) + +#endif // DVEC_H \ No newline at end of file diff --git a/include/data_structures/iVec.h b/include/data_structures/iVec.h new file mode 100644 index 00000000..c75dd6db --- /dev/null +++ b/include/data_structures/iVec.h @@ -0,0 +1,38 @@ +/* + * Copyright 2025 Daniel Cederberg + * + * This file is part of the PSLP project (LP Presolver). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef IVEC_H +#define IVEC_H + +#include + +#include "Vec_macros.h" + +// This macro defines a vector of integers. +DEFINE_VECTOR(int, i) + +__attribute__((unused)) static void print_ivec(iVec *vec) +{ + for (size_t i = 0; i < vec->len; ++i) + { + printf("%d ", vec->data[i]); + } + printf("\n"); +} + +#endif // IVEC_H \ No newline at end of file diff --git a/include/data_structures/u16Vec.h b/include/data_structures/u16Vec.h new file mode 100644 index 00000000..88a72268 --- /dev/null +++ b/include/data_structures/u16Vec.h @@ -0,0 +1,31 @@ +/* + * Copyright 2025 Daniel Cederberg + * + * This file is part of the PSLP project (LP Presolver). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +// This macro defines a vector of uint16_t. +DEFINE_VECTOR(uint16_t, u16) + +__attribute__((unused)) static void print_u16Vec(const u16Vec *vec) +{ + for (int i = 0; i < vec->len; ++i) + { + printf("%d ", vec->data[i]); + } + printf("\n"); +} \ No newline at end of file diff --git a/include/explorers/DTonsEq.h b/include/explorers/DTonsEq.h new file mode 100644 index 00000000..4d08c6b3 --- /dev/null +++ b/include/explorers/DTonsEq.h @@ -0,0 +1,87 @@ +/* + * Copyright 2025 Daniel Cederberg + * + * This file is part of the PSLP project (LP Presolver). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef DTONS_EQ_H +#define DTONS_EQ_H + +#include "PresolveStatus.h" +struct Matrix; +struct Problem; +struct PostsolveInfo; + +/* This function removes doubleton equality rows from the problem. All internal + data structures (such as row sizes, columns sizes, activities, bounds, lists + of ston rows etc.) are updated accordingly. Doubleton rows that appear + when another doubleton row is eliminated are also removed. After this + function, the list of doubleton rows should be empty. + + When this function finishes, the problem is up to date, and no extra + synchronization is needed. + + When we process a doubleton row, we first choose the variable that should be + eliminated according to the following priorities. + 1. Integral ratios. (For example, if the first variable gives an integral + ratio but not the second, we will substitute the first variable regardless + of its column length etc.) + 2. Column lengths. (We substitute the variable with the smallest length.) + 3. We then have a simple check to reject large or small pivots. + 4. We then theck if there is sufficient space in AT for the substitution. + If yes, we substitute the variable. If no, we reject the reduction. + A possible modification is to check if it is possible to substitute the + other variable if there wasn't enough space for substituting the other + variable. + + A doubleton row is not eliminated if: + * there is not sufficient space in A transpose to store the fill-in + caused by the substitution. + * the pivot is too large or too small. + + The current implementation is not that sophisticated. We might want to + reject the reduction if the fill-in is too large, or add a more sophisticated + rejection criteria for numerical stability. Perhaps we should only + allow the substitution if the new bound is not too large? + */ +PresolveStatus remove_dton_eq_rows(struct Problem *prob, int max_shift); + +typedef struct +{ + double old_coeff_stay; + double old_coeff_subst; + double new_coeff_stay; +} Old_and_new_coeff; + +/* This function updates row 'q' in A when the variable 'k' is + substituted using a doubleton row. The argument 'ratio' is aij / aik + where the doubleton row that is substituted is aij * xj + aik * xk = rhs, + and xj stays and xk is substituted. + + The function returns the new coefficient of the variable that stays. + Note that we delete the substituted variable from the row before we insert + the new variable, so we can substitute xk for xj even in rows with no extra + space. + + This function is static inside DTonsEq.c, but we expose it for testing + purposes. +*/ +#ifdef TESTING +Old_and_new_coeff update_row_A_dton(struct Matrix *A, int i, int q, int j, int k, + double aij, double aik, int *row_size, + struct PostsolveInfo *postsolve_info); +#endif + +#endif \ No newline at end of file diff --git a/include/explorers/Parallel_cols.h b/include/explorers/Parallel_cols.h new file mode 100644 index 00000000..b7ba23e7 --- /dev/null +++ b/include/explorers/Parallel_cols.h @@ -0,0 +1,33 @@ +/* + * Copyright 2025 Daniel Cederberg + * + * This file is part of the PSLP project (LP Presolver). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PARALLEL_COLS_H +#define PARALLEL_COLS_H + +#include "PresolveStatus.h" + +// forward declaration +struct Problem; + +/* This function finds parallel columns and merges them. It always + returns UNCHANGED. When this function finishes, the problem is + up to date, and no extra synchronization is needed. +*/ +PresolveStatus remove_parallel_cols(struct Problem *prob); + +#endif // PARALLEL_COLS_H \ No newline at end of file diff --git a/include/explorers/Parallel_rows.h b/include/explorers/Parallel_rows.h new file mode 100644 index 00000000..e8af35a8 --- /dev/null +++ b/include/explorers/Parallel_rows.h @@ -0,0 +1,70 @@ +/* + * Copyright 2025 Daniel Cederberg + * + * This file is part of the PSLP project (LP Presolver). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PARALLEL_ROWS_H +#define PARALLEL_ROWS_H + +#include "PresolveStatus.h" +#include "Tags.h" + +// forward declarations +struct Constraints; +struct Matrix; +struct iVec; + +// this is static and inlined in .c file, but we expose it for testing +#ifdef TESTING +void compute_supp_and_coeff_hash(const struct Matrix *A, const RowTag *rowtags, + int *sparsity_IDs, int *coeff_hashes, + RowTag INACTIVE_TAG); +#endif + +/* This function finds all parallel rows among the active rows of a matrix A. + If you want to find all parallel columns among the active columns, call + it on AT. OBS! When calling this function, it is assumed that coefficients + corresponding to inactive columns have been removed from A. + + The function works by first hashing the sparsity patterns. It then hashes + the coefficients of rows with the same sparsity pattern? Finally, .... + + After completiton: + * 'group_starts' is a vector of size 'n_groups', where 'parallel_rows[i]' is + the index of the first row in group 'i'. + * The indices of the rows in group 'i' are stored in 'parallel_rows' starting + at 'group_starts[i]' and ending at 'group_starts[i+1] - 1'. + + Memory requirements: + * 'parallel_rows' must be of size A->m. (It is used as internal workspace) + * 'sparsity_IDs' must be of size A->m. + * 'coeff_hashes' must be of size A->m. +*/ +void find_parallel_rows(const struct Matrix *A, const RowTag *r_Tags, + struct iVec *group_starts, int *parallel_rows, + int *sparsity_IDs, int *coeff_hashes, RowTag INACTIVE_TAG); + +/* This function finds parallel rows and deletes the redundant ones, or declares + the problem as infeasible if the parallel rows are not consistent. It returns + INFEASIBLE or UNCHANGED. Even if the problem is reduced, UNCHANGED is + returned. + + When this function finishes, the problem is up to date, and no extra + synchronization is needed. +*/ +PresolveStatus remove_parallel_rows(struct Constraints *constraints); + +#endif // PARALLEL_ROWS_H \ No newline at end of file diff --git a/include/explorers/Primal_propagation.h b/include/explorers/Primal_propagation.h new file mode 100644 index 00000000..610b15a2 --- /dev/null +++ b/include/explorers/Primal_propagation.h @@ -0,0 +1,80 @@ +/* + * Copyright 2025 Daniel Cederberg + * + * This file is part of the PSLP project (LP Presolver). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PRIMAL_PROPAGATION_H +#define PRIMAL_PROPAGATION_H + +#include + +#include "PresolveStatus.h" +#include "Tags.h" + +struct Constraints; +struct Bound; +struct Problem; +struct Activity; +struct ConstRowView; + +typedef PresolveStatus (*BoundUpdater)(double, double *, double, int, ColTag *, + struct Problem *, void *); + +/* This function tries to tighten bounds of variables appearing in rows + listed in data->updated_activities. We always tight infinite bounds, and + if finite_bound_tightening is true we also tight finite bounds. We reject too + small bound changes. When an implied bound is not an integer, we loosen + the bound by the derived bound by 'BOUND_MARGINAL'. Note that this + function implicitly takes care of forced rows. + + The function returns INFEASIBLE if infeasibility was discovered and + otherwise UNCHANGED, even if the problem was reduced. This is a design + choice. + + Like Gurobi, we only perform at most one round of domain propagation on + each constraint in each presolver round. + + After this function, data->updated_activities is empty. Furthermore, + when this function finishes, the problem is up to date, and no extra + synchronization is needed. +*/ +PresolveStatus propagate_primal(struct Problem *prob, bool finite_bound_tightening); + +/* This function uses the row defined by 'row' to tighten bounds of variables + appearing in the row. The arguments arg1 and arg2 are passed to the bound + updaters. + + We expose this function because it is used in primal + propagation and will be used dual propagation. In primal propagation we use + it as + + status = bound_tightening_single_row( + row, act, problem, bounds, col_tags, + &finite_bound_tightening, &bound_marginal, + (BoundUpdater)update_lb_within_propagation, + (BoundUpdater)update_ub_within_propagation); + + and in dual propagation we'll use it differently. +: */ +PresolveStatus bound_tightening_single_row(const struct ConstRowView *row, + const struct Activity *act, + struct Problem *problem, + struct Bound *bounds, ColTag *col_tags, + void *arg1, BoundUpdater updater_lb, + BoundUpdater updater_ub); + +void remove_redundant_bounds(struct Constraints *constraints); +#endif // PRIMAL_PROPAGATION_H \ No newline at end of file diff --git a/include/explorers/SimpleReductions.h b/include/explorers/SimpleReductions.h new file mode 100644 index 00000000..c0bfe661 --- /dev/null +++ b/include/explorers/SimpleReductions.h @@ -0,0 +1,116 @@ +/* + * Copyright 2025 Daniel Cederberg + * + * This file is part of the PSLP project (LP Presolver). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SIMPLEREDUCTIONS_H +#define SIMPLEREDUCTIONS_H + +#include "PresolveStatus.h" +#include "Tags.h" + +// forward declarations +struct Problem; +struct Constraints; +struct Bound; +struct Matrix; + +/* Closely related to 'delete_inactive_cols_from_A_and_AT'. While that + function updates A and AT, this function updates the lhs, rhs and + activities when deleting fixed variables. This function does not + update A or AT. + + This function does not clear the list of fixed columns to delete. +*/ +void delete_fixed_cols_from_problem(struct Problem *prob); + +/* Eliminates singleton rows. A variable is fixed if the row is an equality. + If the row is an inequality the bounds are updated and the row is marked + as inactive. Note that we only call 'set_row_to_inactive' for inequalities + and not for equalities, since for equalities fixing the variable will + append the column to fixedColsToDelete, and this ensures that the + coefficient of the column is removed from A and AT. This is a very important + implementation detail. + + When this function finishes, the problem is up to date, and no + extra synchronization is needed. New singleton rows may have been created. +*/ +PresolveStatus remove_ston_rows(struct Problem *prob); + +/* Loops through the empty rows and checks feasibility with respect to the + feasibility tolerance. An empty row is marked as inactive. Note, however, + that 'set_row_to_inactive' isn't called, since there is no need to append + an empty row to the list of rows that should be deleted (since there are + no coefficients of an empty row that must be deleted). +*/ +PresolveStatus remove_empty_rows(const struct Constraints *constraints); + +/* Sets small coefficients of A to zero according to the rules + in "Presolve Reductions in Mixed Integer Programming Section 3.1". + + Note that this includes fixing variables with very close lower and upper + bounds. + + If this function is called, clean_small_coefficients_AT should never be + called. + + Note that the remaining part of the code only calls this code if + A is in CSR format. +*/ +void clean_small_coeff_A(struct Matrix *A, const struct Bound *bounds, + const RowTag *row_tags, const ColTag *col_tags, double *rhs, + double *lhs); + +/* Checks for redundant and infeasible constraints. Note that this + function does *not* check for forcing constraints. We use + similar identical numerics as in Presolve Reductions + in Mixed Integer Programming Section 3.1. + + This function only processes activities inside + internaldata->updated_activities. + + TODO (don't remove this todo, it's not really a todo): + As Gurobi, we are very aggresive with declaring constraints + as redundant here. If you ever get weird bugs, check this + function. + + TODO: (very important, don't remove!) How should we treat equalities? + We probably want to try different strategies when we are done. + One simple strategy is to just ignore equalities. I think this + makes sense from a numerical perspective. Try this! + + When this function finishes, the problem is up to date, and no + extra synchronization is needed. + + Note that this function does not clear updated_activities! +*/ +PresolveStatus check_activities(struct Problem *prob); + +/* Loops through the empty columns and fixes them. An empty column is marked + as fixed and the column size is set to SIZEINACTIVECOL. Note, however, + that 'set_col_to_fixed' isn't called, since there is no need to append an + empty column to the list of fixed columns that should be deleted (since there + are no coefficients of an empty column that must be deleted). We DO NOT even + have to call remove_fixed_cols(). +*/ +PresolveStatus remove_empty_cols(struct Problem *prob); + +void remove_variables_with_close_bounds(struct Problem *prob); + +// only used for debugging purposes +// void fix_cols_with_equal_bounds(struct Constraints *constraints); + +#endif // SIMPLEREDUCTIONS_H \ No newline at end of file diff --git a/include/explorers/Simple_dual_fix.h b/include/explorers/Simple_dual_fix.h new file mode 100644 index 00000000..67355a3d --- /dev/null +++ b/include/explorers/Simple_dual_fix.h @@ -0,0 +1,35 @@ +/* + * Copyright 2025 Daniel Cederberg + * + * This file is part of the PSLP project (LP Presolver). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SIMPLE_DUAL_FIX_H +#define SIMPLE_DUAL_FIX_H + +#include "PresolveStatus.h" + +// forward declaration +struct Problem; + +/* Uses locks and objective coefficients to fix variables at one of their + bounds. Note that a variable may be fixed to infinity, making the + constraints it appears in redundant. See Gurobi paper Section 4.4. + + When this function finishes, the problem is up to date, and no extra + synchronization is needed. */ +PresolveStatus simple_dual_fix(struct Problem *prob); + +#endif // SIMPLE_DUAL_FIX_H \ No newline at end of file diff --git a/include/explorers/StonCols.h b/include/explorers/StonCols.h new file mode 100644 index 00000000..eae396e0 --- /dev/null +++ b/include/explorers/StonCols.h @@ -0,0 +1,69 @@ +/* + * Copyright 2025 Daniel Cederberg + * + * This file is part of the PSLP project (LP Presolver). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef STONCOLS_HPP +#define STONCOLS_HPP + +#include "PresolveStatus.h" + +// forward declaration +struct Problem; + +/* The following reductions are possible for a column singleton xk appearing + in an equality i: + 1. Equality i is redundant if xk is (implied) free. + 2. If xk is implied free from either below or above it can be removed + if the equality is changed to an inequality: + + Original constraint: aik*xk + \sum_{j \neq k} aij * xj = bi + + 1. If aik > 0 and xk is implied free from above the new constraint is + \sum_{j \neq k} aij * xj <= bi - ell_k * aik. + 2. If aik < 0 and xk is implied free from above the new constraint is + \sum_{j \neq k} aij * xj >= bi - ell_k * aik. + 3. If aik > 0 and xk is implied free from below the new constraint is + \sum_{j \neq k} aij * xj >= bi - u_k * aik. + 4. If aik < 0 and xk is implied free from below the new constraint is + \sum_{j \neq k} aij * xj <= bi - u_k * aik. + + Why do we want to transform an equality constraint to an inequality, + you may ask. This may remove locks and lead to further reductions etc. + + The following reductions are possible for a column singleton in an inequality + constraint: + 1. Dual fix to lower bound. + 2. Dual fix to upper bound. + 3. Convert lower/upper inequality to equality constraint. + 4. The constraint is redundant if the column singleton is free. +*/ + +/* This function removes column singletons from the problem. When this function + finishes, the problem is up to date, and no extra synchronization is needed. + This also means that the internal data structures have been updated. + + After having deleted the column singletons, new column singletons may arise. + This function deletes those new singletons as well, and it continues until + no new column singletons are found. In other words, it eliminates chains + of column singletons. + + The function returns UNBNDORINFEAS if the problem is unbounded or infeasible, + or UNCHANGED (even if the problem was reduced). +*/ +PresolveStatus remove_ston_cols(struct Problem *prob); + +#endif // STONCOLS_HPP \ No newline at end of file diff --git a/include/explorers/Timer.h b/include/explorers/Timer.h new file mode 100644 index 00000000..52d7af8d --- /dev/null +++ b/include/explorers/Timer.h @@ -0,0 +1,43 @@ +/* + * Copyright 2025 Daniel Cederberg + * + * This file is part of the PSLP project (LP Presolver). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TIMER_H +#define TIMER_H + +#include "time.h" + +typedef struct +{ + struct timespec start, end; +} Timer; + +// Macro to compute elapsed time in seconds +#define GET_ELAPSED_SECONDS(timer) \ + (((timer).end.tv_sec - (timer).start.tv_sec) + \ + ((double) ((timer).end.tv_nsec - (timer).start.tv_nsec) * 1e-9)) + +#define RUN_AND_TIME(func, timer, time_variable, result_var, ...) \ + do \ + { \ + clock_gettime(CLOCK_MONOTONIC, &timer.start); \ + (result_var) = func(__VA_ARGS__); \ + clock_gettime(CLOCK_MONOTONIC, &timer.end); \ + (time_variable) += GET_ELAPSED_SECONDS(timer); \ + } while (0) + +#endif // TIMER_H \ No newline at end of file diff --git a/src/core/Activity.c b/src/core/Activity.c new file mode 100644 index 00000000..a380e785 --- /dev/null +++ b/src/core/Activity.c @@ -0,0 +1,834 @@ +/* + * Copyright 2025 Daniel Cederberg + * + * This file is part of the PSLP project (LP Presolver). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Activity.h" +#include "Bounds.h" +#include "Matrix.h" +#include "Memory_wrapper.h" +#include "Numerics.h" +#include "RowColViews.h" +#include "glbopts.h" + +Activity *new_activities(const Matrix *A, const ColTag *col_tags, + const Bound *bounds) +{ + Activity *activities = (Activity *) ps_malloc(A->m, sizeof(Activity)); + RETURN_PTR_IF_NULL(activities, NULL); + int i, j, start, end; + int *cols = A->i; + double *vals = A->x; + + for (i = 0; i < A->m; ++i) + { + Activity *act = activities + i; + act->n_inf_min = 0; + act->n_inf_max = 0; + act->max = 0.0; + act->min = 0.0; + act->status = NOT_ADDED; + + start = A->p[i].start; + end = A->p[i].end; + + // record infinite contributions + for (j = start; j < end; ++j) + { + if (vals[j] > 0) + { + if (HAS_TAG(col_tags[cols[j]], C_TAG_UB_INF)) + { + act->n_inf_max++; + } + + if (HAS_TAG(col_tags[cols[j]], C_TAG_LB_INF)) + { + act->n_inf_min++; + } + } + else + { + if (HAS_TAG(col_tags[cols[j]], C_TAG_LB_INF)) + { + act->n_inf_max++; + } + + if (HAS_TAG(col_tags[cols[j]], C_TAG_UB_INF)) + { + act->n_inf_min++; + } + } + } + + // compute max act if it is useful + if (act->n_inf_max == 0) + { + for (j = start; j < end; ++j) + { + if (vals[j] > 0) + { + assert(!HAS_TAG(col_tags[cols[j]], C_TAG_UB_INF)); + assert(bounds[cols[j]].ub != INF); + act->max += vals[j] * bounds[cols[j]].ub; + } + else + { + assert(!HAS_TAG(col_tags[cols[j]], C_TAG_LB_INF)); + assert(bounds[cols[j]].lb != -INF); + act->max += vals[j] * bounds[cols[j]].lb; + } + } + } +#ifndef NDEBUG + else + { + act->max = INVALID_ACT_DEBUG; + } +#endif + + // compute min act if it is useful + if (act->n_inf_min == 0) + { + for (j = start; j < end; ++j) + { + if (vals[j] > 0) + { + assert(!HAS_TAG(col_tags[cols[j]], C_TAG_LB_INF)); + assert(bounds[cols[j]].lb != -INF); + act->min += vals[j] * bounds[cols[j]].lb; + } + else + { + assert(!HAS_TAG(col_tags[cols[j]], C_TAG_UB_INF)); + assert(bounds[cols[j]].ub != INF); + act->min += vals[j] * bounds[cols[j]].ub; + } + } + } +#ifndef NDEBUG + else + { + act->min = INVALID_ACT_DEBUG; + } +#endif + } + + return activities; +} + +void free_activities(Activity *activities) +{ + PS_FREE(activities); +} + +void Activities_init(const Matrix *A, const ColTag *col_tags, const RowTag *row_tags, + const Bound *bounds, Activity *acts) +{ + + for (int i = 0; i < A->m; ++i) + { + if (HAS_TAG(row_tags[i], R_TAG_INACTIVE)) + { + DEBUG(acts[i].status = NOT_ADDED;); + continue; + } + + Activity_init(acts + i, A->x + A->p[i].start, A->i + A->p[i].start, + A->p[i].end - A->p[i].start, bounds, col_tags); + } +} + +void Activity_init(Activity *act, const double *vals, const int *cols, int len, + const Bound *bounds, const ColTag *col_tags) +{ + act->n_inf_min = 0; + act->n_inf_max = 0; + act->max = 0.0; + act->min = 0.0; + act->status = NOT_ADDED; + int j; + + // record infinite contributions + for (j = 0; j < len; ++j) + { + if (vals[j] > 0) + { + if (HAS_TAG(col_tags[cols[j]], C_TAG_UB_INF)) + { + act->n_inf_max++; + } + + if (HAS_TAG(col_tags[cols[j]], C_TAG_LB_INF)) + { + act->n_inf_min++; + } + } + else + { + if (HAS_TAG(col_tags[cols[j]], C_TAG_LB_INF)) + { + act->n_inf_max++; + } + + if (HAS_TAG(col_tags[cols[j]], C_TAG_UB_INF)) + { + act->n_inf_min++; + } + } + } + + // compute max act if it is useful + if (act->n_inf_max == 0) + { + for (j = 0; j < len; ++j) + { + if (vals[j] > 0) + { + assert(!HAS_TAG(col_tags[cols[j]], C_TAG_UB_INF)); + assert(bounds[cols[j]].ub != INF); + act->max += vals[j] * bounds[cols[j]].ub; + } + else + { + assert(!HAS_TAG(col_tags[cols[j]], C_TAG_LB_INF)); + assert(bounds[cols[j]].lb != -INF); + act->max += vals[j] * bounds[cols[j]].lb; + } + } + } +#ifndef NDEBUG + else + { + act->max = INVALID_ACT_DEBUG; + } +#endif + + // compute min act if it is useful + if (act->n_inf_min == 0) + { + for (j = 0; j < len; ++j) + { + if (vals[j] > 0) + { + assert(!HAS_TAG(col_tags[cols[j]], C_TAG_LB_INF)); + assert(bounds[cols[j]].lb != -INF); + act->min += vals[j] * bounds[cols[j]].lb; + } + else + { + assert(!HAS_TAG(col_tags[cols[j]], C_TAG_UB_INF)); + assert(bounds[cols[j]].ub != INF); + act->min += vals[j] * bounds[cols[j]].ub; + } + } + } +#ifndef NDEBUG + else + { + act->min = INVALID_ACT_DEBUG; + } +#endif +} + +double compute_min_act_tags(const double *vals, const int *cols, int len, + const Bound *bounds, const ColTag *col_tags) +{ + double min_act = 0.0; + for (int j = 0; j < len; ++j) + { + if (vals[j] > 0) + { + if (!HAS_TAG(col_tags[cols[j]], C_TAG_LB_INF)) + { + assert(bounds[cols[j]].lb != -INF); + min_act += vals[j] * bounds[cols[j]].lb; + } + } + else + { + if (!HAS_TAG(col_tags[cols[j]], C_TAG_UB_INF)) + { + assert(bounds[cols[j]].ub != INF); + min_act += vals[j] * bounds[cols[j]].ub; + } + } + } + return min_act; +} + +double compute_max_act_tags(const double *vals, const int *cols, int len, + const Bound *bounds, const ColTag *col_tags) +{ + double max_act = 0.0; + for (int j = 0; j < len; ++j) + { + if (vals[j] > 0) + { + if (!HAS_TAG(col_tags[cols[j]], C_TAG_UB_INF)) + { + assert(bounds[cols[j]].ub != INF); + max_act += vals[j] * bounds[cols[j]].ub; + } + } + else + { + if (!HAS_TAG(col_tags[cols[j]], C_TAG_LB_INF)) + { + assert(bounds[cols[j]].lb != -INF); + max_act += vals[j] * bounds[cols[j]].lb; + } + } + } + return max_act; +} + +double compute_min_act_no_tags(const double *vals, const int *cols, int len, + const Bound *bounds) +{ + double min_act = 0.0; + for (int j = 0; j < len; ++j) + { + if (vals[j] > 0) + { + assert(bounds[cols[j]].lb != -INF); + min_act += vals[j] * bounds[cols[j]].lb; + } + else + { + assert(bounds[cols[j]].ub != INF); + min_act += vals[j] * bounds[cols[j]].ub; + } + } + return min_act; +} + +double compute_max_act_no_tags(const double *vals, const int *cols, int len, + const Bound *bounds) +{ + double max_act = 0.0; + for (int j = 0; j < len; ++j) + { + if (vals[j] > 0) + { + assert(bounds[cols[j]].ub != INF); + max_act += vals[j] * bounds[cols[j]].ub; + } + else + { + assert(bounds[cols[j]].lb != -INF); + max_act += vals[j] * bounds[cols[j]].lb; + } + } + return max_act; +} + +double compute_min_act_one_tag(const double *vals, const int *cols, int len, + const Bound *bounds, int col_to_avoid) +{ + assert(col_to_avoid >= 0); + double min_act = 0.0; + for (int j = 0; j < len; ++j) + { + if (cols[j] == col_to_avoid) + { + continue; + } + + if (vals[j] > 0) + { + assert(bounds[cols[j]].lb != -INF); + min_act += vals[j] * bounds[cols[j]].lb; + } + else + { + assert(bounds[cols[j]].ub != INF); + min_act += vals[j] * bounds[cols[j]].ub; + } + } + return min_act; +} + +double compute_max_act_one_tag(const double *vals, const int *cols, int len, + const Bound *bounds, int col_to_avoid) +{ + assert(col_to_avoid >= 0); + double max_act = 0.0; + for (int j = 0; j < len; ++j) + { + if (cols[j] == col_to_avoid) + { + continue; + } + + if (vals[j] > 0) + { + assert(bounds[cols[j]].ub != INF); + max_act += vals[j] * bounds[cols[j]].ub; + } + else + { + assert(bounds[cols[j]].lb != -INF); + max_act += vals[j] * bounds[cols[j]].lb; + } + } + return max_act; +} + +Altered_Activity update_act_bound_change(Activity *act, double coeff, + double old_bound, double new_bound, + bool finite_bound, bool lower, + const double *vals, const int *cols, + int len, const Bound *bounds) +{ + // --------------------------------------------------------------------- + // If a lower bound has been updated + // --------------------------------------------------------------------- + if (lower) + { + if (coeff < 0) + { + if (act->n_inf_max == 0) + { + assert(finite_bound); + act->max += (new_bound - old_bound) * coeff; + } + else if (!finite_bound) + { + act->n_inf_max--; + + if (act->n_inf_max == 0) + { + act->max = compute_max_act_no_tags(vals, cols, len, bounds); + } + } + + return MAX_ALTERED; + } + else + { + if (act->n_inf_min == 0) + { + assert(finite_bound); + act->min += (new_bound - old_bound) * coeff; + } + else if (!finite_bound) + { + act->n_inf_min--; + + if (act->n_inf_min == 0) + { + act->min = compute_min_act_no_tags(vals, cols, len, bounds); + } + } + return MIN_ALTERED; + } + } + // ------------------------------------------------------------------------ + // If an upper bound has been updated + // ------------------------------------------------------------------------ + else + { + if (coeff < 0) + { + if (act->n_inf_min == 0) + { + assert(finite_bound); + act->min += (new_bound - old_bound) * coeff; + } + else if (!finite_bound) + { + act->n_inf_min--; + + if (act->n_inf_min == 0) + { + act->min = compute_min_act_no_tags(vals, cols, len, bounds); + } + } + return MIN_ALTERED; + } + else + { + if (act->n_inf_max == 0) + { + assert(finite_bound); + act->max += (new_bound - old_bound) * coeff; + } + else if (!finite_bound) + { + act->n_inf_max--; + + if (act->n_inf_max == 0) + { + act->max = compute_max_act_no_tags(vals, cols, len, bounds); + } + } + return MAX_ALTERED; + } + } +} + +void update_activities_bound_change(Activity *activities, const Matrix *A, + const Bound *bounds, const double *vals, + const int *rows, int len, double old_bound, + double new_bound, double finite_bound, + bool lower, + iVec *updated_activities HUGE_BOUND_PARAM) +{ + assert(!IS_HUGE(new_bound) || huge_bound_ok); + assert(!finite_bound || (lower && new_bound > old_bound) || + (!lower && new_bound < old_bound)); + + Altered_Activity altered; + for (int ii = 0; ii < len; ++ii) + { + int i = rows[ii]; + Activity *act = activities + i; + altered = update_act_bound_change(act, vals[ii], old_bound, new_bound, + finite_bound, lower, A->x + A->p[i].start, + A->i + A->p[i].start, + A->p[i].end - A->p[i].start, bounds); + + if (((act->n_inf_max == 0 && (altered & MAX_ALTERED)) || + (act->n_inf_min == 0 && (altered & MIN_ALTERED)))) + { + if (act->status == NOT_ADDED) + { + act->status = ADDED; + iVec_append(updated_activities, rows[ii]); + } + else if (act->status == PROPAGATED_THIS_ROUND) + { + act->status = PROPAGATE_NEXT_ROUND; + iVec_append(updated_activities, rows[ii]); + } + } + } +} + +void update_activities_fixed_col(Activity *activities, const Matrix *A, + const Bound *bounds, const double *vals, + const int *rows, int len, double old_ub, + double old_lb, double val, bool ub_update, + bool lb_update, bool is_ub_inf, bool is_lb_inf, + iVec *updated_activities) +{ + Altered_Activity altered1; + Altered_Activity altered2; + Altered_Activity altered; + + for (int ii = 0; ii < len; ++ii) + { + altered1 = NO_RECOMPUTE; + altered2 = NO_RECOMPUTE; + int i = rows[ii]; + Activity *act = activities + i; + + if (ub_update) + { + altered1 = update_act_bound_change( + act, vals[ii], old_ub, val, !is_ub_inf, false, A->x + A->p[i].start, + A->i + A->p[i].start, A->p[i].end - A->p[i].start, bounds); + } + + if (lb_update) + { + altered2 = update_act_bound_change( + act, vals[ii], old_lb, val, !is_lb_inf, true, A->x + A->p[i].start, + A->i + A->p[i].start, A->p[i].end - A->p[i].start, bounds); + } + + altered = altered1 | altered2; + if (((act->n_inf_max == 0 && (altered & MAX_ALTERED)) || + (act->n_inf_min == 0 && (altered & MIN_ALTERED)))) + { + if (act->status == NOT_ADDED) + { + act->status = ADDED; + iVec_append(updated_activities, i); + } + else if (act->status == PROPAGATED_THIS_ROUND) + { + act->status = PROPAGATE_NEXT_ROUND; + iVec_append(updated_activities, i); + } + } + } +} + +Altered_Activity update_activity_coeff_change(Activity *act, double lb, double ub, + double old_coeff, double new_coeff, + ColTag cTag) +{ + assert(!HAS_TAG(cTag, C_TAG_INACTIVE)); + bool is_lb_finite = !HAS_TAG(cTag, C_TAG_LB_INF); + bool is_ub_finite = !HAS_TAG(cTag, C_TAG_UB_INF); + + int n_inf_min_before = act->n_inf_min; + int n_inf_max_before = act->n_inf_max; + + // ---------------------------------------- + // remove contribution from old coefficient + // ---------------------------------------- + if (old_coeff > 0) + { + if (is_lb_finite) + { + if (act->n_inf_min == 0) + { + act->min -= old_coeff * lb; + } + } + else + { + act->n_inf_min--; + } + + if (is_ub_finite) + { + if (act->n_inf_max == 0) + { + act->max -= old_coeff * ub; + } + } + else + { + act->n_inf_max--; + } + } + else if (old_coeff < 0) + { + if (is_lb_finite) + { + if (act->n_inf_max == 0) + { + act->max -= old_coeff * lb; + } + } + else + { + act->n_inf_max--; + } + + if (is_ub_finite) + { + if (act->n_inf_min == 0) + { + act->min -= old_coeff * ub; + } + } + else + { + act->n_inf_min--; + } + } + + // ----------------------------------- + // add contribution of new coefficient + // ----------------------------------- + if (new_coeff > 0) + { + if (is_lb_finite) + { + if (act->n_inf_min == 0) + { + act->min += new_coeff * lb; + } + } + else + { + act->n_inf_min++; + } + + if (is_ub_finite) + { + if (act->n_inf_max == 0) + { + act->max += new_coeff * ub; + } + } + else + { + act->n_inf_max++; + } + } + else if (new_coeff < 0) + { + if (is_lb_finite) + { + if (act->n_inf_max == 0) + { + act->max += new_coeff * lb; + } + } + else + { + act->n_inf_max++; + } + + if (is_ub_finite) + { + if (act->n_inf_min == 0) + { + act->min += new_coeff * ub; + } + } + else + { + act->n_inf_min++; + } + } + +#ifndef NDEBUG + if (n_inf_min_before == 0 && act->n_inf_min == 1) + { + act->min = INVALID_ACT_DEBUG; + } + + if (n_inf_max_before == 0 && act->n_inf_max == 1) + { + act->max = INVALID_ACT_DEBUG; + } +#endif + + // recompute from scratch if necessary + if (act->n_inf_min == 0 && n_inf_min_before == 1) + { + return MIN_ALTERED_RECOMPUTE; + } + + if (act->n_inf_max == 0 && n_inf_max_before == 1) + { + return MAX_ALTERED_RECOMPUTE; + } + + return NO_RECOMPUTE; +} + +void remove_finite_lb_from_activities(const ConstColView *col, Activity *acts, + double bound) +{ + + for (int ii = 0; ii < *col->len; ii++) + { + int i = col->rows[ii]; + Activity *act = acts + i; + + if (col->vals[ii] > 0) + { + if (act->n_inf_min == 0) + { + act->min -= col->vals[ii] * bound; + } + + act->n_inf_min++; + } + else if (col->vals[ii] < 0) + { + if (act->n_inf_max == 0) + { + act->max -= col->vals[ii] * bound; + } + act->n_inf_max++; + } + + DEBUG(acts[i].min = + (acts[i].n_inf_min != 0) ? INVALID_ACT_DEBUG : acts[i].min); + DEBUG(acts[i].max = + (acts[i].n_inf_max != 0) ? INVALID_ACT_DEBUG : acts[i].max); + } + + return; +} + +void remove_finite_ub_from_activities(const ConstColView *col, Activity *acts, + double bound) +{ + + for (int ii = 0; ii < *col->len; ii++) + { + int i = col->rows[ii]; + Activity *act = acts + i; + + if (col->vals[ii] > 0) + { + if (act->n_inf_max == 0) + { + act->max -= col->vals[ii] * bound; + } + + act->n_inf_max++; + } + else if (col->vals[ii] < 0) + { + if (act->n_inf_min == 0) + { + act->min -= col->vals[ii] * bound; + } + act->n_inf_min++; + } + + DEBUG(acts[i].min = + (acts[i].n_inf_min != 0) ? INVALID_ACT_DEBUG : acts[i].min); + DEBUG(acts[i].max = + (acts[i].n_inf_max != 0) ? INVALID_ACT_DEBUG : acts[i].max); + } + + return; +} + +void recompute_n_infs(Activity *act, const double *vals, const int *cols, int len, + const ColTag *coltags) +{ + int n_inf_max = 0; + int n_inf_min = 0; + + for (int j = 0; j < len; ++j) + { + if (HAS_TAG(coltags[cols[j]], C_TAG_INACTIVE)) + { + continue; + } + + if (vals[j] > 0) + { + if (HAS_TAG(coltags[cols[j]], C_TAG_UB_INF)) + { + n_inf_max++; + } + + if (HAS_TAG(coltags[cols[j]], C_TAG_LB_INF)) + { + n_inf_min++; + } + } + else + { + if (HAS_TAG(coltags[cols[j]], C_TAG_LB_INF)) + { + n_inf_max++; + } + + if (HAS_TAG(coltags[cols[j]], C_TAG_UB_INF)) + { + n_inf_min++; + } + } + } + + act->n_inf_max = n_inf_max; + act->n_inf_min = n_inf_min; +} \ No newline at end of file diff --git a/src/core/Constraints.c b/src/core/Constraints.c new file mode 100644 index 00000000..f67b553c --- /dev/null +++ b/src/core/Constraints.c @@ -0,0 +1,322 @@ +/* + * Copyright 2025 Daniel Cederberg + * + * This file is part of the PSLP project (LP Presolver). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Constraints.h" +#include "Bounds.h" +#include "Debugger.h" +#include "Locks.h" +#include "Matrix.h" +#include "State.h" +#include "Workspace.h" +#include "glbopts.h" +#include "utils.h" + +Constraints *constraints_new(Matrix *A, Matrix *AT, double *lhs, double *rhs, + Bound *bounds, State *state, RowTag *row_tags, + ColTag *col_tags) +{ + Constraints *constraints = (Constraints *) ps_malloc(1, sizeof(Constraints)); + RETURN_PTR_IF_NULL(constraints, NULL); + + constraints->lhs = lhs; + constraints->rhs = rhs; + constraints->m = A->m; + constraints->n = A->n; + constraints->A = A; + constraints->AT = AT; + constraints->bounds = bounds; + constraints->state = state; + constraints->row_tags = row_tags; + constraints->col_tags = col_tags; + + return constraints; +} + +void free_constraints(Constraints *constraints) +{ + if (!constraints) + { + return; + } + + PS_FREE(constraints->lhs); + PS_FREE(constraints->rhs); + free_matrix(constraints->A); + free_matrix(constraints->AT); + PS_FREE(constraints->bounds); + PS_FREE(constraints->row_tags); + PS_FREE(constraints->col_tags); + PS_FREE(constraints); +} + +void delete_inactive_rows(Constraints *constraints) +{ + iVec *rows_to_delete = constraints->state->rows_to_delete; + + if (rows_to_delete->len == 0) + { + return; + } + + int i, j, row, col, shift; + bool is_rhs_inf, is_lhs_inf; + int *row_sizes = constraints->state->row_sizes; + int *col_sizes = constraints->state->col_sizes; + Lock *locks = constraints->state->col_locks; + Matrix *A = constraints->A; + Matrix *AT = constraints->AT; + RowTag *row_tags = constraints->row_tags; + RowRange *row_r = A->p; + RowRange *col_r = AT->p; + + // ------------------------------------------------------------------------ + // Delete rows of A. + // (We also update column sizes and locks). + // ------------------------------------------------------------------------ + for (i = 0; i < rows_to_delete->len; ++i) + { + row = rows_to_delete->data[i]; + assert(row_sizes[row] != SIZE_INACTIVE_ROW); + + // skip empty rows + if (row_sizes[row] == 0) + { + row_sizes[row] = SIZE_INACTIVE_ROW; + continue; + } + + A->nnz -= row_sizes[row]; + row_sizes[row] = SIZE_INACTIVE_ROW; + is_rhs_inf = HAS_TAG(row_tags[row], R_TAG_RHS_INF); + is_lhs_inf = HAS_TAG(row_tags[row], R_TAG_LHS_INF); + + for (j = row_r[row].start; j < row_r[row].end; ++j) + { + col = A->i[j]; + if (col_sizes[col] == SIZE_INACTIVE_COL) + { + continue; + } + + col_sizes[col]--; + update_lock_coeff_deletion(locks + col, A->x[j], is_rhs_inf, is_lhs_inf); + } + row_r[row].end = row_r[row].start; + } + + // ------------------------------------------------------------------------ + // Process each row of AT and place coefficients contiguously in memory. + // (Looping over rows of AT is much more cache friendly than looping over + // the columns of AT that change) + // ------------------------------------------------------------------------ + iVec *empty_cols = constraints->state->empty_cols; + iVec *ston_cols = constraints->state->ston_cols; + const ColTag *col_tags = constraints->col_tags; + for (col = 0; col < AT->m; ++col) + { + // skip rows of AT that haven't changed + if (HAS_TAG(col_tags[col], C_TAG_INACTIVE) || + col_sizes[col] == col_r[col].end - col_r[col].start) + { + continue; + } + + assert(col_sizes[col] != SIZE_INACTIVE_COL); + + // check for new empty and singleton columns, + switch (col_sizes[col]) + { + case 0: + iVec_append(empty_cols, col); + col_r[col].end = col_r[col].start; + break; + case 1: + iVec_append(ston_cols, col); + break; + } + + // place coefficients contiguously in memory + shift = 0; + for (j = col_r[col].start; j < col_r[col].end; ++j) + { + if (row_sizes[AT->i[j]] == SIZE_INACTIVE_ROW) + { + shift++; + } + else + { + AT->x[j - shift] = AT->x[j]; + AT->i[j - shift] = AT->i[j]; + } + } + + // update end of column + col_r[col].end -= shift; + assert(col_r[col].start + col_sizes[col] == col_r[col].end); + } + + AT->nnz = A->nnz; + iVec_clear_no_resize(rows_to_delete); +} + +void delete_inactive_cols_from_A_and_AT(Constraints *constraints) +{ + int i, j, row, col, shift; + iVec *fixed_cols_to_delete = constraints->state->fixed_cols_to_delete; + iVec *sub_cols_to_delete = constraints->state->sub_cols_to_delete; + int *row_sizes = constraints->state->row_sizes; + int *col_sizes = constraints->state->col_sizes; + Matrix *A = constraints->A; + const Matrix *AT = constraints->AT; + const RowTag *row_tags = constraints->row_tags; + RowRange *row_r = A->p; + RowRange *col_r = AT->p; + + // ------------------------------------------------------------------------ + // Delete rows of A and update column sizes. + // ------------------------------------------------------------------------ + for (i = 0; i < fixed_cols_to_delete->len; ++i) + { + col = fixed_cols_to_delete->data[i]; + col_sizes[col] = SIZE_INACTIVE_COL; + + for (j = col_r[col].start; j < col_r[col].end; ++j) + { + if (row_sizes[AT->i[j]] == SIZE_INACTIVE_ROW) + { + continue; + } + + row_sizes[AT->i[j]]--; + } + col_r[col].end = col_r[col].start; + } + + for (i = 0; i < sub_cols_to_delete->len; ++i) + { + col = sub_cols_to_delete->data[i]; + col_sizes[col] = SIZE_INACTIVE_COL; + + for (j = col_r[col].start; j < col_r[col].end; ++j) + { + if (row_sizes[AT->i[j]] == SIZE_INACTIVE_ROW) + { + continue; + } + + row_sizes[AT->i[j]]--; + } + col_r[col].end = col_r[col].start; + } + + // ------------------------------------------------------------------------ + // Process each row of A and place coefficients contiguously in memory. + // ------------------------------------------------------------------------ + iVec *empty_rows = constraints->state->empty_rows; + iVec *ston_rows = constraints->state->ston_rows; + iVec *dton_rows = constraints->state->dton_rows; + for (row = 0; row < A->m; ++row) + { + // skip rows of A that haven't changed + if (HAS_TAG(row_tags[row], R_TAG_INACTIVE) || + row_sizes[row] == row_r[row].end - row_r[row].start) + { + continue; + } + + assert(row_sizes[row] != SIZE_INACTIVE_ROW && + !HAS_TAG(row_tags[row], R_TAG_INACTIVE)); + + // check for new empty, singleton and doubleton rows + switch (row_sizes[row]) + { + case 0: + assert(!iVec_contains(empty_rows, row)); + iVec_append(empty_rows, row); + A->nnz -= (row_r[row].end - row_r[row].start); + row_r[row].end = row_r[row].start; + break; + case 1: + assert(!iVec_contains(ston_rows, row)); + iVec_append(ston_rows, row); + break; + case 2: + if (HAS_TAG(row_tags[row], R_TAG_EQ)) + { + assert(!iVec_contains(dton_rows, row)); + iVec_append(dton_rows, row); + } + break; + } + + // place coefficients contiguously in memory + shift = 0; + for (j = row_r[row].start; j < row_r[row].end; ++j) + { + col = A->i[j]; + if (col_sizes[col] == SIZE_INACTIVE_COL) + { + shift++; + } + else + { + A->x[j - shift] = A->x[j]; + A->i[j - shift] = A->i[j]; + } + } + + // update end of row + row_r[row].end -= shift; + A->nnz -= shift; + assert(row_r[row].start + row_sizes[row] == row_r[row].end); + } + + constraints->AT->nnz = A->nnz; + iVec_clear_no_resize(fixed_cols_to_delete); + iVec_clear_no_resize(sub_cols_to_delete); +} + +static void bounds_shrink(Bound *ptr, int *map, int len) +{ + int new_len = 0; + for (int i = 0; i < len; ++i) + { + if (map[i] != -1) + { + new_len++; + ptr[map[i]] = ptr[i]; + } + } +} + +void constraints_clean(Constraints *constraints, Mapping *map, bool remove_all) +{ + const int *row_sizes = constraints->state->row_sizes; + const int *col_sizes = constraints->state->col_sizes; + + remove_extra_space(constraints->A, row_sizes, col_sizes, remove_all, map->cols); + remove_extra_space(constraints->AT, col_sizes, row_sizes, remove_all, map->rows); + + dPtr_shrink(constraints->rhs, map->rows, constraints->m); + dPtr_shrink(constraints->lhs, map->rows, constraints->m); + rowTagPtr_shrink(constraints->row_tags, map->rows, constraints->m); + colTagPtr_shrink(constraints->col_tags, map->cols, constraints->n); + bounds_shrink(constraints->bounds, map->cols, constraints->n); + constraints->m = constraints->A->m; + constraints->n = constraints->A->n; +} \ No newline at end of file diff --git a/src/core/CoreTransformation.c b/src/core/CoreTransformation.c new file mode 100644 index 00000000..db3b97ad --- /dev/null +++ b/src/core/CoreTransformation.c @@ -0,0 +1,533 @@ +/* + * Copyright 2025 Daniel Cederberg + * + * This file is part of the PSLP project (LP Presolver). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Activity.h" +#include "Bounds.h" +#include "Constraints.h" +#include "CoreTransformations.h" +#include "Locks.h" +#include "Matrix.h" +#include "Numerics.h" +#include "Problem.h" +#include "RowColViews.h" +#include "State.h" + +PresolveStatus fix_col(Constraints *constraints, int col, double val, double ck) +{ + State *data = constraints->state; + const Matrix *A = constraints->A; + Bound *bounds = constraints->bounds; + double old_lb = bounds[col].lb; + double old_ub = bounds[col].ub; + ColTag *col_tag = constraints->col_tags + col; + bool is_ub_inf = HAS_TAG(*col_tag, C_TAG_UB_INF); + bool is_lb_inf = HAS_TAG(*col_tag, C_TAG_LB_INF); + assert(constraints->state->col_sizes[col] > 0); + assert(!HAS_TAG(*col_tag, C_TAG_INACTIVE)); + + if ((!is_ub_inf && val >= old_ub + FEAS_TOL) || + (!is_lb_inf && val <= old_lb - FEAS_TOL)) + { + return INFEASIBLE; + } + + const Matrix *AT = constraints->AT; + const double *vals = AT->x + AT->p[col].start; + const int *rows = AT->i + AT->p[col].start; + int len = AT->p[col].end - AT->p[col].start; + + set_col_to_fixed(col, col_tag, data->fixed_cols_to_delete); + save_retrieval_fixed_col(data->postsolve_info, col, val, ck, vals, rows, len); + + bool ub_update = (is_ub_inf || val != old_ub); + bool lb_update = (is_lb_inf || val != old_lb); + bounds[col].ub = val; + bounds[col].lb = val; + + update_activities_fixed_col(data->activities, A, bounds, vals, rows, len, old_ub, + old_lb, val, ub_update, lb_update, is_ub_inf, + is_lb_inf, data->updated_activities); + + return REDUCED; +} + +void fix_col_to_negative_inf(Constraints *constraints, int col) +{ + assert(!HAS_TAG(constraints->col_tags[col], C_TAG_INACTIVE)); + assert(HAS_TAG(constraints->col_tags[col], C_TAG_LB_INF)); + assert(constraints->state->col_locks[col].down == 0); + + Matrix *AT = constraints->AT; + RowTag *row_tags = constraints->row_tags; + PostsolveInfo *postsolve_info = constraints->state->postsolve_info; + + // store postsolve information + save_retrieval_fixed_col_inf(postsolve_info, col, -1, constraints, + constraints->bounds[col].ub); + + for (int i = AT->p[col].start; i < AT->p[col].end; ++i) + { + int row = AT->i[i]; + // if the same row has two variables that we fix to neg inf, it + // will be marked as inactive when the second variable is fixed + if (!HAS_TAG(row_tags[row], R_TAG_INACTIVE)) + { + set_row_to_inactive(row, row_tags + row, + constraints->state->rows_to_delete, postsolve_info, + 0.0); + } + } + + // It is not needed to append the column to fixed_cols_to_delete + // since all rows that the column appears in will be deleted + UPDATE_TAG(constraints->col_tags[col], C_TAG_FIXED); + constraints->state->col_sizes[col] = SIZE_INACTIVE_COL; + AT->p[col].end = AT->p[col].start; + constraints->bounds[col].lb = -INF; + constraints->bounds[col].ub = -INF; +} + +void fix_col_to_positive_inf(Constraints *constraints, int col) +{ + assert(!HAS_TAG(constraints->col_tags[col], C_TAG_INACTIVE)); + assert(HAS_TAG(constraints->col_tags[col], C_TAG_UB_INF)); + assert(constraints->state->col_locks[col].up == 0); + + Matrix *AT = constraints->AT; + RowTag *row_tags = constraints->row_tags; + PostsolveInfo *postsolve_info = constraints->state->postsolve_info; + + // store postsolve information + save_retrieval_fixed_col_inf(postsolve_info, col, 1, constraints, + constraints->bounds[col].lb); + + for (int i = AT->p[col].start; i < AT->p[col].end; ++i) + { + int row = AT->i[i]; + + // if the same row has two variables that we fix to pos inf, it + // will be marked as inactive when the second variable is fixed + if (!HAS_TAG(row_tags[row], R_TAG_INACTIVE)) + { + set_row_to_inactive(row, row_tags + row, + constraints->state->rows_to_delete, postsolve_info, + 0.0); + } + } + + // It is not needed to append the column to fixed_cols_to_delete + // since all rows that the column appears in will be deleted + UPDATE_TAG(constraints->col_tags[col], C_TAG_FIXED); + constraints->state->col_sizes[col] = SIZE_INACTIVE_COL; + AT->p[col].end = AT->p[col].start; + constraints->bounds[col].lb = INF; + constraints->bounds[col].ub = INF; +} + +PresolveStatus update_lb(Constraints *constraints, int col, double new_lb, + int row HUGE_BOUND_PARAM) +{ + assert(!IS_HUGE(new_lb) || huge_bound_ok); + ColTag *col_tag = constraints->col_tags + col; + Bound *bounds = constraints->bounds; + double ub = bounds[col].ub; + double lb = bounds[col].lb; + assert(!HAS_TAG(constraints->col_tags[col], C_TAG_INACTIVE)); + + if (!HAS_TAG(*col_tag, C_TAG_UB_INF) && new_lb >= ub + FEAS_TOL) + { + return INFEASIBLE; + } + + bool is_lb_inf = HAS_TAG(*col_tag, C_TAG_LB_INF); + + // update the lower bound if it is tighter + if (is_lb_inf || new_lb >= lb + FEAS_TOL) + { + const Matrix *AT = constraints->AT; + const double *vals = AT->x + AT->p[col].start; + const int *rows = AT->i + AT->p[col].start; + int len = AT->p[col].end - AT->p[col].start; + State *data = constraints->state; + Activity *activities = data->activities; + + bounds[col].lb = new_lb; + update_activities_bound_change(activities, constraints->A, bounds, vals, + rows, len, lb, new_lb, !is_lb_inf, true, + data->updated_activities HUGE_BOUND_ARG); + REMOVE_TAG(*col_tag, C_TAG_LB_INF); + + const Matrix *A = constraints->A; + const double *row_vals = A->x + A->p[row].start; + const int *row_cols = A->i + A->p[row].start; + int row_len = A->p[row].end - A->p[row].start; + + save_retrieval_bound_change_no_row(data->postsolve_info, col, new_lb, ub, + false); + save_retrieval_bound_change_the_row(data->postsolve_info, row, row_cols, + row_vals, row_len, 1); + return REDUCED; + } + + return UNCHANGED; +} + +PresolveStatus update_ub(Constraints *constraints, int col, double new_ub, + int row HUGE_BOUND_PARAM) +{ + assert(!IS_HUGE(new_ub) || huge_bound_ok); + ColTag *col_tag = constraints->col_tags + col; + Bound *bounds = constraints->bounds; + double ub = bounds[col].ub; + double lb = bounds[col].lb; + + assert(!HAS_TAG(constraints->col_tags[col], C_TAG_INACTIVE)); + + if (!HAS_TAG(*col_tag, C_TAG_LB_INF) && new_ub <= lb - FEAS_TOL) + { + return INFEASIBLE; + } + + bool is_ub_inf = HAS_TAG(*col_tag, C_TAG_UB_INF); + + // update the upper bound if it is tighter + if (is_ub_inf || new_ub <= ub - FEAS_TOL) + { + const Matrix *AT = constraints->AT; + const double *vals = AT->x + AT->p[col].start; + const int *rows = AT->i + AT->p[col].start; + int len = AT->p[col].end - AT->p[col].start; + State *data = constraints->state; + Activity *activities = data->activities; + + bounds[col].ub = new_ub; + update_activities_bound_change(activities, constraints->A, bounds, vals, + rows, len, ub, new_ub, !is_ub_inf, false, + data->updated_activities HUGE_BOUND_ARG); + REMOVE_TAG(*col_tag, C_TAG_UB_INF); + + const Matrix *A = constraints->A; + const double *row_vals = A->x + A->p[row].start; + const int *row_cols = A->i + A->p[row].start; + int row_len = A->p[row].end - A->p[row].start; + + save_retrieval_bound_change_no_row(data->postsolve_info, col, new_ub, lb, + true); + + save_retrieval_bound_change_the_row(data->postsolve_info, row, row_cols, + row_vals, row_len, 1); + } + + return UNCHANGED; +} + +bool implied_free_from_above_by_row(double Aik, const ConstRowView *row, double lb, + double ub, ColTag col_tag, const Activity *act, + const ColTag *col_tags, const Bound *bounds) +{ + if (HAS_TAG(col_tag, C_TAG_UB_INF)) + { + return true; + } + + assert(ub != INF && Aik != 0); + double implied_ub = INF; + + if (Aik > 0 && !HAS_TAG(*row->tag, R_TAG_RHS_INF)) + { + // standard case + if (act->n_inf_min == 0) + { + assert(!IS_ABS_INF(act->min) && !IS_ABS_INF(*row->rhs) && + !IS_ABS_INF(lb)); + implied_ub = lb + (*row->rhs - act->min) / Aik; + } + // Gondzio's case (important for implied free column singletons) + else if (act->n_inf_min == 1 && HAS_TAG(col_tag, C_TAG_LB_INF)) + { + double min_temp = compute_min_act_tags(row->vals, row->cols, *row->len, + bounds, col_tags); + assert(!IS_ABS_INF(min_temp) && !IS_ABS_INF(*row->rhs)); + implied_ub = (*row->rhs - min_temp) / Aik; + } + } + else if (Aik < 0 && !HAS_TAG(*row->tag, R_TAG_LHS_INF)) + { + if (act->n_inf_max == 0) + { + assert(!IS_ABS_INF(act->max) && !IS_ABS_INF(*row->lhs) && + !IS_ABS_INF(lb)); + implied_ub = lb + (*row->lhs - act->max) / Aik; + } + // Gondzio's case (important for implied free column singletons) + else if (act->n_inf_max == 1 && HAS_TAG(col_tag, C_TAG_LB_INF)) + { + double max_temp = compute_max_act_tags(row->vals, row->cols, *row->len, + bounds, col_tags); + assert(!IS_ABS_INF(max_temp) && !IS_ABS_INF(*row->lhs)); + implied_ub = (*row->lhs - max_temp) / Aik; + } + } + + assert(!IS_NEG_INF(implied_ub)); + return (implied_ub - ub <= 0); +} + +bool implied_free_from_below_by_row(double Aik, const ConstRowView *row, double lb, + double ub, ColTag col_tag, const Activity *act, + const ColTag *col_tags, const Bound *bounds) +{ + if (HAS_TAG(col_tag, C_TAG_LB_INF)) + { + return true; + } + + assert(Aik != 0); + assert(lb != -INF); + double implied_lb = -INF; + + if (Aik > 0 && !HAS_TAG(*row->tag, R_TAG_LHS_INF)) + { + // standard case + if (act->n_inf_max == 0) + { + assert(!IS_ABS_INF(act->max) && !IS_ABS_INF(*row->lhs) && + !IS_ABS_INF(ub)); + implied_lb = ub + (*row->lhs - act->max) / Aik; + } + // Gondzio's case (important for implied free column singletons) + else if (act->n_inf_max == 1 && HAS_TAG(col_tag, C_TAG_UB_INF)) + { + double max_temp = compute_max_act_tags(row->vals, row->cols, *row->len, + bounds, col_tags); + + assert(!IS_ABS_INF(max_temp) && !IS_ABS_INF(*row->lhs)); + implied_lb = (*row->lhs - max_temp) / Aik; + } + } + else if (Aik < 0 && !HAS_TAG(*row->tag, R_TAG_RHS_INF)) + { + if (act->n_inf_min == 0) + { + assert(!IS_ABS_INF(act->min) && !IS_ABS_INF(*row->rhs) && + !IS_ABS_INF(ub)); + implied_lb = ub + (*row->rhs - act->min) / Aik; + } + // Gondzio's case (important for implied free column singletons) + else if (act->n_inf_min == 1 && HAS_TAG(col_tag, C_TAG_UB_INF)) + { + double min_temp = compute_min_act_tags(row->vals, row->cols, *row->len, + bounds, col_tags); + assert(!IS_ABS_INF(min_temp) && !IS_ABS_INF(*row->rhs)); + implied_lb = (*row->rhs - min_temp) / Aik; + } + } + + assert(!IS_POS_INF(implied_lb)); + return (lb - implied_lb <= 0); +} + +PresolveStatus change_rhs_of_ineq(RowView *row, double new_rhs, Lock *locks, + iVec *dton_rows, PostsolveInfo *postsolve_info, + int other_row_idx, double ratio) +{ + assert(!HAS_TAG(*row->tag, (R_TAG_EQ | R_TAG_INACTIVE))); + assert(!IS_ABS_INF(new_rhs)); + assert(other_row_idx >= 0); + + if (!HAS_TAG(*row->tag, R_TAG_LHS_INF) && *row->lhs >= new_rhs + FEAS_TOL) + { + return INFEASIBLE; + } + + bool is_rhs_inf = HAS_TAG(*row->tag, R_TAG_RHS_INF); + assert(is_rhs_inf || *row->rhs > new_rhs); + + // ------------------------------------------------------------------------- + // update locks + // ------------------------------------------------------------------------- + int len = *row->len; + if (is_rhs_inf) + { + for (int i = 0; i < len; ++i) + { + if (row->vals[i] > 0) + { + locks[row->cols[i]].up++; + } + else + { + locks[row->cols[i]].down++; + } + } + } + + // update row tag and the actual rhs + *row->rhs = new_rhs; + REMOVE_TAG(*row->tag, R_TAG_RHS_INF); + + // check if the row becomes an equality constraint + if (!HAS_TAG(*row->tag, R_TAG_LHS_INF) && + IS_EQUAL_FEAS_TOL(*row->lhs, *row->rhs)) + { + *row->rhs = *row->lhs; + UPDATE_TAG(*row->tag, R_TAG_EQ); + + if (len == 2) + { + iVec_append(dton_rows, row->i); + } + } + + // dual postsolve + save_retrieval_rhs_or_lhs_change(postsolve_info, row->i, row->vals, row->cols, + len, new_rhs, other_row_idx, ratio, false); + + return REDUCED; +} + +PresolveStatus change_lhs_of_ineq(RowView *row, double new_lhs, Lock *locks, + iVec *dton_rows, PostsolveInfo *postsolve_info, + int other_row_idx, double ratio) +{ + assert(!HAS_TAG(*row->tag, (R_TAG_EQ | R_TAG_INACTIVE))); + assert(new_lhs != INF && new_lhs != -INF); + assert(other_row_idx >= 0); + + if (!HAS_TAG(*row->tag, R_TAG_RHS_INF) && *row->rhs <= new_lhs - FEAS_TOL) + { + return INFEASIBLE; + } + + bool is_lhs_inf = HAS_TAG(*row->tag, R_TAG_LHS_INF); + assert(is_lhs_inf || *row->lhs < new_lhs); + + // ------------------------------------------------------------------------- + // update locks + // ------------------------------------------------------------------------- + int len = *row->len; + if (is_lhs_inf) + { + for (int i = 0; i < len; ++i) + { + if (row->vals[i] > 0) + { + locks[row->cols[i]].down++; + } + else + { + locks[row->cols[i]].up++; + } + } + } + + // update row tag and the actual lhs + *row->lhs = new_lhs; + REMOVE_TAG(*row->tag, R_TAG_LHS_INF); + + // check if the row becomes an equality constraint + if (!HAS_TAG(*row->tag, R_TAG_RHS_INF) && + IS_EQUAL_FEAS_TOL(*row->rhs, *row->lhs)) + { + *row->lhs = *row->rhs; + UPDATE_TAG(*row->tag, R_TAG_EQ); + + if (len == 2) + { + iVec_append(dton_rows, row->i); + } + } + + // dual postsolve + save_retrieval_rhs_or_lhs_change(postsolve_info, row->i, row->vals, row->cols, + len, new_lhs, other_row_idx, ratio, true); + + return REDUCED; +} + +bool is_ub_implied_free(const ConstColView *col, Activity *acts, const double *lhs, + const double *rhs, const RowTag *row_tags, const Matrix *A, + const ColTag *col_tags, const Bound *bounds) +{ + assert(!HAS_TAG(*col->tag, (C_TAG_INACTIVE | C_TAG_UB_INF))); + + for (int ii = 0; ii < *col->len; ++ii) + { + int i = col->rows[ii]; + assert(!HAS_TAG(row_tags[i], R_TAG_INACTIVE)); + + int len = A->p[i].end - A->p[i].start; + ConstRowView row = + new_const_rowview(A->x + A->p[i].start, A->i + A->p[i].start, &len, + A->p + i, lhs + i, rhs + i, row_tags + i, i); + + if (implied_free_from_above_by_row(col->vals[ii], &row, *col->lb, *col->ub, + *col->tag, acts + i, col_tags, bounds)) + { + DEBUG(acts[i].min = + (acts[i].n_inf_min != 0) ? INVALID_ACT_DEBUG : acts[i].min); + DEBUG(acts[i].max = + (acts[i].n_inf_max != 0) ? INVALID_ACT_DEBUG : acts[i].max); + return true; + } + + DEBUG(acts[i].min = + (acts[i].n_inf_min != 0) ? INVALID_ACT_DEBUG : acts[i].min); + DEBUG(acts[i].max = + (acts[i].n_inf_max != 0) ? INVALID_ACT_DEBUG : acts[i].max); + } + + return false; +} + +bool is_lb_implied_free(const ConstColView *col, Activity *acts, const double *lhs, + const double *rhs, const RowTag *row_tags, const Matrix *A, + const ColTag *col_tags, const Bound *bounds) + +{ + assert(!HAS_TAG(*col->tag, (C_TAG_INACTIVE | C_TAG_LB_INF))); + + for (int ii = 0; ii < *col->len; ++ii) + { + int i = col->rows[ii]; + assert(!HAS_TAG(row_tags[i], R_TAG_INACTIVE)); + + int len = A->p[i].end - A->p[i].start; + ConstRowView row = + new_const_rowview(A->x + A->p[i].start, A->i + A->p[i].start, &len, + A->p + i, lhs + i, rhs + i, row_tags + i, i); + + assert(col->vals[ii] != 0); + if (implied_free_from_below_by_row(col->vals[ii], &row, *col->lb, *col->ub, + *col->tag, acts + i, col_tags, bounds)) + { + DEBUG(acts[i].min = + (acts[i].n_inf_min != 0) ? INVALID_ACT_DEBUG : acts[i].min); + DEBUG(acts[i].max = + (acts[i].n_inf_max != 0) ? INVALID_ACT_DEBUG : acts[i].max); + return true; + } + + DEBUG(acts[i].min = + (acts[i].n_inf_min != 0) ? INVALID_ACT_DEBUG : acts[i].min); + DEBUG(acts[i].max = + (acts[i].n_inf_max != 0) ? INVALID_ACT_DEBUG : acts[i].max); + } + + return false; +} \ No newline at end of file diff --git a/src/core/Debugger.c b/src/core/Debugger.c new file mode 100644 index 00000000..d1e0ef92 --- /dev/null +++ b/src/core/Debugger.c @@ -0,0 +1,811 @@ +/* + * Copyright 2025 Daniel Cederberg + * + * This file is part of the PSLP project (LP Presolver). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Debugger.h" +#include "Activity.h" +#include "Bounds.h" +#include "Constraints.h" +#include "Locks.h" +#include "Matrix.h" +#include "Numerics.h" +#include "PresolveStats.h" +#include "State.h" +#include "glbopts.h" +#include +#include + +#ifdef __has_include +#if __has_include() +#include +#else +#define RUNNING_ON_VALGRIND 0 +#endif +#else +#include // fallback +#endif + +void run_debugger(const Constraints *constraints, bool finished) +{ + verify_activities(constraints); + verify_row_and_col_sizes(constraints); + verify_empty_rows(constraints); + verify_ston_rows(constraints); + verify_doubleton_rows(constraints); + verify_empty_cols(constraints); + verify_ston_cols(constraints); + verify_no_duplicates_lists(constraints->state); + verify_A_and_AT(constraints, false); + verify_locks(constraints->AT, constraints->state->col_locks, + constraints->col_tags, constraints->row_tags); + verify_row_tags(constraints); + + if (finished) + { + verify_empty_when_finished(constraints); + verify_A_and_AT(constraints, true); + } +} + +int ASSERT_NO_ZEROS_D(const double *x, size_t len) +{ + for (size_t i = 0; i < len; ++i) + { + assert(x[i] != 0); + } + + return 0; +} + +void print_int_array(const int *arr, size_t len) +{ + for (size_t i = 0; i < len; ++i) + { + printf("%d ", arr[i]); + } + printf("\n"); +} + +void print_double_array(const double *arr, size_t len) +{ + for (size_t i = 0; i < len; ++i) + { + printf("%f ", arr[i]); + } + printf("\n"); +} + +void verify_empty_rows(const Constraints *constraints) +{ + const iVec *empty_rows = constraints->state->empty_rows; + const int *row_sizes = constraints->state->row_sizes; + int n_rows = constraints->m; + char *lookup = ps_calloc(n_rows, sizeof(char)); + + // verify that all appended rows are empty rows + for (int i = 0; i < empty_rows->len; ++i) + { + assert(row_sizes[empty_rows->data[i]] == 0); + lookup[empty_rows->data[i]] = 1; + } + + // verify that all empty rows have been appended using a look-up + // table + for (int i = 0; i < n_rows; i++) + { + if (row_sizes[i] == 0) + { + assert(lookup[i]); + } + } + + PS_FREE(lookup); +} + +void verify_empty_cols(const Constraints *constraints) +{ + const iVec *empty_cols = constraints->state->empty_cols; + const int *col_sizes = constraints->state->col_sizes; + int n_cols = constraints->n; + char *lookup = ps_calloc(n_cols, sizeof(char)); + + // verify that all appended columns are empty columns + for (int i = 0; i < empty_cols->len; ++i) + { + assert(col_sizes[empty_cols->data[i]] == 0); + lookup[empty_cols->data[i]] = 1; + } + + // verify that all empty rows have been appended using a look-up + // table + for (int i = 0; i < n_cols; i++) + { + if (col_sizes[i] == 0) + { + assert(lookup[i]); + } + } + + PS_FREE(lookup); +} + +void verify_ston_cols(const Constraints *constraints) +{ + const iVec *ston_cols = constraints->state->ston_cols; + const int *col_sizes = constraints->state->col_sizes; + int n_cols = constraints->n; + char *lookup = ps_calloc(n_cols, sizeof(char)); + + // verify that all appended columns are ston columns or + // empty/inactive + for (int i = 0; i < ston_cols->len; ++i) + { + assert(col_sizes[ston_cols->data[i]] <= 1); + lookup[ston_cols->data[i]] = 1; + } + + // verify that all ston columns have been appended using a look-up + // table + for (int i = 0; i < constraints->n; i++) + { + if (col_sizes[i] == 1) + { + assert(lookup[i]); + } + } + + PS_FREE(lookup); +} + +void verify_ston_rows(const Constraints *constraints) +{ + const iVec *ston_rows = constraints->state->ston_rows; + const int *row_sizes = constraints->state->row_sizes; + int n_rows = constraints->m; + char *lookup = ps_calloc(n_rows, sizeof(char)); + + // verify that all appended rows are empty rows + for (int i = 0; i < ston_rows->len; ++i) + { + // if (!colTag.compare(ColTag::kFixed))?? + assert(row_sizes[ston_rows->data[i]] == 1); + lookup[ston_rows->data[i]] = 1; + } + + // verify that all ston rows have been appended using a look-up + // table + for (int i = 0; i < n_rows; i++) + { + if (row_sizes[i] == 1) + { + assert(lookup[i]); + } + } + + PS_FREE(lookup); +} + +void verify_doubleton_rows(const Constraints *constraints) +{ + const iVec *doubleton_rows = constraints->state->dton_rows; + const int *row_sizes = constraints->state->row_sizes; + RowTag *row_tags = constraints->row_tags; + int n_rows = constraints->m; + char *lookup = ps_calloc(n_rows, sizeof(char)); + + // verify that all appended rows have either size 2 and are equality rows, + // or they have been reduced to size 1 or less + for (int i = 0; i < doubleton_rows->len; ++i) + { + lookup[doubleton_rows->data[i]] = 1; + assert(row_sizes[doubleton_rows->data[i]] <= 2); + bool condition = HAS_TAG(row_tags[doubleton_rows->data[i]], + (R_TAG_EQ | R_TAG_INACTIVE)) || + row_sizes[doubleton_rows->data[i]] < 2; + + if (!condition) + { + printf("row_tags[%d] = %d\n", doubleton_rows->data[i], + row_tags[doubleton_rows->data[i]]); + printf("row_sizes[%d] = %d\n", doubleton_rows->data[i], + row_sizes[doubleton_rows->data[i]]); + } + + assert(condition); + } + + // verify that all dton rows have been appended using a look-up + // table + for (int i = 0; i < n_rows; i++) + { + if (row_sizes[i] == 2 && HAS_TAG(row_tags[i], R_TAG_EQ)) + { + assert(lookup[i]); + } + } + + PS_FREE(lookup); +} + +// Comparison function for qsort +static int compare_ints(const void *a, const void *b) +{ + return (*(int *) a - *(int *) b); +} + +void verify_no_duplicates(const iVec *vec) +{ + for (int i = 0; i < vec->len; ++i) + { + for (int j = i + 1; j < vec->len; ++j) + { + assert(vec->data[i] != vec->data[j]); + } + } +} + +void verify_no_duplicates_sort_ptr(const int *data, int len) +{ + int *temp = (int *) ps_malloc(len, sizeof(int)); + memcpy(temp, data, len * sizeof(int)); + qsort(temp, len, sizeof(int), compare_ints); + + for (int i = 0; i < len - 1; ++i) + { + assert(temp[i] >= 0); + assert(temp[i] != temp[i + 1]); + } + + PS_FREE(temp); +} + +void verify_no_duplicates_sort(const iVec *vec) +{ + verify_no_duplicates_sort_ptr(vec->data, vec->len); +} + +void verify_no_duplicates_ptr(const int *data, int len) +{ + for (int i = 0; i < len; ++i) + { + for (int j = i + 1; j < len; ++j) + { + assert(data[i] != data[j]); + } + } +} + +void verify_nonnegative_iVec(const iVec *vec) +{ + for (int i = 0; i < vec->len; ++i) + { + assert(vec->data[i] >= 0); + } +} + +void verify_no_duplicates_lists(const State *data) +{ + // verify_no_duplicates(data->updated_activities); + // verify_no_duplicates(data->dton_rows); + // verify_no_duplicates(data->ston_rows); + // verify_no_duplicates(data->fixed_cols_to_delete); + // verify_no_duplicates(data->sub_cols_to_delete); + // verify_no_duplicates(data->rows_to_delete); + + verify_nonnegative_iVec(data->updated_activities); + verify_nonnegative_iVec(data->dton_rows); + verify_nonnegative_iVec(data->ston_rows); + verify_nonnegative_iVec(data->fixed_cols_to_delete); + verify_nonnegative_iVec(data->sub_cols_to_delete); + verify_nonnegative_iVec(data->rows_to_delete); + + verify_no_duplicates_sort(data->updated_activities); + verify_no_duplicates_sort(data->dton_rows); + verify_no_duplicates_sort(data->ston_rows); + verify_no_duplicates_sort(data->fixed_cols_to_delete); + verify_no_duplicates_sort(data->sub_cols_to_delete); + verify_no_duplicates_sort(data->rows_to_delete); +} + +void verify_row_tags(const Constraints *constraints) +{ + const RowTag *row_tags = constraints->row_tags; + const double *lhs = constraints->lhs; + const double *rhs = constraints->rhs; + + for (int i = 0; i < constraints->m; ++i) + { + if (lhs[i] == -INF && !HAS_TAG(row_tags[i], R_TAG_INACTIVE)) + { + assert(HAS_TAG(row_tags[i], R_TAG_LHS_INF)); + } + + if (rhs[i] == INF && !HAS_TAG(row_tags[i], R_TAG_INACTIVE)) + { + assert(HAS_TAG(row_tags[i], R_TAG_RHS_INF)); + } + + if (HAS_TAG(row_tags[i], R_TAG_LHS_INF)) + { + assert(lhs[i] == -INF); + } + + if (HAS_TAG(row_tags[i], R_TAG_RHS_INF)) + { + assert(rhs[i] == INF); + } + + if (!HAS_TAG(row_tags[i], R_TAG_LHS_INF) && + !HAS_TAG(row_tags[i], R_TAG_RHS_INF) && lhs[i] == rhs[i]) + { + assert(HAS_TAG(row_tags[i], (R_TAG_EQ | R_TAG_INACTIVE))); + } + } +} + +static void verify_CSR_matrix(const Matrix *A, bool compressed) +{ + int i, j, nnz; + assert(A->m >= 0 && A->n >= 0 && A->nnz >= 0); + assert(A->n_alloc >= A->nnz); + + if (A->nnz > 0) + { + assert(A->i != NULL && A->x != NULL && A->p != NULL); + } + // verify that row ranges are ascending + for (i = 0; i < A->m; ++i) + { + assert(A->p[i + 1].start >= A->p[i].end && A->p[i].end >= A->p[i].start); + } + + // verify that columns within a row are sorted + for (i = 0; i < A->m; ++i) + { + for (j = A->p[i].start + 1; j < A->p[i].end; ++j) + { + assert(A->i[j] > A->i[j - 1]); + } + } + + // verify that no explicit zeros are stored are that nnz count is correct + nnz = 0; + for (i = 0; i < A->m; ++i) + { + for (j = A->p[i].start; j < A->p[i].end; ++j) + { + assert(A->x[j] != 0); + } + + nnz += A->p[i].end - A->p[i].start; + } + + assert(nnz == A->nnz); + + if (compressed) + { + assert(A->p[A->m].start == A->nnz); + assert(A->p[A->m].end == A->nnz); + assert(A->p[0].start == 0); + } +} + +bool verify_A_and_AT_consistency(const Matrix *A, const Matrix *AT) +{ + int *work_n_cols = (int *) ps_malloc(A->n, sizeof(int)); + Matrix *real_AT = transpose(A, work_n_cols); + + // check that nnz and dimensions are consistent + assert(real_AT->m == AT->m); + assert(real_AT->n == AT->n); + assert(real_AT->nnz == AT->nnz); + + int i, j, k, start_AT, end_AT, start_real_AT, end_real_AT; + + // check that values are consistent + for (i = 0; i < AT->m; ++i) + { + start_AT = AT->p[i].start; + end_AT = AT->p[i].end; + start_real_AT = real_AT->p[i].start; + end_real_AT = real_AT->p[i].end; + + assert(end_AT - start_AT == end_real_AT - start_real_AT); + + for (j = start_AT, k = start_real_AT; j < end_AT; ++j, ++k) + { + assert(!IS_ABS_INF(AT->x[j]) && !IS_ABS_INF(real_AT->x[k])); + if (AT->x[j] != real_AT->x[k] || AT->i[j] != real_AT->i[k]) + { + PS_FREE(work_n_cols); + free_matrix(real_AT); + return false; + } + } + } + + PS_FREE(work_n_cols); + free_matrix(real_AT); + return true; +} + +static void verify_no_inactive_cols(const Matrix *A, ColTag *col_tags) +{ + int i, j, start, end; + for (i = 0; i < A->m; ++i) + { + start = A->p[i].start; + end = A->p[i].end; + + for (j = start; j < end; ++j) + { + assert(!HAS_TAG(col_tags[A->i[j]], C_TAG_INACTIVE)); + } + } +} + +void verify_A_and_AT(const Constraints *constraints, bool compressed) +{ + const Matrix *A = constraints->A; + const Matrix *AT = constraints->AT; + + // verify that both A and AT are valid CSR matrices + verify_CSR_matrix(A, compressed); + verify_CSR_matrix(AT, compressed); + + // verify that A and AT are consistent + assert(verify_A_and_AT_consistency(A, AT)); + + // verify that A does not store the coefficients of inactive columns + verify_no_inactive_cols(A, constraints->col_tags); + + // verify that AT does not store the coefficients of inactive rows + verify_no_inactive_cols(AT, constraints->row_tags); +} + +void verify_locks(const Matrix *AT, const Lock *locks, const ColTag *col_tags, + const RowTag *row_tags) +{ + int up, down, i, col, row, n_cols; + n_cols = AT->m; + double Aik; + + for (col = 0; col < n_cols; ++col) + { + if (HAS_TAG(col_tags[col], C_TAG_INACTIVE)) + { + continue; + } + + up = 0; + down = 0; + + for (i = AT->p[col].start; i < AT->p[col].end; ++i) + { + row = AT->i[i]; + Aik = AT->x[i]; + + if ((Aik > 0 && !HAS_TAG(row_tags[row], R_TAG_RHS_INF)) || + (Aik < 0 && !HAS_TAG(row_tags[row], R_TAG_LHS_INF))) + { + up++; + } + + if ((Aik > 0 && !HAS_TAG(row_tags[row], R_TAG_LHS_INF)) || + (Aik < 0 && !HAS_TAG(row_tags[row], R_TAG_RHS_INF))) + { + down++; + } + } + + assert(up == locks[col].up); + assert(down == locks[col].down); + } +} + +void verify_empty_when_finished(const Constraints *constraints) +{ + assert(constraints->state->empty_cols->len == 0); + assert(constraints->state->empty_rows->len == 0); + assert(constraints->state->rows_to_delete->len == 0); + assert(constraints->state->fixed_cols_to_delete->len == 0); + assert(constraints->state->sub_cols_to_delete->len == 0); + + for (int i = 0; i < constraints->A->m; ++i) + { + assert(!HAS_TAG(constraints->row_tags[i], R_TAG_INACTIVE)); + } + + for (int i = 0; i < constraints->A->n; ++i) + { + assert(!HAS_TAG(constraints->col_tags[i], C_TAG_INACTIVE)); + } +} + +void verify_row_and_col_sizes(const Constraints *constraints) +{ + const int *row_sizes = constraints->state->row_sizes; + const int *col_sizes = constraints->state->col_sizes; + const Matrix *A = constraints->A; + const Matrix *AT = constraints->AT; + const ColTag *col_tags = constraints->col_tags; + const RowTag *row_tags = constraints->row_tags; + int i; + + // check that row sizes are correct + for (i = 0; i < constraints->A->m; ++i) + { + if (HAS_TAG(row_tags[i], R_TAG_INACTIVE)) + { + assert(row_sizes[i] == SIZE_INACTIVE_ROW); + assert(A->p[i].start == A->p[i].end); + } + else + { + assert(row_sizes[i] == A->p[i].end - A->p[i].start); + } + + if (row_sizes[i] == SIZE_INACTIVE_ROW) + { + assert(HAS_TAG(row_tags[i], R_TAG_INACTIVE)); + } + } + + // check that column sizes are correct + for (i = 0; i < constraints->A->n; ++i) + { + if (HAS_TAG(col_tags[i], C_TAG_INACTIVE)) + { + if (col_sizes[i] != SIZE_INACTIVE_COL) + { + printf("col_sizes[%d] = %d\n", i, col_sizes[i]); + } + + assert(col_sizes[i] == SIZE_INACTIVE_COL); + if (AT->p[i].start != AT->p[i].end) + { + printf("AT->p[%d].start = %d\n", i, AT->p[i].start); + printf("AT->p[%d].end = %d\n", i, AT->p[i].end); + } + assert(AT->p[i].start == AT->p[i].end); + } + else + { + assert(col_sizes[i] == AT->p[i].end - AT->p[i].start); + } + + if (col_sizes[i] == SIZE_INACTIVE_COL) + { + assert(HAS_TAG(col_tags[i], C_TAG_INACTIVE)); + } + } +} + +void verify_activity(const ColTag *col_tags, const Bound *bounds, Activity activity, + RowTag row_tag, const int *cols, const double *vals, int len) +{ + int n_inf_min = 0; + int n_inf_max = 0; + double min = 0.0; + double max = 0.0; + int i, col; + + if (HAS_TAG(row_tag, R_TAG_INACTIVE)) + { + return; + } + + // count the infinite contributions + for (i = 0; i < len; ++i) + { + col = cols[i]; + if (HAS_TAG(col_tags[col], C_TAG_INACTIVE)) + { + continue; + } + + if (vals[i] > 0) + { + if (HAS_TAG(col_tags[col], C_TAG_UB_INF)) + { + n_inf_max++; + } + + if (HAS_TAG(col_tags[col], C_TAG_LB_INF)) + { + n_inf_min++; + } + } + else + { + if (HAS_TAG(col_tags[col], C_TAG_LB_INF)) + { + n_inf_max++; + } + + if (HAS_TAG(col_tags[col], C_TAG_UB_INF)) + { + n_inf_min++; + } + } + } + + assert(n_inf_max == activity.n_inf_max); + assert(n_inf_min == activity.n_inf_min); + + // if there is a max infinite contribution, the max activity should be + // INVALID_ACT_DEBUG + if (n_inf_max > 0) + { + if (activity.max != INVALID_ACT_DEBUG) + { + printf("n_inf_max = %d\n", n_inf_max); + printf("activity.max = %f\n", activity.max); + } + assert(activity.max == INVALID_ACT_DEBUG); + } + else + { + // count max activity + for (i = 0; i < len; ++i) + { + col = cols[i]; + if (HAS_TAG(col_tags[col], C_TAG_INACTIVE)) + { + continue; + } + + if (vals[i] > 0) + { + assert(!HAS_TAG(col_tags[col], C_TAG_UB_INF) && + bounds[col].ub != INF); + max += vals[i] * bounds[col].ub; + } + else + { + assert(!HAS_TAG(col_tags[col], C_TAG_LB_INF) && + bounds[col].lb != -INF); + max += vals[i] * bounds[col].lb; + } + } + } + + // if there is a min infinite contribution, the min activity should be + // INVALID_ACT_DEBUG + if (n_inf_min > 0) + { + assert(activity.min == INVALID_ACT_DEBUG); + } + else + { + // count min activity + for (i = 0; i < len; ++i) + { + col = cols[i]; + if (HAS_TAG(col_tags[col], C_TAG_INACTIVE)) + { + continue; + } + + if (vals[i] > 0) + { + assert(!HAS_TAG(col_tags[col], C_TAG_LB_INF) && + bounds[col].lb != -INF); + min += vals[i] * bounds[col].lb; + } + else + { + assert(!HAS_TAG(col_tags[col], C_TAG_UB_INF) && + bounds[col].ub != INF); + min += vals[i] * bounds[col].ub; + } + } + } +} + +void verify_activities(const Constraints *constraints) +{ + int n_rows = constraints->m; + const Activity *activities = constraints->state->activities; + const RowTag *row_tags = constraints->row_tags; + const RowRange *row_ranges = constraints->A->p; + const int *cols = constraints->A->i; + const double *vals = constraints->A->x; + + for (int i = 0; i < n_rows; ++i) + { + verify_activity(constraints->col_tags, constraints->bounds, activities[i], + row_tags[i], cols + row_ranges[i].start, + vals + row_ranges[i].start, + row_ranges[i].end - row_ranges[i].start); + } +} + +void ASSERT_NO_ACTIVE_STON_ROWS(const Matrix *A, const RowTag *row_tags) +{ + for (int i = 0; i < A->m; ++i) + { + assert(A->p[i].end - A->p[i].start != 1 || + HAS_TAG(row_tags[i], R_TAG_INACTIVE)); + } +} + +int ASSERT_INCREASING_I(const int *x, size_t len) +{ + for (size_t i = 1; i < len; ++i) + { + assert(x[i] > x[i - 1]); + } + + return 0; +} + +void print_matrix(const Matrix *A) +{ + for (int i = 0; i < A->m; i++) + { + int k = A->p[i].start; + + for (int j = 0; j < A->n; j++) + { + if (k < A->p[i].end && A->i[k] == j) + { + printf("%6.2f ", A->x[k]); + k++; + } + else + { + printf("%6.2f ", 0.0); + } + } + printf("\n"); + } +} + +void verify_problem_up_to_date(const Constraints *constraints) +{ + assert(constraints->state->rows_to_delete->len == 0); + assert(constraints->state->fixed_cols_to_delete->len == 0); + assert(constraints->state->sub_cols_to_delete->len == 0); +} + +void verify_row_states(const Activity *acts, const iVec *updated_activities) +{ + for (int i = 0; i < updated_activities->len; ++i) + { + int row = updated_activities->data[i]; + assert(acts[row].status == ADDED); + assert(acts[row].n_inf_min == 0 || acts[row].n_inf_max == 0); + } +} + +void run_debugger_stats_consistency_check(const PresolveStats *stats) +{ + int total_removed = stats->nnz_removed_trivial + stats->nnz_removed_fast + + stats->nnz_removed_primal_propagation + + stats->nnz_removed_parallel_rows + + stats->nnz_removed_parallel_cols; + + assert(stats->nnz_original - stats->nnz_reduced == total_removed); + + // valgrind timing is not reliable +#ifndef RUNNING_ON_VALGRIND + double time_medium = stats->ps_time_primal_propagation + + stats->ps_time_parallel_rows + stats->ps_time_parallel_cols; + assert((stats->ps_time_medium - time_medium) / MAX(1e-2, time_medium) < 0.05); +#endif +} \ No newline at end of file diff --git a/src/core/Locks.c b/src/core/Locks.c new file mode 100644 index 00000000..8019d2a7 --- /dev/null +++ b/src/core/Locks.c @@ -0,0 +1,190 @@ +/* + * Copyright 2025 Daniel Cederberg + * + * This file is part of the PSLP project (LP Presolver). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Locks.h" +#include "Matrix.h" +#include "Memory_wrapper.h" +#include "RowColViews.h" +#include + +Lock *new_locks(const Matrix *A, const RowTag *row_tags) +{ + Lock *locks = (Lock *) ps_calloc(A->n, sizeof(Lock)); + RETURN_PTR_IF_NULL(locks, NULL); + int i, j, start, end; + + for (i = 0; i < A->m; ++i) + { + start = A->p[i].start; + end = A->p[i].end; + + for (j = start; j < end; ++j) + { + if (A->x[j] > 0) + { + if (!HAS_TAG(row_tags[i], R_TAG_RHS_INF)) + { + locks[A->i[j]].up++; + } + + if (!HAS_TAG(row_tags[i], R_TAG_LHS_INF)) + { + locks[A->i[j]].down++; + } + } + else + { + if (!HAS_TAG(row_tags[i], R_TAG_RHS_INF)) + { + locks[A->i[j]].down++; + } + + if (!HAS_TAG(row_tags[i], R_TAG_LHS_INF)) + { + locks[A->i[j]].up++; + } + } + } + } + + return locks; +} + +void free_locks(Lock *locks) +{ + PS_FREE(locks); +} + +void update_locks_eq_to_ineq(Lock *locks, const double *vals, const int *cols, + int len, bool to_upper) +{ + // -------------------------------------------------------- + // if the new constraint is an upper inequality constraint + // -------------------------------------------------------- + if (to_upper) + { + for (int i = 0; i < len; i++) + { + assert(vals[i] != 0); + if (vals[i] > 0) + { + locks[cols[i]].down--; + } + else + { + locks[cols[i]].up--; + } + } + } + // -------------------------------------------------------- + // if the new constraint is a lower inequality constraint + // -------------------------------------------------------- + else + { + for (int i = 0; i < len; i++) + { + assert(vals[i] != 0); + if (vals[i] > 0) + { + locks[cols[i]].up--; + } + else + { + locks[cols[i]].down--; + } + } + } +} + +void update_locks_ineq_to_eq(Lock *locks, const double *vals, const int *cols, + int len, bool is_new_constraint_lhs) +{ + // -------------------------------- + // if we get a new LHS constraint + // -------------------------------- + if (is_new_constraint_lhs) + { + for (int i = 0; i < len; i++) + { + assert(vals[i] != 0); + if (vals[i] > 0) + { + locks[cols[i]].down++; + } + else + { + locks[cols[i]].up++; + } + } + } + // ------------------------------- + // if we get a new RHS constraint + // ------------------------------- + else + { + for (int i = 0; i < len; i++) + { + assert(vals[i] != 0); + if (vals[i] > 0) + { + locks[cols[i]].up++; + } + else + { + locks[cols[i]].down++; + } + } + } +} + +void count_locks_one_column(const RowView *col, Lock *lock, const RowTag *row_tags) +{ + lock->up = 0; + lock->down = 0; + + const int *rows = col->cols; + + for (int i = 0; i < *col->len; ++i) + { + if (col->vals[i] > 0) + { + if (!HAS_TAG(row_tags[rows[i]], R_TAG_RHS_INF)) + { + lock->up++; + } + + if (!HAS_TAG(row_tags[rows[i]], R_TAG_LHS_INF)) + { + lock->down++; + } + } + else + { + assert(col->vals[i] < 0); + if (!HAS_TAG(row_tags[rows[i]], R_TAG_RHS_INF)) + { + lock->down++; + } + + if (!HAS_TAG(row_tags[rows[i]], R_TAG_LHS_INF)) + { + lock->up++; + } + } + } +} diff --git a/src/core/Matrix.c b/src/core/Matrix.c new file mode 100644 index 00000000..26e85c18 --- /dev/null +++ b/src/core/Matrix.c @@ -0,0 +1,611 @@ +/* + * Copyright 2025 Daniel Cederberg + * + * This file is part of the PSLP project (LP Presolver). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Matrix.h" +#include "Debugger.h" +#include "Memory_wrapper.h" +#include "Numerics.h" +#include "RowColViews.h" +#include "glbopts.h" +#include "stdlib.h" +#include "string.h" + +Matrix *matrix_new(const double *Ax, const int *Ai, const int *Ap, int n_rows, + int n_cols, int nnz) +{ + DEBUG(ASSERT_NO_ZEROS_D(Ax, nnz)); + Matrix *A = matrix_alloc(n_rows, n_cols, nnz); + RETURN_PTR_IF_NULL(A, NULL); + int offset, i, row_size, row_alloc; + + offset = 0; + for (i = 0; i < n_rows; ++i) + { + A->p[i].start = Ap[i] + offset; + memcpy(A->x + A->p[i].start, Ax + Ap[i], + (Ap[i + 1] - Ap[i]) * sizeof(double)); + + memcpy(A->i + A->p[i].start, Ai + Ap[i], (Ap[i + 1] - Ap[i]) * sizeof(int)); + + A->p[i].end = Ap[i + 1] + offset; + row_size = A->p[i].end - A->p[i].start; + row_alloc = calc_memory_row(row_size, EXTRA_ROW_SPACE, EXTRA_MEMORY_RATIO); + offset += row_alloc - row_size; + } + + A->p[n_rows].start = Ap[n_rows] + offset; + A->p[n_rows].end = A->p[n_rows].start; + + return A; +} + +// needed for the transpose function +Matrix *matrix_alloc(int n_rows, int n_cols, int nnz) +{ + Matrix *A = (Matrix *) ps_malloc(1, sizeof(Matrix)); + RETURN_PTR_IF_NULL(A, NULL); + + A->m = n_rows; + A->n = n_cols; + A->nnz = nnz; + A->n_alloc = calc_memory(nnz, n_rows, EXTRA_ROW_SPACE, EXTRA_MEMORY_RATIO); + +#ifdef TESTING + A->i = (int *) ps_calloc(A->n_alloc, sizeof(int)); + A->p = (RowRange *) ps_calloc(n_rows + 1, sizeof(RowRange)); + A->x = (double *) ps_calloc(A->n_alloc, sizeof(double)); +#else + A->i = (int *) ps_malloc(A->n_alloc, sizeof(int)); + A->p = (RowRange *) ps_malloc(n_rows + 1, sizeof(RowRange)); + A->x = (double *) ps_malloc(A->n_alloc, sizeof(double)); +#endif + + if (!A->i || !A->p || !A->x) + { + free_matrix(A); + return NULL; + } + + return A; +} + +Matrix *matrix_new_no_extra_space(const double *Ax, const int *Ai, const int *Ap, + int n_rows, int n_cols, int nnz) +{ + Matrix *A = (Matrix *) ps_malloc(1, sizeof(Matrix)); + RETURN_PTR_IF_NULL(A, NULL); + + A->m = n_rows; + A->n = n_cols; + A->nnz = nnz; + A->n_alloc = nnz; + A->i = (int *) ps_malloc(A->n_alloc, sizeof(int)); + A->p = (RowRange *) ps_malloc(n_rows + 1, sizeof(RowRange)); + A->x = (double *) ps_malloc(A->n_alloc, sizeof(double)); + + if (!A->i || !A->p || !A->x) + { + free_matrix(A); + return NULL; + } + + memcpy(A->x, Ax, nnz * sizeof(double)); + memcpy(A->i, Ai, nnz * sizeof(int)); + + for (int i = 0; i <= n_rows; ++i) + { + A->p[i].start = Ap[i]; + A->p[i].end = Ap[i + 1]; + } + + return A; +} + +Matrix *transpose(const Matrix *A, int *work_n_cols) +{ + Matrix *AT = matrix_alloc(A->n, A->m, A->nnz); + RETURN_PTR_IF_NULL(AT, NULL); + int i, j, start; + int *count = work_n_cols; + memset(count, 0, A->n * sizeof(int)); + + // ------------------------------------------------------------------- + // compute nnz in each column of A + // ------------------------------------------------------------------- + for (i = 0; i < A->m; ++i) + { + for (j = A->p[i].start; j < A->p[i].end; ++j) + { + count[A->i[j]]++; + } + } + + // ------------------------------------------------------------------ + // compute row pointers, taking the extra space into account + // ------------------------------------------------------------------ + AT->p[0].start = 0; + + for (i = 0; i < A->n; ++i) + { + start = AT->p[i].start; + AT->p[i].end = start + count[i]; + AT->p[i + 1].start = + start + calc_memory_row(count[i], EXTRA_ROW_SPACE, EXTRA_MEMORY_RATIO); + count[i] = start; + } + + AT->p[A->n].start = AT->n_alloc; + AT->p[A->n].end = AT->n_alloc; + + // ------------------------------------------------------------------ + // fill transposed matrix + // ------------------------------------------------------------------ + for (i = 0; i < A->m; ++i) + { + for (j = A->p[i].start; j < A->p[i].end; j++) + { + AT->x[count[A->i[j]]] = A->x[j]; + AT->i[count[A->i[j]]] = i; + count[A->i[j]]++; + } + } + + return AT; +} + +int calc_memory(int nnz, int n_rows, int extra_row_space, double memory_ratio) +{ + return (int) (nnz * memory_ratio) + n_rows * extra_row_space; +} + +int calc_memory_row(int size, int extra_row_space, double memory_ratio) +{ + return (int) (size * memory_ratio) + extra_row_space; +} + +void free_matrix(Matrix *A) +{ + if (A) + { + PS_FREE(A->i); + PS_FREE(A->p); + PS_FREE(A->x); + } + + PS_FREE(A); +} + +void remove_extra_space(Matrix *A, const int *row_sizes, const int *col_sizes, + bool remove_all, int *col_idxs_map) +{ + int i, j, start, end, len, row_alloc, curr, n_deleted_rows, col_count; + double extra_row_space = (remove_all) ? 0.0 : EXTRA_ROW_SPACE; + double extra_mem_ratio = (remove_all) ? 1.0 : EXTRA_MEMORY_RATIO; + curr = 0; + n_deleted_rows = 0; + + // -------------------------------------------------------------------------- + // loop through the rows and remove redundant space, including inactive + // rows. + // -------------------------------------------------------------------------- + for (i = 0; i < A->m; ++i) + { + if (row_sizes[i] == SIZE_INACTIVE_ROW) + { + n_deleted_rows++; + continue; + } + + start = A->p[i].start; + end = A->p[i].end; + len = end - start; + row_alloc = calc_memory_row(len, extra_row_space, extra_mem_ratio); + memmove(A->x + curr, A->x + start, len * sizeof(double)); + memmove(A->i + curr, A->i + start, len * sizeof(int)); + A->p[i - n_deleted_rows].start = curr; + A->p[i - n_deleted_rows].end = curr + len; + curr += row_alloc; + } + + A->m -= n_deleted_rows; + A->p[A->m].start = curr; + A->p[A->m].end = curr; + + // shrink size + A->x = realloc(A->x, curr * sizeof(double)); + A->i = (int *) ps_realloc(A->i, curr, sizeof(int)); + A->p = (RowRange *) ps_realloc(A->p, A->m + 1, sizeof(RowRange)); + + // ------------------------------------------------------------------------- + // compute new column indices + // ------------------------------------------------------------------------- + col_count = 0; + for (i = 0; i < A->n; ++i) + { + if (col_sizes[i] == SIZE_INACTIVE_COL) + { + col_idxs_map[i] = -1; + } + else + { + col_idxs_map[i] = (col_count++); + } + } + A->n = col_count; + + // ------------------------------------------------------------------------- + // update column indices + // ------------------------------------------------------------------------- + for (i = 0; i < A->m; ++i) + { + for (j = A->p[i].start; j < A->p[i].end; ++j) + { + A->i[j] = col_idxs_map[A->i[j]]; + } + } +} + +bool shift_row(Matrix *A, int row, int extra_space, int max_shift) +{ + int left, right, missing_space, remaining_shifts, left_shifts; + int right_shifts, space_left, space_right, n_move_right, n_move_left; + int next_start, next_end; + bool shift_left; + RowRange *row_r = A->p; + left = row; + right = row + 1; + remaining_shifts = max_shift; + left_shifts = 0; + right_shifts = 0; + missing_space = extra_space - (row_r[right].start - row_r[row].end); + + if (missing_space <= 0) + { + return true; + } + + // ------------------------------------------------------------------------ + // compute the new start index for row 'row' and a lower bound on the start + // index for the the first active row following row 'row'. + // ------------------------------------------------------------------------ + while (missing_space > 0) + { + if (left == 0 && right == A->m) + { + return false; + } + + // space_left is the number of steps we can shift 'left' row to the + // left without overwriting row 'left - 1'. + // space_right is the number of steps we can shift 'right' row to + // the right without overwriting row 'right + 1'. + assert(left >= 0 && right <= A->m); + space_left = (left == 0) ? 0 : row_r[left].start - row_r[left - 1].end; + space_right = + (right == A->m) ? 0 : row_r[right + 1].start - row_r[right].end; + assert(space_left >= 0 && space_right >= 0); + + // number of elements that must be moved left resp. right if we shift + // in a certain direction + n_move_right = row_r[right].end - row_r[right].start; + n_move_left = row_r[left].end - row_r[left].start; + + // decide which direction to shift + if (left == 0) + { + if (right != A->m && n_move_right <= remaining_shifts) + { + shift_left = false; + } + else + { + return false; + } + } + else if (right == A->m) + { + if (left != 0 && n_move_left <= remaining_shifts) + { + shift_left = true; + } + else + { + return false; + } + } + else if (n_move_left == 0) + { + shift_left = true; + } + else if (n_move_right == 0) + { + shift_left = false; + } + else if (n_move_left <= remaining_shifts && + (space_left / (double) (n_move_left) >= + space_right / (double) (n_move_right))) + { + shift_left = true; + } + else if (n_move_right <= remaining_shifts) + { + shift_left = false; + } + else + { + return false; + } + + assert(!(shift_left && left == 0) && !(!shift_left && right == A->m)); + + if (shift_left) + { + left_shifts = MIN(missing_space, space_left); + missing_space -= left_shifts; + remaining_shifts -= n_move_left; + left -= 1; + } + else + { + right_shifts = MIN(missing_space, space_right); + missing_space -= right_shifts; + remaining_shifts -= n_move_right; + right += 1; + } + } + assert(remaining_shifts >= 0); + + // ------------------------------------------------------------------------ + // execute total left shift + // ------------------------------------------------------------------------ + next_start = row_r[left + 1].start - left_shifts; + for (; left < row; left++) + { + int len = row_r[left + 1].end - row_r[left + 1].start; + if (len > 0) + { + memmove(A->x + next_start, A->x + row_r[left + 1].start, + len * sizeof(double)); + memmove(A->i + next_start, A->i + row_r[left + 1].start, + len * sizeof(int)); + } + row_r[left + 1].start = next_start; + row_r[left + 1].end = next_start + len; + next_start += len; + } + + // ------------------------------------------------------------------------ + // execute total right shift + // ------------------------------------------------------------------------ + next_end = row_r[right - 1].end + right_shifts; + for (; right > row + 1; right--) + { + int len = row_r[right - 1].end - row_r[right - 1].start; + if (len > 0) + { + memmove(A->x + next_end - len, A->x + row_r[right - 1].start, + len * sizeof(double)); + memmove(A->i + next_end - len, A->i + row_r[right - 1].start, + len * sizeof(int)); + } + row_r[right - 1].start = next_end - len; + row_r[right - 1].end = next_end; + next_end = row_r[right - 1].start; + } + + assert(row_r[row + 1].start - row_r[row].end == extra_space); + return true; +} + +void print_row_starts(const RowRange *row_ranges, size_t len) +{ + for (size_t i = 0; i < len; ++i) + { + printf("%d ", row_ranges[i].start); + } + printf("\n"); +} + +double insert_or_update_coeff(Matrix *A, int row, int col, double val, int *row_size) +{ + int i, start, end, insertion; + double old_val = 0.0; + start = A->p[row].start; + end = A->p[row].end; + insertion = end; + + // ----------------------------------------------------------------- + // find where it should be inserted + // ----------------------------------------------------------------- + for (i = start; i < end; ++i) + { + if (A->i[i] >= col) + { + insertion = i; + break; + } + } + + // ----------------------------------------------------------------- + // Insert the new value if it is nonzero. If it exists or should be + // inserted in the end, we don't need to shift values. + // ----------------------------------------------------------------- + if (ABS(val) > ZERO_TOL) + { + // assert(!IS_ZERO_FEAS_TOL(val)); + if (insertion == end) + { + A->x[insertion] = val; + A->i[insertion] = col; + A->p[row].end += 1; + A->nnz += 1; + *row_size += 1; + } + else if (A->i[insertion] == col) + { + old_val = A->x[insertion]; + A->x[insertion] = val; + } + else + { + memmove(A->x + insertion + 1, A->x + insertion, + (end - insertion) * sizeof(double)); + memmove(A->i + insertion + 1, A->i + insertion, + (end - insertion) * sizeof(int)); + + // insert new value + A->x[insertion] = val; + A->i[insertion] = col; + A->p[row].end += 1; + A->nnz += 1; + *row_size += 1; + } + } + // if the new value is zero, we just have to shift + else + { + // we only expect that the new value is zero if the coefficient + // already exists + assert(A->i[insertion] == col); + + // we only have to shift values if the zero is not in the end + if (insertion != end - 1) + { + memmove(A->x + insertion, A->x + insertion + 1, + (end - insertion - 1) * sizeof(double)); + memmove(A->i + insertion, A->i + insertion + 1, + (end - insertion - 1) * sizeof(int)); + } + + A->p[row].end -= 1; + A->nnz -= 1; + *row_size -= 1; + } + + assert(A->p[row].end <= A->p[row + 1].start); + return old_val; +} + +void remove_coeff(RowView *row, int col) +{ + int shift = 0; + int len = *row->len; + for (int i = 0; i < len; ++i) + { + if (row->cols[i] == col) + { + shift = 1; + } + + row->vals[i] = row->vals[i + shift]; + row->cols[i] = row->cols[i + shift]; + } + + assert(shift != 0); + (*row->range).end -= 1; + *row->len -= 1; +} + +void count_rows(const Matrix *A, int *row_sizes) +{ + for (int i = 0; i < A->m; ++i) + { + row_sizes[i] = A->p[i].end - A->p[i].start; + } +} + +#ifdef TESTING +// Function to create a random CSR matrix +Matrix *random_matrix_new(int n_rows, int n_cols, double density) +{ + // allocate memory + int n_alloc_nnz = (int) (density * n_rows * n_cols); + double *Ax = (double *) ps_malloc(n_alloc_nnz, sizeof(double)); + int *Ai = (int *) ps_malloc(n_alloc_nnz, sizeof(int)); + int *Ap = (int *) ps_malloc(n_rows + 1, sizeof(int)); + if (!Ax || !Ai || !Ap) + { + PS_FREE(Ax); + PS_FREE(Ai); + PS_FREE(Ap); + return NULL; + } + + // Initialize random number generator + srand(1); + + int nnz_count = 0; // Counter for nonzero elements + Ap[0] = 0; + + for (int i = 0; i < n_rows; ++i) + { + int row_nnz = 0; + + // Randomly determine the number of nonzeros in this row + for (int j = 0; j < n_cols; ++j) + { + if ((double) rand() / RAND_MAX < density) + { + if (nnz_count >= n_alloc_nnz) + { + break; + } + + Ax[nnz_count] = ((double) (rand() - rand()) / RAND_MAX) * 20.0; + Ai[nnz_count] = j; + ++nnz_count; + ++row_nnz; + } + } + Ap[i + 1] = Ap[i] + row_nnz; + } + + // create matrix in modified CSR format + Matrix *A = matrix_new(Ax, Ai, Ap, n_rows, n_cols, nnz_count); + PS_FREE(Ax); + PS_FREE(Ai); + PS_FREE(Ap); + + return A; +} + +void replace_row_A(Matrix *A, int row, double ratio, double *new_vals, int *cols_new, + int new_len) +{ + int i, len, start, n_new_elements; + len = A->p[row].end - A->p[row].start; + n_new_elements = new_len - len; + + // potentially shift row to get extra space + if (n_new_elements > 0) + { + assert(shift_row(A, row, n_new_elements, 2000)); + } + + // replace the row + start = A->p[row].start; + for (i = 0; i < new_len; ++i) + { + A->x[start + i] = ratio * new_vals[i]; + A->i[start + i] = cols_new[i]; + } + A->p[row].end = A->p[row].start + new_len; + assert(A->p[row].end <= A->p[row + 1].start); +} + +#endif \ No newline at end of file diff --git a/src/core/Postsolver.c b/src/core/Postsolver.c new file mode 100644 index 00000000..e0b2b7c7 --- /dev/null +++ b/src/core/Postsolver.c @@ -0,0 +1,844 @@ +/* + * Copyright 2025 Daniel Cederberg + * + * This file is part of the PSLP project (LP Presolver). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Postsolver.h" +#include "Bounds.h" +#include "Constraints.h" +#include "Matrix.h" +#include "Numerics.h" +#include "State.h" +#include "dVec.h" +#include "debug_macros.h" +#include "glbopts.h" +#include "iVec.h" +#include "u16Vec.h" +#include + +#define INIT_FRAC_POSTSOLVE 0.3 +#define COL_NOT_RETRIEVED INF +#define ROW_NOT_RETRIEVED INF +#define DUMMY_VALUE -382749 + +PostsolveInfo *postsolve_info_new(int n_rows, int n_cols) +{ + PostsolveInfo *info = (PostsolveInfo *) ps_malloc(1, sizeof(PostsolveInfo)); + info->starts = iVec_new((int) (INIT_FRAC_POSTSOLVE * (n_rows + n_cols))); + info->indices = iVec_new((int) (INIT_FRAC_POSTSOLVE * (n_rows + n_cols))); + info->vals = dVec_new((int) (INIT_FRAC_POSTSOLVE * (n_rows + n_cols))); + info->type = u16Vec_new((int) (INIT_FRAC_POSTSOLVE * (n_rows + n_cols))); + if (!info->starts || !info->indices || !info->vals || !info->type) + { + PS_FREE(info->starts); + PS_FREE(info->indices); + PS_FREE(info->vals); + PS_FREE(info->type); + PS_FREE(info); + return NULL; + } + + iVec_append(info->starts, 0); + return info; +} + +void postsolve_info_free(PostsolveInfo *info) +{ + if (!info) + { + return; + } + + iVec_free(info->starts); + iVec_free(info->indices); + dVec_free(info->vals); + u16Vec_free(info->type); + PS_FREE(info); +} + +static inline void copy_reduced_sol_to_orginal(Solution *sol, const double *x, + const double *y, const double *z, + const int *col_map, + const int *row_map) +{ + int dim_x = sol->dim_x; + for (int i = 0; i < dim_x; ++i) + { + if (col_map[i] == -1) + { + sol->x[i] = COL_NOT_RETRIEVED; + sol->z[i] = COL_NOT_RETRIEVED; + continue; + } + + sol->x[i] = x[col_map[i]]; + sol->z[i] = z[col_map[i]]; + } + + int dim_y = sol->dim_y; + for (int i = 0; i < dim_y; ++i) + { + if (row_map[i] == -1) + { + sol->y[i] = ROW_NOT_RETRIEVED; + continue; + } + + sol->y[i] = y[row_map[i]]; + } +} + +// fixes xk and recovers zk = ck - ak^T y +static void retrieve_fix_col(Solution *sol, int col, double val, double ck, + const double *ak_vals, const int *ak_rows, int len) +{ + assert(sol->x[col] == COL_NOT_RETRIEVED); + assert(sol->z[col] == COL_NOT_RETRIEVED); + + sol->x[col] = val; + sol->z[col] = ck; + for (int i = 0; i < len; ++i) + { + assert(sol->y[ak_rows[i]] != ROW_NOT_RETRIEVED); + sol->z[col] -= ak_vals[i] * sol->y[ak_rows[i]]; + } +} + +// retrieves xk and zk +static void retrieve_sub_col(Solution *sol, int k, const int *cols, + const double *vals, int len, double rhs, int i, + double ck) +{ + assert(sol->y[i] != ROW_NOT_RETRIEVED); + assert(sol->x[k] == COL_NOT_RETRIEVED); + assert(sol->z[k] == COL_NOT_RETRIEVED); + + sol->x[k] = rhs; + double aik = 0.0; + for (int ii = 0; ii < len; ++ii) + { + if (cols[ii] == k) + { + aik = vals[ii]; + continue; + } + + assert(sol->x[cols[ii]] != COL_NOT_RETRIEVED); + sol->x[k] -= vals[ii] * sol->x[cols[ii]]; + } + + sol->z[k] = ck - aik * sol->y[i]; + sol->x[k] /= aik; +} + +static void retrieve_fix_col_inf(Solution *sol, const int *indices, + const double *vals) +{ + int i, j, counter, row_len; + const int *cols; + double coeff = 0; + double val, side; + const double *coeffs; + int n_rows = (int) vals[0]; + double extreme_val = vals[1]; + bool fix_to_pos_inf = (indices[0] > 0); + int col = indices[1]; + assert(sol->x[col] == COL_NOT_RETRIEVED); + assert(sol->z[col] == COL_NOT_RETRIEVED); + + counter = 2; + for (i = 0; i < n_rows; ++i) + { + side = vals[counter]; + coeffs = vals + counter + 1; + row_len = indices[counter]; + cols = indices + counter + 1; + counter += row_len + 1; + + for (j = 0; j < row_len; ++j) + { + if (cols[j] == col) + { + coeff = coeffs[j]; + continue; + } + + // If two columns are fixed to pos inf in the same row, we + // pretend one of them is zero while we compute the other one + if (sol->x[cols[j]] == COL_NOT_RETRIEVED) + { + continue; + } + + assert(sol->x[cols[j]] != COL_NOT_RETRIEVED); + side -= coeffs[j] * sol->x[cols[j]]; + } + + val = side / coeff; + if (fix_to_pos_inf) + { + extreme_val = MAX(extreme_val, val); + } + else + { + extreme_val = MIN(extreme_val, val); + } + } + + assert(!IS_ABS_INF(extreme_val)); + sol->x[col] = extreme_val; + sol->z[col] = 0.0; +} + +static void retrieve_parallel_col(Solution *sol, const int *indices, + const double *vals) +{ + int j = indices[0]; + int k = indices[1]; + ColTag cTag_j = (ColTag) indices[2]; + ColTag cTag_k = (ColTag) indices[3]; + assert(indices[4] == DUMMY_VALUE); + double lb_j = vals[0]; + double ub_j = vals[1]; + double lb_k = vals[2]; + double ub_k = vals[3]; + double ratio = vals[4]; + assert(sol->x[j] != COL_NOT_RETRIEVED && sol->x[k] == COL_NOT_RETRIEVED); + assert(sol->z[j] != COL_NOT_RETRIEVED && sol->z[k] == COL_NOT_RETRIEVED); + double x_new_sol = sol->x[j]; + double xj_val, xk_val; + + // ---------------------------------------------------------------- + // try to set xj equal to one of its bounds + // ---------------------------------------------------------------- + if (!HAS_TAG(cTag_j, C_TAG_LB_INF)) + { + xj_val = lb_j; + } + else if (!HAS_TAG(cTag_j, C_TAG_UB_INF)) + { + xj_val = ub_j; + } + else + { + xj_val = 0; + } + + // ---------------------------------------------------------------- + // check if the corresponding value on xk is feasible + // ---------------------------------------------------------------- + xk_val = (x_new_sol - xj_val) / ratio; + + if (!HAS_TAG(cTag_k, C_TAG_LB_INF) && lb_k >= xk_val + FEAS_TOL) + { + xk_val = lb_k; + xj_val = x_new_sol - ratio * xk_val; + } + else if (!HAS_TAG(cTag_k, C_TAG_UB_INF) && xk_val >= ub_k + FEAS_TOL) + { + xk_val = ub_k; + xj_val = x_new_sol - ratio * xk_val; + } + + assert(!IS_ABS_INF(xj_val) && !IS_ABS_INF(xk_val)); + sol->x[j] = xj_val; + sol->x[k] = xk_val; + + // dual postsolve + bool is_xj_at_bound = + (!HAS_TAG(cTag_j, C_TAG_LB_INF) && IS_EQUAL_FEAS_TOL(xj_val, lb_j)) || + (!HAS_TAG(cTag_j, C_TAG_UB_INF) && IS_EQUAL_FEAS_TOL(xj_val, ub_j)); + bool is_xk_at_bound = + (!HAS_TAG(cTag_k, C_TAG_LB_INF) && IS_EQUAL_FEAS_TOL(xk_val, lb_k)) || + (!HAS_TAG(cTag_k, C_TAG_UB_INF) && IS_EQUAL_FEAS_TOL(xk_val, ub_k)); + + if (is_xj_at_bound && is_xk_at_bound) + { + sol->z[k] = ratio * sol->z[j]; + } + else + { + sol->z[k] = 0.0; + } + +#ifndef NDEBUG + SET_ZERO_IF_SMALL_DUAL_POSTSOLVE(sol->z[j]); + + // if the multiplier is positive the variable should be at its lower bound + if (sol->z[j] > 0) + { + assert(!HAS_TAG(cTag_j, C_TAG_LB_INF) && IS_EQUAL_FEAS_TOL(sol->x[j], lb_j)); + } + // if the multiplier is negative the variable should be at its upper bound + else if (sol->z[j] < 0) + { + assert(!HAS_TAG(cTag_j, C_TAG_UB_INF) && IS_EQUAL_FEAS_TOL(sol->x[j], ub_j)); + } + + // similar checks for variable k + if (sol->z[k] > 0) + { + assert(!HAS_TAG(cTag_k, C_TAG_LB_INF) && IS_EQUAL_FEAS_TOL(sol->x[k], lb_k)); + } + else if (sol->z[k] < 0) + { + assert(!HAS_TAG(cTag_k, C_TAG_UB_INF) && IS_EQUAL_FEAS_TOL(sol->x[k], ub_k)); + } +#endif +} + +void retrieve_deleted_row(Solution *sol, int row, double val) +{ + assert(sol->y[row] == ROW_NOT_RETRIEVED); + sol->y[row] = val; +} + +// yi = yi - (ajk / aik) yj whenever we make the jth constraint sparser by +// zeroing out xk using equality row i +void retrieve_added_row(Solution *sol, const int *rows, const double *vals) +{ + int i = rows[0]; + int j = rows[1]; + assert(sol->y[i] != ROW_NOT_RETRIEVED); + assert(sol->y[j] != ROW_NOT_RETRIEVED); + assert(vals[1] == DUMMY_VALUE); + // it should be a plus sign because the minus sign is included in + // save_retrieval_added_row + sol->y[i] += sol->y[j] * vals[0]; +} + +void retrieve_added_rows(Solution *sol, int i, const int *rows, const double *vals, + int len, double aik) +{ + assert(sol->y[i] != ROW_NOT_RETRIEVED); + + for (int j = 0; j < len; ++j) + { + if (rows[j] != i) + { + assert(sol->y[rows[j]] != ROW_NOT_RETRIEVED); + sol->y[i] -= (vals[j] / aik) * sol->y[rows[j]]; + } + } +} + +void retrieve_bound_change(Solution *sol, int i, int j, const int *cols, + const double *vals, int len, double implied_bound, + double original_other_bound, + int is_original_other_bound_lower_bound) +{ + int k, ii; + double aij = 1.0; + assert(sol->x[j] != COL_NOT_RETRIEVED); + assert(sol->y[i] != ROW_NOT_RETRIEVED); + assert(sol->z[j] != COL_NOT_RETRIEVED); + + // if the variable is equal to one of its original bounds and + // has the right sign on its multiplier we do nothing + if (IS_EQUAL_FEAS_TOL(sol->x[j], original_other_bound)) + { + if ((is_original_other_bound_lower_bound && sol->z[j] >= 0) || + (!is_original_other_bound_lower_bound && sol->z[j] <= 0)) + { + return; + } + } + + // if the implied bound is not active we do nothing + if (!IS_EQUAL_FEAS_TOL(sol->x[j], implied_bound)) + { + // sol->y[i] does not always have to be zero since we possibly got the + // bound from a doubleton equality row assert(sol->y[i] == 0.0); + return; + } + + // find aij + for (ii = 0; ii < len; ++ii) + { + if (cols[ii] == j) + { + aij = vals[ii]; + break; + } + } + assert(ii != len && aij != 0.0); + + // update yi for row i that was used in the bound change + sol->y[i] += sol->z[j] / aij; + // + // update zk for all variables k appearing in row i + for (ii = 0; ii < len; ++ii) + { + k = cols[ii]; + if (k == j) + { + continue; + } + + // for now we cheat for dual postsolve of primal propagation fixing variables + if (sol->z[k] == COL_NOT_RETRIEVED) + { + continue; + } + + assert(sol->z[k] != COL_NOT_RETRIEVED); + sol->z[k] -= (vals[ii] / aij) * sol->z[j]; + } + + sol->z[j] = 0.0; +} + +void retrieve_lhs_change(Solution *sol, int i, const double *vals, const int *cols, + int len, double new_side, int j, double ratio) +{ + assert(sol->y[i] != ROW_NOT_RETRIEVED); + assert(sol->y[j] == ROW_NOT_RETRIEVED || sol->y[j] == 0.0); + + // compute the residual of constraint i + double residual = -new_side; + for (int k = 0; k < len; ++k) + { + assert(sol->x[cols[k]] != COL_NOT_RETRIEVED); + residual += vals[k] * sol->x[cols[k]]; + } + + if (!IS_ZERO_FEAS_TOL(residual)) + { + return; + } + + double cand_val = ratio * sol->y[i]; + if (ratio * cand_val < 0) + { + sol->y[j] = 0.0; + return; + } + + sol->y[j] = cand_val; + sol->y[i] = 0.0; +} + +void retrieve_rhs_change(Solution *sol, int i, const double *vals, const int *cols, + int len, double new_side, int j, double ratio) +{ + assert(sol->y[i] != ROW_NOT_RETRIEVED); + assert(sol->y[j] == ROW_NOT_RETRIEVED || sol->y[j] == 0.0); + + // compute the residual of constraint i + double residual = -new_side; + for (int k = 0; k < len; ++k) + { + assert(sol->x[cols[k]] != COL_NOT_RETRIEVED); + residual += vals[k] * sol->x[cols[k]]; + } + + if (!IS_ZERO_FEAS_TOL(residual)) + { + return; + } + + double cand_val = ratio * sol->y[i]; + if (ratio * cand_val > 0) + { + sol->y[j] = 0.0; + return; + } + + sol->y[j] = cand_val; + sol->y[i] = 0.0; +} + +void retrieve_eq_to_ineq(Solution *sol, int row, double val) +{ + assert(sol->y[row] != ROW_NOT_RETRIEVED); + sol->y[row] += val; +} + +void postsolver_update(PostsolveInfo *info, int n_cols_reduced, int n_rows_reduced, + const int *col_map, const int *row_map) +{ + info->n_cols_reduced = n_cols_reduced; + info->n_rows_reduced = n_rows_reduced; + info->col_map = col_map; + info->row_map = row_map; +} + +void polish_z(Solution *sol, const double *lbs, const double *ubs) +{ + int n_cols = sol->dim_x; + double *x = sol->x; + double *z = sol->z; + for (int k = 0; k < n_cols; ++k) + { + bool is_xk_equal_to_lb = IS_EQUAL_FEAS_TOL(x[k], lbs[k]); + bool is_xk_equal_to_ub = IS_EQUAL_FEAS_TOL(x[k], ubs[k]); + + // if wrong sign we set it to zero + if (is_xk_equal_to_lb && !is_xk_equal_to_ub && z[k] < 0) + { + z[k] = 0.0; + } + else if (!is_xk_equal_to_lb && is_xk_equal_to_ub && z[k] > 0) + { + z[k] = 0.0; + } + } +} + +void postsolver_run(const PostsolveInfo *info, Solution *sol, const double *x, + const double *y, const double *z) +{ + const int *col_map = info->col_map; + const int *row_map = info->row_map; + int n_reductions = info->type->len; + ReductionType *reductions = info->type->data; + const int *indices = info->indices->data; + const double *vals = info->vals->data; + const int *starts = info->starts->data; + assert(n_reductions == info->starts->len - 1); + ReductionType type; + int start, len; + + copy_reduced_sol_to_orginal(sol, x, y, z, col_map, row_map); + + for (int i = n_reductions - 1; i >= 0; --i) + { + type = reductions[i]; + start = starts[i]; + + if (type == FIXED_COL) + { + len = starts[i + 1] - start - 2; + // we might have fixed an empty column so len can be 0 + assert(len >= 0); + retrieve_fix_col(sol, indices[start], vals[start], vals[start + 1], + vals + start + 2, indices + start + 2, len); + } + else if (type == SUB_COL) + { + len = starts[i + 1] - start - 2; + assert(len > 1); + retrieve_sub_col(sol, indices[start], indices + start + 1, + vals + start + 1, len, vals[start], + indices[start + len + 1], vals[start + len + 1]); + } + else if (type == FIXED_COL_INF) + { + retrieve_fix_col_inf(sol, indices + start, vals + start); + } + else if (type == PARALLEL_COL) + { + assert(starts[i + 1] - start == 5); + retrieve_parallel_col(sol, indices + start, vals + start); + } + else if (type == DELETED_ROW) + { + assert(starts[i + 1] - start == 1); + retrieve_deleted_row(sol, indices[start], vals[start]); + } + else if (type == ADDED_ROW) + { + assert(starts[i + 1] - start == 2); + retrieve_added_row(sol, indices + start, vals + start); + } + else if (type == ADDED_ROWS) + { + len = starts[i + 1] - start - 1; + assert(len >= 1); + retrieve_added_rows(sol, indices[start], indices + start + 1, + vals + start + 1, len, vals[start]); + } + else if (type == BOUND_CHANGE_THE_ROW) + { + // get the row that was used to derive the bound changes + len = starts[i + 1] - start - 1; + assert(len > 0); + int num_of_bound_changes = (int) vals[start]; + int row = indices[start]; + const int *row_cols = indices + start + 1; + const double *row_vals = vals + start + 1; + int bound_changes_processed = 0; + int j = i - 1; + + while (bound_changes_processed < num_of_bound_changes) + { + type = reductions[j]; + start = starts[j]; + assert(type == BOUND_CHANGE_NO_ROW || type == FIXED_COL); + + if (type == FIXED_COL) + { + retrieve_fix_col(sol, indices[start], vals[start], + vals[start + 1], vals + start + 2, + indices + start + 2, starts[j + 1] - start - 2); + assert(reductions[j - 1] == BOUND_CHANGE_NO_ROW); + } + else + { + bound_changes_processed += 1; + retrieve_bound_change(sol, row, indices[start], row_cols, + row_vals, len, vals[start], + vals[start + 1], indices[start + 1]); + } + + j -= 1; + } + + i = j + 1; + assert(i >= 0); + assert(i == 0 || reductions[i - 1] != BOUND_CHANGE_NO_ROW); + } + + else if (type == LHS_CHANGE) + { + len = starts[i + 1] - start - 2; + assert(len > 1); + retrieve_lhs_change(sol, indices[start], vals + start + 2, + indices + start + 2, len, vals[start], + indices[start + 1], vals[start + 1]); + } + else if (type == RHS_CHANGE) + { + len = starts[i + 1] - start - 2; + assert(len > 1); + retrieve_rhs_change(sol, indices[start], vals + start + 2, + indices + start + 2, len, vals[start], + indices[start + 1], vals[start + 1]); + } + else if (type == EQ_TO_INEQ) + { + assert(starts[i + 1] - start == 1); + retrieve_eq_to_ineq(sol, indices[start], vals[start]); + } + else + { + assert(type != BOUND_CHANGE_NO_ROW); + assert(false); + } + } + + // polish_z(sol, lbs, ubs); + +#ifndef NDEBUG + for (int i = 0; i < sol->dim_x; ++i) + { + if (sol->x[i] == COL_NOT_RETRIEVED || sol->z[i] == COL_NOT_RETRIEVED) + { + printf("col %d not fully retrieved \n", i); + } + + assert(sol->x[i] != COL_NOT_RETRIEVED); + assert(sol->z[i] != COL_NOT_RETRIEVED); + } + + for (int i = 0; i < sol->dim_y; ++i) + { + assert(sol->y[i] != ROW_NOT_RETRIEVED); + } +#endif +} + +// here we should probably store information so zk = ck - ak^T y +// so must save rows and vals +void save_retrieval_fixed_col(PostsolveInfo *info, int col, double val, double ck, + const double *vals, const int *rows, int len) +{ + u16Vec_append(info->type, FIXED_COL); + iVec_append(info->indices, col); + iVec_append(info->indices, DUMMY_VALUE); + iVec_append_array(info->indices, rows, len); + dVec_append(info->vals, val); + dVec_append(info->vals, ck); + dVec_append_array(info->vals, vals, len); + iVec_append(info->starts, info->indices->len); + assert(info->starts->len == info->type->len + 1); + assert(info->vals->len == info->indices->len); +} + +void save_retrieval_fixed_col_inf(PostsolveInfo *info, int col, int pos_inf, + const Constraints *constraints, double bound) +{ + assert(pos_inf == 1 || pos_inf == -1); + int i, row; + const Matrix *AT = constraints->AT; + const Matrix *A = constraints->A; + int *row_sizes = constraints->state->row_sizes; + double *lhs = constraints->lhs; + double *rhs = constraints->rhs; + RowTag *row_tags = constraints->row_tags; + double side; + + int *rows = AT->i + AT->p[col].start; + int n_rows = AT->p[col].end - constraints->AT->p[col].start; + + u16Vec_append(info->type, FIXED_COL_INF); + iVec_append(info->indices, pos_inf); + iVec_append(info->indices, col); + dVec_append(info->vals, (double) n_rows); + dVec_append(info->vals, bound); + + for (i = 0; i < n_rows; ++i) + { + row = rows[i]; + side = (HAS_TAG(row_tags[row], R_TAG_LHS_INF)) ? rhs[row] : lhs[row]; + dVec_append(info->vals, side); + dVec_append_array(info->vals, A->x + A->p[row].start, row_sizes[row]); + iVec_append(info->indices, row_sizes[row]); + iVec_append_array(info->indices, A->i + A->p[row].start, row_sizes[row]); + + assert(!(HAS_TAG(row_tags[row], R_TAG_LHS_INF) && + HAS_TAG(row_tags[row], R_TAG_RHS_INF))); + assert(HAS_TAG(row_tags[row], (R_TAG_LHS_INF | R_TAG_RHS_INF))); + } + + iVec_append(info->starts, info->indices->len); + assert(info->starts->len == info->type->len + 1); + assert(info->vals->len == info->indices->len); +} + +void save_retrieval_sub_col(PostsolveInfo *info, int col, int *cols, double *coeffs, + int len, double rhs, int i, double ck) +{ + u16Vec_append(info->type, SUB_COL); + iVec_append(info->indices, col); + iVec_append_array(info->indices, cols, len); + iVec_append(info->indices, i); + dVec_append(info->vals, rhs); + dVec_append_array(info->vals, coeffs, len); + dVec_append(info->vals, ck); + iVec_append(info->starts, info->indices->len); + assert(info->starts->len == info->type->len + 1); + assert(info->indices->len == info->vals->len); +} + +void save_retrieval_parallel_col(PostsolveInfo *info, double ub_j, double lb_j, + double lb_k, double ub_k, double ratio, int j, + int k, ColTag cTag_j, ColTag cTag_k) +{ + u16Vec_append(info->type, PARALLEL_COL); + iVec_append(info->indices, j); + iVec_append(info->indices, k); + iVec_append(info->indices, (int) (cTag_j)); + iVec_append(info->indices, (int) (cTag_k)); + iVec_append(info->indices, DUMMY_VALUE); + dVec_append(info->vals, lb_j); + dVec_append(info->vals, ub_j); + dVec_append(info->vals, lb_k); + dVec_append(info->vals, ub_k); + dVec_append(info->vals, ratio); + iVec_append(info->starts, info->indices->len); + assert(info->starts->len == info->type->len + 1); + assert(info->vals->len == info->indices->len); +} + +void save_retrieval_deleted_row(PostsolveInfo *info, int row, double val) +{ + u16Vec_append(info->type, DELETED_ROW); + iVec_append(info->indices, row); + dVec_append(info->vals, val); + iVec_append(info->starts, info->indices->len); + assert(info->starts->len == info->type->len + 1); + assert(info->vals->len == info->indices->len); +} + +void save_retrieval_added_row(PostsolveInfo *info, int i, int j, double ratio) +{ + u16Vec_append(info->type, ADDED_ROW); + iVec_append(info->indices, i); + iVec_append(info->indices, j); + dVec_append(info->vals, ratio); + dVec_append(info->vals, DUMMY_VALUE); + iVec_append(info->starts, info->indices->len); + assert(info->starts->len == info->type->len + 1); + assert(info->vals->len == info->indices->len); +} + +void save_retrieval_added_rows(PostsolveInfo *info, int i, const int *rows, + const double *vals, int len, double aik) +{ + u16Vec_append(info->type, ADDED_ROWS); + iVec_append(info->indices, i); + iVec_append_array(info->indices, rows, len); + dVec_append(info->vals, aik); + dVec_append_array(info->vals, vals, len); + iVec_append(info->starts, info->indices->len); + assert(info->starts->len == info->type->len + 1); + assert(info->vals->len == info->indices->len); +} + +// original bound can be infinite +void save_retrieval_bound_change_no_row(PostsolveInfo *info, int j, + double implied_bound, + double original_other_bound, + int is_original_other_bound_lower_bound) +{ + u16Vec_append(info->type, BOUND_CHANGE_NO_ROW); + iVec_append(info->indices, j); + iVec_append(info->indices, is_original_other_bound_lower_bound); + dVec_append(info->vals, implied_bound); + dVec_append(info->vals, original_other_bound); + iVec_append(info->starts, info->indices->len); + assert(info->starts->len == info->type->len + 1); + assert(info->vals->len == info->indices->len); +} + +void save_retrieval_bound_change_the_row(PostsolveInfo *info, int i, const int *cols, + const double *vals, int len, + int num_of_bound_changes) +{ + u16Vec_append(info->type, BOUND_CHANGE_THE_ROW); + iVec_append(info->indices, i); + iVec_append_array(info->indices, cols, len); + dVec_append(info->vals, (double) num_of_bound_changes); + dVec_append_array(info->vals, vals, len); + iVec_append(info->starts, info->indices->len); + assert(info->starts->len == info->type->len + 1); + assert(info->vals->len == info->indices->len); +} + +void save_retrieval_rhs_or_lhs_change(PostsolveInfo *info, int i, const double *vals, + const int *cols, int len, double new_side, + int j, double ratio, bool is_lhs_change) +{ + if (is_lhs_change) + { + u16Vec_append(info->type, LHS_CHANGE); + } + else + { + u16Vec_append(info->type, RHS_CHANGE); + } + + iVec_append(info->indices, i); + iVec_append(info->indices, j); + iVec_append_array(info->indices, cols, len); + dVec_append(info->vals, new_side); + dVec_append(info->vals, ratio); + dVec_append_array(info->vals, vals, len); + iVec_append(info->starts, info->indices->len); + assert(info->starts->len == info->type->len + 1); + assert(info->vals->len == info->indices->len); +} + +void save_retrieval_eq_to_ineq(PostsolveInfo *info, int row, double val) +{ + u16Vec_append(info->type, EQ_TO_INEQ); + iVec_append(info->indices, row); + dVec_append(info->vals, val); + iVec_append(info->starts, info->indices->len); + assert(info->starts->len == info->type->len + 1); + assert(info->vals->len == info->indices->len); +} \ No newline at end of file diff --git a/src/core/Presolver.c b/src/core/Presolver.c new file mode 100644 index 00000000..68e57e35 --- /dev/null +++ b/src/core/Presolver.c @@ -0,0 +1,668 @@ +/* + * Copyright 2025 Daniel Cederberg + * + * This file is part of the PSLP project (LP Presolver). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "API.h" +#include "Activity.h" +#include "Constraints.h" +#include "CoreTransformations.h" +#include "DTonsEq.h" +#include "Debugger.h" +#include "Locks.h" +#include "Matrix.h" +#include "Memory_wrapper.h" +#include "Numerics.h" +#include "Parallel_cols.h" +#include "Parallel_rows.h" +#include "Postsolver.h" +#include "PresolveStats.h" +#include "Primal_propagation.h" +#include "Problem.h" +#include "RowColViews.h" +#include "SimpleReductions.h" +#include "Simple_dual_fix.h" +#include "State.h" +#include "StonCols.h" +#include "Tags.h" +#include "Timer.h" +#include "Workspace.h" +#include "dVec.h" +#include "glbopts.h" +#include "iVec.h" +#include +#include + +#define NNZ_REMOVED_FAST 0.95 +#define NNZ_REMOVED_CYCLE 0.95 + +typedef uint8_t Complexity; + +enum Complexity +{ + FAST = 0, + MEDIUM = 1 << 0 +}; + +PresolveStats *init_stats(int n_rows, int n_cols, int nnz) +{ + PresolveStats *stats = (PresolveStats *) ps_calloc(1, sizeof(PresolveStats)); + stats->n_rows_original = n_rows; + stats->n_cols_original = n_cols; + stats->nnz_original = nnz; + return stats; +} + +Settings *default_settings() +{ + Settings *stgs = (Settings *) ps_malloc(1, sizeof(Settings)); + RETURN_PTR_IF_NULL(stgs, NULL); + stgs->ston_cols = true; + stgs->dton_eq = true; + stgs->parallel_rows = true; + stgs->parallel_cols = true; + stgs->primal_propagation = true; + stgs->dual_fix = true; + stgs->clean_small_coeff = true; + stgs->finite_bound_tightening = true; + stgs->relax_bounds = true; + stgs->max_shift = 10; + stgs->max_time = 60.0; + stgs->verbose = true; + return stgs; +} + +void set_settings_true(Settings *stgs) +{ + stgs->ston_cols = true; + stgs->dton_eq = true; + stgs->parallel_rows = true; + stgs->parallel_cols = true; + stgs->primal_propagation = true; + stgs->dual_fix = true; + stgs->clean_small_coeff = true; + stgs->finite_bound_tightening = true; + stgs->relax_bounds = true; + stgs->verbose = true; +} + +void set_settings_false(Settings *stgs) +{ + stgs->ston_cols = false; + stgs->dton_eq = false; + stgs->parallel_rows = false; + stgs->parallel_cols = false; + stgs->primal_propagation = false; + stgs->dual_fix = false; + stgs->clean_small_coeff = false; + stgs->finite_bound_tightening = false; + stgs->relax_bounds = false; + stgs->verbose = false; +} + +typedef struct clean_up_scope +{ + Matrix *A, *AT; + double *lhs_copy, *rhs_copy, *c_copy; + int *row_sizes, *col_sizes; + RowTag *row_tags; + Lock *locks; + Activity *activities; + State *data; + Constraints *constraints; + Presolver *presolver; + Objective *obj; + ColTag *col_tags; + Bound *bounds; + Work *work; +} clean_up_scope; + +void presolver_clean_up(clean_up_scope scope) +{ + free_matrix(scope.A); + free_matrix(scope.AT); + PS_FREE(scope.lhs_copy); + PS_FREE(scope.rhs_copy); + PS_FREE(scope.c_copy); + PS_FREE(scope.row_sizes); + PS_FREE(scope.col_sizes); + PS_FREE(scope.row_tags); + PS_FREE(scope.col_tags); + PS_FREE(scope.bounds); + free_activities(scope.activities); + free_locks(scope.locks); + free_work(scope.work); + free_state(scope.data); + free_constraints(scope.constraints); + free_objective(scope.obj); + free_presolver(scope.presolver); +} + +Presolver *new_presolver(const double *Ax, const int *Ai, const int *Ap, int m, + int n, int nnz, const double *lhs, const double *rhs, + const double *lbs, const double *ubs, const double *c, + const Settings *stgs, bool CSR) +{ + Timer timer; + clock_gettime(CLOCK_MONOTONIC, &timer.start); + Matrix *A = NULL, *AT = NULL; + double *lhs_copy = NULL, *rhs_copy = NULL, *c_copy = NULL; + int *row_sizes = NULL, *col_sizes = NULL; + RowTag *row_tags = NULL; + Lock *locks = NULL; + Activity *activities = NULL; + State *data = NULL; + Constraints *constraints = NULL; + Presolver *presolver = NULL; + Objective *obj = NULL; + ColTag *col_tags = NULL; + Bound *bounds = NULL; + Work *work = NULL; + + // --------------------------------------------------------------------------- + // Copy data and allocate memory. For an exact specification of the + // workspace, see the workspace class. The problem object owns all the + // memory that is allocated in this block of code (and therefore frees it). + // --------------------------------------------------------------------------- + int n_rows = m; + int n_cols = n; + lhs_copy = (double *) ps_malloc(n_rows, sizeof(double)); + rhs_copy = (double *) ps_malloc(n_rows, sizeof(double)); + c_copy = (double *) ps_malloc(n_cols, sizeof(double)); + col_tags = (ColTag *) ps_calloc(n_cols, sizeof(ColTag)); + bounds = (Bound *) ps_malloc(n_cols, sizeof(Bound)); + work = new_work(n_rows, n_cols); + row_sizes = (int *) ps_malloc(n_rows, sizeof(int)); + col_sizes = (int *) ps_malloc(n_cols, sizeof(int)); + + if (!lhs_copy || !rhs_copy || !c_copy || !col_tags || !bounds || !work || + !row_sizes || !col_sizes) + { + goto cleanup; + } + + memcpy(lhs_copy, lhs, n_rows * sizeof(double)); + memcpy(rhs_copy, rhs, n_rows * sizeof(double)); + memcpy(c_copy, c, n_cols * sizeof(double)); + + // --------------------------------------------------------------------------- + // Build bounds, row tags, A and AT. + // --------------------------------------------------------------------------- + row_tags = new_rowtags(lhs_copy, rhs_copy, n_rows); + if (!row_tags) goto cleanup; + + for (int i = 0; i < n_cols; i++) + { + bounds[i].lb = lbs[i]; + bounds[i].ub = ubs[i]; + + if (IS_NEG_INF(lbs[i])) + { + UPDATE_TAG(col_tags[i], C_TAG_LB_INF); + } + + if (IS_POS_INF(ubs[i])) + { + UPDATE_TAG(col_tags[i], C_TAG_UB_INF); + } + } + + if (CSR) + { + A = matrix_new_no_extra_space(Ax, Ai, Ap, n_rows, n_cols, nnz); + if (!A) goto cleanup; + + if (stgs->clean_small_coeff) + { + clean_small_coeff_A(A, bounds, row_tags, col_tags, rhs_copy, lhs_copy); + } + + AT = transpose(A, work->iwork_n_cols); + if (!AT) goto cleanup; + } + else + { + AT = matrix_new(Ax, Ai, Ap, n_cols, n_rows, nnz); + if (!AT) goto cleanup; + A = transpose(AT, work->iwork_n_rows); + if (!A) goto cleanup; + } + + // --------------------------------------------------------------------------- + // locks, activities, row sizes and column sizes + // --------------------------------------------------------------------------- + locks = new_locks(A, row_tags); + activities = new_activities(A, col_tags, bounds); + if (!locks || !activities) goto cleanup; + count_rows(A, row_sizes); + count_rows(AT, col_sizes); + + // --------------------------------------------------------------------------- + // Initialize internal data and constraints + // --------------------------------------------------------------------------- + data = new_state(row_sizes, col_sizes, locks, n_rows, n_cols, activities, work, + row_tags); + + if (!data) goto cleanup; + constraints = + constraints_new(A, AT, lhs_copy, rhs_copy, bounds, data, row_tags, col_tags); + if (!constraints) goto cleanup; + + // --------------------------------------------------------------------------- + // Allocate the actual presolver + // --------------------------------------------------------------------------- + presolver = (Presolver *) ps_malloc(1, sizeof(Presolver)); + obj = objective_new(c_copy); + if (!presolver || !obj) goto cleanup; + presolver->stgs = stgs; + presolver->prob = new_problem(constraints, obj); + presolver->stats = init_stats(A->m, A->n, nnz); + presolver->stats->nnz_removed_trivial = nnz - A->nnz; // due to clean_small_coeff + clock_gettime(CLOCK_MONOTONIC, &timer.end); + presolver->stats->ps_time_init = GET_ELAPSED_SECONDS(timer); + DEBUG(run_debugger(constraints, false)); + + // --------------------------------------------------------------------------- + // Allocate space for returning the solution + // --------------------------------------------------------------------------- + presolver->sol = (Solution *) ps_malloc(1, sizeof(Solution)); + if (!presolver->sol) goto cleanup; + presolver->sol->x = (double *) ps_malloc(n_cols, sizeof(double)); + presolver->sol->y = (double *) ps_malloc(n_rows, sizeof(double)); + presolver->sol->z = (double *) ps_malloc(n_cols, sizeof(double)); + presolver->sol->dim_x = n_cols; + presolver->sol->dim_y = n_rows; + if (!presolver->sol->x || !presolver->sol->y || !presolver->sol->z) + { + goto cleanup; + } + + return presolver; + +cleanup: +{ + struct clean_up_scope scope = { + A, AT, lhs_copy, rhs_copy, c_copy, row_sizes, + col_sizes, row_tags, locks, activities, data, constraints, + presolver, obj, col_tags, bounds, work}; + presolver_clean_up(scope); +} + return NULL; +} + +/* This function updates the termination criterion for the presolver. + We terminate if one of the following conditions is satisfied: + 1. The problem is infeasible or unbounded. + 2. A cycle reduced the number of non-zeros by less than + (1 - NNZ_REMOVED_CYCLE)%. + 3. A maximum time limit is reached. +*/ +static inline bool update_termination(int nnz_after_cycle, int nnz_before_cycle, + Complexity complexity, PresolveStatus status, + double max_time, Timer outer_timer) +{ + if (HAS_STATUS(status, UNBNDORINFEAS)) + { + return true; + } + + if (complexity == MEDIUM && + nnz_after_cycle >= NNZ_REMOVED_CYCLE * nnz_before_cycle) + { + return true; + } + + clock_gettime(CLOCK_MONOTONIC, &outer_timer.end); + + if (GET_ELAPSED_SECONDS(outer_timer) >= max_time) + { + return true; + } + + return false; +} + +/* This function updates the complexity class according to + the following rules: + * If the current complexity is fast, the next complexity is + fast if sufficiently many non-zeros were removed, otherwise + it is medium. + * If the current complexity is medium, the next complexity is + fast. +*/ +static inline Complexity update_complexity(Complexity curr_complexity, + int nnz_before_phase, int nnz_after_phase) +{ + if (curr_complexity == FAST) + { + if (nnz_after_phase < NNZ_REMOVED_FAST * nnz_before_phase) + { + return FAST; + } + + return MEDIUM; + } + else if (curr_complexity == MEDIUM) + { + return FAST; + } + else + { + assert(false); + } + return FAST; // to suppress compiler warning +} + +/* This function exhaustively removes singleton rows from the problem. It + also eliminates empty rows and empty columns. If the problem is feasible + and bounded, it returns UNCHANGED (even if the problem was reduced). +*/ +static inline PresolveStatus run_trivial_explorers(Problem *prob, + const Settings *stgs) +{ + PresolveStatus status = UNCHANGED; + + remove_variables_with_close_bounds(prob); + + if (stgs->dual_fix) + { + // after simple dual fix there can be new empty rows and singleton rows, + // and empty columns (if rows are marked as inactive due to a variable + // being set to inf) + status |= remove_empty_cols(prob); + status |= simple_dual_fix(prob); + RETURN_IF_UNBNDORINFEAS(status); + } + + do + { + status = remove_ston_rows(prob); + RETURN_IF_INFEASIBLE(status); + } while (status == REDUCED); + + status = remove_empty_rows(prob->constraints); + RETURN_IF_INFEASIBLE(status); + + status = remove_empty_cols(prob); + RETURN_IF_UNBNDORINFEAS(status); + + assert(prob->constraints->state->ston_rows->len == 0); + assert(prob->constraints->state->empty_rows->len == 0); + assert(prob->constraints->state->empty_cols->len == 0); + + return UNCHANGED; +} + +/* This function applies the fastest reductions, which are doubleton equality + rows, singleton columns, and simple dual fix. It returns UNCHANGED even if + the problem was reduced (provided that the problem does not seem infeasible or + bounded). */ +static inline PresolveStatus run_fast_explorers(Problem *prob, const Settings *stgs) +{ + assert(prob->constraints->state->ston_rows->len == 0); + assert(prob->constraints->state->empty_rows->len == 0); + assert(prob->constraints->state->empty_cols->len == 0); + PresolveStatus status = UNCHANGED; + + if (stgs->ston_cols) + { + status |= remove_ston_cols(prob); + + // after removing singleton columns, there can be new empty columns + // and singleton rows, but no empty rows + assert(prob->constraints->state->empty_rows->len == 0); + status |= run_trivial_explorers(prob, stgs); + } + + if (stgs->dton_eq) + { + status |= remove_dton_eq_rows(prob, stgs->max_shift); + + // after removing doubleton equality rows, there can be new empty rows, + // new singleton rows, and new empty columns + status |= run_trivial_explorers(prob, stgs); + } + + return status; +} + +/* This function applies the medium complexity presolvers, which are domain + propagation and parallel rows. It returns UNCHANGED even if the problem + was reduced (provided that the problem is infeasible and bounded). */ +static inline PresolveStatus +run_medium_explorers(Problem *prob, const Settings *stgs, PresolveStats *stats) +{ + assert(prob->constraints->state->ston_rows->len == 0); + assert(prob->constraints->state->empty_rows->len == 0); + assert(prob->constraints->state->empty_cols->len == 0); + DEBUG(verify_no_duplicates_sort(prob->constraints->state->updated_activities)); + int nnz_before; + int *nnz = &prob->constraints->A->nnz; + PresolveStatus status = UNCHANGED; + Timer timer; + + if (stgs->primal_propagation) + { + // stats + clock_gettime(CLOCK_MONOTONIC, &timer.start); + nnz_before = *nnz; + + // the actual reduction + status |= check_activities(prob); + status |= propagate_primal(prob, stgs->finite_bound_tightening); + + // after dom prop propagation there can be new empty and ston rows rows + status |= run_trivial_explorers(prob, stgs); + + // stats + stats->nnz_removed_primal_propagation += nnz_before - *nnz; + clock_gettime(CLOCK_MONOTONIC, &timer.end); + stats->ps_time_primal_propagation += GET_ELAPSED_SECONDS(timer); + } + + if (stgs->parallel_rows) + { + // stats + nnz_before = *nnz; + clock_gettime(CLOCK_MONOTONIC, &timer.start); + + // the actual reduction (after removing parallel rows there can + // be no new empty or ston rows so no need to run trivial presolvers) + status |= remove_parallel_rows(prob->constraints); + assert(prob->constraints->state->empty_rows->len == 0); + assert(prob->constraints->state->ston_rows->len == 0); + + // stats + stats->nnz_removed_parallel_rows += nnz_before - *nnz; + clock_gettime(CLOCK_MONOTONIC, &timer.end); + stats->ps_time_parallel_rows += GET_ELAPSED_SECONDS(timer); + } + + if (stgs->parallel_cols) + { + // stats + nnz_before = *nnz; + clock_gettime(CLOCK_MONOTONIC, &timer.start); + + // the actual reduction (after removing parallel columns there can + // be no new empty rows or empty cols but might be new stonrows, probably + // although that's rare but it does happen + status |= remove_parallel_cols(prob); + assert(prob->constraints->state->empty_rows->len == 0); + assert(prob->constraints->state->empty_cols->len == 0); + status |= run_trivial_explorers(prob, stgs); + assert(prob->constraints->state->ston_rows->len == 0); + + // stats + stats->nnz_removed_parallel_cols += nnz_before - *nnz; + clock_gettime(CLOCK_MONOTONIC, &timer.end); + stats->ps_time_parallel_cols += GET_ELAPSED_SECONDS(timer); + } + + return status; +} + +static inline void print_start_message(const PresolveStats *stats) +{ + printf("\n\t PSLP v%s - LP presolver \n\t(c) Daniel " + "Cederberg, Stanford University, 2025\n", + CVX_PRESOLVE_VERSION); + printf("Original problem: %d rows, %d columns, %d nnz\n", + stats->n_rows_original, stats->n_cols_original, stats->nnz_original); +} + +static inline void print_end_message(const Matrix *A, const PresolveStats *stats) +{ + printf("Presolved problem: %d rows, %d columns, %d nnz\n", A->m, A->n, A->nnz); + + printf("Presolver init & run time : %.3f seconds, %.3f \n", stats->ps_time_init, + stats->presolve_total_time); +} + +PresolveStatus presolver_run(Presolver *presolver) +{ + Timer inner_timer, outer_timer; + int nnz_before_cycle, nnz_after_cycle; + int nnz_before_phase, nnz_after_phase; + int nnz_before_reduction; + Problem *prob = presolver->prob; + PresolveStats *stats = presolver->stats; + Matrix *A = presolver->prob->constraints->A; + const Settings *stgs = presolver->stgs; + Complexity curr_complexity = FAST; + bool terminate = false; + PresolveStatus status = UNCHANGED; + clock_gettime(CLOCK_MONOTONIC, &outer_timer.start); + + if (stgs->verbose) + { + print_start_message(stats); + } + + DEBUG(run_debugger(prob->constraints, false)); + + // ------------------------------------------------------------------------ + // Main loop for the presolver. The logic is organized into phases and + // cycles. A phase is a sequence of presolvers belonging to a certain + // complexity class. The cycle resets after the medium presolvers. + // ------------------------------------------------------------------------ + nnz_before_cycle = A->nnz; + while (!terminate) + { + // before each phase we run the trivial presolvers + nnz_before_reduction = A->nnz; + status = run_trivial_explorers(prob, stgs); + stats->nnz_removed_trivial += nnz_before_reduction - A->nnz; + RETURN_IF_NOT_UNCHANGED(status); // TODO: this name is misleading! + DEBUG(run_debugger(prob->constraints, false)); + nnz_before_phase = A->nnz; + + // apply presolvers belonging to a certain complexity class + if (curr_complexity == FAST) + { + nnz_before_reduction = A->nnz; + RUN_AND_TIME(run_fast_explorers, inner_timer, stats->ps_time_fast, + status, prob, stgs); + stats->nnz_removed_fast += nnz_before_reduction - A->nnz; + } + else if (curr_complexity == MEDIUM) + { + RUN_AND_TIME(run_medium_explorers, inner_timer, stats->ps_time_medium, + status, prob, stgs, stats); + nnz_after_cycle = A->nnz; + } + + nnz_after_phase = A->nnz; + + terminate = + update_termination(nnz_after_cycle, nnz_before_cycle, curr_complexity, + status, stgs->max_time, outer_timer); + + if (curr_complexity == MEDIUM) + { + // a new cycle starts after the medium presolvers + nnz_before_cycle = nnz_after_cycle; + } + + curr_complexity = + update_complexity(curr_complexity, nnz_before_phase, nnz_after_phase); + } + + if (stgs->relax_bounds) + { + remove_redundant_bounds(prob->constraints); + } + + problem_clean(prob, true); + DEBUG(run_debugger(prob->constraints, true)); + + clock_gettime(CLOCK_MONOTONIC, &outer_timer.end); + stats->n_rows_reduced = A->m; + stats->n_cols_reduced = A->n; + stats->nnz_reduced = A->nnz; + stats->presolve_total_time = GET_ELAPSED_SECONDS(outer_timer); + DEBUG(run_debugger_stats_consistency_check(stats)); + + if (stgs->verbose) + { + print_end_message(A, stats); + } + + return status; +} + +void postsolve(Presolver *presolver, const double *x, const double *y, + const double *z, double obj) +{ + Timer timer; + Solution *sol = presolver->sol; + PresolveStats *stats = presolver->stats; + State *data = presolver->prob->constraints->state; + PostsolveInfo *postsolve_info = data->postsolve_info; + clock_gettime(CLOCK_MONOTONIC, &timer.start); + postsolver_update(postsolve_info, stats->n_cols_reduced, stats->n_rows_reduced, + data->work->mappings->cols, data->work->mappings->rows); + postsolver_run(postsolve_info, sol, x, y, z); + sol->obj = obj + presolver->prob->obj->offset; + clock_gettime(CLOCK_MONOTONIC, &timer.end); + stats->ps_time_post_solve = GET_ELAPSED_SECONDS(timer); + + if (presolver->stgs->verbose) + { + printf("Postsolve time: %.4f seconds\n", stats->ps_time_post_solve); + } +} + +void free_presolver(Presolver *presolver) +{ + if (presolver == NULL) + { + return; + } + + free_problem(presolver->prob); + PS_FREE(presolver->stats); + + if (presolver->sol) + { + PS_FREE(presolver->sol->x); + PS_FREE(presolver->sol->y); + PS_FREE(presolver->sol->z); + PS_FREE(presolver->sol); + } + + PS_FREE(presolver); +} \ No newline at end of file diff --git a/src/core/Problem.c b/src/core/Problem.c new file mode 100644 index 00000000..05c054b7 --- /dev/null +++ b/src/core/Problem.c @@ -0,0 +1,110 @@ +/* + * Copyright 2025 Daniel Cederberg + * + * This file is part of the PSLP project (LP Presolver). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Problem.h" +#include "Constraints.h" +#include "Matrix.h" +#include "Memory_wrapper.h" +#include "State.h" +#include "Workspace.h" +#include "utils.h" + +Problem *new_problem(Constraints *constraints, Objective *obj) +{ + Problem *problem = (Problem *) ps_malloc(1, sizeof(Problem)); + RETURN_PTR_IF_NULL(problem, NULL); + + problem->constraints = constraints; + problem->obj = obj; + + return problem; +} + +void free_problem(Problem *problem) +{ + RETURN_IF_NULL(problem); + RETURN_IF_NULL(problem->constraints); + State *data = problem->constraints->state; + + if (data) + { + free_work(data->work); + PS_FREE(data->col_locks); + PS_FREE(data->activities); + PS_FREE(data->row_sizes); + PS_FREE(data->col_sizes); + free_state(data); + } + + free_constraints(problem->constraints); + free_objective(problem->obj); + PS_FREE(problem); +} + +Objective *objective_new(double *c) +{ + Objective *obj = (Objective *) ps_malloc(1, sizeof(Objective)); + RETURN_PTR_IF_NULL(obj, NULL); + obj->c = c; + obj->offset = 0.0; + + return obj; +} + +void free_objective(Objective *obj) +{ + RETURN_IF_NULL(obj); + PS_FREE(obj->c); + PS_FREE(obj); +} + +void objective_shrink(double *c, int *map, int len) +{ + dPtr_shrink(c, map, len); +} +void fix_var_in_obj(Objective *obj, int col, double value) +{ + obj->offset += obj->c[col] * value; +} + +void problem_clean(Problem *prob, bool remove_all) +{ + Mapping *maps = prob->constraints->state->work->mappings; + int n_cols_old = prob->constraints->n; + int n_rows_old = prob->constraints->m; + constraints_clean(prob->constraints, maps, remove_all); + clean_state(prob->constraints->state, maps, n_rows_old, n_cols_old); + objective_shrink(prob->obj->c, maps->cols, n_cols_old); +} + +void sub_var_in_obj(Objective *obj, const double *vals, const int *cols, int len, + int k, double aik, double rhs) +{ + if (obj->c[k] == 0.0) + { + return; + } + + double ratio = obj->c[k] / aik; + for (int i = 0; i < len; ++i) + { + obj->c[cols[i]] -= ratio * vals[i]; + } + + obj->offset += rhs * ratio; +} \ No newline at end of file diff --git a/src/core/State.c b/src/core/State.c new file mode 100644 index 00000000..ce555ab6 --- /dev/null +++ b/src/core/State.c @@ -0,0 +1,208 @@ +/* + * Copyright 2025 Daniel Cederberg + * + * This file is part of the PSLP project (LP Presolver). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "State.h" +#include "API.h" +#include "Activity.h" +#include "Locks.h" +#include "Numerics.h" + +#include "Workspace.h" +#include "utils.h" + +#define MIN_INIT_STON_ROWS 100 +#define INIT_STON_ROWS_FRACTION 0.01 +#define MIN_INIT_STON_COLS 100 +#define INIT_STON_COLS_FRACTION 0.01 +#define MIN_INIT_EMPTY_ROWS 50 +#define INIT_EMPTY_ROWS_FRACTION 0.001 +#define MIN_INIT_EMPTY_COLS 50 +#define INIT_EMPTY_COLS_FRACTION 0.001 +#define MIN_INIT_FIXED_COLS_TO_DELETE 1000 +#define INIT_FIXED_COLS_TO_DELETE_FRACTION 0.01 +#define MIN_INIT_SUB_COLS_TO_DELETE 1000 +#define INIT_SUB_COLS_TO_DELETE_FRACTION 0.01 +#define MIN_INIT_ROWS_TO_DELETE 1000 +#define INIT_ROWS_TO_DELETE_FRACTION 0.01 +#define MIN_INIT_DTON_ROWS 100 +#define INIT_DTON_ROWS_FRACTION 0.01 +#define MIN_INIT_UPDATED_ACTIVITIES 1000 +#define INIT_UPDATED_ACTIVITIES_FRACTION 0.01 + +State *new_state(int *row_sizes, int *col_sizes, Lock *col_locks, int n_rows, + int n_cols, Activity *activities, Work *work, + const RowTag *row_tags) +{ + State *data = (State *) ps_malloc(1, sizeof(State)); + RETURN_PTR_IF_NULL(data, NULL); + data->col_locks = col_locks; + data->row_sizes = row_sizes; + data->col_sizes = col_sizes; + data->activities = activities; + data->work = work; + + // -------------------------------------------------------------------------- + // allocate a bunch of vectors + // -------------------------------------------------------------------------- + data->ston_rows = + iVec_new(MAX(MIN_INIT_STON_ROWS, (int) (n_rows * INIT_STON_ROWS_FRACTION))); + data->ston_cols = + iVec_new(MAX(MIN_INIT_STON_COLS, (int) (n_cols * INIT_STON_COLS_FRACTION))); + data->dton_rows = + iVec_new(MAX(MIN_INIT_DTON_ROWS, (int) (n_rows * INIT_DTON_ROWS_FRACTION))); + data->empty_cols = iVec_new( + MAX(MIN_INIT_EMPTY_COLS, (int) (n_cols * INIT_EMPTY_COLS_FRACTION))); + data->empty_rows = iVec_new( + MAX(MIN_INIT_EMPTY_ROWS, (int) (n_rows * INIT_EMPTY_ROWS_FRACTION))); + data->updated_activities = + iVec_new(MAX(MIN_INIT_UPDATED_ACTIVITIES, + (int) (n_rows * INIT_UPDATED_ACTIVITIES_FRACTION))); + data->fixed_cols_to_delete = + iVec_new(MAX(MIN_INIT_FIXED_COLS_TO_DELETE, + (int) (n_cols * INIT_FIXED_COLS_TO_DELETE_FRACTION))); + data->sub_cols_to_delete = + iVec_new(MAX(MIN_INIT_SUB_COLS_TO_DELETE, + (int) (n_cols * INIT_SUB_COLS_TO_DELETE_FRACTION))); + data->rows_to_delete = iVec_new( + MAX(MIN_INIT_ROWS_TO_DELETE, (int) (n_rows * INIT_ROWS_TO_DELETE_FRACTION))); + data->postsolve_info = postsolve_info_new(n_rows, n_cols); + + if (!data->ston_rows || !data->ston_cols || !data->dton_rows || + !data->empty_cols || !data->empty_rows || !data->fixed_cols_to_delete || + !data->sub_cols_to_delete || !data->rows_to_delete || !data->postsolve_info) + { + free_state(data); + return NULL; + } + + // -------------------------------------------------------------------------- + // record empty, singleton, and doubleton equality rows + // -------------------------------------------------------------------------- + for (int i = 0; i < n_rows; ++i) + { + switch (row_sizes[i]) + { + case 0: + assert(!iVec_contains(data->empty_rows, i)); + iVec_append(data->empty_rows, i); + break; + case 1: + assert(!iVec_contains(data->ston_rows, i)); + iVec_append(data->ston_rows, i); + break; + case 2: + if (HAS_TAG(row_tags[i], R_TAG_EQ)) + { + assert(!iVec_contains(data->ston_rows, i)); + iVec_append(data->dton_rows, i); + } + break; + } + } + + // -------------------------------------------------------------------------- + // record empty and singleton columns + // -------------------------------------------------------------------------- + for (int i = 0; i < n_cols; ++i) + { + switch (col_sizes[i]) + { + case 0: + iVec_append(data->empty_cols, i); + break; + case 1: + iVec_append(data->ston_cols, i); + break; + } + } + + // -------------------------------------------------------------------------- + // record activities to update + // -------------------------------------------------------------------------- + for (int i = 0; i < n_rows; ++i) + { + if (activities[i].n_inf_min == 0 || activities[i].n_inf_max == 0) + { + activities[i].status = ADDED; + iVec_append(data->updated_activities, i); + } + } + + return data; +} + +void free_state(State *data) +{ + if (!data) + { + return; + } + + iVec_free(data->ston_rows); + iVec_free(data->ston_cols); + iVec_free(data->dton_rows); + iVec_free(data->empty_cols); + iVec_free(data->empty_rows); + iVec_free(data->updated_activities); + iVec_free(data->fixed_cols_to_delete); + iVec_free(data->sub_cols_to_delete); + iVec_free(data->rows_to_delete); + postsolve_info_free(data->postsolve_info); + PS_FREE(data); +} + +static void shrink_locks(Lock *ptr, const int *map, int len) +{ + int new_len = 0; + for (int i = 0; i < len; ++i) + { + if (map[i] != -1) + { + new_len++; + ptr[map[i]] = ptr[i]; + } + } +} + +static void shrink_activities(Activity *ptr, const int *map, int len) +{ + int new_len = 0; + for (int i = 0; i < len; ++i) + { + if (map[i] != -1) + { + new_len++; + ptr[map[i]] = ptr[i]; + } + } +} + +void clean_state(State *data, const Mapping *maps, int n_rows, int n_cols) +{ + iPtr_shrink(data->row_sizes, maps->rows, n_rows); + shrink_activities(data->activities, maps->rows, n_rows); + shrink_locks(data->col_locks, maps->cols, n_cols); + iPtr_shrink(data->col_sizes, maps->cols, n_cols); + + shrink_idx_vector(data->ston_rows, maps->rows); + shrink_idx_vector(data->dton_rows, maps->rows); + shrink_idx_vector(data->empty_rows, maps->rows); + shrink_idx_vector(data->updated_activities, maps->rows); + shrink_idx_vector(data->ston_cols, maps->cols); + shrink_idx_vector(data->empty_cols, maps->cols); +} \ No newline at end of file diff --git a/src/core/Tags.c b/src/core/Tags.c new file mode 100644 index 00000000..3d670554 --- /dev/null +++ b/src/core/Tags.c @@ -0,0 +1,48 @@ +/* + * Copyright 2025 Daniel Cederberg + * + * This file is part of the PSLP project (LP Presolver). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Tags.h" +#include "Memory_wrapper.h" +#include "glbopts.h" + +RowTag *new_rowtags(double *lhs, double *rhs, int n_rows) +{ + RowTag *row_tags = (RowTag *) ps_calloc(n_rows, sizeof(RowTag)); + RETURN_PTR_IF_NULL(row_tags, NULL); + + for (int i = 0; i < n_rows; ++i) + { + if (IS_POS_INF(rhs[i])) + { + rhs[i] = INF; + UPDATE_TAG(row_tags[i], R_TAG_RHS_INF); + } + + if (IS_NEG_INF(lhs[i])) + { + lhs[i] = -INF; + UPDATE_TAG(row_tags[i], R_TAG_LHS_INF); + } + else if (lhs[i] == rhs[i]) + { + UPDATE_TAG(row_tags[i], R_TAG_EQ); + } + } + + return row_tags; +} diff --git a/src/core/Workspace.c b/src/core/Workspace.c new file mode 100644 index 00000000..48af68d2 --- /dev/null +++ b/src/core/Workspace.c @@ -0,0 +1,70 @@ +/* + * Copyright 2025 Daniel Cederberg + * + * This file is part of the PSLP project (LP Presolver). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Workspace.h" +#include "Memory_wrapper.h" +#include "Numerics.h" +#include + +#define INT_VEC_INITIALIZATION 25 + +Work *new_work(int n_rows, int n_cols) +{ + Work *work = (Work *) ps_malloc(1, sizeof(Work)); + RETURN_PTR_IF_NULL(work, NULL); + + work->iwork_n_cols = (int *) ps_calloc(n_cols, sizeof(int)); + work->iwork_n_rows = (int *) ps_calloc(n_rows, sizeof(int)); + work->iwork1_max_nrows_ncols = + (int *) ps_calloc(MAX(n_rows, n_cols), sizeof(int)); + work->iwork2_max_nrows_ncols = + (int *) ps_calloc(MAX(n_rows, n_cols), sizeof(int)); + work->int_vec = iVec_new(INT_VEC_INITIALIZATION); + work->mappings = (Mapping *) ps_malloc(1, sizeof(Mapping)); + work->mappings->cols = (int *) ps_malloc(n_cols, sizeof(int)); + work->mappings->rows = (int *) ps_malloc(n_rows, sizeof(int)); + + if (!work->iwork_n_cols || !work->iwork_n_rows || + !work->iwork1_max_nrows_ncols || !work->iwork2_max_nrows_ncols || + !work->int_vec || !work->mappings || !work->mappings->cols || + !work->mappings->rows) + { + free_work(work); + return NULL; + } + + return work; +} + +void free_work(Work *work) +{ + if (work == NULL) + { + return; + } + + PS_FREE(work->iwork_n_cols); + PS_FREE(work->iwork_n_rows); + PS_FREE(work->iwork1_max_nrows_ncols); + PS_FREE(work->iwork2_max_nrows_ncols); + iVec_free(work->int_vec); + PS_FREE(work->mappings->cols); + PS_FREE(work->mappings->rows); + PS_FREE(work->mappings); + PS_FREE(work); +} diff --git a/src/core/utils.c b/src/core/utils.c new file mode 100644 index 00000000..5ac80c22 --- /dev/null +++ b/src/core/utils.c @@ -0,0 +1,100 @@ +/* + * Copyright 2025 Daniel Cederberg + * + * This file is part of the PSLP project (LP Presolver). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "utils.h" +#include "Numerics.h" + +void dPtr_shrink(double *ptr, const int *map, int len) +{ + int new_len = 0; + for (int i = 0; i < len; ++i) + { + if (map[i] != -1) + { + new_len++; + ptr[map[i]] = ptr[i]; + } + } +} + +void iPtr_shrink(int *ptr, const int *map, int len) +{ + int new_len = 0; + for (int i = 0; i < len; ++i) + { + if (map[i] != -1) + { + new_len++; + ptr[map[i]] = ptr[i]; + } + } +} + +void rowTagPtr_shrink(RowTag *ptr, const int *map, int len) +{ + int new_len = 0; + for (int i = 0; i < len; ++i) + { + if (map[i] != -1) + { + new_len++; + ptr[map[i]] = ptr[i]; + } + } +} + +void colTagPtr_shrink(ColTag *ptr, const int *map, int len) +{ + int new_len = 0; + for (int i = 0; i < len; ++i) + { + if (map[i] != -1) + { + new_len++; + ptr[map[i]] = ptr[i]; + } + } +} + +void shrink_idx_vector(iVec *vec, const int *map) +{ + int len = vec->len; + int curr = 0; + + for (int i = 0; i < len; ++i) + { + if (map[vec->data[i]] != -1) + { + vec->data[curr] = map[vec->data[i]]; + curr++; + } + } + + vec->len = curr; +} + +double get_max_abs(const double *vals, int len) +{ + double max_abs = 0.0; + for (int i = 0; i < len; ++i) + { + double abs_val = ABS(vals[i]); + max_abs = MAX(max_abs, abs_val); + } + return max_abs; +} \ No newline at end of file diff --git a/src/explorers/DtonsEq.c b/src/explorers/DtonsEq.c new file mode 100644 index 00000000..7b9a992f --- /dev/null +++ b/src/explorers/DtonsEq.c @@ -0,0 +1,780 @@ +/* + * Copyright 2025 Daniel Cederberg + * + * This file is part of the PSLP project (LP Presolver). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "DTonsEq.h" +#include "Activity.h" +#include "Bounds.h" +#include "Constraints.h" +#include "CoreTransformations.h" +#include "Debugger.h" +#include "Locks.h" +#include "Matrix.h" +#include "Memory_wrapper.h" +#include "Numerics.h" +#include "Postsolver.h" +#include "Problem.h" +#include "RowColViews.h" +#include "SimpleReductions.h" +#include "State.h" +#include "Timer.h" +#include "Workspace.h" +#include + +/* Checks if there is sufficient space in AT for the substitution. + This includes functionality for shifting rows to create sufficient + space. Column j stays, column k is substituted */ +#ifdef NDEBUG +static inline bool sufficient_space_AT(Matrix *AT, int j, int k, int max_shift) +#else +static inline bool sufficient_space_AT(Matrix *AT, int j, int k, int max_shift, + const RowTag *row_tags) +#endif +{ + int len_col_j, len_col_k, fill_in, jj, kk, free_space; + const int *rows_col_j, *rows_col_k; + + rows_col_j = AT->i + AT->p[j].start; + len_col_j = AT->p[j].end - AT->p[j].start; + rows_col_k = AT->i + AT->p[k].start; + len_col_k = AT->p[k].end - AT->p[k].start; + + // check that there are no inactive rows in column j and k + DEBUG(ASSERT_NO_INACTIVE_ROWS(rows_col_j, row_tags, len_col_j);); + DEBUG(ASSERT_NO_INACTIVE_ROWS(rows_col_k, row_tags, len_col_k);); + + // ----------------------------------------------------------------- + // compute the fill-in in column j caused by substituting column k + // ----------------------------------------------------------------- + fill_in = -1; + jj = 0; + kk = 0; + while (jj < len_col_j && kk < len_col_k) + { + if (rows_col_j[jj] == rows_col_k[kk]) + { + jj += 1; + kk += 1; + } + else if (rows_col_k[kk] < rows_col_j[jj]) + { + kk += 1; + fill_in += 1; + } + else + { + jj += 1; + } + } + fill_in += len_col_k - kk; + + //------------------------------------------------------------------- + // Check if there is sufficient space in AT for the substitution. + // If not, try to shift rows to create space. + //------------------------------------------------------------------- + free_space = AT->p[j + 1].start - AT->p[j].end; + return (free_space >= fill_in || shift_row(AT, j, fill_in, max_shift)); +} + +/* This function checks if a doubleton row should be eliminated. It modifies + the stay and subst variables. If no substitution is possible, stay and subst + are set to -1. If the substitution is possible, stay and subst are set to + the *column indices* of the variables that will stay and be substituted. + The bounds of the variable that will be substituted are NOT modified. +*/ +#ifdef NDEBUG +static inline void can_dton_be_eliminated(Matrix *AT, const double *row_vals, + const int *row_cols, int *stay, int *subst, + int col_size0, int col_size1, + int max_shift) +#else +static inline void can_dton_be_eliminated(Matrix *AT, const double *row_vals, + const int *row_cols, int *stay, int *subst, + int col_size0, int col_size1, + int max_shift, const RowTag *row_tags) +#endif +{ + double a_abs = ABS(row_vals[0]); + double b_abs = ABS(row_vals[1]); + bool is_integral0 = IS_INTEGRAL(a_abs / b_abs); + bool is_integral1 = IS_INTEGRAL(b_abs / a_abs); + + // ------------------------------------------------------------------------ + // Decide which variable to substitute. For a row ax + by = c we can do + // substitutions x = -(b/a)y + c/a or y = -(a/b)x + c/b. If b/a is an + // integer but not a/b we substitute x. If a/b is an integer but not b/a + // we substitute y. + // + // We might want to add additional criteria here? Do we first want to + // check if one of the columns has far more nnz than the other one? + // ------------------------------------------------------------------------ + if (col_size0 == 1 && col_size1 != 1) + { + *subst = 0; + } + else if (col_size0 != 1 && col_size1 == 1) + { + *subst = 1; + } + else if (is_integral0 && !is_integral1) + { + *subst = 1; + } + else if (is_integral1 && !is_integral0) + { + *subst = 0; + } + else + { + if (col_size0 < col_size1) + { + *subst = 0; + } + else + { + *subst = 1; + } + } + + *stay = 1 - *subst; + + // ------------------------------------------------------------------------ + // Simple test to reject large or small pivots. We might want to add a more + // sophisticated check here. We could potentially move this in the code. + // ------------------------------------------------------------------------ + double abs_pivot = row_vals[*stay] / row_vals[*subst]; + abs_pivot = ABS(abs_pivot); + if (abs_pivot > MAX_RATIO_PIVOT || abs_pivot < 1 / MAX_RATIO_PIVOT) + { + assert("Is this ever triggered now with this small threshold?"); + *stay = -1; + *subst = -1; + return; + } + + // ------------------------------------------------------------------------ + // Check if there is sufficient space in AT for the substitution. + // ------------------------------------------------------------------------ +#ifdef NDEBUG + if (!sufficient_space_AT(AT, row_cols[*stay], row_cols[*subst], max_shift)) +#else + if (!sufficient_space_AT(AT, row_cols[*stay], row_cols[*subst], max_shift, + row_tags)) +#endif + { + *stay = -1; + *subst = -1; + return; + } + + *stay = row_cols[*stay]; + *subst = row_cols[*subst]; +} + +// lb and ub are bounds on variable that gets substituted +static inline void modify_bounds(Constraints *constraints, int i, double aij, + double aik, double rhs, double lb_subst, + double ub_subst, int stay, ColTag col_tag_subst) +{ + assert(aij != 0 && aik != 0); + bool same_sign = (aik * aij > 0.0); + + if (same_sign) + { + if (!HAS_TAG(col_tag_subst, C_TAG_LB_INF)) + { + double new_ub_cand = (rhs - aik * lb_subst) / aij; + update_ub(constraints, stay, new_ub_cand, i HUGE_BOUND_IS_NOT_OK); + + if (HAS_TAG(constraints->col_tags[stay], C_TAG_FIXED)) + { + return; + } + } + + if (!HAS_TAG(col_tag_subst, C_TAG_UB_INF)) + { + double new_lb_cand = (rhs - aik * ub_subst) / aij; + update_lb(constraints, stay, new_lb_cand, i HUGE_BOUND_IS_NOT_OK); + } + } + else + { + if (!HAS_TAG(col_tag_subst, C_TAG_LB_INF)) + { + double new_lb_cand = (rhs - aik * lb_subst) / aij; + update_lb(constraints, stay, new_lb_cand, i HUGE_BOUND_IS_NOT_OK); + + if (HAS_TAG(constraints->col_tags[stay], C_TAG_FIXED)) + { + return; + } + } + + if (!HAS_TAG(col_tag_subst, C_TAG_UB_INF)) + { + double new_ub_cand = (rhs - aik * ub_subst) / aij; + update_ub(constraints, stay, new_ub_cand, i HUGE_BOUND_IS_NOT_OK); + } + } +} + +#ifndef TESTING +static inline +#endif + Old_and_new_coeff update_row_A_dton(Matrix *A, int i, int q, int j, int k, + double aij, double aik, int *row_size, + PostsolveInfo *postsolve_info) +{ + int ii, start, end, insertion; + double old_val = 0.0; + int subst_idx = -1; + int diff_row_size = 0; + start = A->p[q].start; + end = A->p[q].end; + insertion = end; + + // ----------------------------------------------------------------- + // find index of variable that is substituted and and the index of + // insertion of the variable that stays + // ----------------------------------------------------------------- + for (ii = start; ii < end; ++ii) + { + if (A->i[ii] == k) + { + subst_idx = ii; + break; + } + } + + assert(subst_idx != -1); + + for (ii = start; ii < end; ++ii) + { + if (A->i[ii] >= j) + { + insertion = ii; + break; + } + } + + // ----------------------------------------------------------------- + // Compute new coefficient of the variable that stays. + // TODO: should we have rejected the reduction if the new value is + // very small? (don't delete todo here) + // ----------------------------------------------------------------- + if (insertion != end && A->i[insertion] == j) + { + old_val = A->x[insertion]; + } + + double new_val = old_val - (aij / aik) * A->x[subst_idx]; + Old_and_new_coeff old_and_new_coeff; + old_and_new_coeff.old_coeff_stay = old_val; + old_and_new_coeff.old_coeff_subst = A->x[subst_idx]; + old_and_new_coeff.new_coeff_stay = new_val; + + save_retrieval_added_row(postsolve_info, i, q, -A->x[subst_idx] / aik); + +#ifndef NDEBUG + if (ABS(A->x[subst_idx]) > 1e-6) + { + assert(ABS(new_val) > 1e-6 || new_val == 0.0); + } + + if (ABS(new_val) < 1e-6 && new_val != 0.0) + { + // when we eliminate row 32833 on momentum and substitute col x20 + // into row 30698 this is triggered, because x20 has a very small + // coefficient in row 30698 BEFORE the substitution + printf("warning debug: small coefficient dtonrow \n"); + } +#endif + + // ------------------------------------------------------------------- + // remove variable that is substituted + // ------------------------------------------------------------------- + memmove(A->x + subst_idx, A->x + subst_idx + 1, + (end - subst_idx - 1) * sizeof(double)); + memmove(A->i + subst_idx, A->i + subst_idx + 1, + (end - subst_idx - 1) * sizeof(int)); + end -= 1; + diff_row_size += 1; + insertion -= (subst_idx < insertion); + + // ------------------------------------------------------------------- + // if we get unexpected cancellation we should remove the coefficient, + // otherwise we insert it + // ------------------------------------------------------------------- + if (ABS(new_val) <= ZERO_TOL) + { + memmove(A->x + insertion, A->x + insertion + 1, + (end - insertion - 1) * sizeof(double)); + memmove(A->i + insertion, A->i + insertion + 1, + (end - insertion - 1) * sizeof(int)); + end -= 1; + diff_row_size += 1; + } + // ----------------------------------------------------------------- + // Insert the new value. If it exists or should be inserted in + // the end, we don't need to shift values. + // ----------------------------------------------------------------- + else + { + if (insertion == end) + { + A->x[insertion] = new_val; + A->i[insertion] = j; + end += 1; + diff_row_size -= 1; + } + else if (A->i[insertion] == j) + { + A->x[insertion] = new_val; + } + else + { + memmove(A->x + insertion + 1, A->x + insertion, + (end - insertion) * sizeof(double)); + memmove(A->i + insertion + 1, A->i + insertion, + (end - insertion) * sizeof(int)); + A->x[insertion] = new_val; + A->i[insertion] = j; + end += 1; + diff_row_size -= 1; + } + } + + A->p[q].end = end; + *row_size -= diff_row_size; + A->nnz -= diff_row_size; + + assert(*row_size == end - start); + DEBUG(ASSERT_INCREASING_I(A->i + start, *row_size);); + DEBUG(ASSERT_NO_ZEROS_D(A->x + start, *row_size);); + assert(A->p[q].end <= A->p[q + 1].start); + return old_and_new_coeff; +} + +static inline void subst_col_in_obj_dton(Objective *obj, int stay, int subst, + double aik, double aij, double rhs) +{ + obj->c[stay] -= (aij / aik) * obj->c[subst]; + obj->offset += (rhs / aik) * obj->c[subst]; +} + +// lhs and rhs should point to the row whose lhs and rhs should be modified +// rTag = row_tags[rows_subst[j]], lhs = lhs + rows_subst[j], +// rhs = rhs + rows_subst[j], rhs_dton +static inline void update_lhs_and_rhs(double *lhs, double *rhs, double aik, + RowTag rTag, double rhs_dton, + double coeff_subst) +{ + if (rhs_dton != 0.0) + { + double change = coeff_subst / aik * rhs_dton; + + if (!HAS_TAG(rTag, R_TAG_LHS_INF)) + { + *lhs -= change; + } + + if (!HAS_TAG(rTag, R_TAG_RHS_INF)) + { + *rhs -= change; + } + } +} + +/* Decides which variable that should be substituted. If the substitution + is rejected for whatever reason, it sets j = -1. */ +#ifdef NDEBUG +static void find_substitution(const RowView *row, const ColTag *col_tags, int *j, + int *k, double *aik, double *aij, Matrix *AT, + int *col_sizes, int max_shift_per_row, + int *iwork_n_rows, int *n_new_dton_rows) +#else +static void find_substitution(const RowView *row, const ColTag *col_tags, int *j, + int *k, double *aik, double *aij, Matrix *AT, + int *col_sizes, int max_shift_per_row, + const RowTag *row_tags, int *iwork_n_rows, + int *n_new_dton_rows) +#endif +{ + + // a previous dtonrow might have changed this dtonrows sparsity pattern, + // or a row that used to be a dtonrow was pushed to the list of dtonrows + // but since then one or both of its variables have been fixed because + // of singleton rows + if (*row->len < 2) + { + *j = -1; + return; + } + + assert(HAS_TAG(*row->tag, R_TAG_EQ) && !HAS_TAG(*row->tag, R_TAG_INACTIVE)); + assert(*row->lhs == *row->rhs && !IS_ABS_INF(*row->rhs)); + + // One of the variables might have been fixed when eliminating + // another doubleton row. However, the variable has not been + // removed from the problem yet so the row size check above + // is not sufficient, and we also need this check + if (HAS_TAG((col_tags[row->cols[0]] | col_tags[row->cols[1]]), C_TAG_INACTIVE)) + { + *j = -1; + return; + } + + // ------------------------------------------------------------------- + // check if there is sufficient space to eliminate the doubleton row, + // if stay == -1 it means that the elimination was rejected + // ------------------------------------------------------------------- +#ifdef NDEBUG + can_dton_be_eliminated(AT, row->vals, row->cols, j, k, col_sizes[row->cols[0]], + col_sizes[row->cols[1]], max_shift_per_row); +#else + can_dton_be_eliminated(AT, row->vals, row->cols, j, k, col_sizes[row->cols[0]], + col_sizes[row->cols[1]], max_shift_per_row, row_tags); +#endif + + if (*j == -1) + { + // append the row so we try it again in next round + iwork_n_rows[*n_new_dton_rows] = row->i; + *n_new_dton_rows += 1; + return; + } + + assert(*j == row->cols[0] || *j == row->cols[1]); + assert(*k == row->cols[0] || *k == row->cols[1]); + assert(!HAS_TAG(col_tags[*j], C_TAG_INACTIVE)); + assert(!HAS_TAG(col_tags[*k], C_TAG_INACTIVE)); + + // Our current implementation of singleton columns does not take + // into account that a column singleton in a dtonrow can always be + // be eliminated. Hence, we might eliminate dtonrows here that + // contains a singleton column. + assert(col_sizes[*j] != 1 || (col_sizes[*j] == 1 && col_sizes[*k] == 1)); + + // ------------------------------------------------------------------- + // Find coefficient that stays. i is the row index, column j + // stays, and column k gets substituted + // ------------------------------------------------------------------- + if (*k == row->cols[0]) + { + assert(*j == row->cols[1]); + *aik = row->vals[0]; + *aij = row->vals[1]; + } + else + { + assert(*j == row->cols[0] && *k == row->cols[1]); + *aik = row->vals[1]; + *aij = row->vals[0]; + } +} + +static void execute_substitution(Constraints *constraints, RowView *row, int j, + int k, double aij, double aik, int *n_new_dton_rows, + double ck) +{ + Matrix *A = constraints->A; + Matrix *AT = constraints->AT; + double *lhs = constraints->lhs; + double *rhs = constraints->rhs; + int *col_sizes = constraints->state->col_sizes; + int *row_sizes = constraints->state->row_sizes; + Activity *activities = constraints->state->activities; + PostsolveInfo *postsolve_info = constraints->state->postsolve_info; + Bound *bounds = constraints->bounds; + RowTag *row_tags = constraints->row_tags; + ColTag *col_tags = constraints->col_tags; + + iVec *empty_rows = constraints->state->empty_rows; + iVec *ston_rows = constraints->state->ston_rows; + int *iwork_n_rows = constraints->state->work->iwork_n_rows; + + Altered_Activity altered1, altered2; + int jj, q, len, old_len; + int *rows_subst; + + rows_subst = AT->i + AT->p[k].start; + len = AT->p[k].end - AT->p[k].start; + RowView rowj_AT = new_rowview(AT->x + AT->p[j].start, AT->i + AT->p[j].start, + col_sizes + j, AT->p + j, NULL, NULL, NULL, j); + + // remove contribution of xk in AT (we do it before the loop to free space) + remove_coeff(&rowj_AT, row->i); + AT->p[k].end = AT->p[k].start; + + // we substitute variable xk from all rows q + for (jj = 0; jj < len; ++jj) + { + q = rows_subst[jj]; + if (HAS_TAG(row_tags[q], R_TAG_INACTIVE)) + { + continue; + } + + RowView sub_row = + new_rowview(A->x + A->p[q].start, A->i + A->p[q].start, row_sizes + q, + A->p + q, lhs + q, rhs + q, row_tags + q, q); + + assert(sub_row.i != row->i); + + // update constraint matrix A (updates row size) + old_len = *sub_row.len; + Old_and_new_coeff coeffs = update_row_A_dton( + A, row->i, sub_row.i, j, k, aij, aik, sub_row.len, postsolve_info); + + // Check row size and push to list of empty rows, stonrows and + // dtonrows. The elimination of one dtonrow might lead to another + // dtonrow, so we must be careful with how we append. We use a + // temporary buffer to handle this. + switch (*sub_row.len) + { + case 0: + assert(!iVec_contains(empty_rows, sub_row.i)); + iVec_append(empty_rows, sub_row.i); + assert(!HAS_TAG(*sub_row.tag, R_TAG_INACTIVE)); + assert(sub_row.range->start == sub_row.range->end); + break; + case 1: + if (old_len != 1) + { + assert(!iVec_contains(ston_rows, sub_row.i)); + iVec_append(ston_rows, sub_row.i); + assert(!HAS_TAG(*sub_row.tag, R_TAG_INACTIVE)); + } + break; + case 2: + if (HAS_TAG(*sub_row.tag, R_TAG_EQ) && old_len != 2) + { + iwork_n_rows[*n_new_dton_rows] = sub_row.i; + *n_new_dton_rows += 1; + assert(!HAS_TAG(*sub_row.tag, R_TAG_INACTIVE)); + } + break; + } + + // update constraint matrix AT (updates column size) + insert_or_update_coeff(AT, j, sub_row.i, coeffs.new_coeff_stay, + col_sizes + j); + + // update lhs and rhs + update_lhs_and_rhs(sub_row.lhs, sub_row.rhs, aik, *sub_row.tag, rhs[row->i], + coeffs.old_coeff_subst); + + // update activity due to the coefficient change of variable that stays + altered1 = update_activity_coeff_change(activities + q, bounds[j].lb, + bounds[j].ub, coeffs.old_coeff_stay, + coeffs.new_coeff_stay, col_tags[j]); + + // update activity due to the coefficient change of variable that is + // substituted + altered2 = + update_activity_coeff_change(activities + q, bounds[k].lb, bounds[k].ub, + coeffs.old_coeff_subst, 0.0, col_tags[k]); + + if ((altered1 | altered2) & MIN_ALTERED_RECOMPUTE) + { + assert(*sub_row.len == A->p[q].end - A->p[q].start); + activities[sub_row.i].min = compute_min_act_no_tags( + sub_row.vals, sub_row.cols, *sub_row.len, bounds); + + if (activities[sub_row.i].status == NOT_ADDED) + { + activities[sub_row.i].status = ADDED; + iVec_append(constraints->state->updated_activities, sub_row.i); + } + } + + if ((altered1 | altered2) & MAX_ALTERED_RECOMPUTE) + { + // if this assertion fails, replace *sub_row.len with the rhs + assert(*sub_row.len == A->p[q].end - A->p[q].start); + activities[sub_row.i].max = compute_max_act_no_tags( + sub_row.vals, sub_row.cols, *sub_row.len, bounds); + + if (activities[sub_row.i].status == NOT_ADDED) + { + activities[sub_row.i].status = ADDED; + iVec_append(constraints->state->updated_activities, sub_row.i); + } + } + } + + // update list of empty and singleton columns + switch (col_sizes[j]) + { + case 0: + assert(!HAS_TAG(col_tags[j], C_TAG_INACTIVE)); + assert(!iVec_contains(constraints->state->empty_cols, j)); + iVec_append(constraints->state->empty_cols, j); + assert(AT->p[j].start == AT->p[j].end); + break; + case 1: + assert(!HAS_TAG(col_tags[j], C_TAG_INACTIVE)); + assert(!iVec_contains(constraints->state->ston_cols, j)); + iVec_append(constraints->state->ston_cols, j); + break; + default: + break; + } + + // mark row of AT as inactive (must do it after loop due to + // assertion in update activities) + col_tags[k] = C_TAG_INACTIVE; + col_sizes[k] = SIZE_INACTIVE_COL; + + assert(AT->p[j].end - AT->p[j].start == col_sizes[j]); + count_locks_one_column(&rowj_AT, constraints->state->col_locks + j, row_tags); + + // postsolve information + save_retrieval_sub_col(postsolve_info, k, row->cols, row->vals, 2, rhs[row->i], + row->i, ck); + save_retrieval_deleted_row(postsolve_info, row->i, ck / aik); +} + +/* We assume the row i is a doubleton equality row with variables +xj and xk, where xk is substituted and xj stays. We eliminate +xk from every row q that contains xk. */ +static PresolveStatus remove_dton_eq_rows__(Problem *prob, int max_shift_per_row) +{ + int ii, i, j, k; + int n_new_dton_rows = 0; + double aik, aij; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + Matrix *AT = constraints->AT; + double *rhs = constraints->rhs; + double *lhs = constraints->lhs; + RowRange *row_r = A->p; + RowTag *row_tags = constraints->row_tags; + ColTag *col_tags = constraints->col_tags; + const iVec *dton_rows = constraints->state->dton_rows; + int *col_sizes = constraints->state->col_sizes; + int *row_sizes = constraints->state->row_sizes; + + const Bound *bounds = constraints->bounds; + PresolveStatus status = UNCHANGED; + int *iwork_n_rows = constraints->state->work->iwork_n_rows; + + DEBUG(verify_no_duplicates_sort(dton_rows)); + + for (ii = 0; ii < dton_rows->len; ++ii) + { + i = dton_rows->data[ii]; + RowView row = + new_rowview(A->x + row_r[i].start, A->i + row_r[i].start, row_sizes + i, + A->p + i, lhs + i, rhs + i, row_tags + i, i); + +#ifdef NDEBUG + // find which variable that should be substituted (j stays, k goes) + find_substitution(&row, col_tags, &j, &k, &aik, &aij, AT, col_sizes, + max_shift_per_row, iwork_n_rows, &n_new_dton_rows); +#else + find_substitution(&row, col_tags, &j, &k, &aik, &aij, AT, col_sizes, + max_shift_per_row, row_tags, iwork_n_rows, + &n_new_dton_rows); +#endif + + // no variable could be substituted + if (j == -1) + { + // todo: here we could run simple primal sparsification? + continue; + } + + status = REDUCED; + + // transfer bounds to variable that stays and skip the reduction + // if the variable that stays was fixed because of the bound change + modify_bounds(constraints, i, aij, aik, *row.rhs, bounds[k].lb, bounds[k].ub, + j, col_tags[k]); + + if (HAS_TAG(col_tags[j], C_TAG_INACTIVE)) + { + continue; + } + + // substitute variable in objective and mark the eliminated row as + // inactive + subst_col_in_obj_dton(prob->obj, j, k, aik, aij, *row.rhs); + RESET_TAG(*row.tag, R_TAG_INACTIVE); + *row.len = SIZE_INACTIVE_ROW; + row.range->end = row.range->start; + A->nnz -= 2; + + // Substitute the variable in the constraints, using special + // functionality + execute_substitution(constraints, &row, j, k, aij, aik, &n_new_dton_rows, + prob->obj->c[k]); + } + + AT->nnz = A->nnz; + + // clear the list of processed dton rows + iVec_clear_no_resize(constraints->state->dton_rows); + + // append the new dton rows + if (n_new_dton_rows > 0) + { + DEBUG(verify_no_duplicates_sort_ptr(iwork_n_rows, n_new_dton_rows)); + iVec_append_array(constraints->state->dton_rows, iwork_n_rows, + n_new_dton_rows); + } + + return status; +} + +PresolveStatus remove_dton_eq_rows(Problem *prob, int max_shift_per_row) +{ + assert(prob->constraints->state->ston_rows->len == 0); + assert(prob->constraints->state->empty_rows->len == 0); + assert(prob->constraints->state->empty_cols->len == 0); + DEBUG(verify_problem_up_to_date(prob->constraints)); + + PresolveStatus status = UNCHANGED; + + // important to have double ptr in case of realloc when appending + iVec **dton_rows = &(prob->constraints->state->dton_rows); + while ((*dton_rows)->len > 0) + { + PresolveStatus temp = remove_dton_eq_rows__(prob, max_shift_per_row); + + if (temp == REDUCED) + { + status = REDUCED; + } + else + { + break; + } + } + + // some variables might have been fixed because of the bound change + if (prob->constraints->state->fixed_cols_to_delete->len > 0) + { + delete_fixed_cols_from_problem(prob); + delete_inactive_cols_from_A_and_AT(prob->constraints); + } + + DEBUG(verify_problem_up_to_date(prob->constraints)); + + return status; +} diff --git a/src/explorers/Parallel_cols.c b/src/explorers/Parallel_cols.c new file mode 100644 index 00000000..6908f8a3 --- /dev/null +++ b/src/explorers/Parallel_cols.c @@ -0,0 +1,343 @@ +/* + * Copyright 2025 Daniel Cederberg + * + * This file is part of the PSLP project (LP Presolver). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Parallel_cols.h" +#include "Activity.h" +#include "Bounds.h" +#include "Constraints.h" +#include "CoreTransformations.h" +#include "Debugger.h" +#include "Matrix.h" +#include "Numerics.h" +#include "Parallel_rows.h" +#include "Problem.h" +#include "RowColViews.h" +#include "SimpleReductions.h" +#include "State.h" +#include "Tags.h" +#include "Workspace.h" + +static inline PresolveStatus process_single_bin(const Problem *prob, const int *bin, + int bin_size) +{ + assert(bin_size > 1); + Constraints *constraints = prob->constraints; + ColTag *col_tags = constraints->col_tags; + Bound *bounds = constraints->bounds; + const Matrix *AT = constraints->AT; + const RowRange *col_ranges = AT->p; + const double *c = prob->obj->c; + Activity *acts = constraints->state->activities; + iVec *sub_cols_to_delete = constraints->state->sub_cols_to_delete; + PostsolveInfo *postsolve_info = constraints->state->postsolve_info; + + bool recount_ninfs = false; + + // column j stays, column k goes + int ii, jj, j, k, len_j; + const int *aj_cols; + double ratio, cj, ck, ak0, lb_j_old, ub_j_old; + const double *aj_vals; + + for (ii = 0; ii < bin_size - 1; ++ii) + { + j = bin[ii]; + + if (HAS_TAG(col_tags[j], C_TAG_INACTIVE)) + { + continue; + } + + cj = c[j]; + aj_vals = AT->x + col_ranges[j].start; + aj_cols = AT->i + col_ranges[j].start; + len_j = col_ranges[j].end - col_ranges[j].start; + recount_ninfs = false; + + for (jj = ii + 1; jj < bin_size; ++jj) + { + k = bin[jj]; + + if (HAS_TAG(col_tags[k], C_TAG_INACTIVE)) + { + continue; + } + + ck = c[k]; + ak0 = AT->x[col_ranges[k].start]; + ratio = ak0 / aj_vals[0]; + assert(ratio != 0); + + // if column j and column k are parallel, remove column k + // if you run into issues with parallel cols activated, try to + // change this + if (IS_EQUAL_FEAS_TOL(ck * aj_vals[0], cj * ak0)) + { + // mark that we must recount n_infs for rows in which column j + // appears in + recount_ninfs = true; + + // --------------------------------------------------------------------- + // Update the bounds of column j + // --------------------------------------------------------------------- + lb_j_old = bounds[j].lb; + ub_j_old = bounds[j].ub; + if (ratio > 0) + { + // update lb + if (HAS_TAG((col_tags[j] | col_tags[k]), C_TAG_LB_INF)) + { + bounds[j].lb = -INF; + UPDATE_TAG(col_tags[j], C_TAG_LB_INF); + } + else + { + assert(!IS_ABS_INF(bounds[j].lb) && + !IS_ABS_INF(bounds[k].lb)); + bounds[j].lb += bounds[k].lb * ratio; + } + + // update ub + if (HAS_TAG((col_tags[j] | col_tags[k]), C_TAG_UB_INF)) + { + bounds[j].ub = INF; + UPDATE_TAG(col_tags[j], C_TAG_UB_INF); + } + else + { + assert(!IS_ABS_INF(bounds[j].ub) && + !IS_ABS_INF(bounds[k].ub)); + bounds[j].ub += bounds[k].ub * ratio; + } + } + else + { + // update lb + if (HAS_TAG(col_tags[j], C_TAG_LB_INF) || + HAS_TAG(col_tags[k], C_TAG_UB_INF)) + { + bounds[j].lb = -INF; + UPDATE_TAG(col_tags[j], C_TAG_LB_INF); + } + else + { + assert(!IS_ABS_INF(bounds[j].lb) && + !IS_ABS_INF(bounds[k].ub)); + bounds[j].lb += bounds[k].ub * ratio; + } + + // update ub + if (HAS_TAG(col_tags[j], C_TAG_UB_INF) || + HAS_TAG(col_tags[k], C_TAG_LB_INF)) + { + bounds[j].ub = INF; + UPDATE_TAG(col_tags[j], C_TAG_UB_INF); + } + else + { + assert(!IS_ABS_INF(bounds[j].ub) && + !IS_ABS_INF(bounds[k].lb)); + bounds[j].ub += bounds[k].lb * ratio; + } + } + + // --------------------------------------------------------------------- + // mark col as substituted + // --------------------------------------------------------------------- + set_col_to_substituted(k, col_tags + k, sub_cols_to_delete); + save_retrieval_parallel_col(postsolve_info, ub_j_old, lb_j_old, + bounds[k].lb, bounds[k].ub, ratio, j, k, + col_tags[j], col_tags[k]); + } + else + { + // we could add an implied check here but it doesn't seem to + // make a difference on miplib. If you do it, don't forget that + // the implied free check implicitly corresponds to a bound + // change that must be dual postsolved + + bool fix_xk_to_lower = false; + bool fix_xk_to_upper = false; + bool fix_xj_to_upper = false; + bool fix_xj_to_lower = false; + + assert(!HAS_TAG(col_tags[k], C_TAG_INACTIVE)); + assert(!HAS_TAG(col_tags[j], C_TAG_INACTIVE)); + + if (ck > ratio * cj) + { + if (ratio > 0) + { + fix_xk_to_lower = HAS_TAG(col_tags[j], C_TAG_UB_INF); + fix_xj_to_upper = HAS_TAG(col_tags[k], C_TAG_LB_INF); + } + else + { + fix_xk_to_lower = HAS_TAG(col_tags[j], C_TAG_LB_INF); + fix_xj_to_lower = HAS_TAG(col_tags[k], C_TAG_LB_INF); + } + } + else + { + assert(ck < ratio * cj); + if (ratio > 0) + { + fix_xk_to_upper = HAS_TAG(col_tags[j], C_TAG_LB_INF); + fix_xj_to_lower = HAS_TAG(col_tags[k], C_TAG_UB_INF); + } + else + { + fix_xk_to_upper = HAS_TAG(col_tags[j], C_TAG_UB_INF); + fix_xj_to_upper = HAS_TAG(col_tags[k], C_TAG_UB_INF); + } + } + + // check if we can fix xk + if (fix_xk_to_lower) + { + if (HAS_TAG(col_tags[k], C_TAG_LB_INF)) + { + return UNBNDORINFEAS; + } + + assert(!HAS_TAG(col_tags[k], C_TAG_INACTIVE)); + assert(!IS_ABS_INF(bounds[k].lb)); + fix_col(constraints, k, bounds[k].lb, ck); + continue; + } + else if (fix_xk_to_upper) + { + if (HAS_TAG(col_tags[k], C_TAG_UB_INF)) + { + return UNBNDORINFEAS; + } + + assert(!HAS_TAG(col_tags[k], C_TAG_INACTIVE)); + assert(!IS_ABS_INF(bounds[k].ub)); + fix_col(constraints, k, bounds[k].ub, ck); + continue; + } + + // check if we can fix xj + if (fix_xj_to_lower) + { + if (HAS_TAG(col_tags[j], C_TAG_LB_INF)) + { + return UNBNDORINFEAS; + } + + assert(!HAS_TAG(col_tags[k], C_TAG_INACTIVE)); + assert(!IS_ABS_INF(bounds[j].lb)); + fix_col(constraints, j, bounds[j].lb, cj); + // break from checking if xj is parallel to other cols since + // we fix it + break; + } + else if (fix_xj_to_upper) + { + if (HAS_TAG(col_tags[j], C_TAG_UB_INF)) + { + return UNBNDORINFEAS; + } + + assert(!HAS_TAG(col_tags[k], C_TAG_INACTIVE)); + assert(!IS_ABS_INF(bounds[j].ub)); + fix_col(constraints, j, bounds[j].ub, cj); + // break from checking if xj is parallel to other cols since + // we fix it + break; + } + } + } + + // we might have to recount n_inf_min and n_inf_max for the rows column + // j appears in due to bound change + if (recount_ninfs) + { + const Matrix *A = constraints->A; + + for (jj = 0; jj < len_j; jj++) + { + int row = aj_cols[jj]; + + recompute_n_infs(acts + row, A->x + A->p[row].start, + A->i + A->p[row].start, + A->p[row].end - A->p[row].start, col_tags); + } + } + } + + return UNCHANGED; +} + +static PresolveStatus process_all_bins(const Problem *prob, const int *parallel_cols, + const iVec *groups) +{ + int n_groups = groups->len - 1; + PresolveStatus status = UNCHANGED; + + for (int i = 0; i < n_groups; ++i) + { + int n_cols_this_group = groups->data[i + 1] - groups->data[i]; + status |= process_single_bin(prob, parallel_cols + groups->data[i], + n_cols_this_group); + } + + return status; +} + +PresolveStatus remove_parallel_cols(Problem *prob) +{ + assert(prob->constraints->state->ston_rows->len == 0); + assert(prob->constraints->state->empty_rows->len == 0); + assert(prob->constraints->state->empty_cols->len == 0); + DEBUG(verify_problem_up_to_date(prob->constraints)); + + Constraints *constraints = prob->constraints; + int *parallel_cols = constraints->state->work->iwork_n_cols; + int *sparsity_IDs = constraints->state->work->iwork1_max_nrows_ncols; + int *coeff_hashes = constraints->state->work->iwork2_max_nrows_ncols; + iVec *group_starts = constraints->state->work->int_vec; + + // finding parallel rows of AT is the same as finding parallel cols of A + find_parallel_rows(constraints->AT, constraints->col_tags, group_starts, + parallel_cols, sparsity_IDs, coeff_hashes, C_TAG_INACTIVE); +#ifndef NDEBUG + // there should be no inactive cols in group_starts here + for (int i = 0; i < group_starts->len - 1; ++i) + { + for (int j = group_starts->data[i]; j < group_starts->data[i + 1]; ++j) + { + assert( + !HAS_TAG(constraints->col_tags[parallel_cols[j]], C_TAG_INACTIVE)); + assert(constraints->state->col_sizes[parallel_cols[j]] != + SIZE_INACTIVE_COL); + } + } +#endif + + PresolveStatus status = process_all_bins(prob, parallel_cols, group_starts); + assert(constraints->state->empty_rows->len == 0); + + delete_fixed_cols_from_problem(prob); + delete_inactive_cols_from_A_and_AT(prob->constraints); + + DEBUG(verify_problem_up_to_date(constraints)); + + return status; +} \ No newline at end of file diff --git a/src/explorers/Parallel_rows.c b/src/explorers/Parallel_rows.c new file mode 100644 index 00000000..6db6f11b --- /dev/null +++ b/src/explorers/Parallel_rows.c @@ -0,0 +1,612 @@ +/* + * Copyright 2025 Daniel Cederberg + * + * This file is part of the PSLP project (LP Presolver). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Parallel_rows.h" +#include "Bounds.h" +#include "Constraints.h" +#include "CoreTransformations.h" +#include "Debugger.h" +#include "Matrix.h" +#include "Numerics.h" +#include "RowColViews.h" +#include "State.h" +#include "Workspace.h" +#include "iVec.h" +#include "limits.h" // for INT_MAX +#include "utils.h" +#include // For round() +#include + +// global variables needed for qsort +static int *global_sparsity_IDs; +static int *global_coeff_hashes; + +// djb2 hash function +static inline uint32_t hash_int_array(const int *arr, int size) +{ + uint32_t hash = 5381; + for (int i = 0; i < size; i++) + { + hash = ((hash << 5) + hash) + arr[i]; + } + + return hash; +} + +#define INV_PRECISION 1e6 + +// djb2 hash function, with normalization of values and rounding of doubles +static inline uint32_t hash_double_array_with_scale(const double *arr, int size) +{ + // find max value of row + double max = ABS(arr[0]); + for (int i = 1; i < size; i++) + { + if (ABS(arr[i]) > max) + { + max = ABS(arr[i]); + } + } + double scale = (arr[0] > 0) ? 1 / max : -1 / max; + + // compute has value + uint32_t hash = 5381; + for (int i = 0; i < size; i++) + { + uint32_t scaled = (uint32_t) (round((arr[i] * scale) * INV_PRECISION)); + hash = ((hash << 5) + hash) + scaled; + } + + return hash; +} + +#ifndef TESTING +static inline +#endif + void compute_supp_and_coeff_hash(const Matrix *A, const RowTag *rowtags, + int *sparsity_IDs, int *coeff_hashes, + RowTag INACTIVE_TAG) +{ + + for (int i = 0; i < A->m; i++) + { + if (HAS_TAG(rowtags[i], INACTIVE_TAG)) + { + // important to set support_ID for inactive rows to place them + // last in the sort + sparsity_IDs[i] = INT_MAX; + continue; + } + + // this check makes sense since we also use this function to + // find parallel columns + assert(!HAS_TAG(rowtags[i], C_TAG_INACTIVE)); + + int len = A->p[i].end - A->p[i].start; + sparsity_IDs[i] = (int) hash_int_array(A->i + A->p[i].start, len); + coeff_hashes[i] = + (int) hash_double_array_with_scale(A->x + A->p[i].start, len); + } +} + +int comparator(const void *a, const void *b) +{ + int idxA = *(const int *) a; + int idxB = *(const int *) b; + + if (global_sparsity_IDs[idxA] < global_sparsity_IDs[idxB]) + { + return -1; + } + + if (global_sparsity_IDs[idxA] > global_sparsity_IDs[idxB]) + { + return 1; + } + + if (global_coeff_hashes[idxA] < global_coeff_hashes[idxB]) + { + return -1; + } + + if (global_coeff_hashes[idxA] > global_coeff_hashes[idxB]) + { + return 1; + } + + return 0; +} + +// Sort function +static inline void sort_rows(int *rows, int n_rows, int *sparsity_IDs, + int *coeff_hashes) +{ + global_sparsity_IDs = sparsity_IDs; + global_coeff_hashes = coeff_hashes; + qsort(rows, n_rows, sizeof(int), comparator); +} + +static inline int get_bin_size(int start, int n_rows, const int *rows, + const int *sparsity_IDs, const int *coeff_hashes) +{ + int sparsity_ID = sparsity_IDs[rows[start]]; + int coeff_hash = coeff_hashes[rows[start]]; + int i; + for (i = start + 1; i < n_rows; ++i) + { + if (sparsity_IDs[rows[i]] != sparsity_ID || + coeff_hashes[rows[i]] != coeff_hash) + { + break; + } + } + return i - start; +} + +#ifndef NDEBUG +// make sure that the rows in the same bin have the same coefficient +// hash value and the same sparsity ID +void ASSERT_BIN_CORRECT(const int *coeff_hashes, const int *sparsity_IDs, + const int *rows, int bin_start, int bin_size, + const RowTag *row_tags, RowTag INACTIVE_TAG) +{ + const int *bin_temp = rows + bin_start; + int coeff_hash_start = coeff_hashes[bin_temp[0]]; + int sparsity_ID_start = sparsity_IDs[bin_temp[0]]; + + for (int k = 1; k < bin_size; ++k) + { + assert(coeff_hashes[bin_temp[k]] == coeff_hash_start); + assert(sparsity_IDs[bin_temp[k]] == sparsity_ID_start); + assert(!HAS_TAG(row_tags[bin_temp[k]], INACTIVE_TAG)); + } +} + +void VERIFY_PARALLEL_ROWS(const Matrix *A, const RowTag *rows_tags, + const int *parallel_rows, const iVec *group_starts) +{ + int i, j, k, start, end, n_rows_this_group, len1; + int n_groups = group_starts->len - 1; + + for (i = 0; i < n_groups; ++i) + { + start = group_starts->data[i]; + end = group_starts->data[i + 1]; + n_rows_this_group = end - start; + + // check that the group has at least two rows + assert(n_rows_this_group > 1); + + // check that all rows in the group are active + const int *temp = parallel_rows + start; + ASSERT_NO_INACTIVE_ROWS(temp, rows_tags, n_rows_this_group); + + // check that all rows have the same support + len1 = A->p[parallel_rows[start]].end - A->p[parallel_rows[start]].start; + int *cols1 = A->i + A->p[parallel_rows[start]].start; + for (j = start + 1; j < end; ++j) + { + assert(len1 == + A->p[parallel_rows[j]].end - A->p[parallel_rows[j]].start); + ARRAYS_EQUAL(cols1, A->i + A->p[parallel_rows[j]].start, len1); + } + + // check that all rows have same normalized coefficients + double *vals1 = A->x + A->p[parallel_rows[start]].start; + for (j = start + 1; j < end; ++j) + { + double *vals2 = A->x + A->p[parallel_rows[j]].start; + double ratio = vals1[0] / vals2[0]; + for (k = 1; k < len1; ++k) + { + assert(IS_ZERO_FEAS_TOL(vals1[k] - ratio * vals2[k])); + } + } + } +} +#endif + +/* In one bin there can theoretically be several groups of parallel rows, + but hopefully it is unlikely that there are more than one group of + parallel rows. We therefore only try to catch one of the groups. + + Due to our choice of hash function, we must verify that the rows + have both the same support and the same coefficients (up to a multiple). + + * Returns the number of parallel rows found in the bin. + * bin: pointer pointing to the FIRST row in the bin + * bin_size: number of rows in the bin + * parallel_rows: parallel rows are stored here, starting from + parallel_rows[0] +*/ +static inline int find_parallel_rows_in_bin(const Matrix *A, const int *bin, + int bin_size, int *parallel_rows, + iVec *group_starts) +{ + assert(bin_size > 1); + int i, j, first_row_in_bin = bin[0]; + int n_new_parallel_rows = 0; + + const int *cols1 = A->i + A->p[bin[0]].start; + const double *vals1 = A->x + A->p[bin[0]].start; + int len1 = A->p[bin[0]].end - A->p[bin[0]].start; + + for (i = 1; i < bin_size; ++i) + { + const int *cols2 = A->i + A->p[bin[i]].start; + const double *vals2 = A->x + A->p[bin[i]].start; + int len2 = A->p[bin[i]].end - A->p[bin[i]].start; + + // check that the rows have same size + if (len1 != len2) + { + continue; + } + + // check that the rows have same support and coefficients up to multiple + // (must start at j = 0 because of support check) + double ratio = vals1[0] / vals2[0]; + for (j = 0; j < len2; ++j) + { + // if we run into numerical issues we might want to do + // diff = vals1[j] / vals2[j] - ratio + double diff = vals1[j] - ratio * vals2[j]; + + if (!IS_ZERO_FEAS_TOL(diff) || cols1[j] != cols2[j]) + { + break; + } + } + + // if j == len2, we have found a parallel row + if (j == len2) + { + parallel_rows[n_new_parallel_rows] = bin[i]; + n_new_parallel_rows++; + } + } + + // add first row in bin + if (n_new_parallel_rows > 0) + { + parallel_rows[n_new_parallel_rows] = first_row_in_bin; + n_new_parallel_rows++; + iVec_append(group_starts, + group_starts->data[group_starts->len - 1] + n_new_parallel_rows); + } + + return n_new_parallel_rows; +} + +// replaced rows with parallel_rows for less memory usage +void find_parallel_rows(const Matrix *A, const RowTag *r_Tags, iVec *group_starts, + int *parallel_rows, int *sparsity_IDs, int *coeff_hashes, + RowTag INACTIVE_TAG) +{ + int i, bin_size; + int n_p_rows_total = 0; + // ------------------------------------------------------------------------ + // compute sparsity_IDs and coeff_hashes + // ------------------------------------------------------------------------ + compute_supp_and_coeff_hash(A, r_Tags, sparsity_IDs, coeff_hashes, INACTIVE_TAG); + + // ------------------------------------------------------------------------ + // Make rows in the same bin appear next to each other. The sort makes sure + // that rows with the same sparsity pattern and the same coefficient hash + // value end up next to each other. + // ------------------------------------------------------------------------ + for (i = 0; i < A->m; i++) + { + parallel_rows[i] = i; + } + + sort_rows(parallel_rows, A->m, sparsity_IDs, coeff_hashes); + +#ifndef NDEBUG + if (INACTIVE_TAG == R_TAG_INACTIVE) + { + ASSERT_NO_ACTIVE_STON_ROWS(A, r_Tags); + } +#endif + + // ------------------------------------------------------------------------ + // Start looping through the bins. Inactive rows have been placed in the + // end of 'rows'. As soon as we meet an inactive row we can stop, since + // all rows appearing after it are also inactive + // ------------------------------------------------------------------------ + iVec_clear_no_resize(group_starts); + iVec_append(group_starts, 0); + for (i = 0; i < A->m; i++) + { + if (HAS_TAG(r_Tags[parallel_rows[i]], INACTIVE_TAG)) + { + break; + } + + bin_size = get_bin_size(i, A->m, parallel_rows, sparsity_IDs, coeff_hashes); + + // find the parallel rows in the bin + if (bin_size > 1) + { + DEBUG(ASSERT_BIN_CORRECT(coeff_hashes, sparsity_IDs, parallel_rows, i, + bin_size, r_Tags, INACTIVE_TAG);); + + n_p_rows_total += find_parallel_rows_in_bin( + A, parallel_rows + i, bin_size, parallel_rows + n_p_rows_total, + group_starts); + + i += bin_size - 1; + } + } + assert(n_p_rows_total == group_starts->data[group_starts->len - 1]); + DEBUG(VERIFY_PARALLEL_ROWS(A, r_Tags, parallel_rows, group_starts);); +} + +static inline PresolveStatus process_single_bin(const Constraints *constraints, + const int *bin, int bin_size) +{ + assert(bin_size > 1); + const RowTag *row_tags = constraints->row_tags; + const Matrix *A = constraints->A; + const double *rhs = constraints->rhs; + const double *lhs = constraints->lhs; + double remaining_row_new_rhs, remaining_row_new_lhs, remaining_row_coeff; + double other_row_coeff, ratio, other_row_rhs_scaled, other_row_lhs_scaled; + double other_row_lhs_scale_factor = 1.0; + double other_row_rhs_scale_factor = 1.0; + int other_row_lhs_index = -1; + int other_row_rhs_index = -1; + bool is_rhs_inf_other_row, is_lhs_inf_other_row; + + // ------------------------------------------------------------------------ + // Initialize quantities for the remaining row. These quantities will be + // updated as we process the bin. + // ------------------------------------------------------------------------ + bool is_remaining_row_eq, is_rhs_inf_remaining_row, is_lhs_inf_remaining_row; + int remaining_row_idx = bin[0]; + is_remaining_row_eq = HAS_TAG(row_tags[remaining_row_idx], R_TAG_EQ); + is_rhs_inf_remaining_row = HAS_TAG(row_tags[remaining_row_idx], R_TAG_RHS_INF); + is_lhs_inf_remaining_row = HAS_TAG(row_tags[remaining_row_idx], R_TAG_LHS_INF); + remaining_row_new_rhs = rhs[remaining_row_idx]; + remaining_row_new_lhs = lhs[remaining_row_idx]; + remaining_row_coeff = A->x[A->p[remaining_row_idx].start]; + + for (int i = 1; i < bin_size; ++i) + { + int other_row_idx = bin[i]; + bool is_other_row_eq = HAS_TAG(row_tags[other_row_idx], R_TAG_EQ); + + if (!is_remaining_row_eq && is_other_row_eq) + { + // swap(remaining_row, other_row) + int temp = remaining_row_idx; + remaining_row_idx = other_row_idx; + other_row_idx = temp; + + is_remaining_row_eq = true; + is_other_row_eq = false; + remaining_row_new_rhs = rhs[remaining_row_idx]; + remaining_row_new_lhs = lhs[remaining_row_idx]; + remaining_row_coeff = A->x[A->p[remaining_row_idx].start]; + } + + assert(!(is_other_row_eq && !is_remaining_row_eq)); + + other_row_coeff = A->x[A->p[other_row_idx].start]; + ratio = remaining_row_coeff / other_row_coeff; + other_row_rhs_scaled = rhs[other_row_idx] * ratio; + other_row_lhs_scaled = lhs[other_row_idx] * ratio; + is_rhs_inf_other_row = HAS_TAG(row_tags[other_row_idx], R_TAG_RHS_INF); + is_lhs_inf_other_row = HAS_TAG(row_tags[other_row_idx], R_TAG_LHS_INF); + + // ----------------------------------------------------------------------- + // if both rows are equalities we check that they are consistent + // ----------------------------------------------------------------------- + if (is_remaining_row_eq && is_other_row_eq) + { + if (!IS_EQUAL_FEAS_TOL(remaining_row_new_rhs, other_row_rhs_scaled)) + { + return INFEASIBLE; + } + } + // ----------------------------------------------------------------------- + // if only the remaining row is an equality we check for infeasibility + // ----------------------------------------------------------------------- + else if (is_remaining_row_eq) + { + bool infeas_rhs = + !is_rhs_inf_other_row && + ((ratio > 0 && remaining_row_new_rhs > other_row_rhs_scaled) || + (ratio < 0 && remaining_row_new_rhs < other_row_rhs_scaled)); + bool infeas_lhs = + !is_lhs_inf_other_row && + ((ratio > 0 && remaining_row_new_rhs < other_row_lhs_scaled) || + (ratio < 0 && remaining_row_new_rhs > other_row_lhs_scaled)); + + if (infeas_rhs || infeas_lhs) + { + return INFEASIBLE; + } + } + // ---------------------------------------------------------------------- + // if both rows are inequalities we find the tightest lhs and rhs + // ---------------------------------------------------------------------- + else + { + assert(!is_remaining_row_eq && !is_other_row_eq); + if (ratio > 0) + { + // possibly update rhs of remaining row + if (!is_rhs_inf_other_row && + (is_rhs_inf_remaining_row || + other_row_rhs_scaled < remaining_row_new_rhs)) + { + other_row_rhs_index = other_row_idx; + other_row_rhs_scale_factor = ratio; + remaining_row_new_rhs = other_row_rhs_scaled; + is_rhs_inf_remaining_row = false; + } + + // possibly update lhs of remaining row + if (!is_lhs_inf_other_row && + (is_lhs_inf_remaining_row || + other_row_lhs_scaled > remaining_row_new_lhs)) + { + other_row_lhs_index = other_row_idx; + other_row_lhs_scale_factor = ratio; + remaining_row_new_lhs = other_row_lhs_scaled; + is_lhs_inf_remaining_row = false; + } + } + else + { + // possibly update lhs of remaining row + if (!is_rhs_inf_other_row && + (is_lhs_inf_remaining_row || + other_row_rhs_scaled > remaining_row_new_lhs)) + { + other_row_lhs_index = other_row_idx; + other_row_lhs_scale_factor = ratio; + remaining_row_new_lhs = other_row_rhs_scaled; + is_lhs_inf_remaining_row = false; + } + + // possibly update rhs of remaining row + if (!is_lhs_inf_other_row && + (is_rhs_inf_remaining_row || + other_row_lhs_scaled < remaining_row_new_rhs)) + { + other_row_rhs_index = other_row_idx; + other_row_rhs_scale_factor = ratio; + remaining_row_new_rhs = other_row_lhs_scaled; + is_rhs_inf_remaining_row = false; + } + } + } + } + + // --------------------------------------------------------------------------------- + // if the remaining row is an inequality we may need to change the rhs and + // lhs + // --------------------------------------------------------------------------------- + PostsolveInfo *postsolve_info = constraints->state->postsolve_info; + if (!is_remaining_row_eq) + { + assert(!HAS_TAG(row_tags[remaining_row_idx], R_TAG_EQ)); + int start = A->p[remaining_row_idx].start; + int len = A->p[remaining_row_idx].end - start; + RowTag *row_tag = constraints->row_tags + remaining_row_idx; + RowView remaining_row = new_rowview(A->x + start, A->i + start, &len, NULL, + constraints->lhs + remaining_row_idx, + constraints->rhs + remaining_row_idx, + row_tag, remaining_row_idx); + + if (!is_rhs_inf_remaining_row && + remaining_row_new_rhs != rhs[remaining_row_idx]) + { + assert(!IS_ABS_INF(remaining_row_new_rhs)); + assert(remaining_row_new_rhs < rhs[remaining_row_idx]); + + if (change_rhs_of_ineq(&remaining_row, remaining_row_new_rhs, + constraints->state->col_locks, + constraints->state->dton_rows, postsolve_info, + other_row_rhs_index, + other_row_rhs_scale_factor) == INFEASIBLE) + { + return INFEASIBLE; + } + } + + if (!is_lhs_inf_remaining_row && + remaining_row_new_lhs != lhs[remaining_row_idx]) + { + assert(!IS_ABS_INF(remaining_row_new_lhs)); + assert(remaining_row_new_lhs > lhs[remaining_row_idx]); + + if (change_lhs_of_ineq(&remaining_row, remaining_row_new_lhs, + constraints->state->col_locks, + constraints->state->dton_rows, postsolve_info, + other_row_lhs_index, + other_row_lhs_scale_factor) == INFEASIBLE) + { + return INFEASIBLE; + } + } + } + + // -------------------------------------------------------------------------------- + // When we arrive here we know which row should remain in the problem. We + // mark all other rows as inactive. + // -------------------------------------------------------------------------------- + for (int i = 0; i < bin_size; ++i) + { + if (bin[i] != remaining_row_idx) + { + set_row_to_inactive(bin[i], constraints->row_tags + bin[i], + constraints->state->rows_to_delete, postsolve_info, + 0.0); + } + } + + return UNCHANGED; +} + +// groups is a vector of size 'n_groups', where 'parallel_rows[i]' is the +// index of the first row in group 'i' +static PresolveStatus process_all_bins(const Constraints *constraints, + const int *parallel_rows, const iVec *groups) +{ + int n_groups = groups->len - 1; + + for (int i = 0; i < n_groups; ++i) + { + int n_rows_this_group = groups->data[i + 1] - groups->data[i]; + if (process_single_bin(constraints, parallel_rows + groups->data[i], + n_rows_this_group) == INFEASIBLE) + { + return INFEASIBLE; + } + } + + return UNCHANGED; +} + +PresolveStatus remove_parallel_rows(Constraints *constraints) +{ + assert(constraints->state->ston_rows->len == 0); + assert(constraints->state->empty_rows->len == 0); + assert(constraints->state->empty_cols->len == 0); + DEBUG(verify_problem_up_to_date(constraints)); + + int *parallel_rows = constraints->state->work->iwork_n_rows; + int *sparsity_IDs = constraints->state->work->iwork1_max_nrows_ncols; + int *coeff_hashes = constraints->state->work->iwork2_max_nrows_ncols; + iVec *group_starts = constraints->state->work->int_vec; + + find_parallel_rows(constraints->A, constraints->row_tags, group_starts, + parallel_rows, sparsity_IDs, coeff_hashes, R_TAG_INACTIVE); + + PresolveStatus status = + process_all_bins(constraints, parallel_rows, group_starts); + + delete_inactive_rows(constraints); + + DEBUG(verify_problem_up_to_date(constraints)); + + return status; +} \ No newline at end of file diff --git a/src/explorers/Primal_propagation.c b/src/explorers/Primal_propagation.c new file mode 100644 index 00000000..cd36d3a3 --- /dev/null +++ b/src/explorers/Primal_propagation.c @@ -0,0 +1,886 @@ +/* + * Copyright 2025 Daniel Cederberg + * + * This file is part of the PSLP project (LP Presolver). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "Primal_propagation.h" +#include "Activity.h" +#include "Bounds.h" +#include "Constraints.h" +#include "CoreTransformations.h" +#include "Debugger.h" +#include "Matrix.h" +#include "Numerics.h" +#include "Problem.h" +#include "RowColViews.h" +#include "SimpleReductions.h" +#include "State.h" +#include "Workspace.h" +#include "utils.h" + +static PresolveStatus update_lb_within_propagation(double new_lb, double *lb, + double ub, int col, ColTag *cTag, + Problem *prob, + bool *finite_bound_tightening) +{ + assert(ABS(new_lb) != INF && !HAS_TAG(*cTag, C_TAG_INACTIVE)); + assert((HAS_TAG(*cTag, C_TAG_LB_INF) || new_lb >= *lb) && !IS_HUGE(new_lb)); + + bool is_ub_inf = HAS_TAG(*cTag, C_TAG_UB_INF); + bool is_lb_inf = HAS_TAG(*cTag, C_TAG_LB_INF); + + Constraints *constraints = prob->constraints; + const Matrix *AT = constraints->AT; + const double *vals = AT->x + AT->p[col].start; + const int *rows = AT->i + AT->p[col].start; + int len = AT->p[col].end - AT->p[col].start; + + // ----------------------------------------------------------------------- + // If ub is finite we compare it to the new lower bound (we check for + // infeasibility and if the lower bound is so close to the ub that we + // can fix the variable). + // ----------------------------------------------------------------------- + if (!is_ub_inf) + { + assert(ABS(ub) != -INF); + + // check for infeasibility + if (new_lb >= ub + FEAS_TOL) + { + return INFEASIBLE; + } + + // check if variable can be fixed + if (new_lb >= ub || (ub - new_lb) * get_max_abs(vals, len) <= FEAS_TOL) + { + save_retrieval_bound_change_no_row(constraints->state->postsolve_info, + col, new_lb, ub, false); + + // doing this later would make the postsolve more elegant + fix_col(constraints, col, ub, prob->obj->c[col]); + return REDUCED; + } + } + + // ----------------------------------------------------------------------- + // We always tighten infinite bounds, and like Gurobi we reject too small + // bound changes. + // ----------------------------------------------------------------------- + if (is_lb_inf || (*finite_bound_tightening && (new_lb - *lb > FEAS_TOL * 1e4) && + (new_lb - *lb > 1e-2 * ABS(*lb)))) + { + // very important to scale bound marginal with ABS + if (!IS_INTEGRAL(new_lb)) + { + new_lb -= BOUND_MARGINAL * ABS(new_lb); + } + + REMOVE_TAG(*cTag, C_TAG_LB_INF); + + double old_bound = *lb; + *lb = new_lb; + + update_activities_bound_change( + constraints->state->activities, constraints->A, constraints->bounds, + vals, rows, len, old_bound, new_lb, !is_lb_inf, true, + constraints->state->updated_activities HUGE_BOUND_IS_NOT_OK); + + save_retrieval_bound_change_no_row(constraints->state->postsolve_info, col, + new_lb, ub, false); + + return REDUCED; + } + + return UNCHANGED; +} + +static PresolveStatus update_ub_within_propagation(double new_ub, double *ub, + double lb, int col, ColTag *cTag, + Problem *prob, + bool *finite_bound_tightening) +{ + assert(new_ub != -INF && new_ub != INF && !HAS_TAG(*cTag, C_TAG_INACTIVE)); + assert((HAS_TAG(*cTag, C_TAG_UB_INF) || new_ub <= *ub) && !IS_HUGE(new_ub)); + + bool is_lb_inf = HAS_TAG(*cTag, C_TAG_LB_INF); + bool is_ub_inf = HAS_TAG(*cTag, C_TAG_UB_INF); + + Constraints *constraints = prob->constraints; + const Matrix *AT = constraints->AT; + const double *vals = AT->x + AT->p[col].start; + const int *rows = AT->i + AT->p[col].start; + int len = AT->p[col].end - AT->p[col].start; + + // ----------------------------------------------------------------------- + // If lb is finite we compare it to the new upper bound (we check for + // infeasibility and if the upper bound is so close to the lb that we + // can fix the variable). + // ----------------------------------------------------------------------- + if (!is_lb_inf) + { + assert(ABS(lb) != -INF); + + if (new_ub <= lb - FEAS_TOL) + { + return INFEASIBLE; + } + + // check if variable can be fixed + if (new_ub <= lb || (new_ub - lb) * get_max_abs(vals, len) <= FEAS_TOL) + { + save_retrieval_bound_change_no_row(constraints->state->postsolve_info, + col, new_ub, lb, true); + + /// doing this later would make the postsolve more elegant + fix_col(constraints, col, lb, prob->obj->c[col]); + return REDUCED; + } + } + + // ----------------------------------------------------------------------- + // We always tighten infinite bounds, and like Gurobi we reject too small + // bound changes. + // ----------------------------------------------------------------------- + if (is_ub_inf || (*finite_bound_tightening && (*ub - new_ub > FEAS_TOL * 1e4) && + (*ub - new_ub > 1e-2 * ABS(*ub)))) + { + // very important to scale bound marginal with ABS + if (!IS_INTEGRAL(new_ub)) + { + new_ub += BOUND_MARGINAL * ABS(new_ub); + } + + REMOVE_TAG(*cTag, C_TAG_UB_INF); + + double old_bound = *ub; + *ub = new_ub; + + update_activities_bound_change( + constraints->state->activities, constraints->A, constraints->bounds, + vals, rows, len, old_bound, new_ub, !is_ub_inf, false, + constraints->state->updated_activities HUGE_BOUND_IS_NOT_OK); + + save_retrieval_bound_change_no_row(constraints->state->postsolve_info, col, + new_ub, lb, true); + + return REDUCED; + } + + return UNCHANGED; +} + +/* Bound tightening based on the right-hand side of the + constraint. + + Bound tightening can occur in several scenarios: + 1) the RHS is finite and the min activity is valid (ie. when + nInfMin == 0). In this case every variable in the constraint + is a candidate for bound tightening. + 2) the RHS is finite and nInfMin == 1. In this case only the variable + contributing with the infinite bound is a candidate for bound + tightening. + 3) If the RHS is infinite, and the max activity is valid, and + there is one variable contributing with an infinite bound to the + min activity, then we may be able to tight the finite bound of + the variable contributing the infinite bound. + + This function does not directly update act, but it may be updated + by other functions called by this function. +*/ +static inline PresolveStatus +bound_tightening_single_row_rhs(const ConstRowView *row, const Activity *act, + Problem *problem, Bound *bounds, ColTag *col_tags, + void *arg1, BoundUpdater updater_lb, + BoundUpdater updater_ub, int *num_of_bounds_changed) +{ + int j = 1; + int k = -1; + double Aik = INF; + double implied_ub, implied_lb, implied_bound, min_act; + PresolveStatus status = UNCHANGED; + PresolveStatus temp_status = UNCHANGED; + bool is_rhs_inf = HAS_TAG(*row->tag, R_TAG_RHS_INF); + bool is_lb_inf = false; + bool is_ub_inf = false; + bool is_col_inactive; + + // ------------------------------------------------------------------ + // Case 1: assume RHS is finite and the min activity is valid. Then + // the following bounds are valid for every xk in row i: + // * if aik > 0: xk <= lb[k] + (rhs[i] - minAct) / aik + // * if aik < 0: xk >= ub[k] + (rhs[i] - minAct) / aik. + // ------------------------------------------------------------------ + if (!is_rhs_inf && act->n_inf_min == 0) + { + assert(ABS(*row->rhs) != INF && ABS(act->min) != INF); + + for (j = 0; j < *row->len; ++j) + { + k = row->cols[j]; + + // In primal propagation this might happen: suppose row 1 is forcing + // and fixes x1. Assume that x1 appears in row 2. When we loop + // through row 2 + // we want to skip x1. + if (HAS_TAG(col_tags[k], C_TAG_INACTIVE)) + { + continue; + } + + Aik = row->vals[j]; + + // -------------------------------------------------------------------- + // if Aik > 0 we might be able to tighten the upper bound if + // implied bound is tighter (and not too large) + // -------------------------------------------------------------------- + if (Aik > 0) + { + assert(!IS_ABS_INF(bounds[k].lb) && + !HAS_TAG(col_tags[k], C_TAG_LB_INF)); + implied_ub = bounds[k].lb + (*row->rhs - act->min) / Aik; + is_ub_inf = HAS_TAG(col_tags[k], C_TAG_UB_INF); + + if ((is_ub_inf || implied_ub < bounds[k].ub) && !IS_HUGE(implied_ub)) + { + temp_status = + updater_ub(implied_ub, &(bounds[k].ub), bounds[k].lb, k, + col_tags + k, problem, arg1); + + if (temp_status == REDUCED) + { + (*num_of_bounds_changed)++; + } + + status |= temp_status; + } + } + // --------------------------------------------------------------------- + // if Aik < 0 we might be able to tighten the lower bound if + // implied bound is tighter (and not too large) + // --------------------------------------------------------------------- + else + { + assert(Aik < 0 && ABS(bounds[k].ub) != INF); + assert(!HAS_TAG(col_tags[k], C_TAG_UB_INF)); + + implied_lb = bounds[k].ub + (*row->rhs - act->min) / Aik; + is_lb_inf = HAS_TAG(col_tags[k], C_TAG_LB_INF); + + if ((is_lb_inf || implied_lb > bounds[k].lb) && !IS_HUGE(implied_lb)) + { + temp_status = + updater_lb(implied_lb, &(bounds[k].lb), bounds[k].ub, k, + col_tags + k, problem, arg1); + + if (temp_status == REDUCED) + { + (*num_of_bounds_changed)++; + } + + status |= temp_status; + } + } + } + } + // ----------------------------------------------------------------------------- + // Case 2: assume RHS is finite and nInfMin = 1. Let xk be the variable + // contributing with the infinite bound. Then the following bounds + // are valid for xk: + // * if aik > 0: xk <= (rhs[i] - \sum_{j != k} aij xj) / aik + // <= (rhs[i] - \sum_{j != k : aij > 0} aij lb[j] + // - \sum_{j != k : aij < 0} aij ub[j]) / aik + // + // * if aik < 0: xk >= (rhs[i] - \sum_{j \neq k} a_ij xj) / aik + // >= (rhs[i] - \sum_{j != k : aij > 0} aij lb[j] + // - \sum_{j != k : aij < 0} aij ub[j]) / aik + // + // Case 3: assume RHS is infinite, that the max activity is valid and + // nInfMin = 1. Let xk be the variable contributing with the + // infinite bound. + // * if aik > 0, then lb[k] = -inf and ub[k] < inf. The following bound + // is valid: xk <= (maxAct - minAct) / aik + // * if aik < 0, then ub[k] = inf and lb[k] > -inf. The following bound + // is valid: xk >= (maxAct - minAct) / aik. + // ----------------------------------------------------------------------------- + else if ((!is_rhs_inf || act->n_inf_max == 0) && act->n_inf_min == 1) + { + // find the variable contributing with the infinite bound + for (j = 0; j < *row->len; ++j) + { + Aik = row->vals[j]; + k = row->cols[j]; + is_lb_inf = HAS_TAG(col_tags[k], C_TAG_LB_INF); + is_ub_inf = HAS_TAG(col_tags[k], C_TAG_UB_INF); + is_col_inactive = HAS_TAG(col_tags[k], C_TAG_INACTIVE); + + if (((Aik > 0 && is_lb_inf) || (Aik < 0 && is_ub_inf)) && + !is_col_inactive) + { + break; + } + } + + assert(j != *row->len && Aik != 0 && !is_col_inactive); + + // ------------------------------------------------------------------------------ + // compute an implied bound and use it if it is tighter + // ------------------------------------------------------------------------------ + min_act = + compute_min_act_one_tag(row->vals, row->cols, *row->len, bounds, k); + assert((is_rhs_inf && act->n_inf_max == 0 && ABS(act->max) != INF) || + (!is_rhs_inf && ABS(*row->rhs) != INF)); + assert(ABS(min_act) != INF && Aik != INF && Aik != 0); + implied_bound = + (is_rhs_inf) ? (act->max - min_act) / Aik : (*row->rhs - min_act) / Aik; + + if (Aik > 0) + { + assert(HAS_TAG(col_tags[k], C_TAG_LB_INF)); + if ((is_ub_inf || implied_bound < bounds[k].ub) && + !IS_HUGE(implied_bound)) + { + temp_status = + updater_ub(implied_bound, &(bounds[k].ub), bounds[k].lb, k, + col_tags + k, problem, arg1); + + if (temp_status == REDUCED) + { + (*num_of_bounds_changed)++; + } + status |= temp_status; + } + } + else + { + assert(Aik < 0 && HAS_TAG(col_tags[k], C_TAG_UB_INF)); + if ((is_lb_inf || implied_bound > bounds[k].lb) && + !IS_HUGE(implied_bound)) + { + temp_status = + updater_lb(implied_bound, &(bounds[k].lb), bounds[k].ub, k, + col_tags + k, problem, arg1); + + if (temp_status == REDUCED) + { + (*num_of_bounds_changed)++; + } + + status |= temp_status; + } + } + } + + return status; +} + +/* Bound tightening based on the left-hand side of the + constraint. Bound tightening can occur in several scenarios: + 1) the LHS is finite and the max activity is valid (ie. when + nInfMax == 0). In this case every variable in the constraint + is a candidate for bound tightening. + 2) the LHS is finite and nInfMax == 1. In this case only the variable + contributing with the infinite bound is a candidate for bound + tightening. + 3) If the LHS is infinite, and the min activity is valid, and + there is one variable contributing with an infinite bound to the + max activity, then we may be able to tight the finite bound of + the variable contributing with the infinite bound. +*/ +static inline PresolveStatus +bound_tightening_single_row_lhs(const ConstRowView *row, const Activity *act, + Problem *problem, Bound *bounds, ColTag *col_tags, + void *arg1, BoundUpdater updater_lb, + BoundUpdater updater_ub, int *num_of_bounds_changed) +{ + int j = -1; + int k = -1; + double Aik = INF; + double implied_ub, implied_lb, implied_bound, max_act; + PresolveStatus status = UNCHANGED; + PresolveStatus temp_status = UNCHANGED; + bool is_lhs_inf = HAS_TAG(*row->tag, R_TAG_LHS_INF); + bool is_lb_inf = false; + bool is_ub_inf = false; + bool is_col_inactive; + + // ------------------------------------------------------------------ + // Case 1: assume LHS is finite and the max activity is valid. Then + // the following bounds are valid for every xk in row i: + // * if aik > 0: xk >= ub[k] + (lhs[i] - maxAct) / aik + // * if aik < 0: xk <= lb[k] + (lhs[i] - maxAct) / aik. + // ------------------------------------------------------------------ + if (!is_lhs_inf && act->n_inf_max == 0) + { + assert(ABS(*row->lhs) != INF && ABS(act->max) != INF); + + for (j = 0; j < *row->len; ++j) + { + k = row->cols[j]; + + // Consider the following scenario. Suppose row 1 is forcing and + // fixes x1. Assume that x1 appears in row 2. When we loop + // through row 2 we want to skip x1. + if (HAS_TAG(col_tags[k], C_TAG_INACTIVE)) + { + continue; + } + + Aik = row->vals[j]; + + // -------------------------------------------------------------------- + // if Aik > 0 we might be able to tighten the upper bound if + // implied bound is tighter (and not too large) + // -------------------------------------------------------------------- + if (Aik > 0) + { + assert(ABS(bounds[k].ub) != INF && + !HAS_TAG(col_tags[k], C_TAG_UB_INF)); + + implied_lb = bounds[k].ub + (*row->lhs - act->max) / Aik; + is_lb_inf = HAS_TAG(col_tags[k], C_TAG_LB_INF); + + if ((is_lb_inf || implied_lb > bounds[k].lb) && !IS_HUGE(implied_lb)) + { + temp_status = + updater_lb(implied_lb, &(bounds[k].lb), bounds[k].ub, k, + col_tags + k, problem, arg1); + + if (temp_status == REDUCED) + { + (*num_of_bounds_changed)++; + } + + status |= temp_status; + } + } + // --------------------------------------------------------------------- + // if Aik < 0 we might be able to tighten the lower bound if + // implied bound is tighter (and not too large) + // --------------------------------------------------------------------- + else + { + assert(Aik < 0 && ABS(bounds[k].lb) != -INF); + assert(!HAS_TAG(col_tags[k], C_TAG_LB_INF)); + + implied_ub = bounds[k].lb + (*row->lhs - act->max) / Aik; + is_ub_inf = HAS_TAG(col_tags[k], C_TAG_UB_INF); + + if ((is_ub_inf || implied_ub < bounds[k].ub) && !IS_HUGE(implied_ub)) + { + temp_status = + updater_ub(implied_ub, &(bounds[k].ub), bounds[k].lb, k, + col_tags + k, problem, arg1); + + if (temp_status == REDUCED) + { + (*num_of_bounds_changed)++; + } + + status |= temp_status; + } + } + } + } + + // ------------------------------------------------------------------------ + // Case 2: assume LHS is finite and nInfMax = 1. Let xk be the variable + // contributing with the infinite bound. Then the following bounds + // are valid for xk: + // * if aik > 0: xk >= (lhs[i] - \sum_{j != k} aij xj) / aik + // >= (lhs[i] - \sum_{j != k : aij > 0} aij ub[j] + // - \sum_{j != k : aij < 0} aij lb[j]) / aik + // + // * if aik < 0: xk <= (lhs[i] - \sum_{j \neq k} a_ij xj) / aik + // <= (lhs[i] - \sum_{j != k : aij > 0} aij ub[j] + // - \sum_{j != k : aij < 0} aij lb[j]) / aik + // + // Case 3: assume LHS is infinite, that the min activity is valid and + // nInfMax = 1. Let xk be the variable contributing with the + // infinite bound. + // * if aik > 0, then ub[k] = inf and lb[k] > -inf. The following bound + // is valid: xk >= (minAct - maxAct) / aik + // * if aik < 0, then lb[k] = -inf and ub[k] < inf. The following bound + // is valid: xk <= (minAct - maxAct) / aik + // ----------------------------------------------------------------------- + else if ((!is_lhs_inf || act->n_inf_min == 0) && act->n_inf_max == 1) + { + // find the variable contributing with the infinite bound + for (j = 0; j < *row->len; ++j) + { + Aik = row->vals[j]; + k = row->cols[j]; + is_lb_inf = HAS_TAG(col_tags[k], C_TAG_LB_INF); + is_ub_inf = HAS_TAG(col_tags[k], C_TAG_UB_INF); + is_col_inactive = HAS_TAG(col_tags[k], C_TAG_INACTIVE); + + if (((Aik > 0 && is_ub_inf) || (Aik < 0 && is_lb_inf)) && + !is_col_inactive) + { + break; + } + } + + assert(j != *row->len && Aik != 0 && !is_col_inactive); + + // ------------------------------------------------------------------------------ + // compute an implied bound and use it if it is tighter + // ------------------------------------------------------------------------------ + max_act = + compute_max_act_one_tag(row->vals, row->cols, *row->len, bounds, k); + assert((act->n_inf_min == 0 && ABS(act->min) != INF && is_lhs_inf) || + (!is_lhs_inf && ABS(*row->lhs) != INF)); + assert(max_act != INF); + assert(Aik != INF && Aik != 0); + implied_bound = + (is_lhs_inf) ? (act->min - max_act) / Aik : (*row->lhs - max_act) / Aik; + + if (Aik > 0) + { + assert(HAS_TAG(col_tags[k], C_TAG_UB_INF)); + + if ((is_lb_inf || implied_bound > bounds[k].lb) && + !IS_HUGE(implied_bound)) + { + temp_status = + updater_lb(implied_bound, &(bounds[k].lb), bounds[k].ub, k, + col_tags + k, problem, arg1); + + if (temp_status == REDUCED) + { + (*num_of_bounds_changed)++; + } + + status |= temp_status; + } + } + else + { + assert(Aik < 0 && HAS_TAG(col_tags[k], C_TAG_LB_INF)); + + if ((is_ub_inf || implied_bound < bounds[k].ub) && + !IS_HUGE(implied_bound)) + { + temp_status = + updater_ub(implied_bound, &(bounds[k].ub), bounds[k].lb, k, + col_tags + k, problem, arg1); + + if (temp_status == REDUCED) + { + (*num_of_bounds_changed)++; + } + + status |= temp_status; + } + } + } + + return status; +} + +/* This function is guaranteed to return infeasible if the problem is + infeasible, but not necessarily reduced if the problem is reduced. This + is a design choice. */ +PresolveStatus bound_tightening_single_row(const ConstRowView *row, + const Activity *act, Problem *problem, + Bound *bounds, ColTag *col_tags, + void *arg1, BoundUpdater updater_lb, + BoundUpdater updater_ub) +{ + assert(!HAS_TAG(*row->tag, R_TAG_INACTIVE) && + (act->n_inf_min <= 1 || act->n_inf_max <= 1)); + + PresolveStatus status = UNCHANGED; + + int num_of_bound_changes = 0; + + status |= bound_tightening_single_row_rhs(row, act, problem, bounds, col_tags, + arg1, updater_lb, updater_ub, + &num_of_bound_changes); + + status |= bound_tightening_single_row_lhs(row, act, problem, bounds, col_tags, + arg1, updater_lb, updater_ub, + &num_of_bound_changes); + + // dual postsolve if a bound change occured (this assumes primal propagation. We + // must make sure this is not called during dual propagation) + if (num_of_bound_changes > 0) + { + save_retrieval_bound_change_the_row( + problem->constraints->state->postsolve_info, row->i, row->cols, + row->vals, *row->len, num_of_bound_changes); + } + return status; +} + +PresolveStatus propagate_primal(Problem *prob, bool finite_bound_tightening) +{ + assert(prob->constraints->state->ston_rows->len == 0); + assert(prob->constraints->state->empty_rows->len == 0); + DEBUG(verify_problem_up_to_date(prob->constraints)); + + // empty columns may occur because of check_activities + // assert(prob->constraints->state->empty_cols->len == 0); + + Constraints *constraints = prob->constraints; + const iVec *updated_activities = constraints->state->updated_activities; + Activity *acts = constraints->state->activities; + const int *row_sizes = constraints->state->row_sizes; + const RowTag *row_tags = constraints->row_tags; + const Matrix *A = constraints->A; + Bound *bounds = constraints->bounds; + ColTag *col_tags = constraints->col_tags; + const double *rhs = constraints->rhs; + const double *lhs = constraints->lhs; + int i, ii, current_len; + PresolveStatus status = UNCHANGED; + + // check that updated_activities has no duplicates, and that all + // activities in updated_activities have status ADDED, and that these + // have n_inf_min = 0 or n_inf_max = 0. + DEBUG(verify_no_duplicates_sort(updated_activities)); + DEBUG(verify_row_states(acts, updated_activities)); + +#ifndef NDEBUG + bool *HAVE_ROWS_BEEN_PROP = (bool *) ps_calloc(A->m, sizeof(bool)); +#endif + + // ------------------------------------------------------------------------- + // Loop through and propagate the rows in order to tighten variable bounds. + // We skip rows that are that are inactive, or will be handled by + // empty rows / stonrows, or do not have the potential to offer a reduction. + // + // Note that bound_tightening_single_row might append to updated_activities, + // so we must be careful when we loop through it. This is why we use the + // double-loop below. The debug code below is used to verify that we + // perform at most one round of domain propagation on each constraint. + // ------------------------------------------------------------------------- + ii = 0; + while (ii < updated_activities->len) + { + current_len = updated_activities->len; + for (; ii < current_len; ++ii) + { + i = updated_activities->data[ii]; + + // can it ever happen that n_inf_min and n_inf_max are both not + // zero? + assert(acts[i].n_inf_min == 0 || acts[i].n_inf_max == 0); + assert(acts[i].status != NOT_ADDED); + + if (row_sizes[i] <= 1 || acts[i].status != ADDED || + !(acts[i].n_inf_min == 0 || acts[i].n_inf_max == 0 || + (acts[i].n_inf_min == 1 && !HAS_TAG(row_tags[i], R_TAG_RHS_INF)) || + (acts[i].n_inf_max == 1 && !HAS_TAG(row_tags[i], R_TAG_LHS_INF)))) + { + continue; + } + + assert(row_sizes[i] > 1 && !HAVE_ROWS_BEEN_PROP[i]); + DEBUG(HAVE_ROWS_BEEN_PROP[i] = true;); + + ConstRowView row = new_const_rowview( + A->x + A->p[i].start, A->i + A->p[i].start, row_sizes + i, A->p + i, + lhs + i, rhs + i, row_tags + i, i); + + acts[i].status = PROPAGATED_THIS_ROUND; + + status = bound_tightening_single_row( + &row, acts + i, prob, bounds, col_tags, &finite_bound_tightening, + (BoundUpdater) update_lb_within_propagation, + (BoundUpdater) update_ub_within_propagation); + + if (HAS_STATUS(status, INFEASIBLE)) + { + return INFEASIBLE; + } + } + } + +#ifndef NDEBUG + for (ii = 0; ii < A->m; ++ii) + { + if (HAS_TAG(row_tags[ii], R_TAG_INACTIVE)) + { + continue; + } + + if (acts[ii].status != NOT_ADDED) + { + assert(HAVE_ROWS_BEEN_PROP[ii]); + } + else + { + assert(!HAVE_ROWS_BEEN_PROP[ii]); + } + } +#endif + + DEBUG(PS_FREE(HAVE_ROWS_BEEN_PROP);); + + // ----------------------------------------------------------------------------- + // Loop through updated activities and add some of them to the next round. + // Also reset the status of all activities. + // ----------------------------------------------------------------------------- + int *iwork_n_rows = constraints->state->work->iwork_n_rows; + int new_len = 0; + for (ii = 0; ii < updated_activities->len; ++ii) + { + if (acts[updated_activities->data[ii]].status == PROPAGATE_NEXT_ROUND) + { + iwork_n_rows[new_len++] = updated_activities->data[ii]; + acts[updated_activities->data[ii]].status = ADDED; + } + } + DEBUG(verify_no_duplicates_sort_ptr(iwork_n_rows, new_len)); + + for (ii = 0; ii < A->m; ++ii) + { + acts[ii].status = NOT_ADDED; + } + + for (ii = 0; ii < new_len; ++ii) + { + acts[iwork_n_rows[ii]].status = ADDED; + } + + iVec_clear_no_resize(constraints->state->updated_activities); + iVec_append_array(constraints->state->updated_activities, iwork_n_rows, new_len); + //---------------------------------------------------------------------------- + + // delete contribution of fixed columns from rhs and lhs etc. + delete_fixed_cols_from_problem(prob); + + // The domain propagation might fix columns, so there may be inactive cols. + // When we fix these columns we might get zero rows etc. + delete_inactive_cols_from_A_and_AT(constraints); + + // We have not yet deleted the zero rows so there should be no rows + // to delete right now. + assert(constraints->state->rows_to_delete->len == 0); + + // the rows that were forcing are empty now so we process them + if (remove_empty_rows(constraints) == INFEASIBLE) + { + return INFEASIBLE; + } + + DEBUG(verify_problem_up_to_date(constraints)); + return UNCHANGED; +} + +// Static variable to hold col_sizes (for qsort) +static const int *global_col_sizes; + +int compare_col_len(const void *a, const void *b) +{ + int idx_a = *(const int *) a; + int idx_b = *(const int *) b; + + if (global_col_sizes[idx_a] < global_col_sizes[idx_b]) + { + return -1; + } + if (global_col_sizes[idx_a] > global_col_sizes[idx_b]) + { + return 1; + } + + // if equal len, sort by index + return idx_a - idx_b; +} + +void remove_redundant_bounds(Constraints *constraints) +{ + const Matrix *A = constraints->A; + const Matrix *AT = constraints->AT; + double *lhs = constraints->lhs; + double *rhs = constraints->rhs; + RowTag *row_tags = constraints->row_tags; + ColTag *col_tags = constraints->col_tags; + Bound *bounds = constraints->bounds; + int n_cols = constraints->n; + Activity *acts = constraints->state->activities; + const int *col_sizes = constraints->state->col_sizes; + + bool is_ub_inf, is_lb_inf; + int ii, jj; + int removed_bounds = 0; + + // ------------------------------------------------------------------------ + // Sort columns by column sizes. We want to process the shortest columns + // first, since when we remove a bound from a variable, the activities of + // all rows containing that variable become invalid. + // ------------------------------------------------------------------------ + int *col_order = constraints->state->work->iwork_n_cols; + for (ii = 0; ii < n_cols; ++ii) + { + col_order[ii] = ii; + } + + global_col_sizes = col_sizes; + qsort(col_order, n_cols, sizeof(int), compare_col_len); + global_col_sizes = NULL; + + for (jj = 0; jj < n_cols; jj++) + { + ii = col_order[jj]; + if (HAS_TAG(col_tags[ii], C_TAG_INACTIVE)) + { + continue; + } + + is_ub_inf = HAS_TAG(col_tags[ii], C_TAG_UB_INF); + is_lb_inf = HAS_TAG(col_tags[ii], C_TAG_LB_INF); + + if (is_ub_inf && is_lb_inf) + { + continue; + } + + ConstColView col = new_const_colview( + AT->x + AT->p[ii].start, AT->i + AT->p[ii].start, col_sizes + ii, + AT->p + ii, &bounds[ii].lb, &bounds[ii].ub, col_tags + ii, ii); + + // TODO: can we check if we can remove both bounds? + if (is_ub_inf) + { + assert(!is_lb_inf); + + if (is_lb_implied_free(&col, acts, lhs, rhs, row_tags, A, col_tags, + bounds)) + { + remove_finite_lb_from_activities(&col, acts, bounds[ii].lb); + removed_bounds++; + DEBUG(bounds[ii].lb = -INF); + UPDATE_TAG(col_tags[ii], C_TAG_LB_INF); + } + } + else + { + assert(!is_ub_inf); + + if (is_ub_implied_free(&col, acts, lhs, rhs, row_tags, A, col_tags, + bounds)) + { + remove_finite_ub_from_activities(&col, acts, bounds[ii].ub); + removed_bounds++; + DEBUG(bounds[ii].ub = INF); + UPDATE_TAG(col_tags[ii], C_TAG_UB_INF); + } + } + } + + DEBUG(verify_activities(constraints)); +} \ No newline at end of file diff --git a/src/explorers/SimpleReductions.c b/src/explorers/SimpleReductions.c new file mode 100644 index 00000000..05d4cae7 --- /dev/null +++ b/src/explorers/SimpleReductions.c @@ -0,0 +1,703 @@ +/* + * Copyright 2025 Daniel Cederberg + * + * This file is part of the PSLP project (LP Presolver). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "SimpleReductions.h" +#include "Activity.h" +#include "Bounds.h" +#include "Constraints.h" +#include "CoreTransformations.h" +#include "Debugger.h" +#include "Locks.h" +#include "Matrix.h" +#include "Numerics.h" +#include "Postsolver.h" +#include "Problem.h" +#include "RowColViews.h" +#include "State.h" + +void delete_fixed_cols_from_problem(Problem *prob) +{ + const Constraints *constraints = prob->constraints; + const Matrix *AT = constraints->AT; + const Bound *bounds = constraints->bounds; + const RowTag *row_tags = constraints->row_tags; + double *lhs = constraints->lhs; + double *rhs = constraints->rhs; + Activity *activities = constraints->state->activities; + const iVec *fixed_cols_to_delete = + prob->constraints->state->fixed_cols_to_delete; + + int i, j, col, len, row; + int *rows; + double *vals; + + for (i = 0; i < fixed_cols_to_delete->len; ++i) + { + col = fixed_cols_to_delete->data[i]; + assert(HAS_TAG(constraints->col_tags[col], C_TAG_FIXED)); + + // if a variable has been fixed to INF it should not have been pushed to + // fixed_cols_to_delete, so it shouldn't appear here + assert(!IS_ABS_INF(bounds[col].ub)); + assert(bounds[col].lb == bounds[col].ub); + + // If the variable has been fixed to 0 or INF we must not do anything. + // It is not necessary to update n_inf_min and n_inf_max (this has + // already been done by boundChange). + double ub = bounds[col].ub; + if (ub == 0) + { + continue; + } + + vals = AT->x + AT->p[col].start; + rows = AT->i + AT->p[col].start; + len = AT->p[col].end - AT->p[col].start; + + // ------------------------------------------------------------------------ + // update left-and-right hand sides of constraints + // ------------------------------------------------------------------------ + for (j = 0; j < len; ++j) + { + row = rows[j]; + + // When a singleton row is used to fix a variable, the singleton row + // will be inactive when trying to remove the contribution of the + // fixed column. We want to skip it. + if (HAS_TAG(row_tags[row], R_TAG_INACTIVE)) + { + continue; + } + + double contribution = vals[j] * ub; + + if (!HAS_TAG(row_tags[row], R_TAG_LHS_INF)) + { + lhs[row] -= contribution; + } + + if (!HAS_TAG(row_tags[row], R_TAG_RHS_INF)) + { + rhs[row] -= contribution; + } + + // not necessary to update n_inf_min and n_in_max since a fixed + // variable has lb = ub = finite value so it doesn't contribute with + // infinite bounds to the activity + if (activities[row].n_inf_max == 0) + { + activities[row].max -= contribution; + } + + if (activities[row].n_inf_min == 0) + { + activities[row].min -= contribution; + } + + // Can a ranged row become an inequality due to numerics? + assert(HAS_TAG(row_tags[row], R_TAG_LHS_INF) || + HAS_TAG(row_tags[row], R_TAG_RHS_INF) || + HAS_TAG(row_tags[row], R_TAG_EQ) || + !IS_EQUAL_FEAS_TOL(lhs[row], rhs[row])); + } + + // fix variable in the objective + fix_var_in_obj(prob->obj, col, ub); + } +} + +static inline PresolveStatus remove_stonrow(Problem *prob, int row) +{ + // assume row 'row' is a singleton with variable k + Constraints *constrs = prob->constraints; + double lhs = constrs->lhs[row]; + double rhs = constrs->rhs[row]; + RowTag *row_tag = constrs->row_tags + row; + RowRange *row_range = constrs->A->p + row; + int k = constrs->A->i[row_range->start]; + double aik = constrs->A->x[row_range->start]; + ColTag col_tag = constrs->col_tags[k]; + int *row_size = constrs->state->row_sizes + row; + PostsolveInfo *postsolve_info = constrs->state->postsolve_info; + + assert(!HAS_TAG(*row_tag, R_TAG_INACTIVE) && + !HAS_TAG(col_tag, C_TAG_SUBSTITUTED)); + + // if the same variable appears in two singleton rows and the first one + // that is processed is an equality row, then the variable has been fixed + // and we should do nothing + if (HAS_TAG(col_tag, C_TAG_FIXED)) + { + return UNCHANGED; + } + + assert(!HAS_TAG(col_tag, C_TAG_INACTIVE)); + + // ---------------------------------------------------------------------- + // if the row is an equality, we fix the variable + // ---------------------------------------------------------------------- + if (HAS_TAG(*row_tag, R_TAG_EQ)) + { + assert(lhs == rhs); + + // printf("fixing col %d to %f from row %d \n", k, rhs / aik, row); + if (fix_col(constrs, k, rhs / aik, prob->obj->c[k]) == INFEASIBLE) + { + return INFEASIBLE; + } + + // We manually mark the row inactive, since it is not necessary to + // append it to the list of rows to delete. (It is not necessary + // because the column has been appended to fixed_cols_to_delete.) + *row_size = SIZE_INACTIVE_ROW; + RESET_TAG(*row_tag, R_TAG_INACTIVE); + row_range->end = row_range->start; + constrs->A->nnz--; + + // dual postsolve: + const Matrix *AT = constrs->AT; + const int *rows = AT->i + AT->p[k].start; + const double *vals = AT->x + AT->p[k].start; + int len = AT->p[k].end - AT->p[k].start; + save_retrieval_added_rows(postsolve_info, row, rows, vals, len, aik); + save_retrieval_deleted_row(postsolve_info, row, prob->obj->c[k] / aik); + } + // ---------------------------------------------------------------------- + // if the row is an inequality, we update the bounds (if they are tighter + // than the current bounds) + // ---------------------------------------------------------------------- + else + { + bool rhs_inf = HAS_TAG(*row_tag, R_TAG_RHS_INF); + bool lhs_inf = HAS_TAG(*row_tag, R_TAG_LHS_INF); + + if (aik > 0) + { + if ((!rhs_inf && (update_ub(constrs, k, rhs / aik, + row HUGE_BOUND_IS_OK) == INFEASIBLE)) || + (!lhs_inf && (update_lb(constrs, k, lhs / aik, + row HUGE_BOUND_IS_OK) == INFEASIBLE))) + { + return INFEASIBLE; + } + } + else + { + if ((!rhs_inf && update_lb(constrs, k, rhs / aik, + row HUGE_BOUND_IS_OK) == INFEASIBLE) || + (!lhs_inf && update_ub(constrs, k, lhs / aik, + row HUGE_BOUND_IS_OK) == INFEASIBLE)) + { + return INFEASIBLE; + } + } + + // must call set_row_to_inactive since the column is not appended to + // fixed_cols_to_delete. + set_row_to_inactive(row, row_tag, constrs->state->rows_to_delete, + postsolve_info, 0.0); + } + + return REDUCED; +} + +PresolveStatus remove_ston_rows(Problem *prob) +{ + iVec *ston_rows = prob->constraints->state->ston_rows; + const int *ston_rows_data = ston_rows->data; + int len = ston_rows->len; + RowTag *row_tags = prob->constraints->row_tags; + DEBUG(verify_no_duplicates_sort(ston_rows)); + + if (len == 0) + { + return UNCHANGED; + } + + // first we process singleton equality rows, then singleton inequalities + // (this ordering simplifies the dual postsolve when the same variable + // appears in an equality and an inequality row) + for (int i = 0; i < len; ++i) + { + int row = ston_rows_data[i]; + + if (!HAS_TAG(row_tags[row], R_TAG_EQ) || + HAS_TAG(row_tags[row], R_TAG_INACTIVE)) + { + continue; + } + + assert(!HAS_TAG(row_tags[row], R_TAG_INACTIVE)); + + if (INFEASIBLE == remove_stonrow(prob, row)) + { + assert(false); + return INFEASIBLE; + } + } + + for (int i = 0; i < len; ++i) + { + int row = ston_rows_data[i]; + + // eliminated equality rows have been marked as inactive + // (or one might remain if the variable in it has been fixed + // by another equality row) + if (HAS_TAG(row_tags[row], (R_TAG_INACTIVE | R_TAG_EQ))) + { + continue; + } + + assert(!HAS_TAG(row_tags[row], R_TAG_EQ)); + + if (INFEASIBLE == remove_stonrow(prob, row)) + { + assert(false); + return INFEASIBLE; + } + } + + // we only updated A->nnz inside remove_stonrow so we need to + // update AT->nnz for consistency + prob->constraints->AT->nnz = prob->constraints->A->nnz; + + iVec_clear_no_resize(ston_rows); + + // singleton inequality rows have been marked as inactive so we + // must remove them + delete_inactive_rows(prob->constraints); + + delete_fixed_cols_from_problem(prob); + + // variables might have been fixed (by ston equality rows) so we + // must remove them + delete_inactive_cols_from_A_and_AT(prob->constraints); + + DEBUG(verify_problem_up_to_date(prob->constraints)); + return REDUCED; +} + +// If the returned row tag is R_TAG_INFEAS, then the constraint is feasible. +// If HAS_TAG(return_tag, R_TAG_RHS_INF), then the rhs is finite and redundant. +// If HAS_TAG(return_tag, R_TAG_LHS_INF), then the lhs is finite and redundant. +static inline RowTag check_activity(const Activity *act, double lhs, double rhs, + RowTag row_tag) +{ + assert(!HAS_TAG(row_tag, R_TAG_EQ)); + RowTag return_tag = R_TAG_NONE; + + // ------------------------------------------------------------------------- + // Compare the activity with the rhs of the constraint. We check for both + // infeasibility and redundancy. + // ------------------------------------------------------------------------- + if (!HAS_TAG(row_tag, R_TAG_RHS_INF)) + { + if (act->n_inf_min == 0 && act->min >= rhs + FEAS_TOL) + { + return R_TAG_INFEAS; + } + else if (act->n_inf_max == 0 && act->max <= rhs + FEAS_TOL) + { + UPDATE_TAG(return_tag, R_TAG_RHS_INF); + } + } + + // ------------------------------------------------------------------------- + // Compare the activity with the lhs of the constraint. We check for both + // infeasibility and redundancy. + // ------------------------------------------------------------------------- + if (!HAS_TAG(row_tag, R_TAG_LHS_INF)) + { + if (act->n_inf_max == 0 && act->max <= lhs - FEAS_TOL) + { + return R_TAG_INFEAS; + } + else if (act->n_inf_min == 0 && act->min >= lhs - FEAS_TOL) + { + UPDATE_TAG(return_tag, R_TAG_LHS_INF); + } + } + +#ifndef NDEBUG + // can it happen that an equality constraint becomes an inequality or + // redundant? + if (HAS_TAG(row_tag, R_TAG_EQ)) + { + assert(!HAS_TAG(return_tag, R_TAG_LHS_INF) && + !HAS_TAG(return_tag, R_TAG_RHS_INF)); + } +#endif + + return return_tag; +} + +PresolveStatus check_activities(Problem *prob) +{ + const iVec *acts_to_check = prob->constraints->state->updated_activities; + + // verify that the same row hasn't been appended twice and that the + // activities are correct + DEBUG(verify_no_duplicates_sort(acts_to_check)); + DEBUG(verify_activities(prob->constraints)); + + const Activity *activities = prob->constraints->state->activities; + const int *row_sizes = prob->constraints->state->row_sizes; + Lock *locks = prob->constraints->state->col_locks; + iVec *rows_to_delete = prob->constraints->state->rows_to_delete; + RowTag *row_tags = prob->constraints->row_tags; + const double *lhs = prob->constraints->lhs; + const double *rhs = prob->constraints->rhs; + const Matrix *A = prob->constraints->A; + + int i, j, row; + + for (i = 0; i < acts_to_check->len; ++i) + { + row = acts_to_check->data[i]; + + // Skip inactive rows, empty rows, and singleton rows. We must include + // the check for an inactive row, since the row size of an inactive + // row may not yet have been updated when this function is called. + // IDEA: it is a design choice to have skip equality rows here, but + // we should try to remove this restriction and see if it matters. + if (HAS_TAG(row_tags[row], (R_TAG_INACTIVE | R_TAG_EQ)) || + row_sizes[row] <= 1) + { + continue; + } + + assert(activities[row].n_inf_min == 0 || activities[row].n_inf_max == 0); + + RowTag status_tag = + check_activity(activities + row, lhs[row], rhs[row], row_tags[row]); + + // ---------------------------------------------------------------- + // if the constraint is redundant + // ---------------------------------------------------------------- + if (HAS_TAG((status_tag | row_tags[row]), R_TAG_LHS_INF) && + HAS_TAG((status_tag | row_tags[row]), R_TAG_RHS_INF)) + { + set_row_to_inactive(row, row_tags + row, rows_to_delete, + prob->constraints->state->postsolve_info, 0.0); + } + // ---------------------------------------------------------------- + // if lhs is finite but redundant + // (it can't happen that both HAS_TAG(status_tag, R_TAG_LHS_INF) + // and HAS_TAG(row_tags[row], R_TAG_LHS_INF) are true because of + // the design of check_activity) + // ---------------------------------------------------------------- + else if (HAS_TAG(status_tag, R_TAG_LHS_INF)) + { + assert(!HAS_TAG(row_tags[row], R_TAG_LHS_INF)); + RESET_TAG(row_tags[row], R_TAG_LHS_INF); + + // update locks + for (j = A->p[row].start; j < A->p[row].end; ++j) + { + assert(A->x[j] != 0); + if (A->x[j] > 0) + { + locks[A->i[j]].down--; + } + else + { + locks[A->i[j]].up--; + } + } + + prob->constraints->lhs[row] = -INF; + } + // ---------------------------------------------------------------- + // if rhs is finite but redundant + // (it can't happen that both HAS_TAG(status_tag, R_TAG_RHS_INF) + // and HAS_TAG(row_tags[row], R_TAG_RHS_INF) are true because of + // the design of check_activity) + // ---------------------------------------------------------------- + else if (HAS_TAG(status_tag, R_TAG_RHS_INF)) + { + assert(!HAS_TAG(row_tags[row], R_TAG_RHS_INF)); + RESET_TAG(row_tags[row], R_TAG_RHS_INF); + + // update locks + for (j = A->p[row].start; j < A->p[row].end; ++j) + { + assert(A->x[j] != 0); + if (A->x[j] > 0) + { + locks[A->i[j]].up--; + } + else + { + locks[A->i[j]].down--; + } + } + + prob->constraints->rhs[row] = INF; + } + // ---------------------------------------------------------------- + // if the constraint is infeasible + // ---------------------------------------------------------------- + else if (HAS_TAG(status_tag, R_TAG_INFEAS)) + { + return INFEASIBLE; + } + } + + delete_inactive_rows(prob->constraints); + + // we should NOT clear updated activities here since we need them in + // primal propagation + DEBUG(verify_problem_up_to_date(prob->constraints)); + return UNCHANGED; +} + +PresolveStatus remove_empty_rows(const Constraints *constraints) +{ + iVec *empty_rows = constraints->state->empty_rows; + const int *empty_rows_data = empty_rows->data; + int len = empty_rows->len; + + if (len == 0) + { + return UNCHANGED; + } + + int *row_sizes = constraints->state->row_sizes; + RowTag *rtags = constraints->row_tags; + const double *lhs = constraints->lhs; + const double *rhs = constraints->rhs; + PostsolveInfo *postsolve_info = constraints->state->postsolve_info; + + for (int i = 0; i < len; ++i) + { + int row = empty_rows_data[i]; + assert(row_sizes[row] == 0); + + bool infeasible = + (!HAS_TAG(rtags[row], R_TAG_LHS_INF) && lhs[row] >= FEAS_TOL) || + (!HAS_TAG(rtags[row], R_TAG_RHS_INF) && rhs[row] <= -FEAS_TOL); + + if (infeasible) + { + return INFEASIBLE; + } + + UPDATE_TAG(rtags[row], R_TAG_INACTIVE); + row_sizes[row] = SIZE_INACTIVE_ROW; + save_retrieval_deleted_row(postsolve_info, row, 0.0); + } + + iVec_clear_no_resize(empty_rows); + return UNCHANGED; +} + +PresolveStatus remove_empty_cols(Problem *prob) +{ + iVec *empty_cols = prob->constraints->state->empty_cols; + const int *empty_cols_data = empty_cols->data; + int len = empty_cols->len; + + double val; + int i, k; + + if (len == 0) + { + return UNCHANGED; + } + + PostsolveInfo *postsolve_info = prob->constraints->state->postsolve_info; + int *col_sizes = prob->constraints->state->col_sizes; + ColTag *col_tags = prob->constraints->col_tags; + Bound *bounds = prob->constraints->bounds; + double *c = prob->obj->c; + double *offset = &(prob->obj->offset); + + for (i = 0; i < len; ++i) + { + k = empty_cols_data[i]; + + assert(col_sizes[k] == 0 && !HAS_TAG(col_tags[k], C_TAG_INACTIVE)); + + // -------------------------------------------------------------------- + // if the objective coefficient is 0 we set the variable to 0 if + // feasible, otherwise we set it equal to one of the bounds + // -------------------------------------------------------------------- + if (c[k] == 0) + { + if (!HAS_TAG(col_tags[k], C_TAG_LB_INF) && bounds[k].lb > 0) + { + val = bounds[k].lb; + } + else if (!HAS_TAG(col_tags[k], C_TAG_UB_INF) && bounds[k].ub < 0) + { + val = bounds[k].ub; + } + else + { + val = 0; + } + } + // ------------------------------------------------------------------ + // fix variable to upper bound + // ------------------------------------------------------------------ + else if (c[k] < 0) + { + if (HAS_TAG(col_tags[k], C_TAG_UB_INF)) + { + return UNBNDORINFEAS; + } + + val = bounds[k].ub; + bounds[k].lb = bounds[k].ub; + } + // ------------------------------------------------------------------ + // fix variable to lower bound + // ------------------------------------------------------------------ + else + { + assert(c[k] > 0); + if (HAS_TAG(col_tags[k], C_TAG_LB_INF)) + { + return UNBNDORINFEAS; + } + + val = bounds[k].lb; + bounds[k].ub = bounds[k].lb; + } + + // add offset to objective + *offset += c[k] * val; + + // mark column as fixed + UPDATE_TAG(col_tags[k], C_TAG_FIXED); + col_sizes[k] = SIZE_INACTIVE_COL; + + // store postsolve information (for empty cols we want zk = ck) + // passing NULL ptrs are safe since the length is 0 + save_retrieval_fixed_col(postsolve_info, k, val, prob->obj->c[k], NULL, NULL, + 0); + } + + iVec_clear_no_resize(empty_cols); + return UNCHANGED; +} + +void clean_small_coeff_A(Matrix *A, const Bound *bounds, const RowTag *row_tags, + const ColTag *col_tags, double *rhs, double *lhs) +{ + int ii, row, col, len, n_rows, *cols; + double Aik_abs, cum_changes, diff, *vals; + bool has_finite_bounds, set_coeff_to_zero; + n_rows = A->m; + + for (row = 0; row < n_rows; ++row) + { + cols = A->i + A->p[row].start; + vals = A->x + A->p[row].start; + len = A->p[row].end - A->p[row].start; + cum_changes = 0.0; + + for (ii = 0; ii < len; ++ii) + { + set_coeff_to_zero = false; + col = cols[ii]; + Aik_abs = ABS(vals[ii]); + + has_finite_bounds = + !HAS_TAG(col_tags[col], (C_TAG_LB_INF | C_TAG_UB_INF)); + + if (has_finite_bounds) + { + assert(ABS(bounds[col].lb) != INF && ABS(bounds[col].ub) != INF); + diff = bounds[col].ub - bounds[col].lb; + assert(diff >= 0); + + // we take care of fixed variables later + if (diff == 0) + { + continue; + } + + if (Aik_abs < CLEAN1 && Aik_abs * diff * len < 1e-2 * FEAS_TOL) + { + set_coeff_to_zero = true; + } + else if (cum_changes + Aik_abs * diff < 1e-1 * FEAS_TOL) + { + cum_changes += Aik_abs * diff; + set_coeff_to_zero = true; + } + } + + if (set_coeff_to_zero) + { + if (!HAS_TAG(row_tags[row], R_TAG_LHS_INF)) + { + assert(bounds[col].lb != -INF); + lhs[row] -= vals[ii] * bounds[col].lb; + } + + if (!HAS_TAG(row_tags[row], R_TAG_RHS_INF)) + { + assert(bounds[col].lb != -INF); + rhs[row] -= vals[ii] * bounds[col].lb; + } + } + + if (Aik_abs < CLEAN2 || set_coeff_to_zero) + { + RowView row_view = + new_rowview(vals, cols, &len, A->p + row, NULL, NULL, NULL, -1); + remove_coeff(&row_view, col); + ii--; + A->nnz--; + } + } + } +} + +void remove_variables_with_close_bounds(Problem *prob) +{ + Constraints *constraints = prob->constraints; + const double *c = prob->obj->c; + const Bound *bounds = constraints->bounds; + const ColTag *col_tags = constraints->col_tags; + int n_cols = constraints->n; + const int *col_sizes = constraints->state->col_sizes; + + for (int ii = 0; ii < n_cols; ++ii) + { + if (col_sizes[ii] <= 0 || + HAS_TAG(col_tags[ii], (C_TAG_LB_INF | C_TAG_UB_INF))) + { + continue; + } + + assert(!HAS_TAG(col_tags[ii], C_TAG_INACTIVE)); + + if (IS_EQUAL_FEAS_TOL(bounds[ii].lb, bounds[ii].ub)) + { + // no need to check return value since bounds are equal + fix_col(constraints, ii, bounds[ii].lb, c[ii]); + } + } + + // remove fixed columns + delete_fixed_cols_from_problem(prob); + delete_inactive_cols_from_A_and_AT(constraints); +} diff --git a/src/explorers/Simple_dual_fix.c b/src/explorers/Simple_dual_fix.c new file mode 100644 index 00000000..ddb79b06 --- /dev/null +++ b/src/explorers/Simple_dual_fix.c @@ -0,0 +1,178 @@ +/* + * Copyright 2025 Daniel Cederberg + * + * This file is part of the PSLP project (LP Presolver). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Simple_dual_fix.h" +#include "Bounds.h" +#include "Constraints.h" +#include "CoreTransformations.h" +#include "Debugger.h" +#include "Locks.h" +#include "Problem.h" +#include "SimpleReductions.h" +#include "State.h" +#include "Workspace.h" +#include "iVec.h" + +// a column that becomes fixed because of dual fix should have zk = ck - ak^T y +static inline PresolveStatus _simple_dual_fix(Constraints *constraints, double ck, + double lb, double ub, Lock lock, + ColTag col_tag, int col, + iVec *cols_to_inf) +{ + assert(!HAS_TAG(col_tag, C_TAG_INACTIVE)); + // -------------------------------------------------- + // see if we can fix the variable to its lower bound + // -------------------------------------------------- + if (ck > 0 && lock.down == 0) + { + if (HAS_TAG(col_tag, C_TAG_LB_INF)) + { + return UNBNDORINFEAS; + } + + assert(!IS_ABS_INF(lb)); + fix_col(constraints, col, lb, ck); + return UNCHANGED; + } + + // --------------------------------------------------- + // see if we can fix the variable to its upper bound + // --------------------------------------------------- + if (ck < 0 && lock.up == 0) + { + if (ck != 0 && HAS_TAG(col_tag, C_TAG_UB_INF)) + { + return UNBNDORINFEAS; + } + + assert(!IS_ABS_INF(ub)); + fix_col(constraints, col, ub, ck); + return UNCHANGED; + } + + // ------------------------------------------------------------------------ + // If the objective coefficient is 0 and there are no downlocks, + // then the variable may be set to -infinity if it is unbounded from below, + // making the constraints it appears in redundant. If the variable is + // bounded from below we can set the variable to any value. Our design + // choice is to set the variable to its lower bound. + // A similar comment applies to the case when there are no uplocks. + // ------------------------------------------------------------------------ + if (ck == 0) + { + if (lock.down == 0) + { + if (HAS_TAG(col_tag, C_TAG_LB_INF)) + { + iVec_append(cols_to_inf, -col); + } + else + { + assert(!IS_ABS_INF(lb)); + fix_col(constraints, col, lb, ck); + } + return UNCHANGED; + } + + if (lock.up == 0) + { + if (HAS_TAG(col_tag, C_TAG_UB_INF)) + { + iVec_append(cols_to_inf, col); + } + else + { + assert(!IS_ABS_INF(ub)); + fix_col(constraints, col, ub, ck); + } + return UNCHANGED; + } + } + + return UNCHANGED; +} + +PresolveStatus simple_dual_fix(Problem *prob) +{ + assert(prob->constraints->state->empty_cols->len == 0); + DEBUG(verify_problem_up_to_date(prob->constraints)); + + Constraints *constraints = prob->constraints; + const double *c = prob->obj->c; + const Bound *bounds = constraints->bounds; + const ColTag *col_tags = constraints->col_tags; + const Lock *locks = constraints->state->col_locks; + int n_cols = constraints->n; + iVec *cols_to_inf = constraints->state->work->int_vec; + iVec_clear_no_resize(cols_to_inf); + PresolveStatus status = UNCHANGED; + + for (int k = 0; k < n_cols; k++) + { + // if it is too slow to loop through all columns, we can keep a list + // of candidate columns to process + if (HAS_TAG(col_tags[k], C_TAG_INACTIVE) || + (locks[k].up > 0 && locks[k].down > 0)) + { + continue; + } + + // if simple_dual_fix returns UNBNDORINFEAS it will be propagated + // to run_fast_presolvers where it is detected + status |= _simple_dual_fix(constraints, c[k], bounds[k].lb, bounds[k].ub, + locks[k], col_tags[k], k, cols_to_inf); + } + + // now fix the columns that can be fixed to inf + // (processing the inf columns after processing the other columns + // simplifies the dual postsolve a lot) + for (int i = 0; i < cols_to_inf->len; ++i) + { + int col = cols_to_inf->data[i]; + + if (col < 0) + { + fix_col_to_negative_inf(constraints, -col); + } + else if (col > 0) + { + fix_col_to_positive_inf(constraints, col); + } + else + { + assert(col == 0); + assert(locks[col].up == 0 || locks[col].down == 0); + if (locks[col].down == 0) + { + fix_col_to_negative_inf(constraints, col); + } + else if (locks[col].up == 0) + { + fix_col_to_positive_inf(constraints, col); + } + } + } + + // after the simple dual fix we might have inactive rows, and fixed + // columns + delete_inactive_rows(prob->constraints); + delete_fixed_cols_from_problem(prob); + delete_inactive_cols_from_A_and_AT(prob->constraints); + + return status; +} \ No newline at end of file diff --git a/src/explorers/StonCols.c b/src/explorers/StonCols.c new file mode 100644 index 00000000..a0b6c536 --- /dev/null +++ b/src/explorers/StonCols.c @@ -0,0 +1,716 @@ +/* + * Copyright 2025 Daniel Cederberg + * + * This file is part of the PSLP project (LP Presolver). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "StonCols.h" +#include "Activity.h" +#include "Bounds.h" +#include "Constraints.h" +#include "CoreTransformations.h" +#include "Debugger.h" +#include "Locks.h" +#include "Matrix.h" +#include "Numerics.h" +#include "Postsolver.h" +#include "Problem.h" +#include "RowColViews.h" +#include "State.h" + +/* Helper for handling the case where a column-singleton variable in an + equality row is only implied free from above. */ +static void handle_impl_free_from_above_eq(RowView *row, int k, double Aik, + double lb, double ub, ColTag *col_tag, + Activity *act, Lock *locks, int *A_nnz, + const Bound *bounds, + PostsolveInfo *postsolve_info, double ck) +{ + /* dual postsolve */ + save_retrieval_eq_to_ineq(postsolve_info, row->i, ck / Aik); + save_retrieval_sub_col(postsolve_info, k, row->cols, row->vals, *row->len, + *row->rhs, row->i, 0.0); + + assert(!IS_ABS_INF(lb) && !HAS_TAG(*col_tag, C_TAG_LB_INF)); + + /* remove coefficient from A */ + remove_coeff(row, k); + (*A_nnz)--; + + if (Aik > 0) + { + /* modify constraint */ + *row->rhs -= lb * Aik; + *row->lhs = -INF; + RESET_TAG(*row->tag, R_TAG_LHS_INF); + + /* update locks */ + update_locks_eq_to_ineq(locks, row->vals, row->cols, *row->len, true); + + /* update activity */ + if (act->n_inf_min == 0) + { + act->min -= lb * Aik; + } + + if (HAS_TAG(*col_tag, C_TAG_UB_INF)) + { + act->n_inf_max--; + + if (act->n_inf_max == 0) + { + act->max = + compute_max_act_no_tags(row->vals, row->cols, *row->len, bounds); + assert(!IS_ABS_INF(act->max)); + } + } + else + { + assert(ub != -INF && ub != INF); + if (act->n_inf_max == 0) + { + act->max -= ub * Aik; + } + } + } + else + { + /* modify constraint and update locks */ + *row->lhs -= lb * Aik; + *row->rhs = INF; + RESET_TAG(*row->tag, R_TAG_RHS_INF); + update_locks_eq_to_ineq(locks, row->vals, row->cols, *row->len, false); + + /* update activity */ + if (act->n_inf_max == 0) + { + act->max -= lb * Aik; + } + + if (HAS_TAG(*col_tag, C_TAG_UB_INF)) + { + act->n_inf_min--; + + if (act->n_inf_min == 0) + { + act->min = + compute_min_act_no_tags(row->vals, row->cols, *row->len, bounds); + assert(!IS_ABS_INF(act->min)); + } + } + else + { + assert(ub != -INF && ub != INF); + if (act->n_inf_min == 0) + { + act->min -= ub * Aik; + } + } + } +} + +/* Helper for handling the case where a column-singleton variable in an + equality row is only implied free from below. */ +static void handle_impl_free_from_below_eq(RowView *row, int k, double Aik, + double lb, double ub, ColTag *col_tag, + Activity *act, Lock *locks, int *A_nnz, + const Bound *bounds, + PostsolveInfo *postsolve_info, double ck) +{ + save_retrieval_eq_to_ineq(postsolve_info, row->i, ck / Aik); + save_retrieval_sub_col(postsolve_info, k, row->cols, row->vals, *row->len, + *row->rhs, row->i, 0.0); + assert(!IS_ABS_INF(ub) && !HAS_TAG(*col_tag, C_TAG_UB_INF)); + + /* remove coefficient from A */ + remove_coeff(row, k); + (*A_nnz)--; + + if (Aik > 0) + { + /* modify constraint */ + *row->lhs -= ub * Aik; + *row->rhs = INF; + RESET_TAG(*row->tag, R_TAG_RHS_INF); + + /* update locks */ + update_locks_eq_to_ineq(locks, row->vals, row->cols, *row->len, false); + + /* update activity */ + if (act->n_inf_max == 0) + { + act->max -= ub * Aik; + } + + if (HAS_TAG(*col_tag, C_TAG_LB_INF)) + { + act->n_inf_min--; + + if (act->n_inf_min == 0) + { + act->min = + compute_min_act_no_tags(row->vals, row->cols, *row->len, bounds); + assert(!IS_ABS_INF(act->min)); + } + } + else + { + assert(lb != -INF && lb != INF); + if (act->n_inf_min == 0) + { + act->min -= lb * Aik; + } + } + } + else + { + /* modify constraint and update locks*/ + *row->rhs -= ub * Aik; + *row->lhs = -INF; + RESET_TAG(*row->tag, R_TAG_LHS_INF); + update_locks_eq_to_ineq(locks, row->vals, row->cols, *row->len, true); + + /* update activity */ + if (act->n_inf_min == 0) + { + act->min -= ub * Aik; + } + if (HAS_TAG(*col_tag, C_TAG_LB_INF)) + { + act->n_inf_max--; + + if (act->n_inf_max == 0) + { + act->max = + compute_max_act_no_tags(row->vals, row->cols, *row->len, bounds); + assert(!IS_ABS_INF(act->max)); + } + } + else + { + assert(lb != -INF && lb != INF); + if (act->n_inf_max == 0) + { + act->max -= lb * Aik; + } + } + } +} + +static PresolveStatus process_colston_eq(RowView *row, ColView *col, Objective *obj, + double Aik, bool impl_free_from_above, + bool impl_free_from_below, Activity *act, + Lock *locks, iVec *rows_to_delete, + int *A_nnz, const Bound *bounds, + iVec *ston_rows, iVec *dton_rows, + PostsolveInfo *postsolve_info) +{ + + assert(*row->lhs == *row->rhs); + assert(!HAS_TAG(*row->tag, (R_TAG_INACTIVE | R_TAG_LHS_INF | R_TAG_RHS_INF))); + + // No reduction is possible for a column singleton that is neither + // implied free from below nor above, so we skip these. + if (!impl_free_from_above && !impl_free_from_below) + { + return UNCHANGED; + } + + // must store for dual postsolve + double ck = obj->c[col->k]; + + // substitute variable in objective and mark it as substituted + // (note that we don't push it to list of substituted cols) + sub_var_in_obj(obj, row->vals, row->cols, *row->len, col->k, Aik, *row->rhs); + UPDATE_TAG(*col->tag, C_TAG_SUBSTITUTED); + *col->len = SIZE_INACTIVE_COL; + col->range->end = col->range->start; + + // ------------------------------------------------------------------------- + // if var is implied free, the constraint is redundant and we remove it. + // ------------------------------------------------------------------------- + if (impl_free_from_above && impl_free_from_below) + { + save_retrieval_sub_col(postsolve_info, col->k, row->cols, row->vals, + *row->len, *row->rhs, row->i, ck); + set_row_to_inactive(row->i, row->tag, rows_to_delete, postsolve_info, + ck / Aik); + return REDUCED; + } + // ------------------------------------------------------------------------- + // If the variable is only implied free from above we must modify the + // constraint when removing it. + // ------------------------------------------------------------------------- + else if (impl_free_from_above) + { + handle_impl_free_from_above_eq(row, col->k, Aik, *col->lb, *col->ub, + col->tag, act, locks, A_nnz, bounds, + postsolve_info, ck); + } + // -------------------------------------------------------------------------- + // If the variable is only implied free from below we must modify the + // constraint when removing it. + // -------------------------------------------------------------------------- + else + { + assert(impl_free_from_below); + handle_impl_free_from_below_eq(row, col->k, Aik, *col->lb, *col->ub, + col->tag, act, locks, A_nnz, bounds, + postsolve_info, ck); + } + + if (*row->len == 1) + { + assert(!iVec_contains(ston_rows, row->i)); + iVec_append(ston_rows, row->i); + } + else if (*row->len == 2 && HAS_TAG(*row->tag, R_TAG_EQ)) + { + assert(!iVec_contains(dton_rows, row->i)); + iVec_append(dton_rows, row->i); + } + + assert(*row->len != 0); + + return REDUCED; +} + +/* Fixes a variable that is a column singleton. + Parameters: + * val: value that variable should be fixed to + * Aik: coefficient in the matrix A of the fixed variable + * (lhs, rhs) belong to the row that the fixed variable appears in + * (vals, cols, len, row_range) correspond to the row of the col_ston + * (col_range, col_tag, col_size) are attributes of the variable that + is fixed + * obj: objective function + * k: index of the fixed variable + * A_nnz: number of nonzeros in the matrix A (OBS: this is modified) + * row_tag: tag of the row that the fixed variable appears in + * act: activity of the row that the fixed variable appears in + * bounds: variable bounds + + @Note: we don't have to check for infeasibility here, because + we only call this function with val equal to one of the + bounds. + */ +static void fix_col_ston(double val, double Aik, RowView *row, ColView *col, + Activity *act, Objective *obj, int *A_nnz, + const Bound *bounds, PostsolveInfo *postsolve_info) +{ + // remove coefficient from A (which removes the variable from A) + remove_coeff(row, col->k); + (*A_nnz)--; + + // set row k from AT to inactive + col->range->end = col->range->start; + *col->len = SIZE_INACTIVE_COL; + UPDATE_TAG(*col->tag, C_TAG_FIXED); + + // update lhs + if (!HAS_TAG(*row->tag, R_TAG_LHS_INF)) + { + *row->lhs -= Aik * val; + } + + // update rhs + if (!HAS_TAG(*row->tag, R_TAG_RHS_INF)) + { + *row->rhs -= Aik * val; + } + + // update min activity + if (act->n_inf_min == 0) + { + act->min -= Aik * val; + } + else if ((Aik > 0 && HAS_TAG(*col->tag, C_TAG_LB_INF)) || + (Aik < 0 && HAS_TAG(*col->tag, C_TAG_UB_INF))) + { + act->n_inf_min--; + + if (act->n_inf_min == 0) + { + act->min = + compute_min_act_no_tags(row->vals, row->cols, *row->len, bounds); + assert(!IS_ABS_INF(act->min)); + } + } + + // update max activity + if (act->n_inf_max == 0) + { + act->max -= Aik * val; + } + else if ((Aik > 0 && HAS_TAG(*col->tag, C_TAG_UB_INF)) || + (Aik < 0 && HAS_TAG(*col->tag, C_TAG_LB_INF))) + { + act->n_inf_max--; + + if (act->n_inf_max == 0) + { + act->max = + compute_max_act_no_tags(row->vals, row->cols, *row->len, bounds); + assert(!IS_ABS_INF(act->max)); + } + } + + // fix variable in objective + fix_var_in_obj(obj, col->k, val); + save_retrieval_fixed_col(postsolve_info, col->k, val, obj->c[col->k], &Aik, + &row->i, 1); +} + +static inline PresolveStatus +process_colston_ineq(RowView *row, ColView *col, Objective *obj, double Aik, + bool impl_free_from_above, bool impl_free_from_below, + Activity *act, Lock *locks, iVec *rows_to_delete, int *A_nnz, + const Bound *bounds, iVec *ston_rows, iVec *dton_rows, + PostsolveInfo *postsolve_info) +{ + bool is_lhs_inf = HAS_TAG(*row->tag, R_TAG_LHS_INF); + bool is_rhs_inf = HAS_TAG(*row->tag, R_TAG_RHS_INF); + bool is_ub_inf = HAS_TAG(*col->tag, C_TAG_UB_INF); + bool is_lb_inf = HAS_TAG(*col->tag, C_TAG_LB_INF); + double ck = obj->c[col->k]; + + // ------------------------------------------------------------------------ + // dual fix to lower bound + // ------------------------------------------------------------------------ + if ((ck > 0 && Aik > 0 && is_lhs_inf) || (ck > 0 && Aik < 0 && is_rhs_inf)) + { + if (is_lb_inf) + { + return UNBNDORINFEAS; + } + assert(!IS_ABS_INF(*col->lb)); + + fix_col_ston(*col->lb, Aik, row, col, act, obj, A_nnz, bounds, + postsolve_info); + + // should not check for dton row here since the constraint is an ineq + if (*row->len == 1) + { + assert(!iVec_contains(ston_rows, row->i)); + iVec_append(ston_rows, row->i); + } + + assert(*row->len != 0); + return REDUCED; + } + + // ------------------------------------------------------------------------ + // dual fix to upper bound + // ------------------------------------------------------------------------ + if ((ck < 0 && Aik < 0 && is_lhs_inf) || (ck < 0 && Aik > 0 && is_rhs_inf)) + { + if (is_ub_inf) + { + return UNBNDORINFEAS; + } + assert(!IS_ABS_INF(*col->ub)); + + fix_col_ston(*col->ub, Aik, row, col, act, obj, A_nnz, bounds, + postsolve_info); + + // should not check for dton row here since the constraint is an ineq + if (*row->len == 1) + { + assert(!iVec_contains(ston_rows, row->i)); + iVec_append(ston_rows, row->i); + } + + assert(*row->len != 0); + return REDUCED; + } + + // ------------------------------------------------------------------------ + // Reduce column singletons that are (implied) free + // ------------------------------------------------------------------------ + if (impl_free_from_above && impl_free_from_below) + { + double new_side; + + // lhs active at an optimal solution + if ((ck > 0 && Aik > 0) || (ck < 0 && Aik < 0)) + { + if (is_lhs_inf) + { + return UNBNDORINFEAS; + } + + new_side = *row->lhs; + } + // rhs active at an optimal solution + else if ((ck > 0 && Aik < 0) || (ck < 0 && Aik > 0)) + { + if (is_rhs_inf) + { + return UNBNDORINFEAS; + } + + new_side = *row->rhs; + } + else + { + assert(ck == 0); + new_side = (is_lhs_inf) ? *row->rhs : *row->lhs; + } + + // substitute variable in objective and mark it as substituted + // (note that we don't push it to list of substituted cols) + sub_var_in_obj(obj, row->vals, row->cols, *row->len, col->k, Aik, new_side); + col->range->end = col->range->start; + UPDATE_TAG(*col->tag, C_TAG_SUBSTITUTED); + *col->len = SIZE_INACTIVE_COL; + + save_retrieval_sub_col(postsolve_info, col->k, row->cols, row->vals, + *row->len, new_side, row->i, ck); + set_row_to_inactive(row->i, row->tag, rows_to_delete, postsolve_info, + ck / Aik); + + return REDUCED; + } + + // ---------------------------------------------------------------- + // try to conclude that the upper part of the constraint is active + // at an optimal solution + // ---------------------------------------------------------------- + if ((ck < 0 && Aik > 0 && impl_free_from_above) || + (ck > 0 && Aik < 0 && impl_free_from_below)) + { + // update locks in case the old lhs was infinite + if (is_lhs_inf) + { + update_locks_ineq_to_eq(locks, row->vals, row->cols, *row->len, true); + } + + assert(!is_rhs_inf); + // update lhs = rhs + *row->lhs = *row->rhs; + RESET_TAG(*row->tag, R_TAG_EQ); + + if (*row->len == 2) + { + assert(!iVec_contains(dton_rows, row->i)); + iVec_append(dton_rows, row->i); + } + return REDUCED; + } + + // ---------------------------------------------------------------- + // try to conclude that the lower part of the constraint is active + // at an optimal solution + // ---------------------------------------------------------------- + if ((ck > 0 && Aik > 0 && impl_free_from_below) || + (ck < 0 && Aik < 0 && impl_free_from_above)) + { + // update locks in case the old rhs was infinite + if (is_rhs_inf) + { + update_locks_ineq_to_eq(locks, row->vals, row->cols, *row->len, false); + } + + assert(!is_lhs_inf); + // update rhs = lhs + *row->rhs = *row->lhs; + RESET_TAG(*row->tag, R_TAG_EQ); + + if (*row->len == 2) + { + assert(!iVec_contains(dton_rows, row->i)); + iVec_append(dton_rows, row->i); + } + + return REDUCED; + } + + return UNCHANGED; +} + +/* Refresh the list of column singletons. A variable that used to be a + column singleton may no longer be a column singleton for one of the + following reasons: + 1. It has been processed as a column singleton and is therefore inactive. + 2. It has been fixed by some other reduction. +*/ +static inline void refresh_ston_cols(const int *col_sizes, iVec *ston_cols) +{ + int n_ston_cols = 0; + int shift = 0; + int len = ston_cols->len; + + for (int i = 0; i < len; i++) + { + if (col_sizes[ston_cols->data[i]] != 1) + { + shift++; + } + else + { + ston_cols->data[i - shift] = ston_cols->data[i]; + n_ston_cols++; + } + } + + ston_cols->len = n_ston_cols; +} + +PresolveStatus remove_ston_cols__(Problem *prob) +{ + Constraints *constraints = prob->constraints; + PostsolveInfo *postsolve_info = constraints->state->postsolve_info; + const Matrix *A = constraints->A; + int *A_nnz = &(constraints->A->nnz); + const Matrix *AT = constraints->AT; + RowTag *row_tags = constraints->row_tags; + ColTag *col_tags = constraints->col_tags; + double *lhs = constraints->lhs; + double *rhs = constraints->rhs; + Bound *bounds = constraints->bounds; + int *row_sizes = constraints->state->row_sizes; + int *col_sizes = constraints->state->col_sizes; + iVec *ston_cols = constraints->state->ston_cols; + iVec *ston_rows = constraints->state->ston_rows; + iVec *dton_rows = constraints->state->dton_rows; + Activity *acts = constraints->state->activities; + Lock *locks = constraints->state->col_locks; + iVec *rows_to_delete = constraints->state->rows_to_delete; + double Aik; + int i, k, kk; + int *row_size; + int *cols; + double *vals; + bool impl_free_from_above, impl_free_from_below; + + PresolveStatus status = UNCHANGED; + + // get up to date information about which columns are singletons + refresh_ston_cols(col_sizes, ston_cols); + + // ------------------------------------------------------------------------ + // Loop over col_stons. We index the column with k and the row with i. + // ------------------------------------------------------------------------ + for (kk = 0; kk < ston_cols->len; ++kk) + { + k = ston_cols->data[kk]; + i = AT->i[AT->p[k].start]; + Aik = AT->x[AT->p[k].start]; + + assert(AT->p[k].end - AT->p[k].start == 1); + assert(!HAS_TAG(col_tags[k], C_TAG_INACTIVE)); + +#ifndef NDEBUG + if (IS_HUGE(Aik) || IS_HUGE(1 / Aik)) + { + printf("debug warning: large coefficient when eliminating col ston\n"); + } + // This happens on map10 (miplib) + // assert(!IS_HUGE(Aik) && !IS_HUGE(1 / Aik) && "Be aware of this!"); +#endif + + // If two column singletons appear in the same constraint, the + // constraint will be inactive when the second column singleton is + // processed. Also skip column singletons appearing in singleton rows. + // It is necessary to both check for inactivity and the row size here. + if (HAS_TAG(row_tags[i], R_TAG_INACTIVE) || row_sizes[i] <= 1) + { + continue; + } + + vals = A->x + A->p[i].start; + cols = A->i + A->p[i].start; + row_size = row_sizes + i; + + ConstRowView const_row_view = new_const_rowview( + vals, cols, row_size, A->p + i, lhs + i, rhs + i, row_tags + i, i); + + impl_free_from_above = implied_free_from_above_by_row( + Aik, &const_row_view, bounds[k].lb, bounds[k].ub, col_tags[k], acts + i, + col_tags, bounds); + + impl_free_from_below = implied_free_from_below_by_row( + Aik, &const_row_view, bounds[k].lb, bounds[k].ub, col_tags[k], acts + i, + col_tags, bounds); + +#ifndef NDEBUG + acts[i].min = (acts[i].n_inf_min != 0) ? INVALID_ACT_DEBUG : acts[i].min; + acts[i].max = (acts[i].n_inf_max != 0) ? INVALID_ACT_DEBUG : acts[i].max; +#endif + + RowView row_view = new_rowview(vals, cols, row_size, A->p + i, lhs + i, + rhs + i, row_tags + i, i); + + ColView col_view = + new_colview(NULL, NULL, col_sizes + k, AT->p + k, &bounds[k].lb, + &bounds[k].ub, col_tags + k, k); + + if (HAS_TAG(row_tags[i], R_TAG_EQ)) + { + + status |= process_colston_eq( + &row_view, &col_view, prob->obj, Aik, impl_free_from_above, + impl_free_from_below, acts + i, locks, rows_to_delete, A_nnz, bounds, + ston_rows, dton_rows, postsolve_info); + } + else + { + status |= process_colston_ineq( + &row_view, &col_view, prob->obj, Aik, impl_free_from_above, + impl_free_from_below, acts + i, locks, rows_to_delete, A_nnz, bounds, + ston_rows, dton_rows, postsolve_info); + } + } + + // process the rows that should be deleted + constraints->AT->nnz = A->nnz; + delete_inactive_rows(constraints); + + if (HAS_STATUS(status, UNBNDORINFEAS)) + { + return UNBNDORINFEAS; + } + else if (HAS_STATUS(status, REDUCED)) + { + return REDUCED; + } + else + { + assert(status == UNCHANGED); + return UNCHANGED; + } +} + +PresolveStatus remove_ston_cols(Problem *prob) +{ + assert(prob->constraints->state->ston_rows->len == 0); + assert(prob->constraints->state->empty_rows->len == 0); + assert(prob->constraints->state->empty_cols->len == 0); + DEBUG(verify_problem_up_to_date(prob->constraints)); + DEBUG(verify_no_duplicates_sort(prob->constraints->state->ston_cols)); + + PresolveStatus status = UNCHANGED; + + do + { + status = remove_ston_cols__(prob); + } while (status == REDUCED); + + DEBUG(verify_problem_up_to_date(prob->constraints)); + + // status will always be UNBNDORINFEAS or UNCHANGED (never REDUCED). + assert(status != REDUCED); + return status; +} \ No newline at end of file diff --git a/tests/all_tests.c b/tests/all_tests.c new file mode 100644 index 00000000..a9f713f0 --- /dev/null +++ b/tests/all_tests.c @@ -0,0 +1,41 @@ +#include "test_Constraints.h" +#include "test_CoreTransformations.h" +#include "test_Matrix.h" +#include "test_Parallel_cols.h" +#include "test_Parallel_rows.h" +#include "test_Presolver.h" +#include "test_SimpleReductions.h" +#include "test_domain_propagation.h" +#include "test_dton.h" +#include "test_iVec.h" +#include "test_postsolve.h" +#include "test_ston.h" + +const char *run_all_tests() +{ + mu_assert("matrix error", test_matrix()); + mu_assert("constraints error", test_constraints()); + mu_assert("iVec error", test_iVec()); + mu_assert("dton error", test_dton()); + mu_assert("core error", test_core()); + mu_assert("ston error", test_ston()); + mu_assert("parallel_rows error", test_parallel_rows()); + mu_assert("simple reductions error", test_simple()); + mu_assert("domain propagation error", test_domain()); + mu_assert("parallel_cols error", test_parallel_cols()); + mu_assert("postsolve error", test_postsolve()); + mu_assert("presolver error", test_presolver()); + return NULL; +} + +int main() +{ + const char *result = run_all_tests(); + if (result != NULL) + { + printf("Test failed: %s\n", result); + return -1; + } + printf("All tests passed!\n"); + return 0; +} \ No newline at end of file diff --git a/tests/minunit.h b/tests/minunit.h new file mode 100644 index 00000000..2412802d --- /dev/null +++ b/tests/minunit.h @@ -0,0 +1,34 @@ +#ifndef MINUNIT_H +#define MINUNIT_H +/* Modified from from http://www.jera.com/techinfo/jtns/jtn002.html */ + +/* Simple Macros for testing */ +#define mu_assert_less(message, a, b) \ + do \ + { \ + if (a > b) \ + { \ + printf("%s: %1.3e > %1.3e\n", message, a, b); \ + return message; \ + } \ + } while (0) + +#define mu_assert(message, test) \ + do \ + { \ + if (!(test)) return message; \ + } while (0) + +#define mu_run_test(test, var) _mu_run_test(#test, test, var) + +#define _mu_run_test(name, test, var) \ + do \ + { \ + printf("*********************************************************\n"); \ + printf("Running test: %s\n", name); \ + const char *message = test(); \ + var++; \ + if (message) return message; \ + } while (0) + +#endif \ No newline at end of file diff --git a/tests/test_Constraints.h b/tests/test_Constraints.h new file mode 100644 index 00000000..903a61ab --- /dev/null +++ b/tests/test_Constraints.h @@ -0,0 +1,364 @@ +#ifndef TEST_CONSTRAINTS_H +#define TEST_CONSTRAINTS_H + +#include "API.h" +#include "Activity.h" +#include "Constraints.h" +#include "Locks.h" +#include "State.h" +#include "glbopts.h" +#include "minunit.h" +#include "test_macros.h" +#include + +// Test 1 - 2: delete_inactive_rows +// Test 3 - 4: delete_inactive_cols_from_A_and_AT + +static int counter_constraints = 0; + +static char *test_1_constraints() +{ + + // build constraints object + double vals[] = {1, -1, 1, 2, -1, 1, 1, 1, 1}; + int cols[] = {0, 1, 2, 0, 3, 0, 1, 2, 3}; + int row_starts[] = {0, 3, 5, 9}; + int n_rows = 3; + int n_cols = 4; + int nnz = 9; + int work_n_cols[n_cols]; + Matrix *A = matrix_new(vals, cols, row_starts, n_rows, n_cols, nnz); + Matrix *AT = transpose(A, work_n_cols); + double lhs[3] = {4, 2, -INF}; + double rhs[3] = {4, 2, 1}; + Bound bounds[n_cols]; + INIT_BOUNDS(bounds, -10, 10, n_cols); + + Lock locks[] = {{.up = 3, .down = 2}, + {.up = 2, .down = 1}, + {.up = 2, .down = 1}, + {.up = 2, .down = 1}}; + + int row_sizes[] = {3, 2, 4}; + int col_sizes[] = {3, 2, 2, 2}; + Activity activities[3] = {0}; + RowTag *row_tags = new_rowtags(lhs, rhs, n_rows); + ColTag col_tags[4] = {0}; + Settings *stgs = default_settings(); + State *data = new_state(row_sizes, col_sizes, locks, n_rows, n_cols, activities, + NULL, row_tags); + Constraints *constraints = + constraints_new(A, AT, lhs, rhs, bounds, data, row_tags, col_tags); + + // remove row 0 + iVec_append(data->rows_to_delete, 0); + delete_inactive_rows(constraints); + + // test for correctness + int correct_col_sizes[] = {2, 1, 1, 2}; + int correct_row_sizes[] = {SIZE_INACTIVE_ROW, 2, 4}; + mu_assert("error col_sizes", ARRAYS_EQUAL(col_sizes, correct_col_sizes, 4)); + mu_assert("error row_sizes", ARRAYS_EQUAL(row_sizes, correct_row_sizes, 3)); + + int work_map[4] = {0}; + remove_extra_space(A, row_sizes, col_sizes, true, work_map); + remove_extra_space(AT, col_sizes, row_sizes, true, work_map); + + double A_vals_correct[] = {2, -1, 1, 1, 1, 1}; + int A_cols_correct[] = {0, 3, 0, 1, 2, 3}; + int A_row_starts_correct[] = {0, 2, 6}; + mu_assert("error A", ARRAYS_EQUAL(A->x, A_vals_correct, 6)); + mu_assert("error A", ARRAYS_EQUAL(A->i, A_cols_correct, 6)); + CHECK_ROW_STARTS(A, A_row_starts_correct); + + double AT_vals_correct[] = {2, 1, 1, 1, -1, 1}; + int AT_cols_correct[] = {0, 1, 1, 1, 0, 1}; + int AT_row_starts_correct[] = {0, 2, 3, 4, 6}; + mu_assert("error AT_vals", ARRAYS_EQUAL(AT->x, AT_vals_correct, 6)); + mu_assert("error AT_cols", ARRAYS_EQUAL(AT->i, AT_cols_correct, 6)); + CHECK_ROW_STARTS(AT, AT_row_starts_correct); + + int correct_up_locks[] = {2, 1, 1, 2}; + int correct_down_locks[] = {1, 0, 0, 1}; + + CHECK_LOCKS(locks, correct_up_locks, correct_down_locks, 4); + + // deallocate memory + free_matrix(A); + free_matrix(AT); + PS_FREE(constraints); + PS_FREE(stgs); + free_state(data); + PS_FREE(row_tags); + + return 0; +} + +static char *test_2_constraints() +{ + + // build constraints object + double vals[] = {1.3, -0.6, 0.26, -0.42, -0.59, -0.9, 0.53, 1.42, + 1.31, 0.47, -1.47, 0.63, -0.93, 1.54, -1.07, -1.46, + -2.37, 0.25, -1.45, 0.71, -1.44, -1.57, -0.44, -0.37, + -0.5, 0.61, 1.38, 0.17, 0.77, 0.41, -0.21, -0.73, + 0.55, -0.18, 1.14, 0.57, -0.22, -1.4, 1.02, 0.74}; + int cols[] = {0, 3, 4, 9, 1, 3, 6, 9, 2, 3, 5, 6, 8, 1, 9, 1, 2, 3, 5, 0, + 2, 4, 5, 7, 4, 6, 9, 1, 4, 6, 7, 8, 1, 3, 5, 7, 1, 2, 4, 8}; + int row_starts[] = {0, 4, 8, 13, 15, 19, 24, 27, 32, 36, 40}; + int n_rows = 10; + int n_cols = 10; + int nnz = 40; + int work_n_cols[n_cols]; + Matrix *A = matrix_new(vals, cols, row_starts, n_rows, n_cols, nnz); + Matrix *AT = transpose(A, work_n_cols); + double lhs[10] = {0.0}; + double rhs[10] = {0.0}; + Bound bounds[n_cols]; + INIT_BOUNDS(bounds, -10, 10, n_cols); + + Lock locks[n_cols]; + int row_sizes[] = {4, 4, 5, 2, 4, 5, 3, 5, 4, 4}; + int col_sizes[] = {2, 6, 4, 5, 5, 4, 4, 3, 3, 4}; + RowTag *row_tags = new_rowtags(lhs, rhs, n_rows); + ColTag col_tags[10] = {0}; + Settings *stgs = default_settings(); + Activity activities[10] = {0}; + State *data = new_state(row_sizes, col_sizes, locks, n_rows, n_cols, activities, + NULL, row_tags); + Constraints *constraints = + constraints_new(A, AT, lhs, rhs, bounds, data, row_tags, col_tags); + + // remove rows 1, 3, 5 + iVec_append_array(data->rows_to_delete, (int[]) {1, 3, 5}, 3); + delete_inactive_rows(constraints); + + // test for correctness + int correct_col_sizes[] = {1, 4, 3, 4, 4, 3, 3, 2, 3, 2}; + int correct_row_sizes[] = { + 4, SIZE_INACTIVE_ROW, 5, SIZE_INACTIVE_ROW, 4, SIZE_INACTIVE_ROW, 3, 5, 4, + 4}; + mu_assert("error col_sizes", ARRAYS_EQUAL(col_sizes, correct_col_sizes, 10)); + mu_assert("error row_sizes", ARRAYS_EQUAL(row_sizes, correct_row_sizes, 10)); + + int work_map[10] = {0}; + remove_extra_space(A, row_sizes, col_sizes, true, work_map); + remove_extra_space(AT, col_sizes, row_sizes, true, work_map); + + double A_vals_correct[] = {1.3, -0.6, 0.26, -0.42, 1.31, 0.47, -1.47, 0.63, + -0.93, -1.46, -2.37, 0.25, -1.45, -0.5, 0.61, 1.38, + 0.17, 0.77, 0.41, -0.21, -0.73, 0.55, -0.18, 1.14, + 0.57, -0.22, -1.4, 1.02, 0.74}; + int A_cols_correct[] = {0, 3, 4, 9, 2, 3, 5, 6, 8, 1, 2, 3, 5, 4, 6, + 9, 1, 4, 6, 7, 8, 1, 3, 5, 7, 1, 2, 4, 8}; + int A_row_starts_correct[] = {0, 4, 9, 13, 16, 21, 25, 29}; + + mu_assert("error A_vals", ARRAYS_EQUAL(A->x, A_vals_correct, 29)); + mu_assert("error A_cols", ARRAYS_EQUAL(A->i, A_cols_correct, 29)); + CHECK_ROW_STARTS(A, A_row_starts_correct); + + double AT_vals_correct[] = {1.3, -1.46, 0.17, 0.55, -0.22, 1.31, -2.37, -1.4, + -0.6, 0.47, 0.25, -0.18, 0.26, -0.5, 0.77, 1.02, + -1.47, -1.45, 1.14, 0.63, 0.61, 0.41, -0.21, 0.57, + -0.93, -0.73, 0.74, -0.42, 1.38}; + int AT_cols_correct[] = {0, 2, 4, 5, 6, 1, 2, 6, 0, 1, 2, 5, 0, 3, 4, + 6, 1, 2, 5, 1, 3, 4, 4, 5, 1, 4, 6, 0, 3}; + int AT_row_starts_correct[] = {0, 1, 5, 8, 12, 16, 19, 22, 24, 27, 29}; + mu_assert("error AT_vals", ARRAYS_EQUAL(AT->x, AT_vals_correct, 27)); + mu_assert("error AT_cols", ARRAYS_EQUAL(AT->i, AT_cols_correct, 27)); + CHECK_ROW_STARTS(AT, AT_row_starts_correct); + + // deallocate memory + free_matrix(A); + free_matrix(AT); + PS_FREE(stgs); + PS_FREE(constraints); + free_state(data); + PS_FREE(row_tags); + + return 0; +} + +static char *test_3_constraints() +{ + + // build constraints object + double vals[] = {1, -1, 1, 2, -1, 1, 1, 1, 1}; + int cols[] = {0, 1, 2, 0, 3, 0, 1, 2, 3}; + int row_starts[] = {0, 3, 5, 9}; + int n_rows = 3; + int n_cols = 4; + int nnz = 9; + int work_n_cols[n_cols]; + Matrix *A = matrix_new(vals, cols, row_starts, n_rows, n_cols, nnz); + Matrix *AT = transpose(A, work_n_cols); + double lhs[3] = {4, 2, -INF}; + double rhs[3] = {4, 2, 1}; + Bound bounds[n_cols]; + INIT_BOUNDS(bounds, -10, 10, n_cols); + + Lock locks[4]; + + int row_sizes[] = {3, 2, 4}; + int col_sizes[] = {3, 2, 2, 2}; + RowTag *row_tags = new_rowtags(lhs, rhs, n_rows); + Activity activities[3] = {0}; + ColTag col_tags[4] = {0}; + Settings *stgs = default_settings(); + State *data = new_state(row_sizes, col_sizes, locks, n_rows, n_cols, activities, + NULL, row_tags); + Constraints *constraints = + constraints_new(A, AT, lhs, rhs, bounds, data, row_tags, col_tags); + + // fix variable 1 + iVec_append(data->fixed_cols_to_delete, 1); + delete_inactive_cols_from_A_and_AT(constraints); + + // test for correctness + int correct_col_sizes[] = {3, SIZE_INACTIVE_COL, 2, 2}; + int correct_row_sizes[] = {2, 2, 3}; + mu_assert("error col_sizes", ARRAYS_EQUAL(col_sizes, correct_col_sizes, 4)); + mu_assert("error row_sizes", ARRAYS_EQUAL(row_sizes, correct_row_sizes, 3)); + + int work_map[4] = {0}; + remove_extra_space(A, row_sizes, col_sizes, true, work_map); + remove_extra_space(AT, col_sizes, row_sizes, true, work_map); + + double A_vals_correct[] = {1, 1, 2, -1, 1, 1, 1}; + int A_cols_correct[] = {0, 1, 0, 2, 0, 1, 2}; + int A_row_starts_correct[] = {0, 2, 4, 7}; + mu_assert("error A", ARRAYS_EQUAL(A->x, A_vals_correct, 7)); + mu_assert("error A", ARRAYS_EQUAL(A->i, A_cols_correct, 7)); + CHECK_ROW_STARTS(A, A_row_starts_correct); + + double AT_vals_correct[] = {1, 2, 1, 1, 1, -1, 1}; + int AT_cols_correct[] = {0, 1, 2, 0, 2, 1, 2}; + int AT_row_starts_correct[] = {0, 3, 5, 7}; + mu_assert("error AT_vals", ARRAYS_EQUAL(AT->x, AT_vals_correct, 7)); + mu_assert("error AT_cols", ARRAYS_EQUAL(AT->i, AT_cols_correct, 7)); + CHECK_ROW_STARTS(AT, AT_row_starts_correct); + + // deallocate memory + free_matrix(A); + free_matrix(AT); + PS_FREE(stgs); + PS_FREE(constraints); + free_state(data); + PS_FREE(row_tags); + + return 0; +} + +static char *test_4_constraints() +{ + + // build constraints object + double vals[] = {1.12, -0.29, -1.41, 0.67, -0.35, -1.15, -0.72, -0.03, + 0.82, 0.32, 1.38, 2.23, -0.44, 0.05, 1.17, 0.21, + 0.68, 1.38, -0.56, -1.03, -0.33, -0.1, 0.9, 2.74, + 0.76, 1.17, 0.04, -2.16, 0.65, -0.02, -0.29, -0.07, + -0.9, 0.59, 0.45, 1.47, -1.75, 0.57, 0.27, 1.09}; + int cols[] = {0, 4, 5, 1, 9, 3, 4, 5, 6, 7, 8, 3, 4, 6, 9, 9, 7, 0, 2, 3, + 4, 6, 7, 2, 3, 5, 6, 8, 9, 1, 2, 3, 5, 6, 8, 9, 2, 6, 7, 9}; + int row_starts[] = {0, 3, 5, 11, 15, 16, 17, 23, 29, 36, 40}; + int n_rows = 10; + int n_cols = 10; + int nnz = 40; + int work_n_cols[n_cols]; + Matrix *A = matrix_new(vals, cols, row_starts, n_rows, n_cols, nnz); + Matrix *AT = transpose(A, work_n_cols); + double lhs[10] = {0.0}; + double rhs[10] = {0.0}; + Bound bounds[n_cols]; + INIT_BOUNDS(bounds, -10, 10, n_cols); + + Lock locks[n_cols]; + + int row_sizes[] = {3, 2, 6, 4, 1, 1, 6, 6, 7, 4}; + int col_sizes[] = {2, 2, 4, 5, 4, 4, 6, 4, 3, 6}; + RowTag *row_tags = new_rowtags(lhs, rhs, n_rows); + Activity activities[10] = {0}; + ColTag col_tags[10] = {0}; + Settings *stgs = default_settings(); + State *data = new_state(row_sizes, col_sizes, locks, n_rows, n_cols, activities, + NULL, row_tags); + Constraints *constraints = + constraints_new(A, AT, lhs, rhs, bounds, data, row_tags, col_tags); + + // remove cols 1, 3, 5 + iVec_append_array(data->fixed_cols_to_delete, (int[]) {1, 3, 5}, 3); + delete_inactive_cols_from_A_and_AT(constraints); + + // test for correctness + int correct_col_sizes[] = { + 2, SIZE_INACTIVE_COL, 4, SIZE_INACTIVE_COL, 4, SIZE_INACTIVE_COL, 6, 4, 3, + 6}; + int correct_row_sizes[] = {2, 1, 4, 3, 1, 1, 5, 4, 4, 4}; + mu_assert("error col_sizes", ARRAYS_EQUAL(col_sizes, correct_col_sizes, 10)); + mu_assert("error row_sizes", ARRAYS_EQUAL(row_sizes, correct_row_sizes, 10)); + + int work_map[10] = {0}; + remove_extra_space(A, row_sizes, col_sizes, true, work_map); + remove_extra_space(AT, col_sizes, row_sizes, true, work_map); + + double A_vals_correct[] = {1.12, -0.29, -0.35, -0.72, 0.82, 0.32, 1.38, -0.44, + 0.05, 1.17, 0.21, 0.68, 1.38, -0.56, -0.33, -0.1, + 0.9, 2.74, 0.04, -2.16, 0.65, -0.29, 0.59, 0.45, + 1.47, -1.75, 0.57, 0.27, 1.09}; + int A_cols_correct[] = {0, 2, 6, 2, 3, 4, 5, 2, 3, 6, 6, 4, 0, 1, 2, + 3, 4, 1, 3, 5, 6, 1, 3, 5, 6, 1, 3, 4, 6}; + int A_row_starts_correct[] = {0, 2, 3, 7, 10, 11, 12, 17, 21, 25, 29}; + + mu_assert("error A_vals", ARRAYS_EQUAL(A->x, A_vals_correct, 29)); + mu_assert("error A_cols", ARRAYS_EQUAL(A->i, A_cols_correct, 29)); + CHECK_ROW_STARTS(A, A_row_starts_correct); + + double AT_vals_correct[] = { + 1.12, 1.38, -0.56, 2.74, -0.29, -1.75, -0.29, -0.72, -0.44, -0.33, + 0.82, 0.05, -0.1, 0.04, 0.59, 0.57, 0.32, 0.68, 0.9, 0.27, + 1.38, -2.16, 0.45, -0.35, 1.17, 0.21, 0.65, 1.47, 1.09}; + int AT_cols_correct[] = {0, 6, 6, 7, 8, 9, 0, 2, 3, 6, 2, 3, 6, 7, 8, + 9, 2, 5, 6, 9, 2, 7, 8, 1, 3, 4, 7, 8, 9}; + int AT_row_starts_correct[] = {0, 2, 6, 10, 16, 20, 23, 29}; + mu_assert("error AT_vals", ARRAYS_EQUAL(AT->x, AT_vals_correct, 29)); + mu_assert("error AT_cols", ARRAYS_EQUAL(AT->i, AT_cols_correct, 29)); + CHECK_ROW_STARTS(AT, AT_row_starts_correct); + + // deallocate memory + free_matrix(A); + free_matrix(AT); + PS_FREE(stgs); + PS_FREE(constraints); + free_state(data); + PS_FREE(row_tags); + + return 0; +} + +static const char *all_tests_constraints() +{ + mu_run_test(test_1_constraints, counter_constraints); + mu_run_test(test_2_constraints, counter_constraints); + mu_run_test(test_3_constraints, counter_constraints); + mu_run_test(test_4_constraints, counter_constraints); + + return 0; +} + +int test_constraints() +{ + const char *result = all_tests_constraints(); + if (result != 0) + { + printf("%s\n", result); + printf("Constraints: TEST FAILED!\n"); + } + else + { + printf("Constraints: ALL TESTS PASSED\n"); + } + printf("Constraints: Tests run: %d\n", counter_constraints); + return result == 0; +} + +#endif // TEST_CONSTRAINTS_H diff --git a/tests/test_CoreTransformations.h b/tests/test_CoreTransformations.h new file mode 100644 index 00000000..0972fc69 --- /dev/null +++ b/tests/test_CoreTransformations.h @@ -0,0 +1,86 @@ +#ifndef TEST_CORETRANSFORMATIONS_H +#define TEST_CORETRANSFORMATIONS_H + +#include "CoreTransformations.h" +#include "Problem.h" +#include "SimpleReductions.h" +#include "minunit.h" +#include + +static int counter_core = 0; + +// test initialization, append, free +static char *test_1_core() +{ + double Ax[] = {1, -1, 1, 2, -1, 1, 1, 1, 1}; + int Ai[] = {0, 1, 2, 0, 3, 0, 1, 2, 3}; + int Ap[] = {0, 3, 5, 9}; + int nnz = 9; + int n_rows = 3; + int n_cols = 4; + + double lhs[] = {4, 2, -INF}; + double rhs[] = {4, 2, 1}; + double lbs[] = {-1, -2, -3, -4}; + double ubs[] = {10, 20, 30, 40}; + double c[] = {0, 0, 0, 0}; + + Settings *stgs = default_settings(); + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Constraints *constraints = presolver->prob->constraints; + + // fix x2 to 1 + fix_col(constraints, 1, 1.0, 0); + + delete_fixed_cols_from_problem(presolver->prob); + + // check that new LB and UB are correct (after call to fixcol) + mu_assert("error", constraints->bounds[1].lb == 1.0); + mu_assert("error", constraints->bounds[1].ub == 1.0); + + // check LHS and RHS + mu_assert("error lhs", constraints->lhs[0] == 5); + mu_assert("error lhs", constraints->lhs[1] == 2); + mu_assert("error lhs", constraints->lhs[2] == -INF); + mu_assert("error rhs", constraints->rhs[0] == 5); + mu_assert("error rhs", constraints->rhs[1] == 2); + mu_assert("error rhs", constraints->rhs[2] == 0); + + // check activities + Activity *act = constraints->state->activities; + + mu_assert("error act", act[0].min == -4 && act[0].max == 40); + mu_assert("error act", act[1].min == -42 && act[1].max == 24); + mu_assert("error act", act[2].min == -8 && act[2].max == 80); + + PS_FREE(stgs); + free_presolver(presolver); + + return 0; +} + +static const char *all_tests_core() +{ + mu_run_test(test_1_core, counter_core); + return 0; +} + +int test_core() +{ + const char *result = all_tests_core(); + if (result != 0) + { + printf("%s\n", result); + printf("core: TEST FAILED!\n"); + } + else + { + printf("core: ALL TESTS PASSED\n"); + } + printf("core: Tests run: %d\n", counter_core); + return result == 0; +} + +#endif // TEST_core_H \ No newline at end of file diff --git a/tests/test_Matrix.h b/tests/test_Matrix.h new file mode 100644 index 00000000..4560d089 --- /dev/null +++ b/tests/test_Matrix.h @@ -0,0 +1,810 @@ +#ifndef TEST_MATRIX_H +#define TEST_MATRIX_H + +#include "Debugger.h" +#include "Matrix.h" +#include "minunit.h" +#include "test_macros.h" +#include + +int counter_matrix = 0; + +/* Summary of tests: +Test 0: matrix_new: allocation and free of a matrix. +Test 1: transpose: transpose a matrix. +Test 2-14: shiftRow +Then some adhoc tests. +*/ + +// test allocation and free +static char *test_0_matrix() +{ + + double vals[] = {1, -1, 2, 1, 1, 1, 1, 3, 1, 1, 1}; + int cols[] = {0, 1, 2, 1, 2, 4, 4, 0, 1, 2, 3}; + int row_starts[] = {0, 3, 6, 7, 9, 10, 11}; + + int work_n_cols[5]; + + Matrix *A = matrix_new(vals, cols, row_starts, 6, 5, 11); + Matrix *AT = transpose(A, work_n_cols); + + mu_assert("error", A); + mu_assert("error", AT); + + free_matrix(A); + free_matrix(AT); + + return 0; +} + +// test transpose +static char *test_1_matrix() +{ + + double vals[] = {1, -1, 2, 1, 1, 1, 1, 3, 1, 1, 1}; + int cols[] = {0, 1, 2, 1, 2, 4, 4, 0, 1, 2, 3}; + int row_starts[] = {0, 3, 6, 7, 9, 10, 11}; + int work_n_cols[5]; + + Matrix *A = matrix_new(vals, cols, row_starts, 6, 5, 11); + Matrix *AT = transpose(A, work_n_cols); + + // remove extra space to simplify test + int row_sizes_AT[6] = {2, 3, 3, 1, 2}; + int col_sizes_AT[6] = {3, 3, 1, 2, 1, 1}; + int col_idxs_map_AT[6]; + remove_extra_space(AT, row_sizes_AT, col_sizes_AT, true, col_idxs_map_AT); + + // correct answer + double AT_vals_correct[] = {1, 3, -1, 1, 1, 2, 1, 1, 1, 1, 1}; + int AT_cols_correct[] = {0, 3, 0, 1, 3, 0, 1, 4, 5, 1, 2}; + int AT_row_starts_correct[] = {0, 2, 5, 8, 9, 11}; + + mu_assert("error, vals not equal", ARRAYS_EQUAL(AT_vals_correct, AT->x, 11)); + mu_assert("error, cols not equal", ARRAYS_EQUAL(AT_cols_correct, AT->i, 11)); + CHECK_ROW_STARTS(AT, AT_row_starts_correct); + + free_matrix(A); + free_matrix(AT); + + return 0; +} + +/* +A = [1 2 3 0 (2 extra) + 4 0 5 6 (2 extra) + 7 0 8 0 (2 extra) + 9 0 10 0] (2 extra) + + Failure because we want the first row to have 3 extra spaces but + max_shift = 2. Then we must shift 4, 5, 6 right with one step. + But this should be rejected. +*/ +static char *test_2_matrix() +{ + double vals[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + int cols[] = {0, 1, 2, 0, 2, 3, 0, 2, 0, 2}; + int row_starts[] = {0, 3, 6, 8, 10}; + int nnz = 10; + int n_rows = 4; + Matrix *A = matrix_new(vals, cols, row_starts, n_rows, 4, nnz); + bool success = shift_row(A, 0, 3, 2); + mu_assert("error, shift_row did not reject shift", !success); + free_matrix(A); + + return 0; +} + +/* +A = [1 2 3 0 (2 extra) + 4 0 5 6 (2 extra) + 7 0 8 0 (2 extra) + 9 0 10 0] (2 extra) + + Previous test, but with shift allowed +*/ +static char *test_3_matrix() +{ + double vals[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + int cols[] = {0, 1, 2, 0, 2, 3, 0, 2, 0, 2}; + int row_starts[] = {0, 3, 6, 8, 10}; + int nnz = 10; + int n_rows = 4; + Matrix *A = matrix_new(vals, cols, row_starts, n_rows, 4, nnz); + bool success = shift_row(A, 0, 3, 3); + mu_assert("error, shift_row did reject shift", success); + + // correct answer + double vals_correct[] = {1, 2, 3, 0, 0, 4, 4, 5, 6, 0, 7, 8, 0, 0, 9, 10, 0, 0}; + + mu_assert("error, vals not equal", ARRAYS_EQUAL(vals_correct, A->x, 18)); + free_matrix(A); + + return 0; +} + +/* +A = [1 2 3 0 (2 extra) + 4 0 5 6 (2 extra) + 7 0 8 0 (2 extra) + 9 0 10 0] (2 extra) + + Success with maxShift = 5. +*/ +static char *test_4_matrix() +{ + double vals[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + int cols[] = {0, 1, 2, 0, 2, 3, 0, 2, 0, 2}; + int row_starts[] = {0, 3, 6, 8, 10}; + int nnz = 10; + int n_rows = 4; + Matrix *A = matrix_new(vals, cols, row_starts, n_rows, 4, nnz); + bool success = shift_row(A, 0, 5, 5); + mu_assert("error, shift_row did reject shift", success); + + // correct answer + double vals_correct[] = {1, 2, 3, 0, 0, 4, 5, 6, 4, 5, 6, 7, 8, 0, 9, 10, 0, 0}; + + mu_assert("error, vals not equal", ARRAYS_EQUAL(vals_correct, A->x, 18)); + + int row_starts_correct[] = {0, 8, 11, 14, 18}; + int row_ends_correct[] = {3, 11, 13, 16, 18}; + + CHECK_ROW_STARTS(A, row_starts_correct); + CHECK_ROW_ENDS(A, row_ends_correct); + + free_matrix(A); + + return 0; +} + +/* +A = [1 2 3 0 (2 extra) + 4 0 5 6 (2 extra) + 7 0 8 0 (2 extra) + 9 0 10 0] (2 extra) + + Failure when maxShift = 4 +*/ +static char *test_5_matrix() +{ + double vals[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + int cols[] = {0, 1, 2, 0, 2, 3, 0, 2, 0, 2}; + int row_starts[] = {0, 3, 6, 8, 10}; + int nnz = 10; + int n_rows = 4; + Matrix *A = matrix_new(vals, cols, row_starts, n_rows, 4, nnz); + bool success = shift_row(A, 0, 5, 4); + mu_assert("error, shift_row did not reject shift", !success); + free_matrix(A); + + return 0; +} + +/* +A = [1 2 3 0 (2 extra) + 4 0 5 6 (2 extra) + 7 0 8 0 (2 extra) + 9 0 10 0] (2 extra) + + Success when we want the last row to have 5 extra spaces. +*/ +static char *test_6_matrix() +{ + double vals[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + int cols[] = {0, 1, 2, 0, 2, 3, 0, 2, 0, 2}; + int row_starts[] = {0, 3, 6, 8, 10}; + int nnz = 10; + int n_rows = 4; + Matrix *A = matrix_new(vals, cols, row_starts, n_rows, 4, nnz); + bool success = shift_row(A, 3, 5, 10); + mu_assert("error, shift_row did reject shift", success); + + // correct answer + double vals_correct[] = {1, 2, 3, 0, 0, 4, 5, 6, 0, 7, 8, 9, 10, 0, 9, 10, 0, 0}; + double cols_correct[] = {0, 1, 2, 0, 0, 0, 2, 3, 0, 0, 2, 0, 2, 0, 0, 2, 0, 0}; + + mu_assert("error, vals not equal", ARRAYS_EQUAL(vals_correct, A->x, 18)); + mu_assert("error, cols not equal", ARRAYS_EQUAL(cols_correct, A->i, 18)); + + int row_starts_correct[] = {0, 5, 9, 11, 18}; + int row_ends_correct[] = {3, 8, 11, 13, 18}; + + CHECK_ROW_STARTS(A, row_starts_correct); + CHECK_ROW_ENDS(A, row_ends_correct); + + free_matrix(A); + + return 0; +} + +/* +A = [1 2 3 0 (2 extra) + 4 0 5 6 (2 extra) + 7 0 8 0 (2 extra) + 9 0 0 0 (2 extra) + 10 0 0 0] (2 extra) + + Row 2 five extra spaces. +*/ +static char *test_7_matrix() +{ + double vals[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + int cols[] = {0, 1, 2, 0, 2, 3, 0, 2, 0, 0}; + int row_starts[] = {0, 3, 6, 8, 9, 10}; + int nnz = 10; + int n_rows = 5; + Matrix *A = matrix_new(vals, cols, row_starts, n_rows, 4, nnz); + bool success = shift_row(A, 2, 5, 10); + mu_assert("error, shift_row did reject shift", success); + + // correct answer + double vals_correct[] = {1, 2, 3, 0, 0, 4, 5, 6, 0, 0, + 7, 8, 0, 0, 9, 0, 0, 9, 10, 0}; + double cols_correct[] = {0, 1, 2, 0, 0, 0, 2, 3, 0, 0, + 0, 2, 0, 0, 0, 0, 0, 0, 0, 0}; + + mu_assert("error, vals not equal", ARRAYS_EQUAL(vals_correct, A->x, 18)); + mu_assert("error, cols not equal", ARRAYS_EQUAL(cols_correct, A->i, 18)); + + int row_starts_correct[] = {0, 5, 10, 17, 18, 20}; + int row_ends_correct[] = {3, 8, 12, 18, 19, 20}; + + CHECK_ROW_STARTS(A, row_starts_correct); + CHECK_ROW_ENDS(A, row_ends_correct); + + free_matrix(A); + + return 0; +} + +/* +A = [1 2 3 0 (2 extra) + 4 0 5 6 (2 extra) + 7 0 8 0 (2 extra) + 9 0 0 0 (2 extra) + 10 0 0 0] (2 extra) + + Row 2 seven extra spaces. +*/ +static char *test_8_matrix() +{ + double vals[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + int cols[] = {0, 1, 2, 0, 2, 3, 0, 2, 0, 0}; + int row_starts[] = {0, 3, 6, 8, 9, 10}; + int nnz = 10; + int n_rows = 5; + Matrix *A = matrix_new(vals, cols, row_starts, n_rows, 4, nnz); + bool success = shift_row(A, 2, 7, 10); + mu_assert("error, shift_row did reject shift", success); + + // correct answer + double vals_correct[] = {1, 2, 3, 0, 0, 4, 5, 6, 0, 7, + 8, 8, 0, 0, 9, 0, 0, 10, 9, 10}; + double cols_correct[] = {0, 1, 2, 0, 0, 0, 2, 3, 0, 0, + 2, 2, 0, 0, 0, 0, 0, 0, 0, 0}; + + mu_assert("error, vals not equal", ARRAYS_EQUAL(vals_correct, A->x, 18)); + mu_assert("error, cols not equal", ARRAYS_EQUAL(cols_correct, A->i, 18)); + + int row_starts_correct[] = {0, 5, 9, 18, 19, 20}; + int row_ends_correct[] = {3, 8, 11, 19, 20, 20}; + + CHECK_ROW_STARTS(A, row_starts_correct); + CHECK_ROW_ENDS(A, row_ends_correct); + + free_matrix(A); + + return 0; +} + +/* +A = [1 2 3 0 (2 extra) + 4 0 5 6 (2 extra) + 7 0 8 0 (2 extra) + 9 0 0 0 (2 extra) + 10 0 0 0] (2 extra) + + Row 2 seven extra spaces when row 3 is made empty. +*/ +static char *test_9_matrix() +{ + double vals[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + int cols[] = {0, 1, 2, 0, 2, 3, 0, 2, 0, 0}; + int row_starts[] = {0, 3, 6, 8, 9, 10}; + int nnz = 10; + int n_rows = 5; + Matrix *A = matrix_new(vals, cols, row_starts, n_rows, 4, nnz); + A->p[3].end = A->p[3].start; + + bool success = shift_row(A, 2, 7, 10); + mu_assert("error, shift_row did reject shift", success); + + // correct answer + double vals_correct[] = {1, 2, 3, 0, 0, 4, 5, 6, 0, 0, + 7, 8, 0, 0, 9, 0, 0, 10, 0, + + 10}; + double cols_correct[] = {0, 1, 2, 0, 0, 0, 2, 3, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, + + 0}; + + mu_assert("error, vals not equal", ARRAYS_EQUAL(vals_correct, A->x, 18)); + mu_assert("error, cols not equal", ARRAYS_EQUAL(cols_correct, A->i, 18)); + + int row_starts_correct[] = {0, 5, 10, 19, 19, 20}; + int row_ends_correct[] = {3, 8, 12, 19, 20, 20}; + + CHECK_ROW_STARTS(A, row_starts_correct); + CHECK_ROW_ENDS(A, row_ends_correct); + + free_matrix(A); + + return 0; +} + +/* + A = [1 2 3 0 (2 extra) + 4 0 5 6 (2 extra) + 7 0 8 0 (2 extra) + 9 0 0 0 (2 extra) + 10 11 0 0 (2 extra) + 12 13 14 0 (2 extra) + 0 15 0 16] (2 extra) + + Several empty rows in right direction +*/ +static char *test_10_matrix() +{ + double vals[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; + int cols[] = {0, 1, 2, 0, 2, 3, 0, 2, 0, 0, 1, 0, 1, 2, 1, 3}; + int row_starts[] = {0, 3, 6, 8, 9, 11, 14, 16}; + int nnz = 16; + int n_rows = 7; + Matrix *A = matrix_new(vals, cols, row_starts, n_rows, 4, nnz); + A->p[3].end = A->p[3].start; + A->p[4].end = A->p[4].start; + A->p[5].end = A->p[5].start; + + bool success = shift_row(A, 2, 11, 10); + mu_assert("error, shift_row did reject shift", success); + + // correct answer + double vals_correct[] = {1, 2, 3, 0, 0, 4, 5, 6, 0, 0, 7, 8, 0, 0, 9, + 0, 0, 10, 11, 0, 0, 12, 13, 14, 0, 0, 15, 16, 0, 0}; + double cols_correct[] = {0, 1, 2, 0, 0, 0, 2, 3, 0, 0, 0, 2, 0, 0, 0, + 0, 0, 0, 1, 0, 0, 0, 1, 2, 0, 0, 1, 3, 0, 0}; + + mu_assert("error, vals not equal", ARRAYS_EQUAL(vals_correct, A->x, 18)); + mu_assert("error, cols not equal", ARRAYS_EQUAL(cols_correct, A->i, 18)); + + int row_starts_correct[] = {0, 5, 10, 23, 23, 23, 26, 30}; + int row_ends_correct[] = {3, 8, 12, 23, 23, 23, 28, 30}; + + CHECK_ROW_STARTS(A, row_starts_correct); + CHECK_ROW_ENDS(A, row_ends_correct); + + free_matrix(A); + + return 0; +} + +/* + A = [1 2 3 0 (2 extra) + 4 0 5 6 (2 extra) + 7 0 8 0 (2 extra) + 9 0 10 0 (2 extra) + 11 12 0 0 (2 extra) + 0 13 14 0] (2 extra) + + Several empty rows in both directions +*/ +static char *test_11_matrix() +{ + double vals[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14}; + int cols[] = {0, 1, 2, 0, 2, 3, 0, 2, 0, 2, 0, 1, 1, 2}; + int row_starts[] = {0, 3, 6, 8, 10, 12, 14}; + int nnz = 14; + int n_rows = 6; + Matrix *A = matrix_new(vals, cols, row_starts, n_rows, 4, nnz); + A->p[1].end = A->p[1].start; + A->p[2].end = A->p[2].start; + A->p[4].end = A->p[4].start; + A->p[5].end = A->p[5].start; + + bool success = shift_row(A, 3, 14, 10); + mu_assert("error, shift_row did reject shift", success); + + // correct answer + double vals_correct[] = {1, 2, 3, 0, 0, 4, 5, 6, 0, 0, 9, 10, 0, + 0, 9, 10, 0, 0, 11, 12, 0, 0, 13, 14, 0, 0}; + double cols_correct[] = {0, 1, 2, 0, 0, 0, 2, 3, 0, 0, 0, 2, 0, + 0, 0, 2, 0, 0, 0, 1, 0, 0, 1, 2, 0, 0}; + + mu_assert("error, vals not equal", ARRAYS_EQUAL(vals_correct, A->x, 18)); + mu_assert("error, cols not equal", ARRAYS_EQUAL(cols_correct, A->i, 18)); + + int row_starts_correct[] = {0, 5, 10, 10, 26, 26, 26}; + int row_ends_correct[] = {3, 5, 10, 12, 26, 26, 26}; + + CHECK_ROW_STARTS(A, row_starts_correct); + CHECK_ROW_ENDS(A, row_ends_correct); + + free_matrix(A); + + return 0; +} + +/* +A = [1 2 3 0 empty + 4 0 0 0 (2 extra) + 7 0 8 0 (2 extra) + 9 0 5 6 (2 extra) + 10 0 11 0] (2 extra) + + Row 2 six extra spaces when first row is made empty. +*/ +static char *test_12_matrix() +{ + double vals[] = {1, 2, 3, 4, 7, 8, 9, 5, 6, 10, 11}; + int cols[] = {0, 1, 2, 0, 0, 2, 0, 2, 3, 0, 2}; + int row_starts[] = {0, 3, 4, 6, 9, 11}; + int nnz = 11; + int n_rows = 5; + Matrix *A = matrix_new(vals, cols, row_starts, n_rows, 4, nnz); + A->p[0].end = A->p[0].start; + + bool success = shift_row(A, 2, 6, 10); + mu_assert("error, shift_row did reject shift", success); + + // correct answer + double vals_correct[] = {1, 2, 3, 4, 7, 8, 0, 0, 7, 8, 0, + 0, 9, 5, 6, 0, 0, 10, 11, 0, 0}; + double cols_correct[] = {0, 1, 2, 0, 0, 2, 0, 0, 0, 2, 0, + 0, 0, 2, 3, 0, 0, 0, 2, 0, 0}; + + mu_assert("error, vals not equal", ARRAYS_EQUAL(vals_correct, A->x, 18)); + mu_assert("error, cols not equal", ARRAYS_EQUAL(cols_correct, A->i, 18)); + + int row_starts_correct[] = {0, 3, 4, 12, 17, 21}; + int row_ends_correct[] = {0, 4, 6, 15, 19, 21}; + + CHECK_ROW_STARTS(A, row_starts_correct); + CHECK_ROW_ENDS(A, row_ends_correct); + + free_matrix(A); + + return 0; +} + +/* +A = [1 2 3 0 (2 extra) + 4 0 5 6 (2 extra) + 7 0 8 0 (2 extra) + 9 0 0 0 (2 extra) + 10 0 0 0] (2 extra) + + Row 2 six extra spaces when last is made empty. +*/ +static char *test_13_matrix() +{ + double vals[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + int cols[] = {0, 1, 2, 0, 2, 3, 0, 2, 0, 0}; + int row_starts[] = {0, 3, 6, 8, 9, 10}; + int nnz = 10; + int n_rows = 5; + Matrix *A = matrix_new(vals, cols, row_starts, n_rows, 4, nnz); + A->p[4].end = A->p[4].start; + + bool success = shift_row(A, 2, 6, 10); + mu_assert("error, shift_row did reject shift", success); + + // correct answer + double vals_correct[] = {1, 2, 3, 0, 0, 4, 5, 6, 0, 0, 7, + 8, 0, 0, 9, 0, 0, 10, 9, 0, 0}; + double cols_correct[] = {0, 1, 2, 0, 0, 0, 2, 3, 0, 0, 0, + 2, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + + mu_assert("error, vals not equal", ARRAYS_EQUAL(vals_correct, A->x, 18)); + mu_assert("error, cols not equal", ARRAYS_EQUAL(cols_correct, A->i, 18)); + + int row_starts_correct[] = {0, 5, 10, 18, 19, 20}; + int row_ends_correct[] = {3, 8, 12, 19, 19, 20}; + + CHECK_ROW_STARTS(A, row_starts_correct); + CHECK_ROW_ENDS(A, row_ends_correct); + + free_matrix(A); + + return 0; +} + +/* +A = [1 2 3 0 0 (0 extra) + 1 2 3 0 0 (0 extra) + 6 (2 extra) + 9 10 (2 extra) empty + 11 12 12 (2 extra) + 13 14 (2 extra) empty + 15 16 (2 extra) + 17 (2 extra) empty + 18 19 20 (2 extra) empty + 21 22 ] (2 extra) + + + Row 2 21 extra spaces. +*/ +static char *test_14_matrix() +{ + double vals[] = {1, 2, 3, 1, 2, 3, 6, 7, 8, 9, 10, 11, + 12, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22}; + int cols[] = {0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 2, 1, + 2, 3, 0, 3, 0, 4, 4, 0, 1, 2, 0, 2}; + int row_starts[] = {0, 3, 6, 7, 9, 11, 14, 16, 18, 19, 22, 24}; + int nnz = 24; + int n_rows = 11; + Matrix *A = matrix_new(vals, cols, row_starts, n_rows, 4, nnz); + A->p[0].end = 5; + A->p[1].end = 10; + A->p[4].end = A->p[4].start; + A->p[6].end = A->p[6].start; + A->p[8].end = A->p[8].start; + A->p[9].end = A->p[9].start; + + bool success = shift_row(A, 2, 21, 20); + mu_assert("error, shift_row did reject shift", success); + + // correct answer + double vals_correct[] = {1, 2, 3, 0, 0, 1, 2, 3, 0, 0, 6, 0, + 0, 7, 8, 0, 0, 9, 10, 0, 0, 11, 12, 12, + 0, 0, 13, 14, 0, 0, 15, 16, 7, 8, + + 11, 12, 12, + + 15, 16, + + 20, 0, 0, 21, 22, 0, 0}; + + mu_assert("error, vals not equal", ARRAYS_EQUAL(vals_correct, A->x, 18)); + + int row_starts_correct[] = {0, 5, 10, 32, 34, 34, 37, 37, 39, 39, 42, 46}; + int row_ends_correct[] = {5, 10, 11, 34, 34, 37, 37, 39, 39, 39, 44, 46}; + + CHECK_ROW_STARTS(A, row_starts_correct); + CHECK_ROW_ENDS(A, row_ends_correct); + + free_matrix(A); + printf("%d", EXTRA_ROW_SPACE); + + return 0; +} + +// +// A = [9 8 0 0 +// 3 0 5 0 +// 1 0 0 0 +// 0 0 1 1] +static char *test_15_matrix() +{ + double vals[] = {9, 8, 3, 5, 1, 1, 1}; + int cols[] = {0, 1, 0, 2, 0, 2, 3}; + int row_starts[] = {0, 2, 4, 5, 7}; + int nnz = 7; + int n_rows = 4; + Matrix *A = matrix_new(vals, cols, row_starts, n_rows, 4, nnz); + int not_used = 0; + + double old_val; + old_val = insert_or_update_coeff(A, 1, 0, 2, + ¬_used); // first in a row, in-place + mu_assert("error old_val", old_val == 3); + old_val = insert_or_update_coeff(A, 3, 1, 4, + ¬_used); // first in a row, new space + mu_assert("error old_val", old_val == 0); + old_val = + insert_or_update_coeff(A, 1, 2, 9, ¬_used); // end of a row, in-place + mu_assert("error old_val", old_val == 5); + old_val = insert_or_update_coeff(A, 0, 2, 1, + ¬_used); // end of a row, new space + mu_assert("error old_val", old_val == 0); + old_val = + insert_or_update_coeff(A, 2, 0, 7, ¬_used); // end of a row, in-place + mu_assert("error old_val", old_val == 1); + old_val = insert_or_update_coeff(A, 2, 2, 3, + ¬_used); // end of a row, new space + mu_assert("error old_val", old_val == 0); + + int row_sizes[] = {3, 2, 2, 3}; + int col_sizes[] = {4, 1, 4, 1}; + int map[4]; + remove_extra_space(A, row_sizes, col_sizes, true, map); + + // correct answer + double vals_correct[] = {9, 8, 1, 2, 9, 7, 3, 4, 1, 1}; + double cols_correct[] = {0, 1, 2, 0, 2, 0, 2, 1, 2, 3}; + mu_assert("error, vals not equal", ARRAYS_EQUAL(vals_correct, A->x, A->nnz)); + + mu_assert("error, cols not equal", ARRAYS_EQUAL(cols_correct, A->i, A->nnz)); + + int row_starts_correct[] = {0, 3, 5, 7, 10}; + int row_ends_correct[] = {3, 5, 7, 10, 10}; + + CHECK_ROW_STARTS(A, row_starts_correct); + CHECK_ROW_ENDS(A, row_ends_correct); + + free_matrix(A); + return 0; +} + +// +// A = [9 8 4 0 +// 3 0 5 0 +// 1 0 0 0 +// 0 0 1 1] +static char *test_16_matrix() +{ + double vals[] = {9, 8, 4, 3, 5, 1, 1, 1}; + int cols[] = {0, 1, 2, 0, 2, 0, 2, 3}; + int row_starts[] = {0, 3, 5, 6, 8}; + int nnz = 8; + int n_rows = 4; + Matrix *A = matrix_new(vals, cols, row_starts, n_rows, 4, nnz); + + int not_used = 0; + double old_val; + old_val = insert_or_update_coeff(A, 1, 1, 7, + ¬_used); // middle of a row, new space + mu_assert("error old_val", old_val == 0); + old_val = insert_or_update_coeff(A, 0, 1, 5, + ¬_used); // middle of a row, existing + mu_assert("error old_val", old_val == 8); + + int row_sizes[] = {3, 3, 1, 2}; + int col_sizes[] = {4, 2, 3, 1}; + int map[4]; + remove_extra_space(A, row_sizes, col_sizes, true, map); + + // correct answer + double vals_correct[] = {9, 5, 4, 3, 7, 5, 1, 1, 1}; + double cols_correct[] = {0, 1, 2, 0, 1, 2, 0, 2, 3}; + mu_assert("error, vals not equal", ARRAYS_EQUAL(vals_correct, A->x, A->nnz)); + + mu_assert("error, cols not equal", ARRAYS_EQUAL(cols_correct, A->i, A->nnz)); + + int row_starts_correct[] = {0, 3, 6, 7, 9}; + int row_ends_correct[] = {3, 6, 7, 9, 9}; + + CHECK_ROW_STARTS(A, row_starts_correct); + CHECK_ROW_ENDS(A, row_ends_correct); + + free_matrix(A); + return 0; +} + +/* insert a zero for a variable that exists + A = [1, 0, 2, 3, + 4, 5, 6, 0, + 3, 0, 0, 1] +*/ +static char *test_17_matrix() +{ + double vals[] = {1, 2, 3, 4, 5, 6, 3, 1}; + int cols[] = {0, 2, 3, 0, 1, 2, 0, 3}; + int row_starts[] = {0, 3, 6, 8}; + int nnz = 8; + int n_rows = 3; + Matrix *A = matrix_new(vals, cols, row_starts, n_rows, 4, nnz); + + int row_size = 3; + double old_val; + old_val = insert_or_update_coeff(A, 1, 2, 0, + &row_size); // middle of a row, new space + + mu_assert("error row_size", row_size == 2); + + int row_sizes[] = {3, 2, 2}; + int col_sizes[] = {3, 1, 1, 2}; + int map[4]; + remove_extra_space(A, row_sizes, col_sizes, true, map); + + double vals_correct[] = {1, 2, 3, 4, 5, 3, 1}; + double cols_correct[] = {0, 2, 3, 0, 1, 0, 3}; + mu_assert("error, vals not equal", ARRAYS_EQUAL(vals_correct, A->x, A->nnz)); + + mu_assert("error, cols not equal", ARRAYS_EQUAL(cols_correct, A->i, A->nnz)); + + int row_starts_correct[] = {0, 3, 5, 7}; + int row_ends_correct[] = {3, 5, 7, 7}; + + CHECK_ROW_STARTS(A, row_starts_correct); + CHECK_ROW_ENDS(A, row_ends_correct); + + free_matrix(A); + return 0; +} + +/* insert a zero for a variable that doesn't exist + A = [1, 0, 2, 3, + 4, 5, 6, 0, + 3, 0, 0, 1] + + OBS: This test should cause an assertion inside insert_or_update_coeff, + because in the code we only expect to call 'insert_or_update_coeff' + with val = 0 when the variable already exists. +*/ +static char *test_18_matrix() +{ + double vals[] = {1, 2, 3, 4, 5, 6, 3, 1}; + int cols[] = {0, 2, 3, 0, 1, 2, 0, 3}; + int row_starts[] = {0, 3, 6, 8}; + int nnz = 8; + int n_rows = 3; + Matrix *A = matrix_new(vals, cols, row_starts, n_rows, 4, nnz); + + int row_size = 2; + double old_val; + old_val = insert_or_update_coeff(A, 2, 1, 0, + &row_size); // middle of a row, new space + + mu_assert("error row_size", row_size == 2); + + int row_sizes[] = {3, 3, 2}; + int col_sizes[] = {3, 1, 2, 2}; + int map[4]; + remove_extra_space(A, row_sizes, col_sizes, true, map); + + mu_assert("error, vals not equal", ARRAYS_EQUAL(vals, A->x, A->nnz)); + + mu_assert("error, cols not equal", ARRAYS_EQUAL(cols, A->i, A->nnz)); + + int row_ends_correct[] = {3, 6, 8, 8}; + + CHECK_ROW_STARTS(A, row_starts); + CHECK_ROW_ENDS(A, row_ends_correct); + + free_matrix(A); + return 0; +} + +static const char *all_tests_matrix() +{ + mu_run_test(test_0_matrix, counter_matrix); + mu_run_test(test_1_matrix, counter_matrix); + mu_run_test(test_2_matrix, counter_matrix); + mu_run_test(test_3_matrix, counter_matrix); + mu_run_test(test_4_matrix, counter_matrix); + mu_run_test(test_5_matrix, counter_matrix); + mu_run_test(test_6_matrix, counter_matrix); + mu_run_test(test_7_matrix, counter_matrix); + mu_run_test(test_8_matrix, counter_matrix); + mu_run_test(test_9_matrix, counter_matrix); + mu_run_test(test_10_matrix, counter_matrix); + mu_run_test(test_11_matrix, counter_matrix); + mu_run_test(test_12_matrix, counter_matrix); + mu_run_test(test_13_matrix, counter_matrix); + mu_run_test(test_14_matrix, counter_matrix); + mu_run_test(test_15_matrix, counter_matrix); + mu_run_test(test_16_matrix, counter_matrix); + mu_run_test(test_17_matrix, counter_matrix); + // mu_run_test(test_18_matrix, counter_matrix); // we don't run this + return 0; +} + +int test_matrix() +{ + const char *result = all_tests_matrix(); + if (result != 0) + { + printf("%s\n", result); + printf("Matrix: TEST FAILED!\n"); + } + else + { + printf("Matrix: ALL TESTS PASSED\n"); + } + printf("Matrix: Tests run: %d\n", counter_matrix); + return result == 0; +} + +#endif // TEST_MATRIX_H diff --git a/tests/test_Parallel_cols.h b/tests/test_Parallel_cols.h new file mode 100644 index 00000000..b11de4c9 --- /dev/null +++ b/tests/test_Parallel_cols.h @@ -0,0 +1,609 @@ +#ifndef TEST_PARALLEL_COLS_H +#define TEST_PARALLEL_COLS_H + +#include "API.h" +#include "Debugger.h" +#include "Matrix.h" +#include "Parallel_cols.h" + +#include "Problem.h" +#include "math.h" +#include "minunit.h" +#include "test_macros.h" +#include + +static int counter_parallel_cols = 0; + +/* Example 8 Gurobi paper (with two columns flipped) */ +static char *test_1_parallel_cols() +{ + double Ax[] = {-2, -1, -1}; + int Ai[] = {0, 1, 2}; + int Ap[] = {0, 3}; + int nnz = 3; + int n_rows = 1; + int n_cols = 3; + + double lhs[] = {-INF}; + double rhs[] = {-10}; + double lbs[] = {0, 0, 0}; + double ubs[] = {4, 3, 5}; + double c[] = {4, 2, 1}; + + Settings *stgs = default_settings(); + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + remove_parallel_cols(prob); + problem_clean(prob, true); + + // check that new A is correct + double Ax_correct[] = {-1, -1}; + int Ai_correct[] = {0, 1}; + int Ap_correct[] = {0, 2}; + mu_assert("error Ax", ARRAYS_EQUAL(Ax_correct, A->x, 2)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai_correct, A->i, 2)); + CHECK_ROW_STARTS(A, Ap_correct); + + // check that new variable bounds are correct + double lbs_correct[] = {0, 0}; + double ubs_correct[] = {11, 5}; + mu_assert("error bounds", + CHECK_BOUNDS(constraints->bounds, lbs_correct, ubs_correct, 2)); + + // check that the objective function is correct + double obj_correct[] = {2, 1}; + mu_assert("error obj", ARRAYS_EQUAL(obj_correct, prob->obj->c, 2)); + mu_assert("error offset", prob->obj->offset == 0); + + PS_FREE(stgs); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + return 0; +} + +/* Based on example 8 gurobi paper + min. [4 2 1]x + s.t. [-2 -1 -1]x <= -10 + 0 <= x <= [INF 3 5] +*/ +static char *test_2_parallel_cols() +{ + double Ax[] = {-2, -1, -1}; + int Ai[] = {0, 1, 2}; + int Ap[] = {0, 3}; + int nnz = 3; + int n_rows = 1; + int n_cols = 3; + + double lhs[] = {-INF}; + double rhs[] = {-10}; + double lbs[] = {0, 0, 0}; + double ubs[] = {INF, 3, 5}; + double c[] = {4, 2, 1}; + + Settings *stgs = default_settings(); + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + remove_parallel_cols(prob); + problem_clean(prob, true); + + // check that new A is correct + double Ax_correct[] = {-1, -1}; + int Ai_correct[] = {0, 1}; + int Ap_correct[] = {0, 2}; + mu_assert("error Ax", ARRAYS_EQUAL(Ax_correct, A->x, 2)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai_correct, A->i, 2)); + CHECK_ROW_STARTS(A, Ap_correct); + + // check that new variable bounds are correct + double lbs_correct[] = {0, 0}; + double ubs_correct[] = {INF, 5}; + mu_assert("error bounds", + CHECK_BOUNDS(constraints->bounds, lbs_correct, ubs_correct, 2)); + + // check that the objective function is correct + double obj_correct[] = {2, 1}; + mu_assert("error obj", ARRAYS_EQUAL(obj_correct, prob->obj->c, 2)); + mu_assert("error offset", prob->obj->offset == 0); + + PS_FREE(stgs); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + return 0; +} + +/* Based on example 8 gurobi paper + min. [4 2 1]x + s.t. [-2 -1 -1]x <= -10 + 0 <= x <= [INF INF 5] +*/ +static char *test_3_parallel_cols() +{ + double Ax[] = {-2, -1, -1}; + int Ai[] = {0, 1, 2}; + int Ap[] = {0, 3}; + int nnz = 3; + int n_rows = 1; + int n_cols = 3; + + double lhs[] = {-INF}; + double rhs[] = {-10}; + double lbs[] = {0, 0, 0}; + double ubs[] = {INF, INF, 5}; + double c[] = {4, 2, 1}; + + Settings *stgs = default_settings(); + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + remove_parallel_cols(prob); + problem_clean(prob, true); + + // check that new A is correct + double Ax_correct[] = {-1, -1}; + int Ai_correct[] = {0, 1}; + int Ap_correct[] = {0, 2}; + mu_assert("error Ax", ARRAYS_EQUAL(Ax_correct, A->x, 2)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai_correct, A->i, 2)); + CHECK_ROW_STARTS(A, Ap_correct); + + // check that new variable bounds are correct + double lbs_correct[] = {0, 0}; + double ubs_correct[] = {INF, 5}; + mu_assert("error bounds", + CHECK_BOUNDS(constraints->bounds, lbs_correct, ubs_correct, 2)); + + // check that the objective function is correct + double obj_correct[] = {2, 1}; + mu_assert("error obj", ARRAYS_EQUAL(obj_correct, prob->obj->c, 2)); + mu_assert("error offset", prob->obj->offset == 0); + + PS_FREE(stgs); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + return 0; +} + +/* Based on example 8 gurobi paper + min. [4 2 1]x + s.t. [-2 -1 -1]x <= -10 + [0, 0, 0] <= x <= [4 3 5] +*/ +static char *test_4_parallel_cols() +{ + double Ax[] = {-2, -1, -1}; + int Ai[] = {0, 1, 2}; + int Ap[] = {0, 3}; + int nnz = 3; + int n_rows = 1; + int n_cols = 3; + + double lhs[] = {-INF}; + double rhs[] = {-10}; + double lbs[] = {0, 0, 0}; + double ubs[] = {4, 3, 5}; + double c[] = {4, 2, 1}; + + Settings *stgs = default_settings(); + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + remove_parallel_cols(prob); + problem_clean(prob, true); + + // check that new A is correct + double Ax_correct[] = {-1, -1}; + int Ai_correct[] = {0, 1}; + int Ap_correct[] = {0, 2}; + mu_assert("error Ax", ARRAYS_EQUAL(Ax_correct, A->x, 2)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai_correct, A->i, 2)); + CHECK_ROW_STARTS(A, Ap_correct); + + // check that new variable bounds are correct + double lbs_correct[] = {0, 0}; + double ubs_correct[] = {11, 5}; + mu_assert("error bounds", + CHECK_BOUNDS(constraints->bounds, lbs_correct, ubs_correct, 2)); + + // check that the objective function is correct + double obj_correct[] = {2, 1}; + mu_assert("error obj", ARRAYS_EQUAL(obj_correct, prob->obj->c, 2)); + mu_assert("error offset", prob->obj->offset == 0); + + PS_FREE(stgs); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + return 0; +} + +/* Based on example 8 gurobi paper + min. [4 2 1]x + s.t. [-2 -1 -1]x <= -10 + [-INF, 0, 0] <= x <= [4 3 5] +*/ +static char *test_5_parallel_cols() +{ + double Ax[] = {-2, -1, -1}; + int Ai[] = {0, 1, 2}; + int Ap[] = {0, 3}; + int nnz = 3; + int n_rows = 1; + int n_cols = 3; + + double lhs[] = {-INF}; + double rhs[] = {-10}; + double lbs[] = {-INF, 0, 0}; + double ubs[] = {4, 3, 5}; + double c[] = {4, 2, 1}; + + Settings *stgs = default_settings(); + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + remove_parallel_cols(prob); + problem_clean(prob, true); + + // check that new A is correct + double Ax_correct[] = {-1, -1}; + int Ai_correct[] = {0, 1}; + int Ap_correct[] = {0, 2}; + mu_assert("error Ax", ARRAYS_EQUAL(Ax_correct, A->x, 2)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai_correct, A->i, 2)); + CHECK_ROW_STARTS(A, Ap_correct); + + // check that new variable bounds are correct + double lbs_correct[] = {-INF, 0}; + double ubs_correct[] = {11, 5}; + mu_assert("error bounds", + CHECK_BOUNDS(constraints->bounds, lbs_correct, ubs_correct, 2)); + + // check that the objective function is correct + double obj_correct[] = {2, 1}; + mu_assert("error obj", ARRAYS_EQUAL(obj_correct, prob->obj->c, 2)); + mu_assert("error offset", prob->obj->offset == 0); + + PS_FREE(stgs); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + return 0; +} + +/* Based on example 8 gurobi paper + min. [4 2 1]x + s.t. [-2 -1 -1]x <= -10 + [-INF, -INF, 0] <= x <= [4 3 5] +*/ +static char *test_6_parallel_cols() +{ + double Ax[] = {-2, -1, -1}; + int Ai[] = {0, 1, 2}; + int Ap[] = {0, 3}; + int nnz = 3; + int n_rows = 1; + int n_cols = 3; + + double lhs[] = {-INF}; + double rhs[] = {-10}; + double lbs[] = {-INF, -INF, 0}; + double ubs[] = {4, 3, 5}; + double c[] = {4, 2, 1}; + + Settings *stgs = default_settings(); + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + remove_parallel_cols(prob); + problem_clean(prob, true); + + // check that new A is correct + double Ax_correct[] = {-1}; + int Ai_correct[] = {0}; + int Ap_correct[] = {0, 1}; + mu_assert("error Ax", ARRAYS_EQUAL(Ax_correct, A->x, 1)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai_correct, A->i, 1)); + CHECK_ROW_STARTS(A, Ap_correct); + + // check that new variable bounds are correct + double lbs_correct[] = {-INF}; + double ubs_correct[] = {11}; + mu_assert("error bounds", + CHECK_BOUNDS(constraints->bounds, lbs_correct, ubs_correct, 1)); + + // check that the objective function is correct + double obj_correct[] = {2}; + mu_assert("error obj", ARRAYS_EQUAL(obj_correct, prob->obj->c, 1)); + + PS_FREE(stgs); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + return 0; +} + +/* Based on example 8 gurobi paper + min. [4 -2 1]x + s.t. [2 -1 -1]x <= -10 + [-INF, 0, 0] <= x <= [4 3 5] +*/ +static char *test_5_negated_parallel_cols() +{ + double Ax[] = {2, -1, -1}; + int Ai[] = {0, 1, 2}; + int Ap[] = {0, 3}; + int nnz = 3; + int n_rows = 1; + int n_cols = 3; + + double lhs[] = {-INF}; + double rhs[] = {-10}; + double lbs[] = {-INF, 0, 0}; + double ubs[] = {4, 3, 5}; + double c[] = {4, -2, 1}; + + Settings *stgs = default_settings(); + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + remove_parallel_cols(prob); + problem_clean(prob, true); + + // check that new A is correct + double Ax_correct[] = {-1, -1}; + int Ai_correct[] = {0, 1}; + int Ap_correct[] = {0, 2}; + mu_assert("error Ax", ARRAYS_EQUAL(Ax_correct, A->x, 2)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai_correct, A->i, 2)); + CHECK_ROW_STARTS(A, Ap_correct); + + // check that new variable bounds are correct + double lbs_correct[] = {-8, 0}; + double ubs_correct[] = {INF, 5}; + mu_assert("error bounds", + CHECK_BOUNDS(constraints->bounds, lbs_correct, ubs_correct, 2)); + + // check that the objective function is correct + double obj_correct[] = {-2, 1}; + mu_assert("error obj", ARRAYS_EQUAL(obj_correct, prob->obj->c, 2)); + mu_assert("error offset", prob->obj->offset == 0); + + PS_FREE(stgs); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + return 0; +} + +/* Based on example 8 gurobi paper + min. [4 -2 1]x + s.t. [2 -1 -1]x <= -10 + [-INF, -INF, 0] <= x <= [4 3 5] +*/ +static char *test_6_negated_parallel_cols() +{ + double Ax[] = {2, -1, -1}; + int Ai[] = {0, 1, 2}; + int Ap[] = {0, 3}; + int nnz = 3; + int n_rows = 1; + int n_cols = 3; + + double lhs[] = {-INF}; + double rhs[] = {-10}; + double lbs[] = {-INF, -INF, 0}; + double ubs[] = {4, 3, 5}; + double c[] = {4, -2, 1}; + + Settings *stgs = default_settings(); + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + remove_parallel_cols(prob); + problem_clean(prob, true); + + // check that new A is correct + double Ax_correct[] = {-1, -1}; + int Ai_correct[] = {0, 1}; + int Ap_correct[] = {0, 2}; + mu_assert("error Ax", ARRAYS_EQUAL(Ax_correct, A->x, 2)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai_correct, A->i, 2)); + CHECK_ROW_STARTS(A, Ap_correct); + + // check that new variable bounds are correct + double lbs_correct[] = {-INF, 0}; + double ubs_correct[] = {INF, 5}; + mu_assert("error bounds", + CHECK_BOUNDS(constraints->bounds, lbs_correct, ubs_correct, 2)); + + // check that the objective function is correct + double obj_correct[] = {-2, 1}; + mu_assert("error obj", ARRAYS_EQUAL(obj_correct, prob->obj->c, 2)); + mu_assert("error offset", prob->obj->offset == 0); + + PS_FREE(stgs); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + return 0; +} + +/* Big test with all bounds equal to infinity + min. [2 3 -4 -3 -6 6 3 1]x + s.t. [1 1 -2 1 2 3 -1 5 + 0 0 0 0 0 -3 1 1 + -2 2 4 -2 -4 -6 2 2 + 0 0 0 0 0 -3 1 3 + 3 3 -6 3 6 9 -3 4 + 4 0 -8 4 8 12 -4 5 + -2 4 4 -2 -4 -6 2 6 + 0 7 0 0 0 0 0 7 + 0 5 0 0 0 0 0 8 + 0 6 0 0 0 0 0 9]x <= [10 1 20 1 30 40 20 70 50 60] +*/ +static char *test_7_parallel_cols() +{ + double Ax[] = {1, 1, -2, 1, 2, 3, -1, 5, -3, 1, 1, -2, 2, 4, -2, -4, -6, + 2, 2, -3, 1, 3, 3, 3, -6, 3, 6, 9, -3, 4, 4, -8, 4, 8, + 12, -4, 5, -2, 4, 4, -2, -4, -6, 2, 6, 7, 7, 5, 8, 6, 9}; + int Ai[] = {0, 1, 2, 3, 4, 5, 6, 7, 5, 6, 7, 0, 1, 2, 3, 4, 5, + 6, 7, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7, 0, 2, 3, 4, + 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7, 1, 7, 1, 7, 1, 7}; + int Ap[] = {0, 8, 11, 19, 22, 30, 37, 45, 47, 49, 51}; + int nnz = 51; + int n_rows = 10; + int n_cols = 8; + + double lhs[] = {-INF, -INF, -INF, -INF, -INF, -INF, -INF, -INF, -INF, -INF}; + double rhs[] = {10, 1, 20, 1, 30, 40, 20, 70, 50, 60}; + double lbs[] = {-INF, -INF, -INF, -INF, -INF, -INF, -INF, -INF}; + double ubs[] = {INF, INF, INF, INF, INF, INF, INF, INF}; + double c[] = {2, 3, -4, -3, -6, 6, 3, 1}; + + Settings *stgs = default_settings(); + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + PresolveStatus status = remove_parallel_cols(prob); + mu_assert("error status", status == UNBNDORINFEAS); + + PS_FREE(stgs); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + return 0; +} + +/* Big test with finite bounds + min. [2 3 -4 -3 -6 6 3 1]x + s.t. [1 1 -2 1 2 3 -1 5 + 0 0 0 0 0 -3 1 1 + -2 2 4 -2 -4 -6 2 2 + 0 0 0 0 0 -3 1 3 + 3 3 -6 3 6 9 -3 4 + 4 0 -8 4 8 12 -4 5 + -2 4 4 -2 -4 -6 2 6 + 0 7 0 0 0 0 0 7 + 0 5 0 0 0 0 0 8 + 0 6 0 0 0 0 0 9]x <= [10 1 20 1 30 40 20 70 50 60] + -i <= x <= i +*/ +static char *test_8_parallel_cols() +{ + double Ax[] = {1, 1, -2, 1, 2, 3, -1, 5, -3, 1, 1, -2, 2, 4, -2, -4, -6, + 2, 2, -3, 1, 3, 3, 3, -6, 3, 6, 9, -3, 4, 4, -8, 4, 8, + 12, -4, 5, -2, 4, 4, -2, -4, -6, 2, 6, 7, 7, 5, 8, 6, 9}; + int Ai[] = {0, 1, 2, 3, 4, 5, 6, 7, 5, 6, 7, 0, 1, 2, 3, 4, 5, + 6, 7, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7, 0, 2, 3, 4, + 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7, 1, 7, 1, 7, 1, 7}; + int Ap[] = {0, 8, 11, 19, 22, 30, 37, 45, 47, 49, 51}; + int nnz = 51; + int n_rows = 10; + int n_cols = 8; + + double lhs[] = {-INF, -INF, -INF, -INF, -INF, -INF, -INF, -INF, -INF, -INF}; + double rhs[] = {10, 1, 20, 1, 30, 40, 20, 70, 50, 60}; + double lbs[] = {-1, -2, -3, -4, -5, -6, -7, -8}; + double ubs[] = {1, 2, 3, 4, 5, 6, 7, 8}; + double c[] = {2, 3, -4, -3, -6, 6, 3, 1}; + + Settings *stgs = default_settings(); + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + remove_parallel_cols(prob); + problem_clean(prob, true); + + // check that new A is correct + double Ax_correct[] = {1, -2, 1, 3, -1, 5, -3, 1, 1, 2, 4, -2, -6, 2, + 2, -3, 1, 3, 3, -6, 3, 9, -3, 4, -8, 4, 12, -4, + 5, 4, 4, -2, -6, 2, 6, 7, 7, 5, 8, 6, 9}; + int Ai_correct[] = {0, 1, 2, 3, 4, 5, 3, 4, 5, 0, 1, 2, 3, 4, + 5, 3, 4, 5, 0, 1, 2, 3, 4, 5, 1, 2, 3, 4, + 5, 0, 1, 2, 3, 4, 5, 0, 5, 0, 5, 0, 5}; + int Ap_correct[] = {0, 6, 9, 15, 18, 24, 29, 35, 37, 39, 41}; + +// on mac the order of the columns is different, so we only run this test on +// linux +#ifdef __linux__ + mu_assert("error Ax", ARRAYS_EQUAL(Ax_correct, A->x, A->nnz)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai_correct, A->i, A->nnz)); + CHECK_ROW_STARTS(A, Ap_correct); + + // check that new variable bounds are correct + double lbs_correct[] = {-2, -3.5, -14, -6, -7, -8}; + double ubs_correct[] = {2, 3.5, 14, 6, 7, 8}; + mu_assert("error bounds", + CHECK_BOUNDS(constraints->bounds, lbs_correct, ubs_correct, 6)); + + // check that the objective function is correct + double obj_correct[] = {3, -4, -3, 6, 3, 1}; + mu_assert("error obj", ARRAYS_EQUAL(obj_correct, prob->obj->c, 6)); + mu_assert("error offset", prob->obj->offset == 0); +#endif // __linux__ + + PS_FREE(stgs); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + return 0; +} + +static const char *all_tests_parallel_cols() +{ + mu_run_test(test_1_parallel_cols, counter_parallel_cols); + mu_run_test(test_2_parallel_cols, counter_parallel_cols); + mu_run_test(test_3_parallel_cols, counter_parallel_cols); + mu_run_test(test_4_parallel_cols, counter_parallel_cols); + mu_run_test(test_5_parallel_cols, counter_parallel_cols); + mu_run_test(test_6_parallel_cols, counter_parallel_cols); + mu_run_test(test_5_negated_parallel_cols, counter_parallel_cols); + mu_run_test(test_6_negated_parallel_cols, counter_parallel_cols); + mu_run_test(test_7_parallel_cols, counter_parallel_cols); + mu_run_test(test_8_parallel_cols, counter_parallel_cols); + + return 0; +} + +int test_parallel_cols() +{ + const char *result = all_tests_parallel_cols(); + if (result != 0) + { + printf("%s\n", result); + printf("parallel_cols: TEST FAILED!\n"); + } + else + { + printf("parallel_cols: ALL TESTS PASSED\n"); + } + printf("parallel_cols: Tests run: %d\n", counter_parallel_cols); + return result == 0; +} + +#endif // TEST_PARALLEL_COLS_H diff --git a/tests/test_Parallel_rows.h b/tests/test_Parallel_rows.h new file mode 100644 index 00000000..9e9f1234 --- /dev/null +++ b/tests/test_Parallel_rows.h @@ -0,0 +1,1287 @@ +#ifndef TEST_PARALLEL_ROWS_H +#define TEST_PARALLEL_ROWS_H + +#include "API.h" +#include "Debugger.h" +#include "Matrix.h" +#include "Parallel_rows.h" + +#include "math.h" +#include "minunit.h" +#include "test_macros.h" +#include + +static int counter_parallel_rows = 0; + +/* Test that rows with the same sparsity pattern are given the same sparsity ID. + A_in = [1, 0, 0, 1, 0] + [0, 1, 0, 1, 0] + [1, 1, 0, 1, 0] + [0, 1, 0, 1, 0] + [1, 0, 0, 1, 0] + */ +static char *test_1_parallel_rows() +{ + double Ax[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; + int Ai[] = {0, 3, 1, 3, 0, 1, 3, 1, 3, 0, 3}; + int Ap[] = {0, 2, 4, 7, 9, 11}; + int nnz = 11; + int n_rows = 5; + int n_cols = 5; + + Matrix *A = matrix_new(Ax, Ai, Ap, n_rows, n_cols, nnz); + RowTag row_tags[] = {R_TAG_NONE, R_TAG_NONE, R_TAG_NONE, R_TAG_NONE, R_TAG_NONE}; + int sparsity_IDs[5]; + int coeff_hashes[5]; + compute_supp_and_coeff_hash(A, row_tags, sparsity_IDs, coeff_hashes, + R_TAG_INACTIVE); + + mu_assert("error", (sparsity_IDs[0] == sparsity_IDs[4] && + sparsity_IDs[1] == sparsity_IDs[3])); + + free_matrix(A); + return 0; +} + +/* Test that rows with the same NORMALIZED coefficients in the same order + are given the same hash value. + A_in = [ 1, 0, 0, 2, 0] + [ 0, 1, 0, -1, 0] + [-0.5, 0, -1, 0, 0] + [ 0.7, 0, 0, 0, 1.4] + [ -3, 3, 0, 0, 0] + [ 1, 0, 0, 3, 0] + */ +static char *test_2_parallel_rows() +{ + double Ax[] = {1, 2, 1, -1, -0.5, -1, 0.7, 1.4, -3, 3, 1, 3}; + int Ai[] = {0, 3, 1, 3, 0, 2, 0, 4, 0, 1, 0, 3}; + int Ap[] = {0, 2, 4, 6, 8, 10, 12}; + int nnz = 12; + int n_rows = 6; + int n_cols = 5; + + Matrix *A = matrix_new(Ax, Ai, Ap, n_rows, n_cols, nnz); + RowTag row_tags[] = {R_TAG_NONE, R_TAG_NONE, R_TAG_NONE, + R_TAG_NONE, R_TAG_NONE, R_TAG_NONE}; + int sparsity_IDs[6]; + int coeff_hashes[6]; + compute_supp_and_coeff_hash(A, row_tags, sparsity_IDs, coeff_hashes, + R_TAG_INACTIVE); + + mu_assert("error", (coeff_hashes[0] == coeff_hashes[2] && + coeff_hashes[0] == coeff_hashes[3] && + coeff_hashes[1] == coeff_hashes[4])); + free_matrix(A); + return 0; +} + +/* Test that two groups of parallel rows are found. + A_in = [ 5/8, 0, 0, 15/8, 0] + [ 0, sqrt(50), 0, 2 * sqrt(50), 0] + [ 0, 1, 0, 3, 0] + [0, -sqrt(5), 0, -2 * sqrt(5), 0] + [-3/4, 0, 0, -9/4, 0] + */ +static char *test_3_parallel_rows() +{ + double Ax[] = {5.0 / 8, 15.0 / 8, sqrt(50), 2 * sqrt(50), 1, + 3, -sqrt(5), -2 * sqrt(5), -3.0 / 4, -9.0 / 4}; + int Ai[] = {0, 3, 1, 3, 1, 3, 1, 3, 0, 3}; + int Ap[] = {0, 2, 4, 6, 8, 10}; + int nnz = 10; + int n_rows = 5; + int n_cols = 5; + + Matrix *A = matrix_new(Ax, Ai, Ap, n_rows, n_cols, nnz); + RowTag row_tags[] = {R_TAG_NONE, R_TAG_NONE, R_TAG_NONE, R_TAG_NONE, R_TAG_NONE}; + + iVec *group_start = iVec_new(10); + int parallel_rows[5]; + int sparsity_IDs[5]; + int coeff_hashes[5]; + + find_parallel_rows(A, row_tags, group_start, parallel_rows, sparsity_IDs, + coeff_hashes, R_TAG_INACTIVE); + + int parallel_rows_correct[] = {4, 0, 3, 1}; + int group_start_correct[] = {0, 2, 4}; + + mu_assert("error", ARRAYS_EQUAL(parallel_rows_correct, parallel_rows, 4)); + mu_assert("error", group_start->len == 3); + mu_assert("error", ARRAYS_EQUAL(group_start_correct, group_start->data, 3)); + + free_matrix(A); + iVec_free(group_start); + return 0; +} + +int compare_ints(const void *a, const void *b) +{ + int arg1 = *(const int *) a; + int arg2 = *(const int *) b; + + if (arg1 < arg2) + { + return -1; + } + + if (arg1 > arg2) + { + return 1; + } + return 0; +} + +/* Test that several groups of parallel rows are found */ +static char *test_4_parallel_rows() +{ + int n_rows = 1000; + int n_cols = 2000; + double density = 0.1; + Matrix *A = random_matrix_new(n_rows, n_cols, density); + int jump = 50; + + // allocate a bunch of extra memory so we can replace rows as we wish + int new_alloc = 2 * A->n_alloc; + A->x = (double *) ps_realloc(A->x, new_alloc, sizeof(double)); + A->i = (int *) ps_realloc(A->i, new_alloc, sizeof(int)); + A->n_alloc = new_alloc; + srand(1337); + + // ------------------------------------------------------------------- + // create first group of parallel rows + // ------------------------------------------------------------------- + int start1 = 0; + int *cols1 = A->i + A->p[start1].start; + double *vals1 = A->x + A->p[start1].start; + int len1 = A->p[start1].end - A->p[start1].start; + + for (int i = 1; i < 8; ++i) + { + double ratio = (double) (rand() - rand()) / RAND_MAX; + replace_row_A(A, start1 + i * jump, ratio, vals1, cols1, len1); + } + + // ------------------------------------------------------------------- + // create second group of parallel rows + // ------------------------------------------------------------------- + int start2 = 10; + int *cols2 = A->i + A->p[start2].start; + double *vals2 = A->x + A->p[start2].start; + int len2 = A->p[start2].end - A->p[start2].start; + + for (int i = 1; i < 8; ++i) + { + double ratio = (double) (rand() - rand()) / RAND_MAX; + replace_row_A(A, start2 + i * jump, ratio, vals2, cols2, len2); + } + + // ------------------------------------------------------------------- + // create third group of parallel rows + // ------------------------------------------------------------------- + int start3 = 20; + int *cols3 = A->i + A->p[start3].start; + double *vals3 = A->x + A->p[start3].start; + int len3 = A->p[start3].end - A->p[start3].start; + + for (int i = 1; i < 8; ++i) + { + double ratio = (double) (rand() - rand()) / RAND_MAX; + replace_row_A(A, start3 + i * jump, ratio, vals3, cols3, len3); + } + + RowTag row_tags[1000]; + for (int i = 0; i < 1000; ++i) + { + row_tags[i] = R_TAG_NONE; + } + iVec *group_start = iVec_new(10); + int parallel_rows[1000]; + int sparsity_IDs[1000]; + int coeff_hashes[1000]; + + find_parallel_rows(A, row_tags, group_start, parallel_rows, sparsity_IDs, + coeff_hashes, R_TAG_INACTIVE); + + mu_assert("error group_starts len", group_start->len == 4); + int group_start_correct[] = {0, 8, 16, 24}; + mu_assert("error group_starts data", + ARRAYS_EQUAL(group_start_correct, group_start->data, 4)); + + // Below we verify that the answer is correct. We need to sort and do some + // hacking because the rows appears in a different order on mac compared to + // linux.) + + int first_group_correct[] = {0, 50, 100, 150, 200, 250, 300, 350}; + int second_group_correct[] = {10, 60, 110, 160, 210, 260, 310, 360}; + int third_group_correct[] = {20, 70, 120, 170, 220, 270, 320, 370}; + int sorted[8]; + + // check that first 8 entries of parallell rows corresponds to one of the + // groups above + for (int i = 0; i < 8; ++i) + { + sorted[i] = parallel_rows[i]; + } + qsort(sorted, 8, sizeof(int), compare_ints); + + if (sorted[0] == 0) + { + mu_assert("error first group", ARRAYS_EQUAL(sorted, first_group_correct, 8)); + } + else if (sorted[0] == 10) + { + mu_assert("error first group", + ARRAYS_EQUAL(sorted, second_group_correct, 8)); + } + else + { + assert(sorted[0] == 20); + mu_assert("error first group", ARRAYS_EQUAL(sorted, third_group_correct, 8)); + } + + // check that next 8 entries of parallell rows corresponds to one of the + // groups above + for (int i = 0; i < 8; ++i) + { + sorted[i] = parallel_rows[i + 8]; + } + qsort(sorted, 8, sizeof(int), compare_ints); + + if (sorted[0] == 0) + { + mu_assert("error first group", ARRAYS_EQUAL(sorted, first_group_correct, 8)); + } + else if (sorted[0] == 10) + { + mu_assert("error first group", + ARRAYS_EQUAL(sorted, second_group_correct, 8)); + } + else + { + assert(sorted[0] == 20); + mu_assert("error first group", ARRAYS_EQUAL(sorted, third_group_correct, 8)); + } + + // check that next 8 entries of parallell rows corresponds to one of the + // groups above + for (int i = 0; i < 8; ++i) + { + sorted[i] = parallel_rows[i + 16]; + } + qsort(sorted, 8, sizeof(int), compare_ints); + + if (sorted[0] == 0) + { + mu_assert("error first group", ARRAYS_EQUAL(sorted, first_group_correct, 8)); + } + else if (sorted[0] == 10) + { + mu_assert("error first group", + ARRAYS_EQUAL(sorted, second_group_correct, 8)); + } + else + { + assert(sorted[0] == 20); + mu_assert("error first group", ARRAYS_EQUAL(sorted, third_group_correct, 8)); + } + + free_matrix(A); + iVec_free(group_start); + return 0; +} + +/* Test that several groups of parallel rows are found, while some are + manually marked as inactive */ +static char *test_5_parallel_rows() +{ + int n_rows = 1000; + int n_cols = 2000; + double density = 0.1; + Matrix *A = random_matrix_new(n_rows, n_cols, density); + int jump = 50; + + // allocate a bunch of extra memory so we can replace rows as we wish + int new_alloc = 2 * A->n_alloc; + A->x = (double *) ps_realloc(A->x, new_alloc, sizeof(double)); + A->i = (int *) ps_realloc(A->i, new_alloc, sizeof(int)); + A->n_alloc = new_alloc; + srand(1337); + + // ------------------------------------------------------------------- + // create first group of parallel rows + // ------------------------------------------------------------------- + int start1 = 0; + int *cols1 = A->i + A->p[start1].start; + double *vals1 = A->x + A->p[start1].start; + int len1 = A->p[start1].end - A->p[start1].start; + + for (int i = 1; i < 8; ++i) + { + double ratio = (double) (rand() - rand()) / RAND_MAX; + replace_row_A(A, start1 + i * jump, ratio, vals1, cols1, len1); + } + + // ------------------------------------------------------------------- + // create second group of parallel rows + // ------------------------------------------------------------------- + int start2 = 10; + int *cols2 = A->i + A->p[start2].start; + double *vals2 = A->x + A->p[start2].start; + int len2 = A->p[start2].end - A->p[start2].start; + + for (int i = 1; i < 8; ++i) + { + double ratio = (double) (rand() - rand()) / RAND_MAX; + replace_row_A(A, start2 + i * jump, ratio, vals2, cols2, len2); + } + + // ------------------------------------------------------------------- + // create third group of parallel rows + // ------------------------------------------------------------------- + int start3 = 20; + int *cols3 = A->i + A->p[start3].start; + double *vals3 = A->x + A->p[start3].start; + int len3 = A->p[start3].end - A->p[start3].start; + + for (int i = 1; i < 8; ++i) + { + double ratio = (double) (rand() - rand()) / RAND_MAX; + replace_row_A(A, start3 + i * jump, ratio, vals3, cols3, len3); + } + + RowTag row_tags[1000]; + for (int i = 0; i < 1000; ++i) + { + row_tags[i] = R_TAG_NONE; + } + iVec *group_start = iVec_new(10); + int parallel_rows[1000] = {0}; + int sparsity_IDs[1000] = {0}; + int coeff_hashes[1000] = {0}; + + row_tags[0] = R_TAG_INACTIVE; + row_tags[200] = R_TAG_INACTIVE; + row_tags[360] = R_TAG_INACTIVE; + row_tags[310] = R_TAG_INACTIVE; + + find_parallel_rows(A, row_tags, group_start, parallel_rows, sparsity_IDs, + coeff_hashes, R_TAG_INACTIVE); + + mu_assert("error group_starts len", group_start->len == 4); + // int group_start_correct[] = {0, 8, 14, 20}; + // mu_assert("error group_starts data", + // ARRAYS_EQUAL(group_start_correct, group_start->data, 4)); + + int first_group_correct[] = {10, 60, 110, 160, 210, 260}; + int second_group_correct[] = {20, 70, 120, 170, 220, 270, 320, 370}; + int third_group_correct[] = {50, 100, 150, 250, 300, 350}; + + // figure out length of the groups + int len_group1 = group_start->data[1] - group_start->data[0]; + int len_group2 = group_start->data[2] - group_start->data[1]; + int len_group3 = group_start->data[3] - group_start->data[2]; + + int sorted[8]; + + // check that first entries of parallell rows corresponds to one of the + // groups above + for (int i = 0; i < len_group1; ++i) + { + sorted[i] = parallel_rows[i]; + } + qsort(sorted, len_group1, sizeof(int), compare_ints); + + if (sorted[0] == 10) + { + mu_assert("error first group", ARRAYS_EQUAL(sorted, first_group_correct, 6)); + } + else if (sorted[0] == 20) + { + mu_assert("error first group", + ARRAYS_EQUAL(sorted, second_group_correct, 8)); + } + else + { + assert(sorted[0] == 50); + mu_assert("error first group", ARRAYS_EQUAL(sorted, third_group_correct, 6)); + } + + // check that next entries of parallell rows corresponds to one of the + // groups above + for (int i = 0; i < len_group2; ++i) + { + sorted[i] = parallel_rows[i + len_group1]; + } + qsort(sorted, len_group2, sizeof(int), compare_ints); + + if (sorted[0] == 10) + { + mu_assert("error first group", ARRAYS_EQUAL(sorted, first_group_correct, 6)); + } + else if (sorted[0] == 20) + { + mu_assert("error first group", + ARRAYS_EQUAL(sorted, second_group_correct, 8)); + } + else + { + assert(sorted[0] == 50); + mu_assert("error first group", ARRAYS_EQUAL(sorted, third_group_correct, 6)); + } + + // check that next entries of parallell rows corresponds to one of the + // groups above + for (int i = 0; i < len_group3; ++i) + { + sorted[i] = parallel_rows[i + len_group1 + len_group2]; + } + qsort(sorted, len_group3, sizeof(int), compare_ints); + + if (sorted[0] == 10) + { + mu_assert("error first group", ARRAYS_EQUAL(sorted, first_group_correct, 6)); + } + else if (sorted[0] == 20) + { + mu_assert("error first group", + ARRAYS_EQUAL(sorted, second_group_correct, 8)); + } + else + { + assert(sorted[0] == 50); + mu_assert("error first group", ARRAYS_EQUAL(sorted, third_group_correct, 6)); + } + + free_matrix(A); + iVec_free(group_start); + return 0; +} + +/* Infeasible problem (similar to test10 old code) + min. [0 0] x + s.t. x1 + x2 = 2 + -3x1 + 4x2 <= 0 + 2x1 + 2x2 <= 3 +*/ +static char *test_6_parallel_rows() +{ + double Ax[] = {1, 1, -3, 4, 2, 2}; + int Ai[] = {0, 1, 0, 1, 0, 1}; + int Ap[] = {0, 2, 4, 6}; + int nnz = 6; + int n_rows = 3; + int n_cols = 2; + + double lhs[] = {2, -INF, -INF}; + double rhs[] = {2, 0, 3}; + double lbs[] = {0, 0}; + double ubs[] = {INF, INF}; + double c[] = {0, 0}; + + Settings *stgs = default_settings(); + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + PresolveStatus status = remove_parallel_rows(constraints); + mu_assert("error", status == INFEASIBLE); + problem_clean(prob, true); + + mu_assert("error Ax", ARRAYS_EQUAL(Ax, A->x, nnz)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai, A->i, nnz)); + CHECK_ROW_STARTS(A, Ap); + + PS_FREE(stgs); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + return 0; +} + +/* Infeasible problem (similar to test 11 old code) + min. [0 0] x + s.t. x1 + x2 = 2 + -3x1 + 4x2 <= 0 + -2x1 - 2x2 <= -5 +*/ +static char *test_7_parallel_rows() +{ + double Ax[] = {1, 1, -3, 4, -2, -2}; + int Ai[] = {0, 1, 0, 1, 0, 1}; + int Ap[] = {0, 2, 4, 6}; + int nnz = 6; + int n_rows = 3; + int n_cols = 2; + + double lhs[] = {2, -INF, -INF}; + double rhs[] = {2, 0, -5}; + double lbs[] = {0, 0}; + double ubs[] = {INF, INF}; + double c[] = {0, 0}; + + Settings *stgs = default_settings(); + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + PresolveStatus status = remove_parallel_rows(constraints); + mu_assert("error", status == INFEASIBLE); + problem_clean(prob, true); + + mu_assert("error Ax", ARRAYS_EQUAL(Ax, A->x, nnz)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai, A->i, nnz)); + CHECK_ROW_STARTS(A, Ap); + + PS_FREE(stgs); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + return 0; +} + +/* Infeasible problem (similar to test 12 old code) + min. [0 0] x + s.t. x1 + x2 = 2 + -3x1 + 4x2 <= 0 + 2x1 + 2x2 >= 5 +*/ +static char *test_8_parallel_rows() +{ + double Ax[] = {1, 1, -3, 4, 2, 2}; + int Ai[] = {0, 1, 0, 1, 0, 1}; + int Ap[] = {0, 2, 4, 6}; + int nnz = 6; + int n_rows = 3; + int n_cols = 2; + + double lhs[] = {2, -INF, 5}; + double rhs[] = {2, 0, INF}; + double lbs[] = {0, 0}; + double ubs[] = {INF, INF}; + double c[] = {0, 0}; + + Settings *stgs = default_settings(); + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + PresolveStatus status = remove_parallel_rows(constraints); + mu_assert("error", status == INFEASIBLE); + problem_clean(prob, true); + + mu_assert("error Ax", ARRAYS_EQUAL(Ax, A->x, nnz)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai, A->i, nnz)); + CHECK_ROW_STARTS(A, Ap); + + PS_FREE(stgs); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + return 0; +} + +/* Infeasible problem (similar to test 13 old code) + min. [0 0] x + s.t. x1 + x2 = 2 + -3x1 + 4x2 <= 0 + -2x1 - 2x2 >= -2 +*/ +static char *test_9_parallel_rows() +{ + double Ax[] = {1, 1, -3, 4, -2, -2}; + int Ai[] = {0, 1, 0, 1, 0, 1}; + int Ap[] = {0, 2, 4, 6}; + int nnz = 6; + int n_rows = 3; + int n_cols = 2; + + double lhs[] = {2, -INF, -2}; + double rhs[] = {2, 0, INF}; + double lbs[] = {0, 0}; + double ubs[] = {INF, INF}; + double c[] = {0, 0}; + + Settings *stgs = default_settings(); + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + PresolveStatus status = remove_parallel_rows(constraints); + mu_assert("error", status == INFEASIBLE); + problem_clean(prob, true); + + mu_assert("error Ax", ARRAYS_EQUAL(Ax, A->x, nnz)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai, A->i, nnz)); + CHECK_ROW_STARTS(A, Ap); + + PS_FREE(stgs); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + return 0; +} + +/* Infeasible problem (similar to test 14 old code) + min. [0 0] x + s.t. x1 + x2 <= 1 + -3x1 + 4x2 <= 0 + x1 + x2 = 2 +*/ +static char *test_10_parallel_rows() +{ + double Ax[] = {1, 1, -3, 4, 1, 1}; + int Ai[] = {0, 1, 0, 1, 0, 1}; + int Ap[] = {0, 2, 4, 6}; + int nnz = 6; + int n_rows = 3; + int n_cols = 2; + + double lhs[] = {-INF, -INF, 2}; + double rhs[] = {1, 0, 2}; + double lbs[] = {0, 0}; + double ubs[] = {INF, INF}; + double c[] = {0, 0}; + + Settings *stgs = default_settings(); + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + PresolveStatus status = remove_parallel_rows(constraints); + mu_assert("error", status == INFEASIBLE); + problem_clean(prob, true); + + mu_assert("error Ax", ARRAYS_EQUAL(Ax, A->x, nnz)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai, A->i, nnz)); + CHECK_ROW_STARTS(A, Ap); + + PS_FREE(stgs); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + return 0; +} + +/* Infeasible problem (similar to test 15 old code) + min. [0 0] x + s.t. -x1 - x2 <= -3 + -3x1 + 4x2 <= 0 + x1 + x2 = 2 +*/ +static char *test_11_parallel_rows() +{ + double Ax[] = {-1, -1, -3, 4, 1, 1}; + int Ai[] = {0, 1, 0, 1, 0, 1}; + int Ap[] = {0, 2, 4, 6}; + int nnz = 6; + int n_rows = 3; + int n_cols = 2; + + double lhs[] = {-INF, -INF, 2}; + double rhs[] = {-3, 0, 2}; + double lbs[] = {0, 0}; + double ubs[] = {INF, INF}; + double c[] = {0, 0}; + + Settings *stgs = default_settings(); + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + PresolveStatus status = remove_parallel_rows(constraints); + mu_assert("error", status == INFEASIBLE); + problem_clean(prob, true); + + mu_assert("error Ax", ARRAYS_EQUAL(Ax, A->x, nnz)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai, A->i, nnz)); + CHECK_ROW_STARTS(A, Ap); + + PS_FREE(stgs); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + return 0; +} + +/* Infeasible problem (similar to test 16 old code) + min. [0 0] x + s.t. x1 + x2 >= 3 + -3x1 + 4x2 <= 0 + x1 + x2 = 2 +*/ +static char *test_12_parallel_rows() +{ + double Ax[] = {1, 1, -3, 4, 1, 1}; + int Ai[] = {0, 1, 0, 1, 0, 1}; + int Ap[] = {0, 2, 4, 6}; + int nnz = 6; + int n_rows = 3; + int n_cols = 2; + + double lhs[] = {3, -INF, 2}; + double rhs[] = {INF, 0, 2}; + double lbs[] = {0, 0}; + double ubs[] = {INF, INF}; + double c[] = {0, 0}; + + Settings *stgs = default_settings(); + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + PresolveStatus status = remove_parallel_rows(constraints); + mu_assert("error", status == INFEASIBLE); + problem_clean(prob, true); + + mu_assert("error Ax", ARRAYS_EQUAL(Ax, A->x, nnz)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai, A->i, nnz)); + CHECK_ROW_STARTS(A, Ap); + + PS_FREE(stgs); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + return 0; +} + +/* Infeasible problem (similar to test 17 old code) + min. [0 0] x + s.t. -x1 - x2 >= -1 + -3x1 + 4x2 <= 0 + x1 + x2 = 2 +*/ +static char *test_13_parallel_rows() +{ + double Ax[] = {-1, -1, -3, 4, 1, 1}; + int Ai[] = {0, 1, 0, 1, 0, 1}; + int Ap[] = {0, 2, 4, 6}; + int nnz = 6; + int n_rows = 3; + int n_cols = 2; + + double lhs[] = {-1, -INF, 2}; + double rhs[] = {INF, 0, 2}; + double lbs[] = {0, 0}; + double ubs[] = {INF, INF}; + double c[] = {0, 0}; + + Settings *stgs = default_settings(); + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + PresolveStatus status = remove_parallel_rows(constraints); + mu_assert("error", status == INFEASIBLE); + problem_clean(prob, true); + + mu_assert("error Ax", ARRAYS_EQUAL(Ax, A->x, nnz)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai, A->i, nnz)); + CHECK_ROW_STARTS(A, Ap); + + PS_FREE(stgs); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + return 0; +} + +/* Remaining row is an equality (similar to test 18 old code) + min. c x + s.t. random constraint + -3q1 b1 <= q1 * a @ x <= 2 q1 b1 + a @ x = b1 + q2 * a @ x = q2 b1 + q3 * a @ x = q3 b1 + random constraint +*/ +static char *test_14_parallel_rows() +{ + + // ----------------------------------------------- + // build A matrix with brute force + // ----------------------------------------------- + double row0_vals[] = {2, 1, -3, 4, -5}; + int row0_cols[] = {0, 1, 2, 3, 4}; + + double ax[3] = {1.3, 2.1, -1.7}; + double ai[3] = {0, 1, 3}; + double b1 = 1.4; + + double row1_vals[3]; + double row3_vals[3]; + double row4_vals[3]; + int row1_cols[3]; + int row3_cols[3]; + int row4_cols[3]; + + double q1 = 1.2; + double q2 = -1.3; + double q3 = 1.5; + + int i; + for (i = 0; i < 3; ++i) + { + row1_vals[i] = q1 * ax[i]; + row3_vals[i] = q2 * ax[i]; + row4_vals[i] = q3 * ax[i]; + row1_cols[i] = ai[i]; + row3_cols[i] = ai[i]; + row4_cols[i] = ai[i]; + } + + double row5_vals[] = {1, 1.1, 1, 1}; + int row5_cols[] = {0, 1, 2, 4}; + + int nnz = 5 + 3 + 3 + 3 + 3 + 4; + int n_rows = 6; + int n_cols = 5; + + double Ax[5 + 3 + 3 + 3 + 3 + 4]; + int Ai[5 + 3 + 3 + 3 + 3 + 4]; + int Ap[7]; + + Ap[0] = 0; + for (i = 0; i < 5; ++i) + { + Ax[i] = row0_vals[i]; + Ai[i] = row0_cols[i]; + } + Ap[1] = Ap[0] + 5; + + for (i = 0; i < 3; ++i) + { + Ax[Ap[1] + i] = row1_vals[i]; + Ai[Ap[1] + i] = row1_cols[i]; + } + Ap[2] = Ap[1] + 3; + + for (i = 0; i < 3; ++i) + { + Ax[Ap[2] + i] = ax[i]; + Ai[Ap[2] + i] = ai[i]; + } + Ap[3] = Ap[2] + 3; + + for (i = 0; i < 3; ++i) + { + Ax[Ap[3] + i] = row3_vals[i]; + Ai[Ap[3] + i] = row3_cols[i]; + } + Ap[4] = Ap[3] + 3; + + for (i = 0; i < 3; ++i) + { + Ax[Ap[4] + i] = row4_vals[i]; + Ai[Ap[4] + i] = row4_cols[i]; + } + Ap[5] = Ap[4] + 3; + + for (i = 0; i < 4; ++i) + { + Ax[Ap[5] + i] = row5_vals[i]; + Ai[Ap[5] + i] = row5_cols[i]; + } + Ap[6] = Ap[5] + 4; + + // ----------------------------------------------- + // begin test + // ----------------------------------------------- + double lhs[] = {-2.1, -3 * q1 * b1, b1, q2 * b1, q3 * b1, -INF}; + double rhs[] = {3.1, 2 * q1 * b1, b1, q2 * b1, q3 * b1, 5}; + double lbs[] = {0, 0, 0, 0, 0}; + double ubs[] = {INF, INF, INF, INF, INF}; + double c[] = {0, 0, 0, 0, 0}; + + Settings *stgs = default_settings(); + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + PresolveStatus status = remove_parallel_rows(constraints); + mu_assert("error", status != INFEASIBLE); + problem_clean(prob, true); + + // check A matrix + double Ax_correct[] = {row0_vals[0], row0_vals[1], row0_vals[2], row0_vals[3], + row0_vals[4], ax[0], ax[1], ax[2], + row5_vals[0], row5_vals[1], row5_vals[2], row5_vals[3]}; + + double Ai_correct[] = {row0_cols[0], row0_cols[1], row0_cols[2], row0_cols[3], + row0_cols[4], ai[0], ai[1], ai[2], + row5_cols[0], row5_cols[1], row5_cols[2], row5_cols[3]}; + double Ap_correct[] = {0, 5, 8, 12}; + + mu_assert("error Ax", ARRAYS_EQUAL(Ax_correct, A->x, 12)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai_correct, A->i, 12)); + CHECK_ROW_STARTS(A, Ap_correct); + + // check row tags + RowTag row_tags_correct[] = {R_TAG_NONE, R_TAG_EQ, R_TAG_LHS_INF}; + mu_assert("error row tags", + ARRAYS_EQUAL(row_tags_correct, constraints->row_tags, 3)); + + // check lhs and rhs + double lhs_correct[] = {-2.1, b1, -INF}; + double rhs_correct[] = {3.1, b1, 5}; + mu_assert("error lhs", ARRAYS_EQUAL(lhs_correct, constraints->lhs, 3)); + mu_assert("error rhs", ARRAYS_EQUAL(rhs_correct, constraints->rhs, 3)); + + PS_FREE(stgs); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + return 0; +} + +/* Remaining row is an inequality (similar to test 19 old code) + min. c x + s.t. random constraint + random constraint + a @ x <= 10 + 3 <= 2 * a @ x <= 22 + -18 <= -2 * a @ x + random constraint + + After presolve we should have random constraint, random constraint, + 1.5 <= a @ x <= 9 (or something equivalent), random constraint +*/ +static char *test_15_parallel_rows() +{ + + // ----------------------------------------------- + // build A matrix with brute force + // ----------------------------------------------- + double row0_vals[] = {2, 1, -3, 4, -5}; + int row0_cols[] = {0, 1, 2, 3, 4}; + + double ax[3] = {1.3, 2.1, -1.7}; + double ai[3] = {0, 1, 3}; + + double row1_vals[3]; + double row3_vals[3]; + double row4_vals[3]; + int row1_cols[3]; + int row3_cols[3]; + int row4_cols[3]; + + double q1 = 1.2; + double q2 = 2; + double q3 = -2; + + int i; + for (i = 0; i < 3; ++i) + { + row1_vals[i] = q1 * ax[i] + i; + row3_vals[i] = q2 * ax[i]; + row4_vals[i] = q3 * ax[i]; + row1_cols[i] = ai[i]; + row3_cols[i] = ai[i]; + row4_cols[i] = ai[i]; + } + + double row5_vals[] = {1, 1.1, 1, 1}; + int row5_cols[] = {0, 1, 2, 4}; + + int nnz = 5 + 3 + 3 + 3 + 3 + 4; + int n_rows = 6; + int n_cols = 5; + + double Ax[5 + 3 + 3 + 3 + 3 + 4]; + int Ai[5 + 3 + 3 + 3 + 3 + 4]; + int Ap[7]; + + Ap[0] = 0; + for (i = 0; i < 5; ++i) + { + Ax[i] = row0_vals[i]; + Ai[i] = row0_cols[i]; + } + Ap[1] = Ap[0] + 5; + + for (i = 0; i < 3; ++i) + { + Ax[Ap[1] + i] = row1_vals[i]; + Ai[Ap[1] + i] = row1_cols[i]; + } + Ap[2] = Ap[1] + 3; + + for (i = 0; i < 3; ++i) + { + Ax[Ap[2] + i] = ax[i]; + Ai[Ap[2] + i] = ai[i]; + } + Ap[3] = Ap[2] + 3; + + for (i = 0; i < 3; ++i) + { + Ax[Ap[3] + i] = row3_vals[i]; + Ai[Ap[3] + i] = row3_cols[i]; + } + Ap[4] = Ap[3] + 3; + + for (i = 0; i < 3; ++i) + { + Ax[Ap[4] + i] = row4_vals[i]; + Ai[Ap[4] + i] = row4_cols[i]; + } + Ap[5] = Ap[4] + 3; + + for (i = 0; i < 4; ++i) + { + Ax[Ap[5] + i] = row5_vals[i]; + Ai[Ap[5] + i] = row5_cols[i]; + } + Ap[6] = Ap[5] + 4; + + // ----------------------------------------------- + // begin test + // ----------------------------------------------- + double lhs[] = {-2.1, -1, -INF, 3, -18, -INF}; + double rhs[] = {3.1, 3, 10, 22, INF, 5}; + double lbs[] = {0, 0, 0, 0, 0}; + double ubs[] = {INF, INF, INF, INF, INF}; + double c[] = {0, 0, 0, 0, 0}; + + Settings *stgs = default_settings(); + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + PresolveStatus status = remove_parallel_rows(constraints); + mu_assert("error", status != INFEASIBLE); + problem_clean(prob, true); + + // check A matrix + double Ax_correct[] = {row0_vals[0], row0_vals[1], row0_vals[2], row0_vals[3], + row0_vals[4], row1_vals[0], row1_vals[1], row1_vals[2], + 2 * ax[0], 2 * ax[1], 2 * ax[2], row5_vals[0], + row5_vals[1], row5_vals[2], row5_vals[3]}; + + double Ai_correct[] = {row0_cols[0], row0_cols[1], row0_cols[2], row0_cols[3], + row0_cols[4], row1_cols[0], row1_cols[1], row1_cols[2], + ai[0], ai[1], ai[2], row5_cols[0], + row5_cols[1], row5_cols[2], row5_cols[3]}; + double Ap_correct[] = {0, 5, 8, 11, 15}; + + mu_assert("error Ax", ARRAYS_EQUAL(Ax_correct, A->x, 15)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai_correct, A->i, 15)); + CHECK_ROW_STARTS(A, Ap_correct); + + // check row tags + RowTag row_tags_correct[] = {R_TAG_NONE, R_TAG_NONE, R_TAG_NONE, R_TAG_LHS_INF}; + mu_assert("error row tags", + ARRAYS_EQUAL(row_tags_correct, constraints->row_tags, 4)); + + // check lhs and rhs + double lhs_correct[] = {-2.1, -1, 3, -INF}; + double rhs_correct[] = {3.1, 3, 18, 5}; + mu_assert("error lhs", ARRAYS_EQUAL(lhs_correct, constraints->lhs, 4)); + mu_assert("error rhs", ARRAYS_EQUAL(rhs_correct, constraints->rhs, 4)); + + PS_FREE(stgs); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + return 0; +} + +/* Remaining row is an inequality (similar to test 20 old code) + min. c x + s.t. random constraint + random constraint + 3 <= 2 * a @ x <= 22 + -18 <= -2 * a @ x + a @ x <= 10 + random constraint + + After presolve we should have random constraint, random constraint, + 1.5 <= a @ x <= 9 (or something equivalent), random constraint +*/ +static char *test_16_parallel_rows() +{ + + // ----------------------------------------------- + // build A matrix with brute force + // ----------------------------------------------- + double row0_vals[] = {2, 1, -3, 4, -5}; + int row0_cols[] = {0, 1, 2, 3, 4}; + + double ax[3] = {1.3, 2.1, -1.7}; + double ai[3] = {0, 1, 3}; + + double row1_vals[3]; + double row3_vals[3]; + double row2_vals[3]; + int row1_cols[3]; + int row3_cols[3]; + int row2_cols[3]; + + int i; + for (i = 0; i < 3; ++i) + { + row1_vals[i] = 1.2 * ax[i] + i; + row2_vals[i] = 2 * ax[i]; + row3_vals[i] = -2 * ax[i]; + row1_cols[i] = ai[i]; + row2_cols[i] = ai[i]; + row3_cols[i] = ai[i]; + } + + double row5_vals[] = {1, 1.1, 1, 1}; + int row5_cols[] = {0, 1, 2, 4}; + + int nnz = 5 + 3 + 3 + 3 + 3 + 4; + int n_rows = 6; + int n_cols = 5; + + double Ax[5 + 3 + 3 + 3 + 3 + 4]; + int Ai[5 + 3 + 3 + 3 + 3 + 4]; + int Ap[7]; + + Ap[0] = 0; + for (i = 0; i < 5; ++i) + { + Ax[i] = row0_vals[i]; + Ai[i] = row0_cols[i]; + } + Ap[1] = Ap[0] + 5; + + for (i = 0; i < 3; ++i) + { + Ax[Ap[1] + i] = row1_vals[i]; + Ai[Ap[1] + i] = row1_cols[i]; + } + Ap[2] = Ap[1] + 3; + + for (i = 0; i < 3; ++i) + { + Ax[Ap[2] + i] = row2_vals[i]; + Ai[Ap[2] + i] = row2_cols[i]; + } + Ap[3] = Ap[2] + 3; + + for (i = 0; i < 3; ++i) + { + Ax[Ap[3] + i] = row3_vals[i]; + Ai[Ap[3] + i] = row3_cols[i]; + } + Ap[4] = Ap[3] + 3; + + for (i = 0; i < 3; ++i) + { + Ax[Ap[4] + i] = ax[i]; + Ai[Ap[4] + i] = ai[i]; + } + Ap[5] = Ap[4] + 3; + + for (i = 0; i < 4; ++i) + { + Ax[Ap[5] + i] = row5_vals[i]; + Ai[Ap[5] + i] = row5_cols[i]; + } + Ap[6] = Ap[5] + 4; + + // ----------------------------------------------- + // begin test + // ----------------------------------------------- + double lhs[] = {-2.1, -1, 3, -18, -INF, -INF}; + double rhs[] = {3.1, 3, 22, INF, 10, 5}; + double lbs[] = {0, 0, 0, 0, 0}; + double ubs[] = {INF, INF, INF, INF, INF}; + double c[] = {0, 0, 0, 0, 0}; + + Settings *stgs = default_settings(); + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + PresolveStatus status = remove_parallel_rows(constraints); + mu_assert("error", status != INFEASIBLE); + problem_clean(prob, true); + + // check A matrix + double Ax_correct[] = {row0_vals[0], row0_vals[1], row0_vals[2], row0_vals[3], + row0_vals[4], row1_vals[0], row1_vals[1], row1_vals[2], + -2 * ax[0], -2 * ax[1], -2 * ax[2], row5_vals[0], + row5_vals[1], row5_vals[2], row5_vals[3]}; + + double Ai_correct[] = {row0_cols[0], row0_cols[1], row0_cols[2], row0_cols[3], + row0_cols[4], row1_cols[0], row1_cols[1], row1_cols[2], + ai[0], ai[1], ai[2], row5_cols[0], + row5_cols[1], row5_cols[2], row5_cols[3]}; + double Ap_correct[] = {0, 5, 8, 11, 15}; + + mu_assert("error Ax", ARRAYS_EQUAL(Ax_correct, A->x, 15)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai_correct, A->i, 15)); + CHECK_ROW_STARTS(A, Ap_correct); + + // check row tags + RowTag row_tags_correct[] = {R_TAG_NONE, R_TAG_NONE, R_TAG_NONE, R_TAG_LHS_INF}; + mu_assert("error row tags", + ARRAYS_EQUAL(row_tags_correct, constraints->row_tags, 4)); + + // check lhs and rhs + double lhs_correct[] = {-2.1, -1, -18, -INF}; + double rhs_correct[] = {3.1, 3, -3, 5}; + mu_assert("error lhs", ARRAYS_EQUAL(lhs_correct, constraints->lhs, 4)); + mu_assert("error rhs", ARRAYS_EQUAL(rhs_correct, constraints->rhs, 4)); + + PS_FREE(stgs); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + return 0; +} + +static const char *all_tests_parallel_rows() +{ + mu_run_test(test_1_parallel_rows, counter_parallel_rows); + mu_run_test(test_2_parallel_rows, counter_parallel_rows); + mu_run_test(test_3_parallel_rows, counter_parallel_rows); + mu_run_test(test_4_parallel_rows, counter_parallel_rows); + mu_run_test(test_5_parallel_rows, counter_parallel_rows); + mu_run_test(test_6_parallel_rows, counter_parallel_rows); + mu_run_test(test_7_parallel_rows, counter_parallel_rows); + mu_run_test(test_8_parallel_rows, counter_parallel_rows); + mu_run_test(test_9_parallel_rows, counter_parallel_rows); + mu_run_test(test_10_parallel_rows, counter_parallel_rows); + mu_run_test(test_11_parallel_rows, counter_parallel_rows); + mu_run_test(test_12_parallel_rows, counter_parallel_rows); + mu_run_test(test_13_parallel_rows, counter_parallel_rows); + mu_run_test(test_14_parallel_rows, counter_parallel_rows); + mu_run_test(test_15_parallel_rows, counter_parallel_rows); + mu_run_test(test_16_parallel_rows, counter_parallel_rows); + + return 0; +} + +int test_parallel_rows() +{ + const char *result = all_tests_parallel_rows(); + if (result != 0) + { + printf("%s\n", result); + printf("parallel_rows: TEST FAILED!\n"); + } + else + { + printf("parallel_rows: ALL TESTS PASSED\n"); + } + printf("parallel_rows: Tests run: %d\n", counter_parallel_rows); + return result == 0; +} + +#endif // TEST_PARALLEL_ROWS_H diff --git a/tests/test_Presolver.h b/tests/test_Presolver.h new file mode 100644 index 00000000..02646063 --- /dev/null +++ b/tests/test_Presolver.h @@ -0,0 +1,64 @@ +#ifndef TEST_PRESOLVER_H +#define TEST_PRESOLVER_H + +#include "API.h" +#include "Debugger.h" + +#include "Problem.h" +#include "Workspace.h" +#include "minunit.h" +#include + +static int counter_presolver = 0; + +/* Test presolver init and free for memory leaks (if valgrind doesn't find any + leaks the programme should be leak-free when executing regularly without any + memory allocation failures, because all memory is allocated when initializing + the presolver) */ +static char *test_00_presolver() +{ + double Ax[] = {2, 1, -1, 3, 2, 1, -2, 1, 4, 3, 1}; + int Ai[] = {0, 1, 2, 3, 4, 0, 1, 0, 1, 3, 4}; + int Ap[] = {0, 5, 7, 11}; + int nnz = 11; + int n_rows = 3; + int n_cols = 5; + double lhs[] = {2, -1, 4}; + double rhs[] = {2, -1, 4}; + double lbs[] = {0.4, 0, 0, 0, 0}; + double ubs[] = {0.6, INF, INF, INF, INF}; + double c[] = {2, -1, -1, 3, 2}; + + Settings *stgs = default_settings(); + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + mu_assert("Presolver initialization failed", presolver != NULL); + PS_FREE(stgs); + free_presolver(presolver); + return 0; +} + +static const char *all_tests_presolver() +{ + mu_run_test(test_00_presolver, counter_presolver); + return 0; +} + +int test_presolver() +{ + const char *result = all_tests_presolver(); + if (result != 0) + { + printf("%s\n", result); + printf("presolver: TEST FAILED!\n"); + } + else + { + printf("presolver: ALL TESTS PASSED\n"); + } + printf("presolver: Tests run: %d\n", counter_presolver); + return result == 0; +} + +#endif // TEST_PRESOLVER_H diff --git a/tests/test_SimpleReductions.h b/tests/test_SimpleReductions.h new file mode 100644 index 00000000..0cdb569d --- /dev/null +++ b/tests/test_SimpleReductions.h @@ -0,0 +1,495 @@ +#ifndef TEST_SIMPLEREDUCTIONS_H +#define TEST_SIMPLEREDUCTIONS_H + +#include "API.h" +#include "Debugger.h" + +#include "minunit.h" +#include "test_SimpleReductions.h" +#include "test_macros.h" +#include + +// clang-format off +/* +The following tests are/should be implemented for the TrivialRowReductions class +(a "✓" indicates that the test has been implemented). +---- Tests based on empty / singleton rows ---- +Test 1: Removal of two feasible empty rows that exist from the start. +Test 2: Removal of an empty infeasible row that exists from the start. +Test 3: Singleton equality row that is infeasible wrt bounds. +Test 4: Two singleton equality rows with the same variable. The problem is + feasible. (✓) +Test 5: Two singleton equality rows with the same variable. The problem is + infeasible. +Test 6: One singleton equality row being processed before a singleton + inequality row with the same variable. The problem is feasible. +Test 7: One singleton equality row being processed before a singleton + inequality row with the same variable. The problem is infeasible. +Test 8: Three singleton equality rows. +Test 9: Three singleton equality rows followed by an empty feasible row. +Test 10: Removal of a singleton two-sided inequality row +---- Tests based on CHANGED (!) row activities ---- +Test 11: Two-sided constraint, upper side declared as redundant (✓) +Test 12: Two-sided constraint, upper side declared as redundant. + This test should fail (it exists just because we should remember this + special case) forcing first constraint. + Test 13: Two-sided constraint, lower side declared as redundant (✓) + Test 14: Two sided constraint, both sides declared as redundant (✓) + Test 15: One sided upper constraint declared as redundant (✓) + Test 16: One sided lower constraint declared as redundant (✓) + Test 17: Constraint declared as infeasible due to min activity (✓) + Test 18: Constraint declared as infeasible due to max activity (✓) +*/ +// clang-format on + +int counter_simple = 0; + +/* Two singleton equality rows with the same variable. The problem is + feasible. + min. [-1 4 5 2 -8 2] x + s.t. [ 7 5 2 0 -2 4] = 7 + [ 2 -3 0 -1 3 1] <= 9 + [ 1 0 0 0 0 0] = 3 + [ 2 0 0 0 0 0] = 6 + [ 0 1 0 -1 0 0] >= -10 + x1, x2, x3, x4, x5, x6 >= 0 +*/ +static char *test_4_simple() +{ + double Ax[] = {7, 5, 2, -2, 4, 2, -3, -1, 3, 1, 1, 2, 1, -1}; + int Ai[] = {0, 1, 2, 4, 5, 0, 1, 3, 4, 5, 0, 0, 1, 3}; + int Ap[] = {0, 5, 10, 11, 12, 14}; + int nnz = 14; + int n_rows = 5; + int n_cols = 6; + + double lhs[] = {7, -INF, 3, 6, -10}; + double rhs[] = {7, 9, 3, 6, INF}; + double lbs[6] = {0}; + double ubs[] = {INF, INF, INF, INF, INF, INF}; + double c[] = {-1, 4, 5, 2, -8, 2}; + + Settings *stgs = default_settings(); + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + PresolveStatus status = remove_ston_rows(prob); + remove_empty_rows(constraints); + problem_clean(prob, true); + + // check that new A is correct + double Ax_correct[] = {5, 2, -2, 4, -3, -1, 3, 1, 1, -1}; + int Ai_correct[] = {0, 1, 3, 4, 0, 2, 3, 4, 0, 2}; + int Ap_correct[] = {0, 4, 8, 10}; + mu_assert("error Ax", ARRAYS_EQUAL(Ax_correct, A->x, 10)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai_correct, A->i, 10)); + CHECK_ROW_STARTS(A, Ap_correct); + + // check that lhs and rhs are correct + double lhs_correct[] = {-14, -INF, -10}; + double rhs_correct[] = {-14, 3, INF}; + mu_assert("error lhs", ARRAYS_EQUAL(lhs_correct, constraints->lhs, 3)); + mu_assert("error rhs", ARRAYS_EQUAL(rhs_correct, constraints->rhs, 3)); + + PS_FREE(stgs); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + return 0; +} + +/* Two-sided constraint, upper side declared as redundant + min. [-1 4 5 2 -8] x + s.t. [ 1 0 0 -1 1] = 2 + -1 <= [ 1 2 3 0 0] <= 6 + [ 1 1 1 -1 -1] >= 2 + -1 <= x1, x2, x3, x4, x5 <= 1 +*/ +static char *test_11_simple() +{ + double Ax[] = {1, -1, 1, 1, 2, 3, 1, 1, 1, -1, -1}; + int Ai[] = {0, 3, 4, 0, 1, 2, 0, 1, 2, 3, 4}; + int Ap[] = {0, 3, 6, 11}; + int nnz = 11; + int n_rows = 3; + int n_cols = 5; + + double lhs[] = {2, -1, 2}; + double rhs[] = {2, 6, INF}; + double lbs[5] = {-1, -1, -1, -1, -1}; + double ubs[] = {1, 1, 1, 1, 1}; + double c[] = {-1, 4, 5, 2, -8}; + + Settings *stgs = default_settings(); + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + PresolveStatus status = check_activities(prob); + problem_clean(prob, true); + + // check that new A is correct + mu_assert("error Ax", ARRAYS_EQUAL(Ax, A->x, 11)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai, A->i, 11)); + CHECK_ROW_STARTS(A, Ap); + + // check that new rowtags are correct + RowTag rowtags_correct[] = {R_TAG_EQ, R_TAG_RHS_INF, R_TAG_RHS_INF}; + mu_assert("error rowtags", + ARRAYS_EQUAL(rowtags_correct, constraints->row_tags, 3)); + + // check that lhs and rhs are correct + double lhs_correct[] = {2, -1, 2}; + double rhs_correct[] = {2, INF, INF}; + mu_assert("error lhs", ARRAYS_EQUAL(lhs_correct, constraints->lhs, 3)); + mu_assert("error rhs", ARRAYS_EQUAL(rhs_correct, constraints->rhs, 3)); + + PS_FREE(stgs); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + return 0; +} + +/* Two-sided constraint, lower side declared as redundant + min. [-1 4 5 2 -8] x + s.t. [ 1 0 0 -1 1] = 2 + -6 <= [ 1 2 3 0 0] <= 2 + [ 1 1 1 -1 -1] >= 2 + -1 <= x1, x2, x3, x4, x5 <= 1 +*/ +static char *test_13_simple() +{ + double Ax[] = {1, -1, 1, 1, 2, 3, 1, 1, 1, -1, -1}; + int Ai[] = {0, 3, 4, 0, 1, 2, 0, 1, 2, 3, 4}; + int Ap[] = {0, 3, 6, 11}; + int nnz = 11; + int n_rows = 3; + int n_cols = 5; + + double lhs[] = {2, -6, 2}; + double rhs[] = {2, 2, INF}; + double lbs[5] = {-1, -1, -1, -1, -1}; + double ubs[] = {1, 1, 1, 1, 1}; + double c[] = {-1, 4, 5, 2, -8}; + + Settings *stgs = default_settings(); + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + PresolveStatus status = check_activities(prob); + problem_clean(prob, true); + + // check that new A is correct + mu_assert("error Ax", ARRAYS_EQUAL(Ax, A->x, 11)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai, A->i, 11)); + CHECK_ROW_STARTS(A, Ap); + + // check that new rowtags are correct + RowTag rowtags_correct[] = {R_TAG_EQ, R_TAG_LHS_INF, R_TAG_RHS_INF}; + mu_assert("error rowtags", + ARRAYS_EQUAL(rowtags_correct, constraints->row_tags, 3)); + + // check that lhs and rhs are correct + double lhs_correct[] = {2, -INF, 2}; + double rhs_correct[] = {2, 2, INF}; + mu_assert("error lhs", ARRAYS_EQUAL(lhs_correct, constraints->lhs, 3)); + mu_assert("error rhs", ARRAYS_EQUAL(rhs_correct, constraints->rhs, 3)); + + PS_FREE(stgs); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + return 0; +} + +/* Two-sided constraint, both sides declared as redundant + min. [-1 4 5 2 -8] x + s.t. [ 1 0 0 -1 1] = 2 + -6 <= [ 1 2 3 0 0] <= 6 + [ 1 1 1 -1 -1] >= 2 + -1 <= x1, x2, x3, x4, x5 <= 1 +*/ +static char *test_14_simple() +{ + double Ax[] = {1, -1, 1, 1, 2, 3, 1, 1, 1, -1, -1}; + int Ai[] = {0, 3, 4, 0, 1, 2, 0, 1, 2, 3, 4}; + int Ap[] = {0, 3, 6, 11}; + int nnz = 11; + int n_rows = 3; + int n_cols = 5; + + double lhs[] = {2, -6, 2}; + double rhs[] = {2, 6, INF}; + double lbs[5] = {-1, -1, -1, -1, -1}; + double ubs[] = {1, 1, 1, 1, 1}; + double c[] = {-1, 4, 5, 2, -8}; + + Settings *stgs = default_settings(); + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + PresolveStatus status = check_activities(prob); + // delete_inactive_rows(constraints, constraints->state->rows_to_delete); + problem_clean(prob, true); + + // check that new A is correct + double Ax_correct[] = {1, -1, 1, 1, 1, 1, -1, -1}; + int Ai_correct[] = {0, 3, 4, 0, 1, 2, 3, 4}; + int Ap_correct[] = {0, 3, 8}; + mu_assert("error Ax", ARRAYS_EQUAL(Ax_correct, A->x, 8)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai_correct, A->i, 8)); + CHECK_ROW_STARTS(A, Ap_correct); + + // check that new rowtags are correct + RowTag rowtags_correct[] = {R_TAG_EQ, R_TAG_RHS_INF}; + mu_assert("error rowtags", + ARRAYS_EQUAL(rowtags_correct, constraints->row_tags, 2)); + + // check that lhs and rhs are correct + double lhs_correct[] = {2, 2}; + double rhs_correct[] = {2, INF}; + mu_assert("error lhs", ARRAYS_EQUAL(lhs_correct, constraints->lhs, 2)); + mu_assert("error rhs", ARRAYS_EQUAL(rhs_correct, constraints->rhs, 2)); + + PS_FREE(stgs); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + return 0; +} + +/* One sided upper constraint declared as redundant + min. [-1 4 5 2 -8] x + s.t. [ 1 0 0 -1 1] = 2 + [ 1 2 3 0 0] <= 6 + [ 1 1 1 -1 -1] >= 2 + -1 <= x1, x2, x3, x4, x5 <= 1 +*/ +static char *test_15_simple() +{ + double Ax[] = {1, -1, 1, 1, 2, 3, 1, 1, 1, -1, -1}; + int Ai[] = {0, 3, 4, 0, 1, 2, 0, 1, 2, 3, 4}; + int Ap[] = {0, 3, 6, 11}; + int nnz = 11; + int n_rows = 3; + int n_cols = 5; + + double lhs[] = {2, -INF, 2}; + double rhs[] = {2, 6, INF}; + double lbs[5] = {-1, -1, -1, -1, -1}; + double ubs[] = {1, 1, 1, 1, 1}; + double c[] = {-1, 4, 5, 2, -8}; + + Settings *stgs = default_settings(); + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + PresolveStatus status = check_activities(prob); + // delete_inactive_rows(constraints, constraints->state->rows_to_delete); + problem_clean(prob, true); + + // check that new A is correct + double Ax_correct[] = {1, -1, 1, 1, 1, 1, -1, -1}; + int Ai_correct[] = {0, 3, 4, 0, 1, 2, 3, 4}; + int Ap_correct[] = {0, 3, 8}; + mu_assert("error Ax", ARRAYS_EQUAL(Ax_correct, A->x, 8)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai_correct, A->i, 8)); + CHECK_ROW_STARTS(A, Ap_correct); + + // check that new rowtags are correct + RowTag rowtags_correct[] = {R_TAG_EQ, R_TAG_RHS_INF}; + mu_assert("error rowtags", + ARRAYS_EQUAL(rowtags_correct, constraints->row_tags, 2)); + + // check that lhs and rhs are correct + double lhs_correct[] = {2, 2}; + double rhs_correct[] = {2, INF}; + mu_assert("error lhs", ARRAYS_EQUAL(lhs_correct, constraints->lhs, 2)); + mu_assert("error rhs", ARRAYS_EQUAL(rhs_correct, constraints->rhs, 2)); + + PS_FREE(stgs); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + return 0; +} + +/* One sided lower constraint declared as redundant + min. [-1 4 5 2 -8] x + s.t. [ 1 0 0 -1 1] = 2 + -6 <= [ 1 2 3 0 0] + [ 1 1 1 -1 -1] >= 2 + -1 <= x1, x2, x3, x4, x5 <= 1 +*/ +static char *test_16_simple() +{ + double Ax[] = {1, -1, 1, 1, 2, 3, 1, 1, 1, -1, -1}; + int Ai[] = {0, 3, 4, 0, 1, 2, 0, 1, 2, 3, 4}; + int Ap[] = {0, 3, 6, 11}; + int nnz = 11; + int n_rows = 3; + int n_cols = 5; + + double lhs[] = {2, -6, 2}; + double rhs[] = {2, INF, INF}; + double lbs[5] = {-1, -1, -1, -1, -1}; + double ubs[] = {1, 1, 1, 1, 1}; + double c[] = {-1, 4, 5, 2, -8}; + + Settings *stgs = default_settings(); + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + PresolveStatus status = check_activities(prob); + // delete_inactive_rows(constraints, constraints->state->rows_to_delete); + problem_clean(prob, true); + + // check that new A is correct + double Ax_correct[] = {1, -1, 1, 1, 1, 1, -1, -1}; + int Ai_correct[] = {0, 3, 4, 0, 1, 2, 3, 4}; + int Ap_correct[] = {0, 3, 8}; + mu_assert("error Ax", ARRAYS_EQUAL(Ax_correct, A->x, 8)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai_correct, A->i, 8)); + CHECK_ROW_STARTS(A, Ap_correct); + + // check that new rowtags are correct + RowTag rowtags_correct[] = {R_TAG_EQ, R_TAG_RHS_INF}; + mu_assert("error rowtags", + ARRAYS_EQUAL(rowtags_correct, constraints->row_tags, 2)); + + // check that lhs and rhs are correct + double lhs_correct[] = {2, 2}; + double rhs_correct[] = {2, INF}; + mu_assert("error lhs", ARRAYS_EQUAL(lhs_correct, constraints->lhs, 2)); + mu_assert("error rhs", ARRAYS_EQUAL(rhs_correct, constraints->rhs, 2)); + + PS_FREE(stgs); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + return 0; +} + +/* Constraint declared as infeasible due to min activity + min. [-1 4 5 2 -8] x + s.t. [ 1 0 0 -1 1] = 3 + [ 1 2 3 0 0] <= 5.99999 + [ 1 1 1 -1 -1] >= 2 + 1 <= x1, x2, x3, x4, x5 <= 4 +*/ +static char *test_17_simple() +{ + double Ax[] = {1, -1, 1, 1, 2, 3, 1, 1, 1, -1, -1}; + int Ai[] = {0, 3, 4, 0, 1, 2, 0, 1, 2, 3, 4}; + int Ap[] = {0, 3, 6, 11}; + int nnz = 11; + int n_rows = 3; + int n_cols = 5; + + double lhs[] = {2, -INF, 2}; + double rhs[] = {2, 5.9999, INF}; + double lbs[5] = {1, 1, 1, 1, 1}; + double ubs[] = {4, 4, 4, 4, 4}; + double c[] = {-1, 4, 5, 2, -8}; + + Settings *stgs = default_settings(); + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + PresolveStatus status = check_activities(prob); + mu_assert("error status", status == INFEASIBLE); + // delete_inactive_rows(constraints, constraints->state->rows_to_delete); + problem_clean(prob, true); + + PS_FREE(stgs); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + return 0; +} + +/* Constraint declared as infeasible due to max activity + min. [-1 4 5 2 -8] x + s.t. [ 1 0 0 -1 1] = 3 + [ 1 2 3 0 0] >= 6.0001 + [ 1 1 1 -1 -1] >= 2 + -1 <= x1, x2, x3, x4, x5 <= 1 +*/ +static char *test_18_simple() +{ + double Ax[] = {1, -1, 1, 1, 2, 3, 1, 1, 1, -1, -1}; + int Ai[] = {0, 3, 4, 0, 1, 2, 0, 1, 2, 3, 4}; + int Ap[] = {0, 3, 6, 11}; + int nnz = 11; + int n_rows = 3; + int n_cols = 5; + + double lhs[] = {2, 6.00001, 2}; + double rhs[] = {2, INF, INF}; + double lbs[5] = {-1, -1, -1, -1, -1}; + double ubs[] = {1, 1, 1, 1, 1}; + double c[] = {-1, 4, 5, 2, -8}; + + Settings *stgs = default_settings(); + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + PresolveStatus status = check_activities(prob); + mu_assert("error status", status == INFEASIBLE); + // delete_inactive_rows(constraints, constraints->state->rows_to_delete); + problem_clean(prob, true); + + PS_FREE(stgs); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + return 0; +} + +static const char *all_tests_simple() +{ + mu_run_test(test_4_simple, counter_simple); + mu_run_test(test_11_simple, counter_simple); + mu_run_test(test_13_simple, counter_simple); + mu_run_test(test_14_simple, counter_simple); + mu_run_test(test_15_simple, counter_simple); + mu_run_test(test_16_simple, counter_simple); + mu_run_test(test_17_simple, counter_simple); + mu_run_test(test_18_simple, counter_simple); + + return 0; +} + +int test_simple() +{ + const char *result = all_tests_simple(); + if (result != 0) + { + printf("%s\n", result); + printf("SimpleReductions: TEST FAILED!\n"); + } + else + { + printf("SimpleReductions: ALL TESTS PASSED\n"); + } + printf("SimpleReductions: Tests run: %d\n", counter_simple); + return result == 0; +} + +#endif // TEST_SIMPLEREDUCTIONS_H diff --git a/tests/test_domain_propagation.h b/tests/test_domain_propagation.h new file mode 100644 index 00000000..4e6b46aa --- /dev/null +++ b/tests/test_domain_propagation.h @@ -0,0 +1,685 @@ +#ifndef TEST_DOMAINPROPAGATION_H +#define TEST_DOMAINPROPAGATION_H + +#include "CoreTransformations.h" +#include "Numerics.h" +#include "Primal_propagation.h" +#include "SimpleReductions.h" +#include "Tags.h" +#include "minunit.h" +#include + +static int counter_domain = 0; + +// clang-format off +/* +The following tests are/should be implemented for the DomainPropagation class +(a "✓" indicates that the test has been implemented). +---- Tests based on forcing rows ---- +Test 1: Lower constraint that is forcing wrt the initial bounds (✓) + (integer arithmetic). +Test 2: Lower constraint that is forcing wrt the initial bounds (✓) + (floating point number arithmetic). +Test 3: Upper constraint that is forcing wrt the initial bounds (✓) + (integer arithmetic). +Test 4: Upper constraint that is forcing wrt the initial bounds (✓) + (floating point number arithmetic). +Test 5: Constraint 1 tightens an infinite upper bound so constraint 2 becomes (✓) + lower forcing (integer arithmetic and NOT all bounds are tightened). +Test 6: Constraint 1 tightens an infinite lower bound so constraint 2 becomes (✓) + upper forcing (integer arithmetic and NOT all bounds are tightened). +Test 7: Test 5 two rows swapped (✓) +Test 8: Test 6 two rows swapped (✓) +*/ +// clang-format on + +/* Lower constraint that is forcing wrt the initial bounds + (integer arithmetic). + min. [-1 -1 -1 3 2]x + s.t. [ 1 0 1 1 3] >= 4 + [-1 -2 2 0 0] x >= 2 + [ 1 4 0 3 -1] <= 5 + [ 2 0 0 -2 2] >= 6 + [ 0 0 0 2 -3] >= 0 + x1, x2 >= 0 + x3 <= 1 + x4, x5 >= 0 +*/ +static char *test_1_domain() +{ + double Ax[] = {1, 1, 1, 3, -1, -2, 2, 1, 4, 3, -1, 2, -2, 2, 2, -3}; + int Ai[] = {0, 2, 3, 4, 0, 1, 2, 0, 1, 3, 4, 0, 3, 4, 3, 4}; + int Ap[] = {0, 4, 7, 11, 14, 16}; + int nnz = 16; + int n_rows = 5; + int n_cols = 5; + + double lhs[] = {4, 2, -INF, 6, 0}; + double rhs[] = {INF, INF, 5, INF, INF}; + double lbs[5] = {0, 0, -INF, 0, 0}; + double ubs[] = {INF, INF, 1, INF, INF}; + double c[] = {-1, -1, -1, 3, 2}; + + Settings *stgs = default_settings(); + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + PresolveStatus status = propagate_primal(prob, true); + mu_assert("error status", status != INFEASIBLE); + problem_clean(prob, true); + + // check that new A is correct + double Ax_correct[] = {1, 3, 3, -1, -2, 2, 2, -3}; + int Ai_correct[] = {0, 1, 0, 1, 0, 1, 0, 1}; + int Ap_correct[] = {0, 2, 4, 6, 8}; + mu_assert("error Ax", ARRAYS_EQUAL(Ax_correct, A->x, 8)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai_correct, A->i, 8)); + CHECK_ROW_STARTS(A, Ap_correct); + + // check that new variable bounds are correct + double lbs_correct[] = {0, 0}; + double ubs_correct[] = {INF, INF}; + mu_assert("error bounds", + CHECK_BOUNDS(constraints->bounds, lbs_correct, ubs_correct, 2)); + + // check that lhs and rhs are correct + double lhs_correct[] = {3, -INF, 6, 0}; + double rhs_correct[] = {INF, 5, INF, INF}; + mu_assert("error lhs", ARRAYS_EQUAL(lhs_correct, constraints->lhs, 4)); + mu_assert("error rhs", ARRAYS_EQUAL(rhs_correct, constraints->rhs, 4)); + + PS_FREE(stgs); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + return 0; +} + +/* Lower constraint that is forcing wrt the initial bounds + (floating point number arithmetic). The reduction is very + sensitive to the size of the perturbation of the second constraint. + min. [-1 -1 -1 3 2]x + s.t. [ 1 0 1 1 3] >= 4 + [-2/13 -1/17 7/3 0 0] x >= -2/(13*3) - 1/(17*7) + 7/(3*9) - + 1e-10 [ 1 4 0 3 -1] <= 5 [ 2 0 0 -2 2] >= 6 + x1 >= 1/3 + x2 >= 1/7 + x3 <= 1/9 + x4, x5 >= 0 +*/ +static char *test_2_domain() +{ + double Ax[] = {1, 1, 1, 3, -2.0 / 13, -1.0 / 17, 7.0 / 3, 1, 4, 3, -1, 2, -2, 2}; + int Ai[] = {0, 2, 3, 4, 0, 1, 2, 0, 1, 3, 4, 0, 3, 4}; + int Ap[] = {0, 4, 7, 11, 14}; + int nnz = 14; + int n_rows = 4; + int n_cols = 5; + + double lhs[] = {4, -2 / (13 * 3) - 1 / (17 * 7) + 7 / (3 * 9) - 1e-10, -INF, 6}; + double rhs[] = {INF, INF, 5, INF}; + double lbs[5] = {1 / 3, 1 / 7, -INF, 0, 0}; + double ubs[] = {INF, INF, 1 / 9, INF, INF}; + double c[] = {-1, -1, -1, 3, 2}; + + Settings *stgs = default_settings(); + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + PresolveStatus status = propagate_primal(prob, true); + mu_assert("error status", status != INFEASIBLE); + problem_clean(prob, true); + + // check that new A is correct + double Ax_correct[] = {1, 3, 3, -1, -2, 2}; + int Ai_correct[] = {0, 1, 0, 1, 0, 1}; + int Ap_correct[] = {0, 2, 4, 6}; + mu_assert("error Ax", ARRAYS_EQUAL(Ax_correct, A->x, 6)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai_correct, A->i, 6)); + CHECK_ROW_STARTS(A, Ap_correct); + + // check that new variable bounds are correct + double lbs_correct[] = {0, 0}; + double ubs_correct[] = {INF, INF}; + mu_assert("error bounds", + CHECK_BOUNDS(constraints->bounds, lbs_correct, ubs_correct, 2)); + + // check that objective offset is correct + mu_assert("error offset", prob->obj->offset == -1 / 3 - 1 / 7 - 1 / 9); + + // check that lhs and rhs are correct + double lhs_correct[] = {4 - 1 / 9 - 1 / 3, -INF, 6 - 2 / 3}; + double rhs_correct[] = {INF, 5 - 1 / 3 - 4 / 7, INF}; + mu_assert("error lhs", ARRAYS_EQUAL(lhs_correct, constraints->lhs, 3)); + mu_assert("error rhs", ARRAYS_EQUAL(rhs_correct, constraints->rhs, 3)); + + PS_FREE(stgs); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + return 0; +} + +/* Upper constraint that is forcing wrt the initial bounds + (integer arithmetic). + min. [1 1 -1 3 2]x + s.t. [ 1 0 1 1 3] >= 4 + [-1 -2 2 0 0] x <= 2 + [ 1 4 0 3 -1] <= 5 + [ 2 0 0 -2 2] >= 6 + x1, x2 <= 0 + x3 >= 1 + x4, x5 >= 0 +*/ +static char *test_3_domain() +{ + double Ax[] = {1, 1, 1, 3, -1, -2, 2, 1, 4, 3, -1, 2, -2, 2}; + int Ai[] = {0, 2, 3, 4, 0, 1, 2, 0, 1, 3, 4, 0, 3, 4}; + int Ap[] = {0, 4, 7, 11, 14}; + int nnz = 14; + int n_rows = 4; + int n_cols = 5; + + double lhs[] = {4, -INF, -INF, 6}; + double rhs[] = {INF, 2, 5, INF}; + double lbs[5] = {-INF, -INF, 1, 0, 0}; + double ubs[] = {0, 0, INF, INF, INF}; + double c[] = {1, -1, -1, 3, 2}; + + Settings *stgs = default_settings(); + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + PresolveStatus status = propagate_primal(prob, true); + mu_assert("error status", status != INFEASIBLE); + problem_clean(prob, true); + + // check that new A is correct + double Ax_correct[] = {1, 3, 3, -1, -2, 2}; + int Ai_correct[] = {0, 1, 0, 1, 0, 1}; + int Ap_correct[] = {0, 2, 4, 6}; + mu_assert("error Ax", ARRAYS_EQUAL(Ax_correct, A->x, 6)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai_correct, A->i, 6)); + CHECK_ROW_STARTS(A, Ap_correct); + + // check that new variable bounds are correct + double lbs_correct[] = {0, 0}; + double ubs_correct[] = {INF, INF}; + mu_assert("error bounds", + CHECK_BOUNDS(constraints->bounds, lbs_correct, ubs_correct, 2)); + + // check that objective offset is correct + mu_assert("error offset", prob->obj->offset == -1); + + // check that lhs and rhs are correct + double lhs_correct[] = {3, -INF, 6}; + double rhs_correct[] = {INF, 5, INF}; + mu_assert("error lhs", ARRAYS_EQUAL(lhs_correct, constraints->lhs, 3)); + mu_assert("error rhs", ARRAYS_EQUAL(rhs_correct, constraints->rhs, 3)); + + PS_FREE(stgs); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + return 0; +} + +/* Upper constraint that is forcing wrt the initial bounds + (floating point number arithmetic). The reduction is very + sensitive to the size of the perturbation of the second constraint. + min. [1 1 -1 3 2]x + s.t. [ 1 0 1 1 3] >= 4 + [-2/13 -1/17 7/3 0 0] x <= -2/(13*3) - 1/(17*7) + 7/(3*9) + + 1e-10 [ 1 4 0 3 -1] <= 5 [ 2 0 0 -2 2] >= 6 + x1 <= 1/3 + x2 <= 1/7 + x3 >= 1/9 + x4, x5 >= 0 +*/ +static char *test_4_domain() +{ + double Ax[] = {1, 1, 1, 3, -2.0 / 13, -1.0 / 17, 7.0 / 3, 1, 4, 3, -1, 2, -2, 2}; + int Ai[] = {0, 2, 3, 4, 0, 1, 2, 0, 1, 3, 4, 0, 3, 4}; + int Ap[] = {0, 4, 7, 11, 14}; + int nnz = 14; + int n_rows = 4; + int n_cols = 5; + + double lhs[] = {4, -INF, -INF, 6}; + double rhs[] = {INF, -2 / (13 * 3) - 1 / (17 * 7) + 7 / (3 * 9) + 1e-10, 5, INF}; + double lbs[5] = {-INF, -INF, 1 / 9, 0, 0}; + double ubs[] = {1 / 3, 1 / 7, INF, INF, INF}; + double c[] = {1, -1, -1, 3, 2}; + + Settings *stgs = default_settings(); + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + PresolveStatus status = propagate_primal(prob, true); + mu_assert("error status", status != INFEASIBLE); + problem_clean(prob, true); + + // check that new A is correct + double Ax_correct[] = {1, 3, 3, -1, -2, 2}; + int Ai_correct[] = {0, 1, 0, 1, 0, 1}; + int Ap_correct[] = {0, 2, 4, 6}; + mu_assert("error Ax", ARRAYS_EQUAL(Ax_correct, A->x, 6)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai_correct, A->i, 6)); + CHECK_ROW_STARTS(A, Ap_correct); + + // check that new variable bounds are correct + double lbs_correct[] = {0, 0}; + double ubs_correct[] = {INF, INF}; + mu_assert("error bounds", + CHECK_BOUNDS(constraints->bounds, lbs_correct, ubs_correct, 2)); + + // check that objective offset is correct + mu_assert("error offset", prob->obj->offset == 1 / 3 + 1 / 7 - 1 / 9); + + // check that lhs and rhs are correct + double lhs_correct[] = {4 - 1 / 9 - 1 / 3, -INF, 6 - 2 / 3}; + double rhs_correct[] = {INF, 5 - 1 / 3 - 4 / 7, INF}; + mu_assert("error lhs", ARRAYS_EQUAL(lhs_correct, constraints->lhs, 3)); + mu_assert("error rhs", ARRAYS_EQUAL(rhs_correct, constraints->rhs, 3)); + + PS_FREE(stgs); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + return 0; +} + +/* Constraint 1 tightens an infinite upper bound so constraint 2 becomes + lower forcing (integer arithmetic and NOT all bounds are tightened). + + min. [1 1 -1 -3 2]x + s.t. [ 1 0 1 0 0] <= 4 + [ 2 1 0 0 0] x >= 10 + [ 1 1 -1 3 -1] <= 7 + [ 2 0 0 -2 2] >= 6 + x1, x2, x3, x4, x5 >= 0 + x2 <= 2 +*/ +static char *test_5_domain() +{ + double Ax[] = {1, 1, 2, 1, 1, 1, -1, 3, -1, 2, -2, 2}; + int Ai[] = {0, 2, 0, 1, 0, 1, 2, 3, 4, 0, 3, 4}; + int Ap[] = {0, 2, 4, 9, 12}; + int nnz = 12; + int n_rows = 4; + int n_cols = 5; + + double lhs[] = {-INF, 10, -INF, 6}; + double rhs[] = {4, INF, 7, INF}; + double lbs[5] = {0, 0, 0, 0, 0}; + double ubs[] = {INF, 2, INF, INF, INF}; + double c[] = {1, 1, -1, -3, 2}; + + Settings *stgs = default_settings(); + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + PresolveStatus status = propagate_primal(prob, true); + mu_assert("error status", status != INFEASIBLE); + problem_clean(prob, true); + + // check that new A is correct + double Ax_correct[] = {1, -1, 3, -1, -2, 2}; + int Ai_correct[] = {0, 0, 1, 2, 1, 2}; + int Ap_correct[] = {0, 1, 4, 6}; + mu_assert("error Ax", ARRAYS_EQUAL(Ax_correct, A->x, 6)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai_correct, A->i, 6)); + CHECK_ROW_STARTS(A, Ap_correct); + + // check that new variable bounds are correct + double lbs_correct[] = {0, 0, 0}; + double ubs_correct[] = {4, INF, INF}; + mu_assert("error bounds", + CHECK_BOUNDS(constraints->bounds, lbs_correct, ubs_correct, 3)); + + // check that objective offset is correct + mu_assert("error offset", prob->obj->offset == 6); + + // check that lhs and rhs are correct + double lhs_correct[] = {-INF, -INF, -2}; + double rhs_correct[] = {0, 1, INF}; + mu_assert("error lhs", ARRAYS_EQUAL(lhs_correct, constraints->lhs, 3)); + mu_assert("error rhs", ARRAYS_EQUAL(rhs_correct, constraints->rhs, 3)); + + PS_FREE(stgs); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + return 0; +} + +/* Constraint 1 tightens an infinite lower bound so constraint 2 becomes + upper forcing (integer arithmetic and NOT all bounds are tightened). + + min. [1 1 -1 -3 2]x + s.t. [ 1 0 -1 0 0] >= 4 + [ 2 1 0 0 0] x <= 10 + [ 1 1 -1 3 -1] <= 7 + [ 2 0 0 -2 2] >= 6 + x2, x4, x5 >= 0 + x3 >= 1 + x1 <= 100 +*/ +static char *test_6_domain() +{ + double Ax[] = {1, -1, 2, 1, 1, 1, -1, 3, -1, 2, -2, 2}; + int Ai[] = {0, 2, 0, 1, 0, 1, 2, 3, 4, 0, 3, 4}; + int Ap[] = {0, 2, 4, 9, 12}; + int nnz = 12; + int n_rows = 4; + int n_cols = 5; + + double lhs[] = {4, -INF, -INF, 6}; + double rhs[] = {INF, 10, 7, INF}; + double lbs[5] = {-INF, 0, 1, 0, 0}; + double ubs[] = {100, INF, INF, INF, INF}; + double c[] = {1, 1, -1, -3, 2}; + + Settings *stgs = default_settings(); + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + PresolveStatus status = propagate_primal(prob, true); + mu_assert("error status", status != INFEASIBLE); + problem_clean(prob, true); + + // check that new A is correct + double Ax_correct[] = {-1, -1, 3, -1, -2, 2}; + int Ai_correct[] = {0, 0, 1, 2, 1, 2}; + int Ap_correct[] = {0, 1, 4, 6}; + mu_assert("error Ax", ARRAYS_EQUAL(Ax_correct, A->x, 6)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai_correct, A->i, 6)); + CHECK_ROW_STARTS(A, Ap_correct); + + // check that new variable bounds are correct + double lbs_correct[] = {1, 0, 0}; + double ubs_correct[] = {96, INF, INF}; + mu_assert("error bounds", + CHECK_BOUNDS(constraints->bounds, lbs_correct, ubs_correct, 3)); + + // check that objective offset is correct + mu_assert("error offset", prob->obj->offset == 5); + + // check that lhs and rhs are correct + double lhs_correct[] = {-1, -INF, -4}; + double rhs_correct[] = {INF, 2, INF}; + mu_assert("error lhs", ARRAYS_EQUAL(lhs_correct, constraints->lhs, 3)); + mu_assert("error rhs", ARRAYS_EQUAL(rhs_correct, constraints->rhs, 3)); + + PS_FREE(stgs); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + return 0; +} + +/* Test 5 with two rows swapped. + + min. [1 1 -1 -3 2]x + s.t. [ 2 1 0 0 0] x >= 10 + [ 1 0 1 0 0] <= 4 + [ 1 1 -1 3 -1] <= 7 + [ 2 0 0 -2 2] >= 6 + x1, x2, x3, x4, x5 >= 0 + x2 <= 2 +*/ +static char *test_7_domain() +{ + double Ax[] = {2, 1, 1, 1, 1, 1, -1, 3, -1, 2, -2, 2}; + int Ai[] = {0, 1, 0, 2, 0, 1, 2, 3, 4, 0, 3, 4}; + int Ap[] = {0, 2, 4, 9, 12}; + int nnz = 12; + int n_rows = 4; + int n_cols = 5; + + double lhs[] = {10, -INF, -INF, 6}; + double rhs[] = {INF, 4, 7, INF}; + double lbs[5] = {0, 0, 0, 0, 0}; + double ubs[] = {INF, 2, INF, INF, INF}; + double c[] = {1, 1, -1, -3, 2}; + + Settings *stgs = default_settings(); + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + PresolveStatus status = propagate_primal(prob, true); + mu_assert("error status", status != INFEASIBLE); + + problem_clean(prob, true); + + // check that new A is correct + double Ax_correct[] = {1, 1, 3, -1, -2, 2}; + int Ai_correct[] = {0, 0, 1, 2, 1, 2}; + int Ap_correct[] = {0, 1, 4, 6}; + mu_assert("error Ax", ARRAYS_EQUAL(Ax_correct, A->x, 6)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai_correct, A->i, 6)); + CHECK_ROW_STARTS(A, Ap_correct); + + // check that new variable bounds are correct + double lbs_correct[] = {0, 0, 0}; + double ubs_correct[] = {2, INF, INF}; + mu_assert("error bounds", + CHECK_BOUNDS(constraints->bounds, lbs_correct, ubs_correct, 3)); + + // check that objective offset is correct + mu_assert("error offset", prob->obj->offset == 4); + + // check that lhs and rhs are correct + double lhs_correct[] = {2, -INF, -2}; + double rhs_correct[] = {INF, 3, INF}; + mu_assert("error lhs", ARRAYS_EQUAL(lhs_correct, constraints->lhs, 3)); + mu_assert("error rhs", ARRAYS_EQUAL(rhs_correct, constraints->rhs, 3)); + + PS_FREE(stgs); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + return 0; +} + +/* Test 6 two rows swapped + + min. [1 1 -1 -3 2]x + s.t. [ 2 1 0 0 0] x <= 10 + [ 1 0 -1 0 0] >= 4 + [ 1 1 -1 3 -1] <= 7 + [ 2 0 0 -2 2] >= 6 + x2, x4, x5 >= 0 + x3 >= 1 + x1 <= 100 +*/ +static char *test_8_domain() +{ + double Ax[] = {2, 1, 1, -1, 1, 1, -1, 3, -1, 2, -2, 2}; + int Ai[] = {0, 1, 0, 2, 0, 1, 2, 3, 4, 0, 3, 4}; + int Ap[] = {0, 2, 4, 9, 12}; + int nnz = 12; + int n_rows = 4; + int n_cols = 5; + + double lhs[] = {-INF, 4, -INF, 6}; + double rhs[] = {10, INF, 7, INF}; + double lbs[5] = {-INF, 0, 1, 0, 0}; + double ubs[] = {100, INF, INF, INF, INF}; + double c[] = {1, 1, -1, -3, 2}; + + Settings *stgs = default_settings(); + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + PresolveStatus status = propagate_primal(prob, true); + mu_assert("error status", status != INFEASIBLE); + problem_clean(prob, true); + + // check that new A is correct + double Ax_correct[] = {-1, -1, 3, -1, -2, 2}; + int Ai_correct[] = {0, 0, 1, 2, 1, 2}; + int Ap_correct[] = {0, 1, 4, 6}; + mu_assert("error Ax", ARRAYS_EQUAL(Ax_correct, A->x, 6)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai_correct, A->i, 6)); + CHECK_ROW_STARTS(A, Ap_correct); + + // check that new variable bounds are correct + double lbs_correct[] = {1, 0, 0}; + double ubs_correct[] = {96, INF, INF}; + mu_assert("error bounds", + CHECK_BOUNDS(constraints->bounds, lbs_correct, ubs_correct, 3)); + + // check that objective offset is correct + mu_assert("error offset", prob->obj->offset == 5); + + // check that lhs and rhs are correct + double lhs_correct[] = {-1, -INF, -4}; + double rhs_correct[] = {INF, 2, INF}; + mu_assert("error lhs", ARRAYS_EQUAL(lhs_correct, constraints->lhs, 3)); + mu_assert("error rhs", ARRAYS_EQUAL(rhs_correct, constraints->rhs, 3)); + + PS_FREE(stgs); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + return 0; +} + +/* Confirm that we can derive integer bounds and then deduce that the variables + are implied free*/ +static char *test_9_domain_integer() +{ + double Ax[] = {2, 3, 1, 8}; + int Ai[] = {0, 1, 0, 1}; + int Ap[] = {0, 2, 4}; + int nnz = 4; + int n_rows = 2; + int n_cols = 2; + + double lhs[] = {-INF, -INF}; + double rhs[] = {8, 8}; + double lbs[5] = {0, 0}; + double ubs[] = {INF, INF}; + double c[] = {1, 1}; + + Settings *stgs = default_settings(); + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + PresolveStatus status = propagate_primal(prob, true); + mu_assert("error status", status != INFEASIBLE); + problem_clean(prob, true); + + mu_assert("error bound", constraints->bounds[0].ub == 4); + mu_assert("error bound", constraints->bounds[1].ub == 1); + mu_assert("error bound", !HAS_TAG(constraints->col_tags[0], C_TAG_UB_INF)); + mu_assert("error bound", !HAS_TAG(constraints->col_tags[1], C_TAG_UB_INF)); + + remove_redundant_bounds(constraints); + + mu_assert("error bound", HAS_TAG(constraints->col_tags[0], C_TAG_UB_INF)); + mu_assert("error bound", HAS_TAG(constraints->col_tags[1], C_TAG_UB_INF)); + + PS_FREE(stgs); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + return 0; +} + +/* Confirm that we can derive decimal bounds and then deduce that the + variables are implied free*/ +static char *test_9_domain_decimal() +{ + double Ax[] = {3, 3, 1, 7}; + int Ai[] = {0, 1, 0, 1}; + int Ap[] = {0, 2, 4}; + int nnz = 4; + int n_rows = 2; + int n_cols = 2; + + double lhs[] = {-INF, -INF}; + double rhs[] = {8, 8}; + double lbs[5] = {0, 0}; + double ubs[] = {INF, INF}; + double c[] = {1, 1}; + + Settings *stgs = default_settings(); + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + PresolveStatus status = propagate_primal(prob, true); + mu_assert("error status", status != INFEASIBLE); + problem_clean(prob, true); + + // mu_assert("error bound", IS_EQUAL_FEAS_TOL(constraints->bounds[0].ub, 8 + // / 3.0)); mu_assert("error bound", IS_EQUAL_FEAS_TOL(constraints->bounds[1].ub, + // 8 / 7.0)); + mu_assert("error bound", !HAS_TAG(constraints->col_tags[0], C_TAG_UB_INF)); + mu_assert("error bound", !HAS_TAG(constraints->col_tags[1], C_TAG_UB_INF)); + + remove_redundant_bounds(constraints); + + mu_assert("error bound", HAS_TAG(constraints->col_tags[0], C_TAG_UB_INF)); + mu_assert("error bound", HAS_TAG(constraints->col_tags[1], C_TAG_UB_INF)); + + PS_FREE(stgs); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + return 0; +} + +static const char *all_tests_domain() +{ + mu_run_test(test_1_domain, counter_domain); + mu_run_test(test_2_domain, counter_domain); + mu_run_test(test_3_domain, counter_domain); + mu_run_test(test_4_domain, counter_domain); + mu_run_test(test_5_domain, counter_domain); + mu_run_test(test_6_domain, counter_domain); + mu_run_test(test_7_domain, counter_domain); + mu_run_test(test_8_domain, counter_domain); + mu_run_test(test_9_domain_integer, counter_domain); + mu_run_test(test_9_domain_decimal, counter_domain); + return 0; +} + +int test_domain() +{ + const char *result = all_tests_domain(); + if (result != 0) + { + printf("%s\n", result); + printf("domain: TEST FAILED!\n"); + } + else + { + printf("domain: ALL TESTS PASSED\n"); + } + printf("domain: Tests run: %d\n", counter_domain); + return result == 0; +} + +#endif \ No newline at end of file diff --git a/tests/test_dton.h b/tests/test_dton.h new file mode 100644 index 00000000..affbe62c --- /dev/null +++ b/tests/test_dton.h @@ -0,0 +1,1731 @@ +#ifndef TEST_DTON_H +#define TEST_DTON_H + +#include "API.h" +#include "DTonsEq.h" +#include "Debugger.h" + +#include "Problem.h" +#include "Workspace.h" +#include "debug_macros.h" +#include "minunit.h" +#include + +static int counter_dton = 0; + +/* test update_row_A_dton + Substitution: x1 = 1 - 2x2 + x1 + + x3 + x4 = 1 -> -2x2 + x3 + x4 = 0 (fill-in) + x1 + x2 + x3 + x4 = 1 -> -x2 + x3 + x4 = 0 (inplace) + 2x1 + 4x2 + x3 + x4 = 1 -> x3 + x4 = 1 (unexpected cancellation) +*/ +static char *test_00_dton() +{ + double Ax[] = {1, 1, 1, 1, 1, 1, 1, 2, 4, 1, 1}; + int Ai[] = {0, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3}; + int Ap[] = {0, 3, 7, 11}; + int nnz = 11; + int n_rows = 3; + int n_cols = 4; + + Matrix *A = matrix_new(Ax, Ai, Ap, n_rows, n_cols, nnz); + + int stay = 1; + int subst = 0; + double ratio = 2; + int row_sizes[] = {3, 4, 4}; + PostsolveInfo *postsolve_info = postsolve_info_new(n_rows, n_cols); + update_row_A_dton(A, 0, 0, stay, subst, 2, 1, row_sizes + 0, postsolve_info); + update_row_A_dton(A, 0, 1, stay, subst, 2, 1, row_sizes + 1, postsolve_info); + update_row_A_dton(A, 0, 2, stay, subst, 2, 1, row_sizes + 2, postsolve_info); + + // check that new row sizes are correct + int row_sizes_correct[] = {3, 3, 2}; + mu_assert("error row_sizes", ARRAYS_EQUAL(row_sizes_correct, row_sizes, 3)); + + int col_sizes[] = {SIZE_INACTIVE_COL, 2, 3, 3}; + int map[4] = {0}; + remove_extra_space(A, row_sizes, col_sizes, true, map); + + // check that new A is correct + double Ax_correct[] = {-2, 1, 1, -1, 1, 1, 1, 1}; + int Ai_correct[] = {0, 1, 2, 0, 1, 2, 1, 2}; + int Ap_correct[] = {0, 3, 6, 8}; + mu_assert("error Ax", ARRAYS_EQUAL(Ax_correct, A->x, 8)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai_correct, A->i, 8)); + CHECK_ROW_STARTS(A, Ap_correct); + mu_assert("wrong nnz", A->nnz == 8); + + postsolve_info_free(postsolve_info); + free_matrix(A); + return 0; +} + +/* test update_row_A_dton + Substitution: x3 = 1 - 2x4 + x1 + 2x2 + x3 + + x5 + x6 = 1 -> x1 + 2x2 - 2x4 + x5 + x6 = 0 + (fill-in) x1 + x2 + x3 + x4 + x5 + x6 = 1 -> x1 + x2 - x4 + x5 + x6 = + 0 (inplace) x1 + x2 + 2x3 + 4x4 + x5 + x6 = 1 -> x1 + x2 + + x5 + + x6 = -1 (unexpected cancellation) +*/ +static char *test_01_dton() +{ + double Ax[] = {1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 4, 1, 1}; + int Ai[] = {0, 1, 2, 4, 5, 0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5}; + int Ap[] = {0, 5, 11, 17}; + int nnz = 17; + int n_rows = 3; + int n_cols = 6; + + Matrix *A = matrix_new(Ax, Ai, Ap, n_rows, n_cols, nnz); + + int stay = 3; + int subst = 2; + double ratio = 2; + int row_sizes[] = {5, 6, 6}; + PostsolveInfo *postsolve_info = postsolve_info_new(n_rows, n_cols); + update_row_A_dton(A, 0, 0, stay, subst, 2, 1, row_sizes + 0, postsolve_info); + update_row_A_dton(A, 0, 1, stay, subst, 2, 1, row_sizes + 1, postsolve_info); + update_row_A_dton(A, 0, 2, stay, subst, 2, 1, row_sizes + 2, postsolve_info); + + // check that new row sizes are correct + int row_sizes_correct[] = {5, 5, 4}; + mu_assert("error row_sizes", ARRAYS_EQUAL(row_sizes_correct, row_sizes, 3)); + + int col_sizes[] = {3, 3, SIZE_INACTIVE_COL, 2, 3, 3}; + int map[6] = {0}; + remove_extra_space(A, row_sizes, col_sizes, true, map); + + // check that new A is correct + double Ax_correct[] = {1, 2, -2, 1, 1, 1, 1, -1, 1, 1, 1, 1, 1, 1}; + int Ai_correct[] = {0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 1, 3, 4}; + int Ap_correct[] = {0, 5, 10, 14}; + mu_assert("error Ax", ARRAYS_EQUAL(Ax_correct, A->x, 14)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai_correct, A->i, 14)); + CHECK_ROW_STARTS(A, Ap_correct); + mu_assert("wrong nnz", A->nnz == 14); + + postsolve_info_free(postsolve_info); + free_matrix(A); + return 0; +} + +/* test update_row_A_dton + Substitution: x5 = 1 - 2x6 + x1 + 2x2 + x3 + x4 + x5 + = 1 -> x1 + 2x2 + x3 + x4 + - 2x6 = 0 + (fill-in) x1 + x2 + x3 + x4 + x5 + x6 = 1 -> x1 + x2 + x3 + x4 + - + x6 = 0 (inplace) x1 + x2 + 2x3 + 4x4 + x5 + x6 = 1 -> x1 + x2 + x3 + + x4 = -1 (unexpected cancellation) +*/ +static char *test_02_dton() +{ + double Ax[] = {1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 4}; + int Ai[] = {0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5}; + int Ap[] = {0, 5, 11, 17}; + int nnz = 17; + int n_rows = 3; + int n_cols = 6; + + Matrix *A = matrix_new(Ax, Ai, Ap, n_rows, n_cols, nnz); + + int stay = 5; + int subst = 4; + double ratio = 2; + int row_sizes[] = {5, 6, 6}; + PostsolveInfo *postsolve_info = postsolve_info_new(n_rows, n_cols); + update_row_A_dton(A, 0, 0, stay, subst, 2, 1, row_sizes + 0, postsolve_info); + update_row_A_dton(A, 0, 1, stay, subst, 2, 1, row_sizes + 1, postsolve_info); + update_row_A_dton(A, 0, 2, stay, subst, 2, 1, row_sizes + 2, postsolve_info); + + // check that new row sizes are correct + int row_sizes_correct[] = {5, 5, 4}; + mu_assert("error row_sizes", ARRAYS_EQUAL(row_sizes_correct, row_sizes, 3)); + + int col_sizes[] = {3, 3, 3, 3, SIZE_INACTIVE_COL, 2}; + int map[6] = {0}; + remove_extra_space(A, row_sizes, col_sizes, true, map); + + // check that new A is correct + double Ax_correct[] = {1, 2, 1, 1, -2, 1, 1, 1, 1, -1, 1, 1, 1, 1}; + int Ai_correct[] = {0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 1, 2, 3}; + int Ap_correct[] = {0, 5, 10, 14}; + mu_assert("error Ax", ARRAYS_EQUAL(Ax_correct, A->x, 14)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai_correct, A->i, 14)); + CHECK_ROW_STARTS(A, Ap_correct); + mu_assert("wrong nnz", A->nnz == 14); + + postsolve_info_free(postsolve_info); + free_matrix(A); + return 0; +} + +/* test update_row_A_dton + Substitution: x2 = 0.5 - 0.5x1 + x1 + + x3 + x4 = 1 -> x1 + x3 + x4 = 1 (unchanged) + x1 + x2 + x3 + x4 = 1 -> 0.5x1 + x3 + x4 = 0.5 (inplace) + 2x1 + 4x2 + x3 + x4 = 1 -> x3 + x4 = 1 (unexpected + cancellation) +*/ +static char *test_03_dton() +{ + double Ax[] = {1, 1, 1, 1, 1, 1, 1, 2, 4, 1, 1}; + int Ai[] = {0, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3}; + int Ap[] = {0, 3, 7, 11}; + int nnz = 11; + int n_rows = 3; + int n_cols = 4; + + Matrix *A = matrix_new(Ax, Ai, Ap, n_rows, n_cols, nnz); + + int stay = 0; + int subst = 1; + double ratio = 0.5; + int row_sizes[] = {3, 4, 4}; + PostsolveInfo *postsolve_info = postsolve_info_new(n_rows, n_cols); + update_row_A_dton(A, 0, 1, stay, subst, 1, 2, row_sizes + 1, postsolve_info); + update_row_A_dton(A, 0, 2, stay, subst, 1, 2, row_sizes + 2, postsolve_info); + + // check that new row sizes are correct + int row_sizes_correct[] = {3, 3, 2}; + mu_assert("error row_sizes", ARRAYS_EQUAL(row_sizes_correct, row_sizes, 3)); + + int col_sizes[] = {2, SIZE_INACTIVE_COL, 3, 3}; + int map[4] = {0}; + remove_extra_space(A, row_sizes, col_sizes, true, map); + + // check that new A is correct + double Ax_correct[] = {1, 1, 1, 0.5, 1, 1, 1, 1}; + int Ai_correct[] = {0, 1, 2, 0, 1, 2, 1, 2}; + int Ap_correct[] = {0, 3, 6, 8}; + mu_assert("error Ax", ARRAYS_EQUAL(Ax_correct, A->x, 8)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai_correct, A->i, 8)); + CHECK_ROW_STARTS(A, Ap_correct); + mu_assert("wrong nnz", A->nnz == 8); + + postsolve_info_free(postsolve_info); + free_matrix(A); + return 0; +} + +/* test update_row_A_dton + Substitution: x4 = 0.5 - 0.5x3 + x1 + 2x2 + x3 + + x5 + x6 = 1 -> x1 + 2x2 + x3 + + x5 + x6 + = 1 (unchanged) x1 + x2 + x3 + x4 + x5 + x6 = 1 -> x1 + x2 + 0.5x3 + + + x5 + x6 = 0.5 (inplace) x1 + x2 + 2x3 + 4x4 + x5 + x6 = 1 -> x1 + + x2 + + x5 + x6 = -1 unexpected cancellation) +*/ +static char *test_04_dton() +{ + double Ax[] = {1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 4, 1, 1}; + int Ai[] = {0, 1, 2, 4, 5, 0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5}; + int Ap[] = {0, 5, 11, 17}; + int nnz = 17; + int n_rows = 3; + int n_cols = 6; + + Matrix *A = matrix_new(Ax, Ai, Ap, n_rows, n_cols, nnz); + + int stay = 2; + int subst = 3; + double ratio = 0.5; + int row_sizes[] = {5, 6, 6}; + PostsolveInfo *postsolve_info = postsolve_info_new(n_rows, n_cols); + update_row_A_dton(A, 0, 1, stay, subst, 1, 2, row_sizes + 1, postsolve_info); + update_row_A_dton(A, 0, 2, stay, subst, 1, 2, row_sizes + 2, postsolve_info); + + // check that new row sizes are correct + int row_sizes_correct[] = {5, 5, 4}; + mu_assert("error row_sizes", ARRAYS_EQUAL(row_sizes_correct, row_sizes, 3)); + + int col_sizes[] = {3, 3, 2, SIZE_INACTIVE_COL, 3, 3}; + int map[6] = {0}; + remove_extra_space(A, row_sizes, col_sizes, true, map); + + // check that new A is correct + double Ax_correct[] = {1, 2, 1, 1, 1, 1, 1, 0.5, 1, 1, 1, 1, 1, 1}; + int Ai_correct[] = {0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 1, 3, 4}; + int Ap_correct[] = {0, 5, 10, 14}; + mu_assert("error Ax", ARRAYS_EQUAL(Ax_correct, A->x, 14)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai_correct, A->i, 14)); + CHECK_ROW_STARTS(A, Ap_correct); + mu_assert("wrong nnz", A->nnz == 14); + + postsolve_info_free(postsolve_info); + free_matrix(A); + return 0; +} + +/* test update_row_A_dton + Substitution: x6 = 0.5 - 0.5x5 + x1 + 2x2 + x3 + x4 + x5 + = 1 -> x1 + 2x2 + x3 + x4 + x5 + = 1 + (unchanged-in) x1 + x2 + x3 + x4 + x5 + x6 = 1 -> x1 + x2 + x3 + x4 + + 0.5 x5 = 0.5 (inplace) x1 + x2 + 2x3 + 4x4 + x5 + x6 = 1 -> x1 + x2 + + x3 + x4 = -1 (unexpected cancellation) +*/ +static char *test_05_dton() +{ + double Ax[] = {1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 4}; + int Ai[] = {0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5}; + int Ap[] = {0, 5, 11, 17}; + int nnz = 17; + int n_rows = 3; + int n_cols = 6; + + Matrix *A = matrix_new(Ax, Ai, Ap, n_rows, n_cols, nnz); + + int stay = 4; + int subst = 5; + double ratio = 0.5; + int row_sizes[] = {5, 6, 6}; + PostsolveInfo *postsolve_info = postsolve_info_new(n_rows, n_cols); + update_row_A_dton(A, 0, 1, stay, subst, 1, 2, row_sizes + 1, postsolve_info); + update_row_A_dton(A, 0, 2, stay, subst, 1, 2, row_sizes + 2, postsolve_info); + + // check that new row sizes are correct + int row_sizes_correct[] = {5, 5, 4}; + mu_assert("error row_sizes", ARRAYS_EQUAL(row_sizes_correct, row_sizes, 3)); + + int col_sizes[] = {3, 3, 3, 3, 2, SIZE_INACTIVE_COL}; + int map[6] = {0}; + remove_extra_space(A, row_sizes, col_sizes, true, map); + + // check that new A is correct + double Ax_correct[] = {1, 2, 1, 1, 1, 1, 1, 1, 1, 0.5, 1, 1, 1, 1}; + int Ai_correct[] = {0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 1, 2, 3}; + int Ap_correct[] = {0, 5, 10, 14}; + mu_assert("error Ax", ARRAYS_EQUAL(Ax_correct, A->x, 14)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai_correct, A->i, 14)); + CHECK_ROW_STARTS(A, Ap_correct); + mu_assert("wrong nnz", A->nnz == 14); + + postsolve_info_free(postsolve_info); + free_matrix(A); + return 0; +} + +/* test update_row_A_dton, WITH NO EXTRA SPACE + Substitution: x1 = 1 - 2x2 + x1 + + x3 + x4 = 1 -> -2x2 + x3 + x4 = 0 (fill-in) + x1 + x2 + x3 + x4 = 1 -> -x2 + x3 + x4 = 0 (inplace) + 2x1 + 4x2 + x3 + x4 = 1 -> x3 + x4 = 1 (unexpected cancellation) +*/ +static char *test_06_dton() +{ + double Ax[] = {1, 1, 1, 1, 1, 1, 1, 2, 4, 1, 1}; + int Ai[] = {0, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3}; + int Ap[] = {0, 3, 7, 11}; + int nnz = 11; + int n_rows = 3; + int n_cols = 4; + + Matrix *A = matrix_new(Ax, Ai, Ap, n_rows, n_cols, nnz); + int row_sizes[] = {3, 4, 4}; + int col_sizes[] = {3, 2, 3, 3}; + int map[4] = {0}; + + remove_extra_space(A, row_sizes, col_sizes, true, map); + + int stay = 1; + int subst = 0; + double ratio = 2; + PostsolveInfo *postsolve_info = postsolve_info_new(n_rows, n_cols); + update_row_A_dton(A, 0, 0, stay, subst, 2, 1, row_sizes + 0, postsolve_info); + update_row_A_dton(A, 0, 1, stay, subst, 2, 1, row_sizes + 1, postsolve_info); + update_row_A_dton(A, 0, 2, stay, subst, 2, 1, row_sizes + 2, postsolve_info); + + // check that new row sizes are correct + int row_sizes_correct[] = {3, 3, 2}; + mu_assert("error row_sizes", ARRAYS_EQUAL(row_sizes_correct, row_sizes, 3)); + + int col_sizes_new[] = {SIZE_INACTIVE_COL, 2, 3, 3}; + remove_extra_space(A, row_sizes, col_sizes_new, true, map); + + // check that new A is correct + double Ax_correct[] = {-2, 1, 1, -1, 1, 1, 1, 1}; + int Ai_correct[] = {0, 1, 2, 0, 1, 2, 1, 2}; + int Ap_correct[] = {0, 3, 6, 8}; + mu_assert("error Ax", ARRAYS_EQUAL(Ax_correct, A->x, 8)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai_correct, A->i, 8)); + CHECK_ROW_STARTS(A, Ap_correct); + mu_assert("wrong nnz", A->nnz == 8); + + postsolve_info_free(postsolve_info); + free_matrix(A); + return 0; +} + +/* dton row eliminated, bounds on the other variable are tightened. +min. [2 -1 -1 3 2]x +s.t. [2 1 -1 3 2] [2] + [1 -2 0 0 0] x = [-1] + [1 4 0 3 1] [4] + 0.4 <= x1 <= 0.6 + x2, x3, x4, x5 >= 0 +*/ +static char *test_1_dton() +{ + double Ax[] = {2, 1, -1, 3, 2, 1, -2, 1, 4, 3, 1}; + int Ai[] = {0, 1, 2, 3, 4, 0, 1, 0, 1, 3, 4}; + int Ap[] = {0, 5, 7, 11}; + int nnz = 11; + int n_rows = 3; + int n_cols = 5; + + double lhs[] = {2, -1, 4}; + double rhs[] = {2, -1, 4}; + double lbs[] = {0.4, 0, 0, 0, 0}; + double ubs[] = {0.6, INF, INF, INF, INF}; + double c[] = {2, -1, -1, 3, 2}; + + Settings *stgs = default_settings(); + set_settings_false(stgs); + stgs->dton_eq = true; + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + remove_dton_eq_rows(prob, 10); + problem_clean(prob, true); + + mu_assert("error row size", CHECK_ROW_SIZES(A, constraints->state->row_sizes)); + mu_assert("error col size", + CHECK_COL_SIZES(constraints->AT, constraints->state->col_sizes)); + + // check that new A is correct + double Ax_correct[] = {5, -1, 3, 2, 6, 3, 1}; + int Ai_correct[] = {0, 1, 2, 3, 0, 2, 3}; + int Ap_correct[] = {0, 4, 7}; + mu_assert("error Ax", ARRAYS_EQUAL(Ax_correct, A->x, 7)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai_correct, A->i, 7)); + CHECK_ROW_STARTS(A, Ap_correct); + + // check new AT + DEBUG(mu_assert("error AT", verify_A_and_AT_consistency(A, constraints->AT))); + + // check that new coltags are correct + ColTag col_tags_correct[] = {C_TAG_NONE, C_TAG_UB_INF, C_TAG_UB_INF, + C_TAG_UB_INF}; + mu_assert("error col_tags", + ARRAYS_EQUAL(col_tags_correct, constraints->col_tags, 4)); + + // check that new variable bounds are correct + double lbs_correct[] = {0.7, 0, 0, 0}; + double ubs_correct[] = {0.8, INF, INF, INF}; + mu_assert("error bounds", + CHECK_BOUNDS(constraints->bounds, lbs_correct, ubs_correct, 4)); + + // check that the objective function is correct + double obj_correct[] = {3, -1, 3, 2}; + mu_assert("error obj", ARRAYS_EQUAL(obj_correct, prob->obj->c, 4)); + mu_assert("error obj", prob->obj->offset == -2.0); + + // check that lhs and rhs are correct + double lhs_correct[] = {4, 5}; + double rhs_correct[] = {4, 5}; + mu_assert("error lhs", ARRAYS_EQUAL(lhs_correct, constraints->lhs, 2)); + mu_assert("error rhs", ARRAYS_EQUAL(rhs_correct, constraints->rhs, 2)); + + PS_FREE(stgs); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + + return 0; +} + +/* dton row eliminated, unexpected cancellation. + min. [2 -1 -1 3 2]x + s.t. [2 1 -1 3 2] [2] + [1 -2 0 0 0] x = [-1] + [1 -2 1 3 1] [4] + 0.4 <= x1 <= 0.6 + x2, x3, x4, x5 >= 0 +*/ +static char *test_2_dton() +{ + double Ax[] = {2, 1, -1, 3, 2, 1, -2, 1, -2, 1, 3, 1}; + int Ai[] = {0, 1, 2, 3, 4, 0, 1, 0, 1, 2, 3, 4}; + int Ap[] = {0, 5, 7, 12}; + int nnz = 12; + int n_rows = 3; + int n_cols = 5; + + double lhs[] = {2, -1, 4}; + double rhs[] = {2, -1, 4}; + double lbs[] = {0.4, 0, 0, 0, 0}; + double ubs[] = {0.6, INF, INF, INF, INF}; + double c[] = {2, -1, -1, 3, 2}; + + Settings *stgs = default_settings(); + set_settings_false(stgs); + stgs->dton_eq = true; + + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + PS_FREE(stgs); + free_presolver(presolver); + + /* + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + remove_dton_eq_rows(prob, 10); + problem_clean(prob, true); + + mu_assert("error", CHECK_ROW_SIZES(constraints->A, + constraints->state->row_sizes)); + mu_assert("error", CHECK_COL_SIZES(constraints->AT, + constraints->state->col_sizes)); + + // check that new A is correct + double Ax_correct[] = {5, -1, 3, 2, 1, 3, 1}; + int Ai_correct[] = {0, 1, 2, 3, 1, 2, 3}; + int Ap_correct[] = {0, 4, 7}; + mu_assert("error Ax", ARRAYS_EQUAL(Ax_correct, A->x, 7)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai_correct, A->i, 7)); + CHECK_ROW_STARTS(A, Ap_correct); + + // check new AT + DEBUG(mu_assert("error AT", verify_A_and_AT_consistency(A, constraints->AT))); + + // check that new variable bounds are correct + double lbs_correct[] = {0.7, 0, 0, 0}; + double ubs_correct[] = {0.8, INF, INF, INF}; + mu_assert("error bounds", + CHECK_BOUNDS(constraints->bounds, lbs_correct, ubs_correct, 4)); + + // check that the objective function is correct + double obj_correct[] = {3, -1, 3, 2}; + mu_assert("error obj", ARRAYS_EQUAL(obj_correct, prob->obj->c, 4)); + mu_assert("error obj", prob->obj->offset == -2.0); + + // check that lhs and rhs are correct + double lhs_correct[] = {4, 5}; + double rhs_correct[] = {4, 5}; + mu_assert("error lhs", ARRAYS_EQUAL(lhs_correct, constraints->lhs, 2)); + mu_assert("error rhs", ARRAYS_EQUAL(rhs_correct, constraints->rhs, 2)); + + */ + // PS_FREE(stgs); + // DEBUG(run_debugger(constraints, false)); + // free_presolver(presolver); + return 0; +} + +/* dton row eliminated, unexpected cancellation resulting in zero column + min. [2 -1 -1 3 2]x + s.t. [2 -4 -1 3 2] [2] + [1 -2 0 0 0] x = [-1] + [1 -2 1 3 1] [4] + 1 <= x1 <= 2 + x3, x4, x5 >= 0 + x2 >= 1.1 +*/ +static char *test_3_dton() +{ + double Ax[] = {2, -4, -1, 3, 2, 1, -2, 1, -2, 1, 3, 1}; + int Ai[] = {0, 1, 2, 3, 4, 0, 1, 0, 1, 2, 3, 4}; + int Ap[] = {0, 5, 7, 12}; + int nnz = 12; + int n_rows = 3; + int n_cols = 5; + + double lhs[] = {2, -1, 4}; + double rhs[] = {2, -1, 4}; + double lbs[] = {1, 1.1, 0, 0, 0}; + double ubs[] = {2, INF, INF, INF, INF}; + double c[] = {2, -1, -1, 3, 2}; + + Settings *stgs = default_settings(); + set_settings_false(stgs); + stgs->dton_eq = true; + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + remove_dton_eq_rows(prob, 10); + problem_clean(prob, true); + + mu_assert("error row_sizes", + CHECK_ROW_SIZES(constraints->A, constraints->state->row_sizes)); + mu_assert("error col_sizes", + CHECK_COL_SIZES(constraints->AT, constraints->state->col_sizes)); + + // check that new A is correct + double Ax_correct[] = {-1, 3, 2, 1, 3, 1}; + int Ai_correct[] = {1, 2, 3, 1, 2, 3}; + int Ap_correct[] = {0, 3, 6}; + mu_assert("error Ax", ARRAYS_EQUAL(Ax_correct, A->x, 6)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai_correct, A->i, 6)); + CHECK_ROW_STARTS(A, Ap_correct); + + // check new AT + DEBUG(mu_assert("error AT", verify_A_and_AT_consistency(A, constraints->AT))); + + // check that new variable bounds are correct + double lbs_correct[] = {1.1, 0, 0, 0}; + double ubs_correct[] = {1.5, INF, INF, INF}; + mu_assert("error bounds", + CHECK_BOUNDS(constraints->bounds, lbs_correct, ubs_correct, 4)); + + // check that the objective function is correct + double obj_correct[] = {3, -1, 3, 2}; + mu_assert("error obj", ARRAYS_EQUAL(obj_correct, prob->obj->c, 4)); + mu_assert("error offset", prob->obj->offset == -2); + + // check that lhs and rhs are correct + double lhs_correct[] = {4, 5}; + double rhs_correct[] = {4, 5}; + mu_assert("error lhs", ARRAYS_EQUAL(lhs_correct, constraints->lhs, 2)); + mu_assert("error rhs", ARRAYS_EQUAL(rhs_correct, constraints->rhs, 2)); + + PS_FREE(stgs); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + return 0; +} + +/* Two identical doubleton rows, feasible problem, + min. [2 -1 -1 3 2]x + s.t. [2 -5 -1 3 2] [2] + [1 -2 0 0 0] x = [-1] + [1 -2 1 3 1] [4] + [1 -2 0 0 0] [-1] + 1 <= x1 <= 2 + x3, x4, x5 >= 0 + x2 >= 1.1 +*/ +static char *test_004_dton() +{ + double Ax[] = {2, -5, -1, 3, 2, 1, -2, 1, -2, 1, 3, 1, 1, -2}; + int Ai[] = {0, 1, 2, 3, 4, 0, 1, 0, 1, 2, 3, 4, 0, 1}; + int Ap[] = {0, 5, 7, 12, 14}; + int nnz = 14; + int n_rows = 4; + int n_cols = 5; + + double lhs[] = {2, -1, 4, -1}; + double rhs[] = {2, -1, 4, -1}; + double lbs[] = {1, 1.1, 0, 0, 0}; + double ubs[] = {2, INF, INF, INF, INF}; + double c[] = {2, -1, -1, 3, 2}; + + Settings *stgs = default_settings(); + set_settings_false(stgs); + stgs->dton_eq = true; + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + remove_dton_eq_rows(prob, 10); + problem_clean(prob, true); + + mu_assert("error", + CHECK_ROW_SIZES(constraints->A, constraints->state->row_sizes)); + mu_assert("error", + CHECK_COL_SIZES(constraints->AT, constraints->state->col_sizes)); + + // check that new A is correct + double Ax_correct[] = {-1, -1, 3, 2, 1, 3, 1}; + int Ai_correct[] = {0, 1, 2, 3, 1, 2, 3}; + int Ap_correct[] = {0, 4, 7, 7}; + mu_assert("error Ax", ARRAYS_EQUAL(Ax_correct, A->x, 7)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai_correct, A->i, 7)); + CHECK_ROW_STARTS(A, Ap_correct); + + // check new AT + DEBUG(mu_assert("error AT", verify_A_and_AT_consistency(A, constraints->AT))); + + // check that new variable bounds are correct + double lbs_correct[] = {1.1, 0, 0, 0}; + double ubs_correct[] = {1.5, INF, INF, INF}; + mu_assert("error bounds", + CHECK_BOUNDS(constraints->bounds, lbs_correct, ubs_correct, 4)); + + // check that the objective function is correct + double obj_correct[] = {3, -1, 3, 2}; + mu_assert("error obj", ARRAYS_EQUAL(obj_correct, prob->obj->c, 4)); + mu_assert("error offset", prob->obj->offset == -2); + + // check that lhs and rhs are correct + double lhs_correct[] = {4, 5, 0}; + double rhs_correct[] = {4, 5, 0}; + mu_assert("error lhs", ARRAYS_EQUAL(lhs_correct, constraints->lhs, 3)); + mu_assert("error rhs", ARRAYS_EQUAL(rhs_correct, constraints->rhs, 3)); + + PS_FREE(stgs); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + return 0; +} + +/* Two identical doubleton rows, feasible problem, empty column, empty row + min. [2 -1 -1 3 2]x + s.t. [2 -4 -1 3 2] [2] + [1 -2 0 0 0] x = [-1] + [1 -2 1 3 1] [4] + [1 -2 0 0 0] [-1] + 1 <= x1 <= 2 + x3, x4, x5 >= 0 + x2 >= 1.1 +*/ +static char *test_4_dton() +{ + double Ax[] = {2, -4, -1, 3, 2, 1, -2, 1, -2, 1, 3, 1, 1, -2}; + int Ai[] = {0, 1, 2, 3, 4, 0, 1, 0, 1, 2, 3, 4, 0, 1}; + int Ap[] = {0, 5, 7, 12, 14}; + int nnz = 14; + int n_rows = 4; + int n_cols = 5; + + double lhs[] = {2, -1, 4, -1}; + double rhs[] = {2, -1, 4, -1}; + double lbs[] = {1, 1.1, 0, 0, 0}; + double ubs[] = {2, INF, INF, INF, INF}; + double c[] = {2, -1, -1, 3, 2}; + + Settings *stgs = default_settings(); + set_settings_false(stgs); + stgs->dton_eq = true; + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + remove_dton_eq_rows(prob, 10); + problem_clean(prob, true); + + mu_assert("error", + CHECK_ROW_SIZES(constraints->A, constraints->state->row_sizes)); + mu_assert("error", + CHECK_COL_SIZES(constraints->AT, constraints->state->col_sizes)); + + // check that new A is correct + double Ax_correct[] = {-1, 3, 2, 1, 3, 1}; + int Ai_correct[] = {1, 2, 3, 1, 2, 3}; + int Ap_correct[] = {0, 3, 6, 6}; + mu_assert("error Ax", ARRAYS_EQUAL(Ax_correct, A->x, 6)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai_correct, A->i, 6)); + CHECK_ROW_STARTS(A, Ap_correct); + + // check new AT + DEBUG(mu_assert("error AT", verify_A_and_AT_consistency(A, constraints->AT))); + + // check that new variable bounds are correct + double lbs_correct[] = {1.1, 0, 0, 0}; + double ubs_correct[] = {1.5, INF, INF, INF}; + mu_assert("error bounds", + CHECK_BOUNDS(constraints->bounds, lbs_correct, ubs_correct, 4)); + + // check that the objective function is correct + double obj_correct[] = {3, -1, 3, 2}; + mu_assert("error obj", ARRAYS_EQUAL(obj_correct, prob->obj->c, 4)); + mu_assert("error obj", prob->obj->offset == -2); + + // check that lhs and rhs are correct + double lhs_correct[] = {4, 5, 0, 0}; + double rhs_correct[] = {4, 5, 0, 0}; + mu_assert("error lhs", ARRAYS_EQUAL(lhs_correct, constraints->lhs, 4)); + mu_assert("error rhs", ARRAYS_EQUAL(rhs_correct, constraints->rhs, 4)); + + PS_FREE(stgs); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + return 0; +} + +/* Two identical doubleton rows, infeasible problem. + min. [2 -1 -1 3 2]x + s.t. [2 -4 -1 3 2] [2] + [1 -2 0 0 0] x = [-1] + [1 -2 1 3 1] [4] + [1 -2 0 0 0] [-1 + 1e-15] + 1 <= x1 <= 2 + x3, x4, x5 >= 0 + x2 >= 1.1 +*/ + +static char *test_5_dton() +{ + double Ax[] = {2, 1, -1, 3, 2, 1, -2, 1, -2, 1, 3, 1}; + int Ai[] = {0, 1, 2, 3, 4, 0, 1, 0, 1, 2, 3, 4}; + int Ap[] = {0, 5, 7, 12}; + int nnz = 12; + int n_rows = 3; + int n_cols = 5; + + double lhs[] = {2, -1, 4}; + double rhs[] = {2, -1, 4}; + double lbs[] = {0.4, 0, 0, 0, 0}; + double ubs[] = {0.6, INF, INF, INF, INF}; + double c[] = {2, -1, -1, 3, 2}; + + Settings *stgs = default_settings(); + set_settings_false(stgs); + stgs->dton_eq = true; + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + PS_FREE(stgs); + // DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + mu_assert("error", 1 == 0); + return 0; +} + +/* Two doubleton rows, presolver wants to substitute the same variable + from both + min. [2 -1 -1 3]x + s.t. [1 0 2 0] [2] + [1 0 0 2] x = [2] + [1 0 2 -4] <= 4 + [0 -2 3 5] <= 5 + [0 1 -6 7] <= 7 + x >= 0 +*/ + +static char *test_6_dton() +{ + double Ax[] = {1, 2, 1, 2, 1, 2, -4, -2, 3, 5, 1, -6, 7}; + int Ai[] = {0, 2, 0, 3, 0, 2, 3, 1, 2, 3, 1, 2, 3}; + int Ap[] = {0, 2, 4, 7, 10, 13}; + int nnz = 13; + int n_rows = 5; + int n_cols = 4; + + double lhs[] = {2, 2, -INF, -INF, -INF}; + double rhs[] = {2, 2, 4, 5, 7}; + double lbs[] = {0, 0, 0, 0}; + double ubs[] = {INF, INF, INF, INF, INF}; + double c[] = {2, -1, -1, 3, 2}; + + Settings *stgs = default_settings(); + set_settings_false(stgs); + stgs->dton_eq = true; + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + remove_dton_eq_rows(prob, 10); + problem_clean(prob, true); + + mu_assert("error row_sizes", + CHECK_ROW_SIZES(constraints->A, constraints->state->row_sizes)); + mu_assert("error col_sizes", + CHECK_COL_SIZES(constraints->AT, constraints->state->col_sizes)); + + // check that new A is correct + double Ax_correct[] = {-4, -2, 8, 1, 1}; + int Ai_correct[] = {1, 0, 1, 0, 1}; + int Ap_correct[] = {0, 1, 3, 5}; + mu_assert("error Ax", ARRAYS_EQUAL(Ax_correct, A->x, 5)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai_correct, A->i, 5)); + CHECK_ROW_STARTS(A, Ap_correct); + + // check new AT + DEBUG(mu_assert("error AT", verify_A_and_AT_consistency(A, constraints->AT))); + + // check that new variable bounds are correct + double lbs_correct[] = {0, 0}; + double ubs_correct[] = {INF, 1}; + mu_assert("error bounds", + CHECK_BOUNDS(constraints->bounds, lbs_correct, ubs_correct, 2)); + + // check that the objective function is correct + double obj_correct[] = {-1, -2}; + mu_assert("error obj", ARRAYS_EQUAL(obj_correct, prob->obj->c, 2)); + mu_assert("error offset", prob->obj->offset == 4); + + // check that lhs and rhs are correct + double lhs_correct[] = {-INF, -INF, -INF}; + double rhs_correct[] = {2, 5, 7}; + mu_assert("error lhs", ARRAYS_EQUAL(lhs_correct, constraints->lhs, 2)); + mu_assert("error rhs", ARRAYS_EQUAL(rhs_correct, constraints->rhs, 2)); + + PS_FREE(stgs); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + return 0; +} + +/* Two doubleton rows, eliminating one of them causes the other dton row + to become a singleton row + min. [2 -1 -1 3 2]x + s.t. [2 -4 -1 3 2] [2] + [1 -2 0 0 0] x = [-1] + [0 -2 1 3 1] [4] + [1 -1 0 0 0] [1] + 0 <= x1 <= 5 + x2, x3, x4, x5 >= 0 +*/ +static char *test_7_dton() +{ + double Ax[] = {2, -4, -1, 3, 2, 1, -2, -2, 1, 3, 1, 1, -1}; + int Ai[] = {0, 1, 2, 3, 4, 0, 1, 1, 2, 3, 4, 0, 1}; + int Ap[] = {0, 5, 7, 11, 13}; + int nnz = 13; + int n_rows = 4; + int n_cols = 5; + + double lhs[] = {2, -1, 4, 1}; + double rhs[] = {2, -1, 4, 1}; + double lbs[] = {0, 0, 0, 0, 0}; + double ubs[] = {5, INF, INF, INF, INF}; + double c[] = {2, -1, -1, 3, 2}; + + Settings *stgs = default_settings(); + set_settings_false(stgs); + stgs->dton_eq = true; + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + remove_dton_eq_rows(prob, 10); + problem_clean(prob, true); + + mu_assert("error row_sizes", + CHECK_ROW_SIZES(constraints->A, constraints->state->row_sizes)); + mu_assert("error col_sizes", + CHECK_COL_SIZES(constraints->AT, constraints->state->col_sizes)); + + // check that new A is correct + double Ax_correct[] = {-1, 3, 2, -2, 1, 3, 1, 1}; + int Ai_correct[] = {1, 2, 3, 0, 1, 2, 3, 0}; + int Ap_correct[] = {0, 3, 7, 8}; + mu_assert("error Ax", ARRAYS_EQUAL(Ax_correct, A->x, 8)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai_correct, A->i, 8)); + CHECK_ROW_STARTS(A, Ap_correct); + + // check new AT + DEBUG(mu_assert("error AT", verify_A_and_AT_consistency(A, constraints->AT))); + + // check that new variable bounds are correct + double lbs_correct[] = {0.5, 0, 0, 0}; + double ubs_correct[] = {3, INF, INF, INF}; + mu_assert("error bounds", + CHECK_BOUNDS(constraints->bounds, lbs_correct, ubs_correct, 4)); + + // check that the objective function is correct + double obj_correct[] = {3, -1, 3, 2}; + mu_assert("error obj", ARRAYS_EQUAL(obj_correct, prob->obj->c, 4)); + mu_assert("error offset", prob->obj->offset == -2); + + // check that lhs and rhs are correct + double lhs_correct[] = {4, 4, 2}; + double rhs_correct[] = {4, 4, 2}; + mu_assert("error lhs", ARRAYS_EQUAL(lhs_correct, constraints->lhs, 2)); + mu_assert("error rhs", ARRAYS_EQUAL(rhs_correct, constraints->rhs, 2)); + + PS_FREE(stgs); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + return 0; +} + +/* doubleton row with two fill-in in a column accepted + min. [-2 -1 -1 -3]x + s.t. [1 0 2 1] = [2] + [0 1 0 1] = [3] + [2 1 0 0] <= [5] + [4 1 0 0] <= [6] + [2 0 2 0] x <= [7] + [1 0 0 1] <= [9] + [2 0 0 1] <= [10] + [3 0 0 1] <= [13] + [4 0 0 1] <= [14] + x >= 0 + +*/ +static char *test_8_dton() +{ + double Ax[] = {1, 2, 1, 1, 1, 2, 1, 4, 1, 2, 2, 1, 1, 2, 1, 3, 1, 4, 1}; + int Ai[] = {0, 2, 3, 1, 3, 0, 1, 0, 1, 0, 2, 0, 3, 0, 3, 0, 3, 0, 3}; + int Ap[] = {0, 3, 5, 7, 9, 11, 13, 15, 17, 19}; + int nnz = 19; + int n_rows = 9; + int n_cols = 4; + + double lhs[] = {2, 3, -INF, -INF, -INF, -INF, -INF, -INF, -INF}; + double rhs[] = {2, 3, 5, 6, 7, 9, 10, 13, 14}; + double lbs[4] = {0}; + double ubs[4] = {INF, INF, INF, INF}; + double c[] = {2, -1, -1, 3}; + + Settings *stgs = default_settings(); + set_settings_false(stgs); + stgs->dton_eq = true; + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + remove_dton_eq_rows(prob, 10); + problem_clean(prob, true); + + mu_assert("error row_sizes", + CHECK_ROW_SIZES(constraints->A, constraints->state->row_sizes)); + mu_assert("error col_sizes", + CHECK_COL_SIZES(constraints->AT, constraints->state->col_sizes)); + + // check that new A is correct + double Ax_correct[] = {1, 2, 1, 2, -1, 4, -1, 2, 2, 1, 1, 2, 1, 3, 1, 4, 1}; + int Ai_correct[] = {0, 1, 2, 0, 2, 0, 2, 0, 1, 0, 2, 0, 2, 0, 2, 0, 2}; + int Ap_correct[] = {0, 3, 5, 7, 9, 11, 13, 15, 17}; + mu_assert("error Ax", ARRAYS_EQUAL(Ax_correct, A->x, 17)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai_correct, A->i, 17)); + CHECK_ROW_STARTS(A, Ap_correct); + + // check new AT + DEBUG(mu_assert("error AT", verify_A_and_AT_consistency(A, constraints->AT))); + + // check that new variable bounds are correct + double lbs_correct[] = {0, 0, 0}; + double ubs_correct[] = {INF, INF, 3}; + mu_assert("error bounds", + CHECK_BOUNDS(constraints->bounds, lbs_correct, ubs_correct, 3)); + + // check that lhs and rhs are correct + double lhs_correct[] = {2, -INF, -INF, -INF, -INF, -INF, -INF, -INF}; + double rhs_correct[] = {2, 2, 3, 7, 9, 10, 13, 14}; + mu_assert("error lhs", ARRAYS_EQUAL(lhs_correct, constraints->lhs, 8)); + mu_assert("error rhs", ARRAYS_EQUAL(rhs_correct, constraints->rhs, 8)); + + PS_FREE(stgs); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + return 0; +} + +/* doubleton row with three fill-in in a column accepted + min. [-2 -1 -1 -3]x + s.t. [1 0 2 1] = [2] + [0 1 0 1] = [3] + [2 1 0 0] <= [5] + [4 1 0 0] <= [6] + [2 1 2 0] x <= [7] + [1 0 0 1] <= [9] + [2 0 0 1] <= [10] + [3 0 0 1] <= [13] + [4 0 0 1] <= [14] + x >= 0 +*/ +static char *test_9_dton() +{ + double Ax[] = {1, 2, 1, 1, 1, 2, 1, 4, 1, 2, 1, 2, 1, 1, 2, 1, 3, 1, 4, 1}; + int Ai[] = {0, 2, 3, 1, 3, 0, 1, 0, 1, 0, 1, 2, 0, 3, 0, 3, 0, 3, 0, 3}; + int Ap[] = {0, 3, 5, 7, 9, 12, 14, 16, 18, 20}; + int nnz = 20; + int n_rows = 9; + int n_cols = 4; + + double lhs[] = {2, 3, -INF, -INF, -INF, -INF, -INF, -INF, -INF}; + double rhs[] = {2, 3, 5, 6, 7, 9, 10, 13, 14}; + double lbs[4] = {0}; + double ubs[] = {INF, INF, INF, INF, INF}; + double c[] = {-2, -1, -1, -3}; + + Settings *stgs = default_settings(); + set_settings_false(stgs); + stgs->dton_eq = true; + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + remove_dton_eq_rows(prob, 10); + problem_clean(prob, true); + + mu_assert("error row_sizes", + CHECK_ROW_SIZES(constraints->A, constraints->state->row_sizes)); + mu_assert("error col_sizes", + CHECK_COL_SIZES(constraints->AT, constraints->state->col_sizes)); + + // check that new A is correct + double Ax_correct[] = {1, 2, 1, 2, -1, 4, -1, 2, 2, -1, 1, 1, 2, 1, 3, 1, 4, 1}; + int Ai_correct[] = {0, 1, 2, 0, 2, 0, 2, 0, 1, 2, 0, 2, 0, 2, 0, 2, 0, 2}; + int Ap_correct[] = {0, 3, 5, 7, 10, 12, 14, 16, 18}; + mu_assert("error Ax", ARRAYS_EQUAL(Ax_correct, A->x, 18)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai_correct, A->i, 18)); + CHECK_ROW_STARTS(A, Ap_correct); + + // check new AT + DEBUG(mu_assert("error AT", verify_A_and_AT_consistency(A, constraints->AT))); + + // check that new variable bounds are correct + double lbs_correct[] = {0, 0, 0}; + double ubs_correct[] = {INF, INF, 3}; + mu_assert("error bounds", + CHECK_BOUNDS(constraints->bounds, lbs_correct, ubs_correct, 3)); + + // check that lhs and rhs are correct + double lhs_correct[] = {2, -INF, -INF, -INF, -INF, -INF, -INF, -INF}; + double rhs_correct[] = {2, 2, 3, 4, 9, 10, 13, 14}; + mu_assert("error lhs", ARRAYS_EQUAL(lhs_correct, constraints->lhs, 8)); + mu_assert("error rhs", ARRAYS_EQUAL(rhs_correct, constraints->rhs, 8)); + + PS_FREE(stgs); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + return 0; +} + +/* doubleton row with three fill-in in a column accepted + min. [-2 -1 -1 -3]x + s.t. [1 0 2 1] = [2] + [0 1 0 1] = [3] + [2 1 0 0] <= [5] + [4 1 0 0] <= [6] + [2 1 2 0] x <= [7] + [1 0 0 1] <= [9] + [2 0 0 1] <= [10] + [3 0 0 1] <= [13] + [4 0 0 1] <= [14] + x1, x2, x4 >= 0 + x3 free + +*/ +static char *test_10_dton() +{ + double Ax[] = {1, 2, 1, 1, 1, 2, 1, 4, 1, 2, 1, 2, 1, 1, 2, 1, 3, 1, 4, 1}; + int Ai[] = {0, 2, 3, 1, 3, 0, 1, 0, 1, 0, 1, 2, 0, 3, 0, 3, 0, 3, 0, 3}; + int Ap[] = {0, 3, 5, 7, 9, 12, 14, 16, 18, 20}; + int nnz = 20; + int n_rows = 9; + int n_cols = 4; + + double lhs[] = {2, 3, -INF, -INF, -INF, -INF, -INF, -INF, -INF}; + double rhs[] = {2, 3, 5, 6, 7, 9, 10, 13, 14}; + double lbs[] = {0, 0, -INF, 0}; + double ubs[] = {INF, INF, INF, INF}; + double c[] = {2, -1, -1, 3}; + + Settings *stgs = default_settings(); + set_settings_false(stgs); + stgs->dton_eq = true; + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + remove_dton_eq_rows(prob, 10); + problem_clean(prob, true); + + mu_assert("error row_sizes", + CHECK_ROW_SIZES(constraints->A, constraints->state->row_sizes)); + mu_assert("error col_sizes", + CHECK_COL_SIZES(constraints->AT, constraints->state->col_sizes)); + + // check that new A is correct + double Ax_correct[] = {1, 2, 1, 2, -1, 4, -1, 2, 2, -1, 1, 1, 2, 1, 3, 1, 4, 1}; + int Ai_correct[] = {0, 1, 2, 0, 2, 0, 2, 0, 1, 2, 0, 2, 0, 2, 0, 2, 0, 2}; + int Ap_correct[] = {0, 3, 5, 7, 10, 12, 14, 16, 18}; + mu_assert("error Ax", ARRAYS_EQUAL(Ax_correct, A->x, 18)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai_correct, A->i, 18)); + CHECK_ROW_STARTS(A, Ap_correct); + + // check new AT + DEBUG(mu_assert("error AT", verify_A_and_AT_consistency(A, constraints->AT))); + + // check that new variable bounds are correct + double lbs_correct[] = {0, -INF, 0}; + double ubs_correct[] = {INF, INF, 3}; + mu_assert("error bounds", + CHECK_BOUNDS(constraints->bounds, lbs_correct, ubs_correct, 3)); + + // check that lhs and rhs are correct + double lhs_correct[] = {2, -INF, -INF, -INF, -INF, -INF, -INF, -INF}; + double rhs_correct[] = {2, 2, 3, 4, 9, 10, 13, 14}; + mu_assert("error lhs", ARRAYS_EQUAL(lhs_correct, constraints->lhs, 8)); + mu_assert("error rhs", ARRAYS_EQUAL(rhs_correct, constraints->rhs, 8)); + + PS_FREE(stgs); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + return 0; +} + +/* + doubleton row with four fill-in rejected + min. [-2 -1 -1 -3]x + s.t. [1 0 2 1] = [2] + [0 1 0 2] = [3] + [2 1 0 0] <= [5] + [4 1 0 0] <= [6] + [2 1 0 0] x <= [7] + [1 1 0 0] <= [9] + [2 0 0 1] <= [10] + [3 0 0 1] <= [13] + [4 0 0 1] <= [14] + x1, x2, x4 >= 0 + x3 free +*/ +static char *test_11_dton() +{ + double Ax[] = {1, 2, 1, 1, 2, 2, 1, 4, 1, 2, 1, 1, 1, 2, 1, 3, 1, 4, 1}; + int Ai[] = {0, 2, 3, 1, 3, 0, 1, 0, 1, 0, 1, 0, 1, 0, 3, 0, 3, 0, 3}; + int Ap[] = {0, 3, 5, 7, 9, 11, 13, 15, 17, 19}; + int nnz = 19; + int n_rows = 9; + int n_cols = 4; + + double lhs[] = {2, 3, -INF, -INF, -INF, -INF, -INF, -INF, -INF}; + double rhs[] = {2, 3, 5, 6, 7, 9, 10, 13, 14}; + double lbs[] = {0, 0, -INF, 0}; + double ubs[] = {INF, INF, INF, INF}; + double c[] = {2, -1, -1, 3}; + + Settings *stgs = default_settings(); + set_settings_false(stgs); + stgs->dton_eq = true; + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + PresolveStatus status = remove_dton_eq_rows(prob, 0); + // mu_assert("error status", status == UNCHANGED); + problem_clean(prob, true); + + mu_assert("error row_sizes", + CHECK_ROW_SIZES(constraints->A, constraints->state->row_sizes)); + mu_assert("error col_sizes", + CHECK_COL_SIZES(constraints->AT, constraints->state->col_sizes)); + + // print_matrix(A); + + // check that new A is correct + mu_assert("error Ax", ARRAYS_EQUAL(Ax, A->x, nnz)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai, A->i, nnz)); + CHECK_ROW_STARTS(A, Ap); + + PS_FREE(stgs); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + return 0; +} + +/* + doubleton row with four fill-in accepted (one extra space is released + by another reduction) + min. [-2 -1 -1 -3]x + s.t. [1 0 2 1] = [2] + [0 1 0 2] = [3] + [2 1 0 0] <= [5] + [4 1 0 0] <= [6] + [2 1 0 0] x <= [7] + [1 1 0 0] <= [9] + [2 0 0 1] <= [10] + [3 0 0 1] <= [13] + [4 0 0 1] <= [14] + x1, x2, x4 >= 0 + x3 free +*/ +static char *test_12_dton() +{ + double Ax[] = {2, 1, -1, 3, 2, 1, -2, 1, -2, 1, 3, 1}; + int Ai[] = {0, 1, 2, 3, 4, 0, 1, 0, 1, 2, 3, 4}; + int Ap[] = {0, 5, 7, 12}; + int nnz = 12; + int n_rows = 3; + int n_cols = 5; + + double lhs[] = {2, -1, 4}; + double rhs[] = {2, -1, 4}; + double lbs[] = {0.4, 0, 0, 0, 0}; + double ubs[] = {0.6, INF, INF, INF, INF}; + double c[] = {2, -1, -1, 3, 2}; + + Settings *stgs = default_settings(); + set_settings_false(stgs); + stgs->dton_eq = true; + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + PS_FREE(stgs); + // DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + mu_assert("error", 1 == 1); + return 0; +} + +/* + corner case that caused a nasty bug on a NETLIB problem + (excessive fill-in in one column due to two doubleton rows + with one common variable). We set max shift to 0, but both + reductions should be allowed since we can eat up the space + in column 1 without actually shifting any elements (since + column 1 is inactive after the first dton reduction.) + min. [-2 -1 -1 -3, -1]x + s.t. [2, -1, 0, 0, 0] = 0 + [2, 0, -1, 0, 0] = 0 + [0, 1, 0, 1, 2] <= 3 + [0, 1, 0, 3, 4] <= 4 + [0, 0, 2, 5, 6] x <= 5 + [0, 0, 2, 7, 8] <= 6 + [0, 0, 1, 7, 8] <= 7 + [2, 0, 0, 7, 8] <= 8 + [3, 0, 0, 7, 8] <= 9 + [4, 0, 0, 7, 8] <= 10 + [5, 0, 0, 7, 8] <= 11 + [6, 0, 0, 7, 8] <= 12 + x1, x2, x3, x4 >= 0 +*/ +static char *test_13_dton() +{ + double Ax[] = {2, -1, 2, -1, 1, 1, 2, 1, 3, 4, 2, 5, 6, 2, 7, 8, 1, + 7, 8, 2, 7, 8, 3, 7, 8, 4, 7, 8, 5, 7, 8, 6, 7, 8}; + int Ai[] = {0, 1, 0, 2, 1, 3, 4, 1, 3, 4, 2, 3, 4, 2, 3, 4, 2, + 3, 4, 0, 3, 4, 0, 3, 4, 0, 3, 4, 0, 3, 4, 0, 3, 4}; + int Ap[] = {0, 2, 4, 7, 10, 13, 16, 19, 22, 25, 28, 31, 34}; + int nnz = 34; + int n_rows = 12; + int n_cols = 5; + + double lhs[] = {0, 0, -INF, -INF, -INF, -INF, + -INF, -INF, -INF, -INF, -INF, -INF}; + double rhs[] = {0, 0, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; + double lbs[] = {0, 0, 0, 0, 0}; + double ubs[] = {INF, INF, INF, INF, INF}; + double c[] = {-2, -1, -1, 3, -1}; + + Settings *stgs = default_settings(); + set_settings_false(stgs); + stgs->dton_eq = true; + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + remove_dton_eq_rows(prob, 0); + problem_clean(prob, true); + + mu_assert("error row_sizes", + CHECK_ROW_SIZES(constraints->A, constraints->state->row_sizes)); + mu_assert("error col_sizes", + CHECK_COL_SIZES(constraints->AT, constraints->state->col_sizes)); + + // check that new A is correct + double Ax_correct[] = {2, 1, 2, 2, 3, 4, 4, 5, 6, 4, 7, 8, 2, 7, 8, + 2, 7, 8, 3, 7, 8, 4, 7, 8, 5, 7, 8, 6, 7, 8}; + int Ai_correct[] = {0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, + 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2}; + int Ap_correct[] = {0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30}; + mu_assert("error Ax", ARRAYS_EQUAL(Ax_correct, A->x, 30)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai_correct, A->i, 30)); + CHECK_ROW_STARTS(A, Ap_correct); + + PS_FREE(stgs); + DEBUG(mu_assert("error AT", verify_A_and_AT_consistency(A, constraints->AT))); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + + return 0; +} + +/* Same as the previous test, but we change columns 0 and 1. We set + max shift to 0. The second reduction is then rejected, because although there + is free space on the left we can't eat it up. Very interesting. + Is this how we want it to be? */ +static char *test_14_dton() +{ + double Ax[] = {-1, 2, 2, -1, 1, 1, 2, 1, 3, 4, 2, 5, 6, 2, 7, 8, 1, + 7, 8, 2, 7, 8, 3, 7, 8, 4, 7, 8, 5, 7, 8, 6, 7, 8}; + int Ai[] = {0, 1, 1, 2, 0, 3, 4, 0, 3, 4, 2, 3, 4, 2, 3, 4, 2, + 3, 4, 1, 3, 4, 1, 3, 4, 1, 3, 4, 1, 3, 4, 1, 3, 4}; + int Ap[] = {0, 2, 4, 7, 10, 13, 16, 19, 22, 25, 28, 31, 34}; + int nnz = 34; + int n_rows = 12; + int n_cols = 5; + + double lhs[] = {0, 0, -INF, -INF, -INF, -INF, + -INF, -INF, -INF, -INF, -INF, -INF}; + double rhs[] = {0, 0, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; + double lbs[] = {0, 0, 0, 0, 0}; + double ubs[] = {INF, INF, INF, INF, INF}; + double c[] = {-2, -1, -1, 3, -1}; + + Settings *stgs = default_settings(); + set_settings_false(stgs); + stgs->dton_eq = true; + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + remove_dton_eq_rows(prob, 0); + problem_clean(prob, true); + + mu_assert("error row_sizes", + CHECK_ROW_SIZES(constraints->A, constraints->state->row_sizes)); + mu_assert("error col_sizes", + CHECK_COL_SIZES(constraints->AT, constraints->state->col_sizes)); + + // check that new A is correct + double Ax_correct[] = {2, -1, 2, 1, 2, 2, 3, 4, 2, 5, 6, 2, 7, 8, 1, 7, + 8, 2, 7, 8, 3, 7, 8, 4, 7, 8, 5, 7, 8, 6, 7, 8}; + int Ai_correct[] = {0, 1, 0, 2, 3, 0, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, + 3, 0, 2, 3, 0, 2, 3, 0, 2, 3, 0, 2, 3, 0, 2, 3}; + int Ap_correct[] = {0, 2, 5, 8, 11, 14, 17, 20, 23, 26, 29, 32}; + mu_assert("error Ax", ARRAYS_EQUAL(Ax_correct, A->x, 32)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai_correct, A->i, 32)); + CHECK_ROW_STARTS(A, Ap_correct); + + // check new AT + DEBUG(mu_assert("error AT", verify_A_and_AT_consistency(A, constraints->AT))); + + PS_FREE(stgs); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + + return 0; +} + +/* Same as previous test, but with max shift set to 4. + The reduction should be accepted because shifting the + right row requires shifting four elements. */ +static char *test_15_dton() +{ + double Ax[] = {-1, 2, 2, -1, 1, 1, 2, 1, 3, 4, 2, 5, 6, 2, 7, 8, 1, + 7, 8, 2, 7, 8, 3, 7, 8, 4, 7, 8, 5, 7, 8, 6, 7, 8}; + int Ai[] = {0, 1, 1, 2, 0, 3, 4, 0, 3, 4, 2, 3, 4, 2, 3, 4, 2, + 3, 4, 1, 3, 4, 1, 3, 4, 1, 3, 4, 1, 3, 4, 1, 3, 4}; + int Ap[] = {0, 2, 4, 7, 10, 13, 16, 19, 22, 25, 28, 31, 34}; + int nnz = 34; + int n_rows = 12; + int n_cols = 5; + + double lhs[] = {0, 0, -INF, -INF, -INF, -INF, + -INF, -INF, -INF, -INF, -INF, -INF}; + double rhs[] = {0, 0, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; + double lbs[] = {0, 0, 0, 0, 0}; + double ubs[] = {INF, INF, INF, INF, INF}; + double c[] = {-2, -1, -1, 3, -1}; + + Settings *stgs = default_settings(); + set_settings_false(stgs); + stgs->dton_eq = true; + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + remove_dton_eq_rows(prob, 4); + problem_clean(prob, true); + + mu_assert("error row_sizes", + CHECK_ROW_SIZES(constraints->A, constraints->state->row_sizes)); + mu_assert("error col_sizes", + CHECK_COL_SIZES(constraints->AT, constraints->state->col_sizes)); + + // check that new A is correct + double Ax_correct[] = {2, 1, 2, 2, 3, 4, 4, 5, 6, 4, 7, 8, 2, 7, 8, + 2, 7, 8, 3, 7, 8, 4, 7, 8, 5, 7, 8, 6, 7, 8}; + int Ai_correct[] = {0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, + 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2}; + int Ap_correct[] = {0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30}; + mu_assert("error Ax", ARRAYS_EQUAL(Ax_correct, A->x, 30)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai_correct, A->i, 30)); + CHECK_ROW_STARTS(A, Ap_correct); + + // check new AT + DEBUG(mu_assert("error AT", verify_A_and_AT_consistency(A, constraints->AT))); + + PS_FREE(stgs); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + return 0; +} + +/* Same as previous test, but with max shift set to 3. + The reduction should be rejected because shifting the + right row requires shifting four elements. */ +static char *test_16_dton() +{ + double Ax[] = {-1, 2, 2, -1, 1, 1, 2, 1, 3, 4, 2, 5, 6, 2, 7, 8, 1, + 7, 8, 2, 7, 8, 3, 7, 8, 4, 7, 8, 5, 7, 8, 6, 7, 8}; + int Ai[] = {0, 1, 1, 2, 0, 3, 4, 0, 3, 4, 2, 3, 4, 2, 3, 4, 2, + 3, 4, 1, 3, 4, 1, 3, 4, 1, 3, 4, 1, 3, 4, 1, 3, 4}; + int Ap[] = {0, 2, 4, 7, 10, 13, 16, 19, 22, 25, 28, 31, 34}; + int nnz = 34; + int n_rows = 12; + int n_cols = 5; + + double lhs[] = {0, 0, -INF, -INF, -INF, -INF, + -INF, -INF, -INF, -INF, -INF, -INF}; + double rhs[] = {0, 0, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; + double lbs[] = {0, 0, 0, 0, 0}; + double ubs[] = {INF, INF, INF, INF, INF}; + double c[] = {-2, -1, -1, 3, -1}; + + Settings *stgs = default_settings(); + set_settings_false(stgs); + stgs->dton_eq = true; + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + remove_dton_eq_rows(prob, 3); + problem_clean(prob, true); + + mu_assert("error row_sizes", + CHECK_ROW_SIZES(constraints->A, constraints->state->row_sizes)); + mu_assert("error col_sizes", + CHECK_COL_SIZES(constraints->AT, constraints->state->col_sizes)); + + // check that new A is correct + double Ax_correct[] = {2, -1, 2, 1, 2, 2, 3, 4, 2, 5, 6, 2, 7, 8, 1, 7, + 8, 2, 7, 8, 3, 7, 8, 4, 7, 8, 5, 7, 8, 6, 7, 8}; + int Ai_correct[] = {0, 1, 0, 2, 3, 0, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, + 3, 0, 2, 3, 0, 2, 3, 0, 2, 3, 0, 2, 3, 0, 2, 3}; + int Ap_correct[] = {0, 2, 5, 8, 11, 14, 17, 20, 23, 26, 29, 32}; + mu_assert("error Ax", ARRAYS_EQUAL(Ax_correct, A->x, 32)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai_correct, A->i, 32)); + CHECK_ROW_STARTS(A, Ap_correct); + + PS_FREE(stgs); // check new AT + DEBUG(run_debugger(constraints, false)); + DEBUG(mu_assert("error AT", verify_A_and_AT_consistency(A, constraints->AT))); + free_presolver(presolver); + + return 0; +} + +/* We eliminate a doubleton row and substitute a variable in a row with + no extra space. We get in-place fill-in in A, but that should be + fine. + + min. [2 -1 -1 3 2]x + s.t. [2 1 0 0 0] [2] + [0 1 1 3 0] x >= [5] + [0 -2 1 3 1] [4] + [1 -1 0 1 1] [1] + 0 <= x1 <= 5 + x2, x3, x4, x5 >= 0 +*/ +static char *test_17_dton() +{ + double Ax[] = {2, 1, 1, 1, 3, -2, 1, 3, 1, 1, -1, 1, 1}; + int Ai[] = {0, 1, 1, 2, 3, 1, 2, 3, 4, 0, 1, 3, 4}; + int Ap[] = {0, 2, 5, 9, 13}; + int nnz = 13; + int n_rows = 4; + int n_cols = 5; + + double lhs[] = {2, 5, 4, 1}; + double rhs[] = {2, INF, 4, 1}; + double lbs[] = {0, 0, 0, 0, 0}; + double ubs[] = {5, INF, INF, INF, INF}; + double c[] = {2, -1, -1, 3, 2}; + + Settings *stgs = default_settings(); + set_settings_false(stgs); + stgs->dton_eq = true; + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + State *data = constraints->state; + remove_extra_space(A, data->row_sizes, data->col_sizes, true, + data->work->mappings->cols); + + remove_dton_eq_rows(prob, 0); + problem_clean(prob, true); + + // check that new A is correct + double Ax_correct[] = {-2, 1, 3, 4, 1, 3, 1, 3, 1, 1}; + int Ai_correct[] = {0, 1, 2, 0, 1, 2, 3, 0, 2, 3}; + int Ap_correct[] = {0, 3, 7, 10}; + mu_assert("error Ax", ARRAYS_EQUAL(Ax_correct, A->x, 10)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai_correct, A->i, 10)); + CHECK_ROW_STARTS(A, Ap_correct); + + PS_FREE(stgs); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + return 0; +} + +/* We eliminate a doubleton row without having any extra space in + AT (and A). We get in-place fill-in in AT and A, but that + should be fine. + + min. [2 -1 -1 3 2]x + s.t. [2 1 0 0 0] [2] + [0 1 1 3 0] x >= [5] + [1 -1 0 1 1] [1] + 0 <= x1 <= 5 + x2, x3, x4, x5 >= 0 +*/ +static char *test_18_dton() +{ + double Ax[] = {2, 1, 1, 1, 3, 1, -1, 1, 1}; + int Ai[] = {0, 1, 1, 2, 3, 0, 1, 3, 4}; + int Ap[] = {0, 2, 5, 9}; + int nnz = 9; + int n_rows = 3; + int n_cols = 5; + + double lhs[] = {2, 5, 1}; + double rhs[] = {2, INF, 1}; + double lbs[] = {0, 0, 0, 0, 0}; + double ubs[] = {5, INF, INF, INF, INF}; + double c[] = {2, -1, -1, 3, 2}; + + Settings *stgs = default_settings(); + set_settings_false(stgs); + stgs->dton_eq = true; + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + Matrix *AT = constraints->AT; + State *data = constraints->state; + remove_extra_space(A, data->row_sizes, data->col_sizes, true, + data->work->mappings->cols); + remove_extra_space(AT, data->col_sizes, data->row_sizes, true, + data->work->mappings->rows); + + remove_dton_eq_rows(prob, 0); + problem_clean(prob, true); + + // check that new A is correct + double Ax_correct[] = {-2, 1, 3, 3, 1, 1}; + int Ai_correct[] = {0, 1, 2, 0, 2, 3}; + int Ap_correct[] = {0, 3, 6}; + mu_assert("error Ax", ARRAYS_EQUAL(Ax_correct, A->x, 6)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai_correct, A->i, 6)); + CHECK_ROW_STARTS(A, Ap_correct); + + PS_FREE(stgs); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + return 0; +} + +/* We eliminate a doubleton row and substitute a variable in a row with + no extra space. We get in-place fill-in, but that should be fine. + + Same as above, but infeasible constraint. But this should affect + anything, but we still get a weird bug! What happened was that + we got a chain of doubleton rows, so the entire A matrix was + eliminated. We don't run this test for now since it might not be + a bug. + + min. [2 -1 -1 3 2]x + s.t. [2 1 0 0 0] [2] + [0 1 1 3 0] x = [-4] + [0 -2 1 3 1] [4] + [1 -1 0 1 0] [1] + 0 <= x1 <= 5 + x2, x3, x4, x5 >= 0 +*/ +static char *test_19_dton() +{ + double Ax[] = {2, 1, 1, 1, 3, -2, 1, 3, 1, 1, -1, 1}; + int Ai[] = {0, 1, 1, 2, 3, 1, 2, 3, 4, 0, 1, 3}; + int Ap[] = {0, 2, 5, 9, 12}; + int nnz = 12; + int n_rows = 4; + int n_cols = 5; + + double lhs[] = {2, -4, 4, 1}; + double rhs[] = {2, -4, 4, 1}; + double lbs[] = {0, 0, 0, 0, 0}; + double ubs[] = {5, INF, INF, INF, INF}; + double c[] = {2, -1, -1, 3, 2}; + + Settings *stgs = default_settings(); + set_settings_false(stgs); + stgs->dton_eq = true; + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + State *data = constraints->state; + remove_extra_space(A, data->row_sizes, data->col_sizes, true, + data->work->mappings->cols); + remove_dton_eq_rows(prob, 0); + problem_clean(prob, true); + + PS_FREE(stgs); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + return 0; +} + +static const char *all_tests_dton() +{ + mu_run_test(test_00_dton, counter_dton); // implemented + mu_run_test(test_01_dton, counter_dton); // implemented + mu_run_test(test_02_dton, counter_dton); // implemented + mu_run_test(test_03_dton, counter_dton); // implemented + mu_run_test(test_04_dton, counter_dton); // implemented + mu_run_test(test_05_dton, counter_dton); // implemented + mu_run_test(test_06_dton, counter_dton); // implemented + mu_run_test(test_1_dton, counter_dton); // implemented + mu_run_test(test_2_dton, counter_dton); // implemented + mu_run_test(test_3_dton, counter_dton); // implemented + mu_run_test(test_004_dton, counter_dton); // implemented + mu_run_test(test_4_dton, counter_dton); // implemented + // mu_run_test(test_5_dton, counter_dton); + mu_run_test(test_6_dton, counter_dton); // implemented + mu_run_test(test_7_dton, counter_dton); // implemented + mu_run_test(test_8_dton, counter_dton); // implemented + mu_run_test(test_9_dton, counter_dton); // implemented + mu_run_test(test_10_dton, counter_dton); // implemented + mu_run_test(test_11_dton, counter_dton); // implemented + // mu_run_test(test_12_dton, counter_dton); + mu_run_test(test_13_dton, counter_dton); // implemented + mu_run_test(test_14_dton, counter_dton); // implemented + mu_run_test(test_15_dton, counter_dton); // implemented + mu_run_test(test_16_dton, counter_dton); // implemented + mu_run_test(test_17_dton, counter_dton); // implemented + mu_run_test(test_18_dton, counter_dton); // implemented + // mu_run_test(test_19_dton, counter_dton); // implemented but we don't + // run it + return 0; +} + +int test_dton() +{ + const char *result = all_tests_dton(); + if (result != 0) + { + printf("%s\n", result); + printf("dton: TEST FAILED!\n"); + } + else + { + printf("dton: ALL TESTS PASSED\n"); + } + printf("dton: Tests run: %d\n", counter_dton); + return result == 0; +} + +#endif // TEST_DTON_H diff --git a/tests/test_iVec.h b/tests/test_iVec.h new file mode 100644 index 00000000..f10aa71e --- /dev/null +++ b/tests/test_iVec.h @@ -0,0 +1,90 @@ +#ifndef TEST_IVEC_H +#define TEST_IVEC_H + +#include "iVec.h" +#include "minunit.h" +#include + +static int counter_iVec = 0; + +#define CMP_IVEC(vec, arr) \ + do \ + { \ + for (size_t i = 0; i < (vec)->len; ++i) \ + { \ + mu_assert("error, vec->data[i] != arr[i]", (vec)->data[i] == (arr)[i]); \ + } \ + } while (0) + +// test initialization, append, free +static char *test_1_iVec() +{ + iVec *vec = iVec_new(3); + iVec_append(vec, 1); + iVec_append(vec, -1); + iVec_append(vec, 2); + mu_assert("error, vec->len != 3", vec->len == 3); + mu_assert("error, vec->capacity != 3", vec->capacity == 3); + + int correct_data1[] = {1, -1, 2}; + CMP_IVEC(vec, correct_data1); + + iVec_append(vec, 5); + mu_assert("error, vec->len != 4", vec->len == 4); + mu_assert("error, vec->capacity != 6", vec->capacity == 6); + int correct_data2[] = {1, -1, 2, 5}; + CMP_IVEC(vec, correct_data2); + + iVec_free(vec); + return 0; +} + +// test initialization several vectors, append, free +static char *test_2_iVec() +{ + iVec *vec1 = iVec_new(3); + iVec *vec2 = iVec_new(4); + iVec_append(vec1, 1); + iVec_append(vec1, -1); + iVec_append(vec1, 2); + iVec_append(vec2, 5); + iVec_append(vec2, 4); + iVec_append(vec2, 6); + iVec_append(vec2, 7); + + int correct_data1[] = {1, -1, 2}; + CMP_IVEC(vec1, correct_data1); + + int correct_data2[] = {5, 4, 6, 7}; + CMP_IVEC(vec2, correct_data2); + + iVec_free(vec1); + iVec_free(vec2); + + return 0; +} + +static const char *all_tests_iVec() +{ + mu_run_test(test_1_iVec, counter_iVec); + mu_run_test(test_2_iVec, counter_iVec); + return 0; +} + +int test_iVec() +{ + const char *result = all_tests_iVec(); + if (result != 0) + { + printf("%s\n", result); + printf("iVec: TEST FAILED!\n"); + } + else + { + printf("iVec: ALL TESTS PASSED\n"); + } + printf("iVec: Tests run: %d\n", counter_iVec); + return result == 0; +} + +#endif // TEST_IVEC_H \ No newline at end of file diff --git a/tests/test_macros.h b/tests/test_macros.h new file mode 100644 index 00000000..00c1defa --- /dev/null +++ b/tests/test_macros.h @@ -0,0 +1,154 @@ +#ifndef TEST_MACROS_H +#define TEST_MACROS_H + +#include "Bounds.h" +#include "Matrix.h" +#include "Numerics.h" + +#define INIT_BOUNDS(bounds, low_b, upp_b, len) \ + do \ + { \ + for (size_t i = 0; i < (len); i++) \ + { \ + (bounds)[i].lb = (low_b); \ + (bounds)[i].ub = (upp_b); \ + } \ + } while (0) + +#define CHECK_ROW_STARTS(A, row_starts_correct) \ + do \ + { \ + int row_starts[A->m + 1]; \ + for (int i = 0; i < A->m + 1; ++i) \ + { \ + row_starts[i] = A->p[i].start; \ + } \ + mu_assert("error, row_starts not equal", \ + ARRAYS_EQUAL(row_starts, row_starts_correct, A->m + 1)); \ + } while (0) + +#define CHECK_ROW_ENDS(A, row_ends_correct) \ + do \ + { \ + int row_ends[A->m + 1]; \ + for (int i = 0; i < A->m + 1; ++i) \ + { \ + row_ends[i] = A->p[i].end; \ + } \ + mu_assert("error, row_starts not equal", \ + ARRAYS_EQUAL(row_ends, row_ends_correct, A->m + 1)); \ + } while (0) + +#define CHECK_LOCKS(locks, correct_up, correct_down, size) \ + do \ + { \ + for (int i = 0; i < size; ++i) \ + { \ + mu_assert("Error up lock", correct_up[i] == locks[i].up); \ + mu_assert("Error down lock", correct_down[i] == locks[i].down); \ + } \ + } while (0) + +#define PRINT_ROW_STARTS(A) \ + do \ + { \ + for (size_t i = 0; i < A->m + 1; ++i) \ + { \ + printf("%d ", A->p[i].start); \ + } \ + printf("\n"); \ + } while (0) + +#define PRINT_ROW_ENDS(A) \ + do \ + { \ + for (size_t i = 0; i < A->m + 1; ++i) \ + { \ + printf("%d ", A->p[i].end); \ + } \ + printf("\n"); \ + } while (0) + +bool CHECK_ROW_SIZES(const Matrix *A, const int *row_sizes) +{ + for (int i = 0; i < A->m; i++) + { + int row_size = A->p[i].end - A->p[i].start; + if (row_size != row_sizes[i]) + { + return false; + } + } + + return true; +} + +bool CHECK_COL_SIZES(const Matrix *AT, const int *col_sizes) +{ + for (int i = 0; i < AT->m; i++) + { + int col_size = AT->p[i].end - AT->p[i].start; + if (col_size != col_sizes[i]) + { + return false; + } + } + return true; +} + +bool CHECK_BOUNDS(const Bound *bounds, const double *lbs, const double *ubs, + int size) +{ + for (int i = 0; i < size; i++) + { + if (bounds[i].lb != lbs[i] || bounds[i].ub != ubs[i]) + { + return false; + } + } + return true; +} + +bool contains_int(const int *arr, int len, int val) +{ + for (int i = 0; i < len; ++i) + { + if (arr[i] == val) + { + return true; + } + } + return false; +} + +bool is_solution_correct(const double *x, const double *correct_x, const double *y, + const double *correct_y, const double *z, + const double *correct_z, int n_rows, int n_cols, double tol) +{ + for (int i = 0; i < n_cols; ++i) + { + if (ABS(x[i] - correct_x[i]) > tol) + { + printf("i = %d, x = %f, correct_x = %f\n", i, x[i], correct_x[i]); + return false; + } + + if (ABS(z[i] - correct_z[i]) > tol) + { + printf("i = %d, z = %f, correct_z = %f\n", i, z[i], correct_z[i]); + return false; + } + } + + for (int i = 0; i < n_rows; ++i) + { + if (ABS(y[i] - correct_y[i]) > tol) + { + printf("i = %d, y = %f, correct_y = %f\n", i, y[i], correct_y[i]); + return false; + } + } + return true; +} + +#endif // TEST_MACROS_H \ No newline at end of file diff --git a/tests/test_postsolve.h b/tests/test_postsolve.h new file mode 100644 index 00000000..12046ebe --- /dev/null +++ b/tests/test_postsolve.h @@ -0,0 +1,1002 @@ +#ifndef TEST_POSTSOLVE_H +#define TEST_POSTSOLVE_H + +#include "API.h" +#include "CoreTransformations.h" +#include "Numerics.h" + +#include "SimpleReductions.h" +#include "minunit.h" +#include + +#define POSTSOLVE_TOL_FEAS 1e-6 +static int counter_postsolve = 0; + +/* Singleton equality row. + min. [-1 1 -1 1 -1 2 -0.2 0.7] + s.t. [ 0.1 0.2 -0.3 -0.1 0.4 -0.4 0.1 0.3] = 0.5 + [ 0 0 1 0 0 0 0 0] = 3 + -2 <= [ 0.3 0.5 -0.6 -0.3 0.2 -0.2 0.4 0.1] <= 2 + -10 <= x <= 10 +*/ +static char *test_0_postsolve() +{ + double Ax[] = {0.1, 0.2, -0.3, -0.1, 0.4, -0.4, 0.1, 0.3, 1, + 0.3, 0.5, -0.6, -0.3, 0.2, -0.2, 0.4, 0.1}; + int Ai[] = {0, 1, 2, 3, 4, 5, 6, 7, 2, 0, 1, 2, 3, 4, 5, 6, 7}; + int Ap[] = {0, 8, 9, 17}; + int nnz = 17; + int n_rows = 3; + int n_cols = 8; + + double lhs[] = {0.5, 3, -2}; + double rhs[] = {0.5, 3, 2}; + double lbs[] = {-10, -10, -10, -10, -10, -10, -10, -10}; + double ubs[] = {10, 10, 10, 10, 10, 10, 10, 10}; + double c[] = {-1, 1, -1, 1, -1, 2, -0.2, 0.7}; + + Settings *stgs = default_settings(); + set_settings_true(stgs); + stgs->parallel_cols = false; + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + + presolver_run(presolver); + + Mapping *maps = prob->constraints->state->work->mappings; + int *rows_map = maps->rows; + int *cols_map = maps->cols; + + // construct optimal primal solution to reduced problem (computed offline) + double x[] = {10., -10., -10., 2.71428571, -10., -6.85714286, -10.}; + double y[] = {-2.57142857, 0.14285714}; + double z[] = {-0.78571429, 1.44285714, 0.78571429, 0., 1., 0., 1.45714286}; + double obj = 0.0; + postsolve(presolver, x, y, z, obj); + + // check that the primal solution to the original problem is correct + double correct_x[] = {10., -10., 3., -10., 2.71428571, -10., -6.85714286, -10.}; + double correct_y[] = {-2.57142857, -1.68571429, 0.14285714}; + double correct_z[] = {-0.78571429, 1.44285714, 0., 0.78571429, + 0., 1., 0., 1.45714286}; + + mu_assert("postsolve error", + is_solution_correct(presolver->sol->x, correct_x, presolver->sol->y, + correct_y, presolver->sol->z, correct_z, n_rows, + n_cols, POSTSOLVE_TOL_FEAS)); + PS_FREE(stgs); + free_presolver(presolver); + return 0; +} + +/* Singleton equality row and doubleton row. + min. [-1 1 -1 1 -1 2 -0.2 0.7] + s.t. [ 0.1 0.2 -0.3 -0.1 0.4 -0.4 0.1 0.3] = 0.5 + [ 0 0 1 0 0 0 0 0] = 3 + [ 0 0 0 1 1 0 0 0] = 4 + -2 <= [ 0.3 0.5 -0.6 -0.3 0.2 -0.2 0.4 0.1] <= 2 + -10 <= x <= 10 +*/ +static char *test_1_postsolve() +{ + double Ax[] = {0.1, 0.2, -0.3, -0.1, 0.4, -0.4, 0.1, 0.3, 1, 1, + 1, 0.3, 0.5, -0.6, -0.3, 0.2, -0.2, 0.4, 0.1}; + int Ai[] = {0, 1, 2, 3, 4, 5, 6, 7, 2, 3, 4, 0, 1, 2, 3, 4, 5, 6, 7}; + int Ap[] = {0, 8, 9, 11, 19}; + int nnz = 19; + int n_rows = 4; + int n_cols = 8; + + double lhs[] = {0.5, 3, 4, -2}; + double rhs[] = {0.5, 3, 4, 2}; + double lbs[] = {-10, -10, -10, -10, -10, -10, -10, -10}; + double ubs[] = {10, 10, 10, 10, 10, 10, 10, 10}; + double c[] = {-1, 1, -1, 1, -1, 2, -0.2, 0.7}; + + Settings *stgs = default_settings(); + set_settings_true(stgs); + stgs->parallel_cols = false; + // stgs->ston_cols = false; + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + + presolver_run(presolver); + + Mapping *maps = prob->constraints->state->work->mappings; + int *rows_map = maps->rows; + int *cols_map = maps->cols; + + // construct optimal primal solution to reduced problem (computed offline) + double x[] = {10, -10, 0.5333333, -10, 0.6666666, -10}; + double y[] = {-4.66666667, 0.66666667}; + double z[] = {-0.73333333, 1.6, 0., 0.26666667, 0., 2.03333333}; + double obj = 0.0; + postsolve(presolver, x, y, z, obj); + + // check that the primal solution to the original problem is correct + double correct_x[] = {10, -10, 3, 0.5333333, 3.4666666, -10, 0.6666666, -10}; + double correct_y[] = {-4.66666667, -2., 0.73333333, 0.66666667}; + double correct_z[] = {-0.73333333, 1.6, 0., 0., 0., 0.26666667, 0., 2.03333333}; + + mu_assert("postsolve error", + is_solution_correct(presolver->sol->x, correct_x, presolver->sol->y, + correct_y, presolver->sol->z, correct_z, n_rows, + n_cols, POSTSOLVE_TOL_FEAS)); + PS_FREE(stgs); + free_presolver(presolver); + return 0; +} + +/* Another singleton equality row test. */ +static char *test_singleton_eq() +{ + double Ax[] = {1.0, -1, 2, 3, 1, 2, -3, 5, 7, 9, 5, 2, -1, 3, 4, 6}; + int Ai[] = {0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 2, 0, 1, 2, 3, 4}; + int Ap[] = {0, 5, 10, 11, 16}; + int nnz = 16; + int n_rows = 4; + int n_cols = 5; + + double lhs[] = {2.0, 3, 1, 4}; + double rhs[] = {2.0, 3, 1, 4}; + double lbs[] = {-5.0, -5, -5, -5, -5}; + double ubs[] = {5.0, 5, 5, 5, 5}; + double c[] = {1.0, 2, -1, 3, -2}; + + Settings *stgs = default_settings(); + set_settings_true(stgs); + stgs->parallel_cols = false; + stgs->primal_propagation = false; + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + + presolver_run(presolver); + + Mapping *maps = prob->constraints->state->work->mappings; + int *rows_map = maps->rows; + int *cols_map = maps->cols; + + // construct optimal primal solution to reduced problem (computed offline) + double x[] = {5., -2.525, -1.8875, -0.2625}; + double y[] = {3.0625, -2.8125, 3.375}; + double z[] = {-3.1875, 0., 0., 0., 0.}; + double obj = 0.0; + postsolve(presolver, x, y, z, obj); + + // check that the primal solution to the original problem is correct + double correct_x[] = {5., -2.525, 0.2, -1.8875, -0.2625}; + double correct_y[] = {3.0625, -2.8125, -0.6375, 3.375}; + double correct_z[] = {-3.1875, 0., 0., 0., 0.}; + + mu_assert("postsolve error", + is_solution_correct(presolver->sol->x, correct_x, presolver->sol->y, + correct_y, presolver->sol->z, correct_z, n_rows, + n_cols, POSTSOLVE_TOL_FEAS)); + PS_FREE(stgs); + free_presolver(presolver); + return 0; +} + +/* Singleton equality row and doubleton row with implied bound active. + min. [-1 1 -1 1 -1 2 -0.2 0.7] + s.t. [ 0.1 0.2 -0.3 -0.1 0.4 -0.4 0.1 0.3] = 0.5 + [ 0 0 1 0 0 0 0 0] = 3 + [ 0 0 0 1 1 0 0 0] = 4 + -2 <= [ 0.3 0.5 -0.6 -0.3 0.2 -0.2 0.4 0.1] <= 2 + -10 <= x <= 10 +*/ +static char *test_1_postsolve_implied_bound_active() +{ + double Ax[] = {0.1, 0.2, -0.3, -0.1, 0.4, -0.4, 0.1, 0.3, 1, 1, + 1, 0.3, 0.5, -0.6, -0.3, 0.2, -0.2, 0.4, 0.1}; + int Ai[] = {0, 1, 2, 3, 4, 5, 6, 7, 2, 3, 4, 0, 1, 2, 3, 4, 5, 6, 7}; + int Ap[] = {0, 8, 9, 11, 19}; + int nnz = 19; + int n_rows = 4; + int n_cols = 8; + + double lhs[] = {0.5, 3, 4, -2}; + double rhs[] = {0.5, 3, 4, 2}; + double lbs[] = {-10, -10, -10, -10, -10, -10, -10, -10}; + double ubs[] = {10, 10, 10, 10, 4.0 - 8.0 / 5.0, 10, 10, 10}; + double c[] = {-1, 1, -1, 1, -1, 2, -0.2, 0.7}; + + Settings *stgs = default_settings(); + set_settings_true(stgs); + stgs->parallel_cols = false; + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + + presolver_run(presolver); + + Mapping *maps = prob->constraints->state->work->mappings; + int *rows_map = maps->rows; + int *cols_map = maps->cols; + + // construct optimal primal solution to reduced problem (computed offline) + double x[] = {10.0, -10.0, 1.6, -10.0, 6.0, -10.0}; + double y[] = {-2.0, 0.0}; + double z[] = {-0.8, 1.4, 1.0, 1.2, 0.0, 1.3}; + double obj = 0.0; + postsolve(presolver, x, y, z, obj); + + // check that the primal solution to the original problem is correct + double correct_x[] = {10., -10., 3., 1.6, 2.4, -10., 6., -10.}; + double correct_y[] = {-2., -1.6, 0.8, 0.0}; + double correct_z[] = {-0.8, 1.4, 0., 0., -1., 1.2, 0., 1.3}; + + mu_assert("postsolve error", + is_solution_correct(presolver->sol->x, correct_x, presolver->sol->y, + correct_y, presolver->sol->z, correct_z, n_rows, + n_cols, POSTSOLVE_TOL_FEAS)); + PS_FREE(stgs); + free_presolver(presolver); + return 0; +} + +/* Singleton inequality row */ +static char *test_singleton_ineq_row() +{ + double Ax[] = {1, 1, 1, 1}; + int Ai[] = {0, 0, 1, 2}; + int Ap[] = {0, 1, 4}; + int nnz = 4; + int n_rows = 2; + int n_cols = 3; + + double lhs[] = {4.0, 5}; + double rhs[] = {INF, INF}; + double lbs[] = {-1.0, 0, 0}; + double ubs[] = {INF, INF, INF}; + double c[] = {3.0, 1, 2}; + + Settings *stgs = default_settings(); + set_settings_true(stgs); + stgs->parallel_cols = false; + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + + presolver_run(presolver); + + Mapping *maps = prob->constraints->state->work->mappings; + int *rows_map = maps->rows; + int *cols_map = maps->cols; + + // construct optimal primal solution to reduced problem (computed offline) + double x[] = {4.0, 1.0, 0.0}; + double y[] = {1.0}; + double z[] = {2.0, 0.0, 1.0}; + double obj = 0.0; + postsolve(presolver, x, y, z, obj); + + // check that the primal solution to the original problem is correct + double correct_x[] = {4.0, 1.0, 0.0}; + double correct_y[] = {2.0, 1.0}; + double correct_z[] = {0.0, 0.0, 1.0}; + + mu_assert("postsolve error", + is_solution_correct(presolver->sol->x, correct_x, presolver->sol->y, + correct_y, presolver->sol->z, correct_z, n_rows, + n_cols, POSTSOLVE_TOL_FEAS)); + PS_FREE(stgs); + free_presolver(presolver); + return 0; +} + +/* Two free column singletons in different equality constraints + + min. [1 1 -1 3 2]x + s.t.[-1, 0, 2, 3, 4 ] [2] + [ 0, -1, 5, 6, 7 ] x = [5] + [ 0, 0, 8, 9, 10 ] [6] + [ 0, 0, 11, 12, 13 ] [8] + x3, x4, x5 >= 0 + x1, x2 free +*/ +static char *test_2_postsolve() +{ + double Ax[] = {-1, 2, 3, 4, -1, 5, 6, 7, 8, 9, 10, 11, 12, 13}; + int Ai[] = {0, 2, 3, 4, 1, 2, 3, 4, 2, 3, 4, 2, 3, 4}; + int Ap[] = {0, 4, 8, 11, 14}; + int nnz = 14; + int n_rows = 4; + int n_cols = 5; + + double lhs[] = {2, 5, 6, 8}; + double rhs[] = {2, 5, 6, 8}; + double lbs[] = {-INF, -INF, 0, 0, 0}; + double ubs[] = {INF, INF, INF, INF, INF}; + double c[] = {1, 1, -1, 3, 2}; + + Settings *stgs = default_settings(); + set_settings_true(stgs); + stgs->primal_propagation = false; + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + + presolver_run(presolver); + + // construct optimal primal solution to reduced problem (computed offline) + double x[] = {0.33333333, 0.0, 0.33333333}; + double y[] = {10.83333333, -7.33333333}; + double z[] = {0.0, 2.5, 0.0}; + double obj = 0.0; + postsolve(presolver, x, y, z, obj); + + // check that the primal solution to the original problem is correct + double correct_x[] = {0.0, -1.0, 0.33333333, 0.0, 0.33333333}; + double correct_y[] = {-1.0, -1.0, 10.83333333, -7.33333333}; + double correct_z[] = {0.0, 0.0, 0.0, 2.5, 0.0}; + + mu_assert("postsolve error", + is_solution_correct(presolver->sol->x, correct_x, presolver->sol->y, + correct_y, presolver->sol->z, correct_z, n_rows, + n_cols, POSTSOLVE_TOL_FEAS)); + PS_FREE(stgs); + free_presolver(presolver); + return 0; +} + +/* Example 10 in test_ston, but modified to be feasible */ +static char *test_implied_free_col_ston_in_inequality_postsolve() +{ + double Ax[] = {3.0, 5, 2, -1, 1, -3, -2, -2, 1}; + int Ai[] = {0, 2, 3, 0, 1, 3, 0, 2, 3}; + int Ap[] = {0, 3, 6, 9}; + int nnz = 9; + int n_rows = 3; + int n_cols = 4; + + double lhs[] = {-INF, 8, -INF}; + double rhs[] = {5.0, 10, 6}; + double lbs[] = {0.0, 8, 0.0, 0.0}; + double ubs[] = {INF, INF, INF, INF}; + double c[] = {1.0, 2, -4, -3}; + + Settings *stgs = default_settings(); + set_settings_true(stgs); + stgs->primal_propagation = false; + stgs->parallel_cols = false; + stgs->dual_fix = false; + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + + presolver_run(presolver); + + // construct optimal primal solution to reduced problem (computed offline) + double x[] = {0., 1., 0.}; + double y[] = {-0.8, 0.}; + double z[] = {5.4, 0., 4.6}; + double obj = 0.0; + + postsolve(presolver, x, y, z, obj); + + // check that the primal solution to the original problem is correct + double correct_x[] = {0., 8., 1., 0.}; + double correct_y[] = {-0.8, 2., 0.}; + double correct_z[] = {5.4, 0., 0., 4.6}; + + mu_assert("postsolve error", + is_solution_correct(presolver->sol->x, correct_x, presolver->sol->y, + correct_y, presolver->sol->z, correct_z, n_rows, + n_cols, POSTSOLVE_TOL_FEAS)); + PS_FREE(stgs); + free_presolver(presolver); + return 0; +} + +/* Example 12 in test_ston, simple dual fix to lower bound */ +static char *test_col_ston_dual_fix() +{ + double Ax[] = {3.0, -1, -6, 3, -2, 5, -1, 4, 3, 4}; + int Ai[] = {0, 2, 3, 0, 1, 2, 3, 0, 2, 3}; + int Ap[] = {0, 3, 7, 10}; + int nnz = 10; + int n_rows = 3; + int n_cols = 4; + + double lhs[] = {-INF, 0, -INF}; + double rhs[] = {0.0, INF, 5}; + double lbs[] = {0.0, 1, 0, 0}; + double ubs[] = {INF, INF, INF, INF}; + double c[] = {4.0, 1, -2, 7}; + + Settings *stgs = default_settings(); + set_settings_true(stgs); + stgs->primal_propagation = false; + stgs->parallel_cols = false; + stgs->dual_fix = false; + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + + presolver_run(presolver); + + // construct optimal primal solution to reduced problem (computed offline) + double x[] = {0., 1.66666667, 0.}; + double y[] = {0., 0., -0.66666667}; + double z[] = {6.66666667, 0., 9.66666667}; + double obj = 0.0; + + postsolve(presolver, x, y, z, obj); + + // check that the primal solution to the original problem is correct + double correct_x[] = {0., 1., 1.66666667, 0.}; + double correct_y[] = {0., 0., -0.66666667}; + double correct_z[] = {6.66666667, 1., 0., 9.66666667}; + + mu_assert("postsolve error", + is_solution_correct(presolver->sol->x, correct_x, presolver->sol->y, + correct_y, presolver->sol->z, correct_z, n_rows, + n_cols, POSTSOLVE_TOL_FEAS)); + PS_FREE(stgs); + free_presolver(presolver); + return 0; +} + +/* Chain of column singletons, so order in postsolve matters */ +static char *test_3_postsolve() +{ + double Ax[] = {1.0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 1, 4, 4, 4, 4, 4, 4, 4, 4, 1, 1, + 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1}; + int Ai[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, + 6, 7, 8, 9, 10, 2, 3, 4, 5, 6, 7, 8, 9, 10, 3, 4, + 5, 6, 7, 8, 9, 10, 3, 4, 5, 6, 7, 8, 9, 10}; + int Ap[] = {0, 11, 21, 30, 38, 46}; + int nnz = 46; + int n_rows = 5; + int n_cols = 11; + + double lhs[] = {5.0, 4, 3, 1, 2}; + double rhs[] = {5.0, 4, 3, 1, 2}; + double lbs[] = {-INF, -INF, -INF, 0, 0, 0, 0, 0, 0, 0, -INF}; + double ubs[] = {INF, INF, INF, 1, 2, 3, 4, 3, 6, 7, 8}; + double c[] = {1.0, 1, 1.03, -5, 2.3, 3.1, -2, 1.05, -3, 4, 1}; + + Settings *stgs = default_settings(); + set_settings_true(stgs); + stgs->primal_propagation = false; + stgs->parallel_cols = false; + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + + presolver_run(presolver); + + // construct optimal primal solution to reduced problem (computed offline) + double x[] = {1., 0., 0., 4., 0., 6., 0., -10.}; + double y[] = {-0.12, -6.}; + double z[] = {0., 1.3, 2.1, -3., 0.05, -4., 3., 0.}; + double obj = 0.0; + + postsolve(presolver, x, y, z, obj); + + // check that the primal solution to the original problem is correct + double correct_x[] = {-3., 4., -1., 1., 0., 0., 4., 0., 6., 0., -10.}; + double correct_y[] = {1., -1., 2.03, -0.12, -6.}; + double correct_z[] = {0., 0., 0., 0., 1.3, 2.1, -3., 0.05, -4., 3., 0.}; + + mu_assert("postsolve error", + is_solution_correct(presolver->sol->x, correct_x, presolver->sol->y, + correct_y, presolver->sol->z, correct_z, n_rows, + n_cols, POSTSOLVE_TOL_FEAS)); + PS_FREE(stgs); + free_presolver(presolver); + return 0; +} + +/* One variable fixed to infinity + + min. [1 -2 1 0 3]x + + s.t [2 3 -4 0 3] = 7 + [1 -1 1 1 2] <= 7 + [2 1 3 -1 4] >= 3 + -10 <= x1, x2, x3, x5 <= 10 + -INF <= x4 <= 10 +*/ +static char *test_4_postsolve() +{ + double Ax[] = {2, 3, -4, 3, 1, -1, 1, 1, 2, 2, 1, 3, -1, 4}; + int Ai[] = {0, 1, 2, 4, 0, 1, 2, 3, 4, 0, 1, 2, 3, 4}; + int Ap[] = {0, 4, 9, 14}; + int nnz = 14; + int n_rows = 3; + int n_cols = 5; + + double lhs[] = {7, -INF, 3}; + double rhs[] = {7, 7, INF}; + double lbs[] = {-10, -10, -10, -INF, -10}; + double ubs[] = {10, 10, 10, 10, 10}; + double c[] = {1, -2, 1, 0, 3}; + + Settings *stgs = default_settings(); + set_settings_true(stgs); + stgs->primal_propagation = false; + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + + presolver_run(presolver); + + // construct optimal primal solution to reduced problem (computed offline) + double x[] = {-10, 10, -6.75, -10}; + double y[] = {-0.25}; + double z[] = {1.5, -1.25, 0., 3.75}; + double obj = 0.0; + + postsolve(presolver, x, y, z, obj); + + // check that the primal solution to the original problem is correct + double correct_x[] = {-10.0, 10.0, -6.75, -73.25, -10.0}; + double correct_y[] = {-0.25, 0., 0.}; + double correct_z[] = {1.5, -1.25, 0., 0., 3.75}; + + mu_assert("postsolve error", + is_solution_correct(presolver->sol->x, correct_x, presolver->sol->y, + correct_y, presolver->sol->z, correct_z, n_rows, + n_cols, POSTSOLVE_TOL_FEAS)); + PS_FREE(stgs); + free_presolver(presolver); + return 0; +} + +/* Parallel columns and several other reductions + + min. [2 3 -4 -3 -6 6 3 1]x + s.t. [1 1 -2 1 2 3 -1 5 + 0 0 0 0 0 0 0 1 + -2 2 4 -2 -4 -6 2 2 + 0 0 0 0 0 0 0 3 + 3 3 -6 3 6 9 -3 4 + 4 0 -8 4 8 12 -4 5 + -2 4 4 -2 -4 -6 2 6 + 0 7 0 0 0 0 0 7 + 0 5 0 0 0 0 0 8 + 0 6 0 0 0 0 0 9]x <= [10 1 20 1 30 40 20 70 50 60] + -i <= x <= i +*/ +static char *test_6_postsolve() +{ + double Ax[] = {1, 1, -2, 1, 2, 3, -1, 5, 1, -2, 2, 4, -2, -4, -6, 2, + 2, 3, 3, 3, -6, 3, 6, 9, -3, 4, 4, -8, 4, 8, 12, -4, + 5, -2, 4, 4, -2, -4, -6, 2, 6, 7, 7, 5, 8, 6, 9}; + int Ai[] = {0, 1, 2, 3, 4, 5, 6, 7, 7, 0, 1, 2, 3, 4, 5, 6, + 7, 7, 0, 1, 2, 3, 4, 5, 6, 7, 0, 2, 3, 4, 5, 6, + 7, 0, 1, 2, 3, 4, 5, 6, 7, 1, 7, 1, 7, 1, 7}; + int Ap[] = {0, 8, 9, 17, 18, 26, 33, 41, 43, 45, 47}; + int nnz = 47; + int n_rows = 10; + int n_cols = 8; + + double lhs[] = {-INF, -INF, -INF, -INF, -INF, -INF, -INF, -INF, -INF, -INF}; + double rhs[] = {10, 1, 20, 1, 30, 40, 20, 70, 50, 60}; + double lbs[] = {-1, -2, -3, -4, -5, -6, -7, -8}; + double ubs[] = {1, 2, 3, 4, 5, 6, 7, 8}; + double c[] = {2, 3, -4, -3, -6, 6, 3, 1}; + + Settings *stgs = default_settings(); + set_settings_true(stgs); + // with dual reductions and domain propagation enabled we reduce the problem + // much more. Would be interesting to add this test again with those + // reductions enabled + stgs->dual_fix = false; + stgs->primal_propagation = false; + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + + presolver_run(presolver); + + // construct optimal primal solution to reduced problem (computed offline). + // on linux vs mac the parallel column kept is different, leading to + // different postsolved solutions +#ifdef __linux__ + double x[] = {-2., 12.5, 21., -8.}; + double y[] = {0., 0., 0., 0., 0., 0., 0., 0.}; + double z[] = {3., -4., -3., 1.}; + double obj = 0.0; +#else + double x[] = {-2., -8.33333333, -21., -8.}; + double y[] = {0., 0., 0., 0., 0., 0., 0., 0.}; + double z[] = {3., 6., 3., 1.}; + double obj = 0.0; +#endif + + postsolve(presolver, x, y, z, obj); + + // check that the primal solution to the original problem is correct + double correct_x[] = {-1.0, -2.0, 3.0, 4.0, 5.0, -6.0, -7.0, -8.0}; + double correct_y[] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}; + double correct_z[] = {2., 3., -4., -3., -6., 6., 3., 1.}; + + mu_assert("postsolve error", + is_solution_correct(presolver->sol->x, correct_x, presolver->sol->y, + correct_y, presolver->sol->z, correct_z, n_rows, + n_cols, POSTSOLVE_TOL_FEAS)); + + PS_FREE(stgs); + free_presolver(presolver); + return 0; +} + +/* same as test 6 but with domain propagation active */ +static char *test_7_postsolve() +{ + double Ax[] = {1, 1, -2, 1, 2, 3, -1, 5, 1, -2, 2, 4, -2, -4, -6, 2, + 2, 3, 3, 3, -6, 3, 6, 9, -3, 4, 4, -8, 4, 8, 12, -4, + 5, -2, 4, 4, -2, -4, -6, 2, 6, 7, 7, 5, 8, 6, 9}; + int Ai[] = {0, 1, 2, 3, 4, 5, 6, 7, 7, 0, 1, 2, 3, 4, 5, 6, + 7, 7, 0, 1, 2, 3, 4, 5, 6, 7, 0, 2, 3, 4, 5, 6, + 7, 0, 1, 2, 3, 4, 5, 6, 7, 1, 7, 1, 7, 1, 7}; + int Ap[] = {0, 8, 9, 17, 18, 26, 33, 41, 43, 45, 47}; + int nnz = 47; + int n_rows = 10; + int n_cols = 8; + + double lhs[] = {-INF, -INF, -INF, -INF, -INF, -INF, -INF, -INF, -INF, -INF}; + double rhs[] = {10, 1, 20, 1, 30, 40, 20, 70, 50, 60}; + double lbs[] = {-1, -2, -3, -4, -5, -6, -7, -8}; + double ubs[] = {1, 2, 3, 4, 5, 6, 7, 8}; + double c[] = {2, 3, -4, -3, -6, 6, 3, 1}; + + Settings *stgs = default_settings(); + set_settings_true(stgs); + // with dual reduction enabled we reduce the problem much more. + // Would be interesting to add this test again with that reductions enabled + stgs->dual_fix = false; + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + + presolver_run(presolver); + + // construct optimal primal solution to reduced problem (computed offline) +#ifdef __linux__ + double x[] = {-2., 12.5, 21., -8.}; + double y[] = {0., 0., 0., 0., 0.}; + double z[] = {3., -4., -3., 1.}; + double obj = 0.0; +#else + double x[] = {-25., -2., 21., -8.}; + double y[] = {0., 0., 0., 0., 0.}; + double z[] = {2., 3., -3., 1.}; + double obj = 0.0; + +#endif + + postsolve(presolver, x, y, z, obj); + + // check that the primal solution to the original problem is correct + double correct_x[] = {-1.0, -2.0, 3.0, 4.0, 5.0, -6.0, -7.0, -8.0}; + double correct_y[] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}; + double correct_z[] = {2., 3., -4., -3., -6., 6., 3., 1.}; + + // on mac another solution seems to be postsolved, because a different parallel + // column is kept. So for non-linux we just check the objective value (not + // feasibility) + mu_assert("postsolve error", + is_solution_correct(presolver->sol->x, correct_x, presolver->sol->y, + correct_y, presolver->sol->z, correct_z, n_rows, + n_cols, POSTSOLVE_TOL_FEAS)); + + PS_FREE(stgs); + free_presolver(presolver); + return 0; +} + +/* Doubleton row. + min. [2 -1 -1 3 2] + s.t. [ 2 1 -1 3 2] = 0.5 + [ 1 -2 0 0 0] = 3 + [ 1, 4, 0, 3, 1] = 4 + 0 <= x2, x3, x4, x5 +*/ +static char *test_8_postsolve() +{ + double Ax[] = {2, 1, -1, 3, 2, 1, -2, 1, 4, 3, 1}; + int Ai[] = {0, 1, 2, 3, 4, 0, 1, 0, 1, 3, 4}; + int Ap[] = {0, 5, 7, 11}; + int nnz = 11; + int n_rows = 3; + int n_cols = 5; + + double lhs[] = {2, -1, 4}; + double rhs[] = {2, -1, 4}; + double lbs[] = {-INF, 0, 0, 0, 0}; + double ubs[] = {INF, INF, INF, INF, INF}; + double c[] = {2, -1, -1, 3, 2}; + + Settings *stgs = default_settings(); + set_settings_true(stgs); + stgs->parallel_cols = false; + stgs->ston_cols = false; + stgs->primal_propagation = false; + stgs->dual_fix = false; + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + + presolver_run(presolver); + + Mapping *maps = prob->constraints->state->work->mappings; + int *rows_map = maps->rows; + int *cols_map = maps->cols; + + // construct optimal primal solution to reduced problem (computed offline) + double x[] = {0.83333333, 0.16666667, 0., 0.}; + double y[] = {1., -0.33333333}; + double z[] = {0., 0., 1., 0.33333333}; + double obj = 0.0; + postsolve(presolver, x, y, z, obj); + + // check that the primal solution to the original problem is correct + double correct_x[] = {0.66666667, 0.83333333, 0.16666667, 0., 0.}; + double correct_y[] = {1., 0.33333333, -0.33333333}; + double correct_z[] = {0., 0., 0., 1., 0.33333333}; + + mu_assert("postsolve error", + is_solution_correct(presolver->sol->x, correct_x, presolver->sol->y, + correct_y, presolver->sol->z, correct_z, n_rows, + n_cols, POSTSOLVE_TOL_FEAS)); + PS_FREE(stgs); + free_presolver(presolver); + return 0; +} + +/* Parallel rows. + min. [1 1.1 1.2] + s.t. [ 1 1 1] >= 1 + [ 2 2 2] >= 4 + [ -3 -3 -3] <= -9 + 0 <= x1, x2, x3 +*/ +static char *test_9_postsolve() +{ + double Ax[] = {1.0, 1.0, 1.0, 2.0, 2.0, 2.0, -3.0, -3.0, -3.0}; + int Ai[] = {0, 1, 2, 0, 1, 2, 0, 1, 2}; + int Ap[] = {0, 3, 6, 9}; + int nnz = 9; + int n_rows = 3; + int n_cols = 3; + + double lhs[] = {1, 4, -INF}; + double rhs[] = {INF, INF, -9}; + double lbs[] = {0, 0, 0}; + double ubs[] = {INF, INF, INF}; + double c[] = {1.0, 1.1, 1.2}; + + Settings *stgs = default_settings(); + set_settings_true(stgs); + stgs->primal_propagation = false; + stgs->parallel_cols = false; + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + + presolver_run(presolver); + + Mapping *maps = prob->constraints->state->work->mappings; + int *rows_map = maps->rows; + int *cols_map = maps->cols; + + // construct optimal primal solution to reduced problem (computed offline) + double x[] = {3., 0., 0.}; + double y[] = {0.5}; + double z[] = {0., 0.1, 0.2}; + double obj = 0.0; + postsolve(presolver, x, y, z, obj); + + // check that the primal solution to the original problem is correct + double correct_x[] = {3., 0., 0.}; + double correct_y[] = {0., 0., -0.33333333}; + double correct_z[] = {0., 0.1, 0.2}; + + mu_assert("postsolve error", + is_solution_correct(presolver->sol->x, correct_x, presolver->sol->y, + correct_y, presolver->sol->z, correct_z, n_rows, + n_cols, POSTSOLVE_TOL_FEAS)); + PS_FREE(stgs); + free_presolver(presolver); + return 0; +} + +/* implied bound active at optimal solution together with original bound*/ +static char *test_pathological_ston_one() +{ + double Ax[] = {1.0, 1, 2, 3, 4, -1, -5}; + int Ai[] = {0, 1, 2, 3, 1, 2, 3}; + int Ap[] = {0, 1, 4, 7}; + int nnz = 7; + int n_rows = 3; + int n_cols = 4; + + double lhs[] = {-INF, 3, 4}; + double rhs[] = {3.0, 3, 4}; + double lbs[] = {3.0, -5, -5, -5}; + double ubs[] = {INF, 5, 5, 5}; + double c[] = {1.0, 2, 3, -4}; + + Settings *stgs = default_settings(); + set_settings_true(stgs); + stgs->parallel_cols = false; + stgs->primal_propagation = false; + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + + presolver_run(presolver); + + Mapping *maps = prob->constraints->state->work->mappings; + int *rows_map = maps->rows; + int *cols_map = maps->cols; + + // construct optimal primal solution to reduced problem (computed offline) + double x[] = {3.64705882, -5., 3.11764706}; + double y[] = {-0.35294118, 0.58823529}; + double z[] = {0., 4.29411765, 0.}; + double obj = 0.0; + postsolve(presolver, x, y, z, obj); + + // check that the primal solution to the original problem is correct + double correct_x[] = {3., 3.64705882, -5., 3.11764706}; + double correct_y[] = {0., -0.35294118, 0.58823529}; + double correct_z[] = {1., 0., 4.29411765, 0.}; + + mu_assert("postsolve error", + is_solution_correct(presolver->sol->x, correct_x, presolver->sol->y, + correct_y, presolver->sol->z, correct_z, n_rows, + n_cols, POSTSOLVE_TOL_FEAS)); + PS_FREE(stgs); + free_presolver(presolver); + return 0; +} + +/* implied bound active at optimal solution together with original bound*/ +static char *test_pathological_ston_two() +{ + double Ax[] = {1.0, 1, 2, 3, 4, -1, -5}; + int Ai[] = {0, 1, 2, 3, 1, 2, 3}; + int Ap[] = {0, 1, 4, 7}; + int nnz = 7; + int n_rows = 3; + int n_cols = 4; + + double lhs[] = {-INF, 3, 4}; + double rhs[] = {3.0, 3, 4}; + double lbs[] = {3.0, -5, -5, -5}; + double ubs[] = {INF, 5, 5, 5}; + double c[] = {-2.0, 2, 3, -4}; + + Settings *stgs = default_settings(); + set_settings_true(stgs); + stgs->parallel_cols = false; + stgs->primal_propagation = false; + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + + presolver_run(presolver); + + Mapping *maps = prob->constraints->state->work->mappings; + int *rows_map = maps->rows; + int *cols_map = maps->cols; + + // construct optimal primal solution to reduced problem (computed offline) + double x[] = {3.64705882, -5., 3.11764706}; + double y[] = {-0.35294118, 0.58823529}; + double z[] = {0., 4.29411765, 0.}; + double obj = 0.0; + postsolve(presolver, x, y, z, obj); + + // check that the primal solution to the original problem is correct + double correct_x[] = {3., 3.64705882, -5., 3.11764706}; + double correct_y[] = {-2., -0.35294118, 0.58823529}; + double correct_z[] = {0., 0., 4.29411765, 0.}; + + mu_assert("postsolve error", + is_solution_correct(presolver->sol->x, correct_x, presolver->sol->y, + correct_y, presolver->sol->z, correct_z, n_rows, + n_cols, POSTSOLVE_TOL_FEAS)); + PS_FREE(stgs); + free_presolver(presolver); + return 0; +} + +static const char *all_tests_postsolve() +{ + mu_run_test(test_0_postsolve, counter_postsolve); + mu_run_test(test_1_postsolve, counter_postsolve); + mu_run_test(test_1_postsolve_implied_bound_active, counter_postsolve); + mu_run_test(test_singleton_ineq_row, counter_postsolve); + mu_run_test(test_2_postsolve, counter_postsolve); + mu_run_test(test_implied_free_col_ston_in_inequality_postsolve, + counter_postsolve); + mu_run_test(test_col_ston_dual_fix, counter_postsolve); + mu_run_test(test_3_postsolve, counter_postsolve); + mu_run_test(test_4_postsolve, counter_postsolve); + mu_run_test(test_6_postsolve, counter_postsolve); + mu_run_test(test_7_postsolve, counter_postsolve); + mu_run_test(test_8_postsolve, counter_postsolve); + mu_run_test(test_9_postsolve, counter_postsolve); + mu_run_test(test_singleton_eq, counter_postsolve); + mu_run_test(test_pathological_ston_one, counter_postsolve); + mu_run_test(test_pathological_ston_two, counter_postsolve); + // all tests above pass + + return 0; +} + +int test_postsolve() +{ + const char *result = all_tests_postsolve(); + if (result != 0) + { + printf("%s\n", result); + printf("postsolve: TEST FAILED!\n"); + } + else + { + printf("postsolve: ALL TESTS PASSED\n"); + } + printf("postsolve: Tests run: %d\n", counter_postsolve); + return result == 0; +} + +#endif \ No newline at end of file diff --git a/tests/test_ston.h b/tests/test_ston.h new file mode 100644 index 00000000..dcc1f55d --- /dev/null +++ b/tests/test_ston.h @@ -0,0 +1,1104 @@ +#ifndef TEST_ston_H +#define TEST_ston_H + +#include "API.h" +#include "Debugger.h" + +#include "StonCols.h" +#include "debug_macros.h" +#include "minunit.h" +#include + +/* +# The following tests are/should be implemented for the StonCols class +# (a "✓" indicates that the test has been implemented). +# ---- Tests for equality column singletons ----- +# Test 1: Free column singleton in equality constraint (✓) +# Test 2: Implied free column singleton in equality constraint (✓) +# Test 3: Two free column singletons in two different equality constraint (✓) +# Test 4: One free column singleton and one non-free column singleton in (✓) +# the same equality constraint (very interesting!) +# Test 5: Column singleton unbounded from below in equality constraint (✓) +# (negative coefficient) +# Test 6: Column singleton unbounded from above in equality constraint (✓) +# (negative coefficient) +# Test 7: Column singleton unbounded from below in equality constraint (✓) +# (positive coefficient) +# Test 8: Column singleton unbounded from above in equality constraint (✓) +# (positive coefficient) +# Test 9: Chain of column singletons (✓) +# ---- Tests for inequality column singletons ---- +# Test 10: Implied free column singleton in two-sided inequality constraint (✓) +# Test 12: Dual fix to lower bound (✓) +# Test 13: Dual fix to upper bound (✓) +# Test 14: Upper part of a constraint is active at an optimal solution (✓) +# Test 15: Lower part of a constraint is active at an optimal solution (✓) +*/ + +static int counter_ston = 0; + +/* Free column singleton in equality constraint + min. [1 1 -1 3 2]x + s.t. [2 1 0 1 0] [2] + [1 4 -2 6 8] x = [1] + [1 4 0 3 0] [4] + [2 0 0 0 1] >= 6 + x1, x2, x5 >= 0 + x3, x4 free +*/ +static char *test_01_ston() +{ + double Ax[] = {2, 1, 1, 1, 4, -2, 6, 8, 1, 4, 3, 2, 1}; + int Ai[] = {0, 1, 3, 0, 1, 2, 3, 4, 0, 1, 3, 0, 4}; + int Ap[] = {0, 3, 8, 11, 13}; + int nnz = 13; + int n_rows = 4; + int n_cols = 5; + + double lhs[] = {2, 1, 4, 6}; + double rhs[] = {2, 1, 4, INF}; + double lbs[] = {0, 0, -INF, -INF, 0}; + double ubs[] = {INF, INF, INF, INF, INF}; + double c[] = {1, 1, -1, 3, 2}; + + Settings *stgs = default_settings(); + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + remove_ston_cols(prob); + problem_clean(prob, true); + + mu_assert("error", + CHECK_ROW_SIZES(constraints->A, constraints->state->row_sizes)); + mu_assert("error", + CHECK_COL_SIZES(constraints->AT, constraints->state->col_sizes)); + + // check that new A is correct + double Ax_correct[] = {2, 1, 1, 1, 4, 3, 2, 1}; + int Ai_correct[] = {0, 1, 2, 0, 1, 2, 0, 3}; + int Ap_correct[] = {0, 3, 6, 8}; + mu_assert("error Ax", ARRAYS_EQUAL(Ax_correct, A->x, 8)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai_correct, A->i, 8)); + CHECK_ROW_STARTS(A, Ap_correct); + + // check new AT + DEBUG(mu_assert("error AT", verify_A_and_AT_consistency(A, constraints->AT))); + + // check new rowtags + RowTag rowtags_correct[] = {R_TAG_EQ, R_TAG_EQ, R_TAG_RHS_INF}; + mu_assert("error rowtags", + ARRAYS_EQUAL(rowtags_correct, constraints->row_tags, 3)); + + // check new coltags + ColTag coltags_correct[] = {C_TAG_UB_INF, C_TAG_UB_INF, + C_TAG_UB_INF | C_TAG_LB_INF, C_TAG_UB_INF}; + + mu_assert("error coltags", + ARRAYS_EQUAL(coltags_correct, constraints->col_tags, 4)); + + // check that new variable bounds are correct + double lbs_correct[] = {0, 0, -INF, 0}; + double ubs_correct[] = {INF, INF, INF, INF}; + mu_assert("error bounds", + CHECK_BOUNDS(constraints->bounds, lbs_correct, ubs_correct, 4)); + + // check that the objective function is correct + double obj_correct[] = {0.5, -1, 0, -2}; + mu_assert("error obj", ARRAYS_EQUAL(obj_correct, prob->obj->c, 4)); + mu_assert("error obj", prob->obj->offset == 0.5); + + // check that lhs and rhs are correct + double lhs_correct[] = {2, 4, 6}; + double rhs_correct[] = {2, 4, INF}; + mu_assert("error lhs", ARRAYS_EQUAL(lhs_correct, constraints->lhs, 3)); + mu_assert("error rhs", ARRAYS_EQUAL(rhs_correct, constraints->rhs, 3)); + + PS_FREE(stgs); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + + return 0; +} + +/* Implied free column singleton in equality constraint + + min. [1 1 -1 3 2]x + s.t. [2 1 0 1 1] [2] + [1 4 -2 6 8] x = [0] + [1 4 0 3 1] [4] + x1, x2, x3, x4, x5 >= 0 +*/ +static char *test_02_ston() +{ + double Ax[] = {2, 1, 1, 1, 1, 4, -2, 6, 8, 1, 4, 3, 1}; + int Ai[] = {0, 1, 3, 4, 0, 1, 2, 3, 4, 0, 1, 3, 4}; + int Ap[] = {0, 4, 9, 13}; + int nnz = 13; + int n_rows = 3; + int n_cols = 5; + + double lhs[] = {2, 0, 4}; + double rhs[] = {2, 0, 4}; + double lbs[] = {0, 0, 0, 0, 0}; + double ubs[] = {INF, INF, INF, INF, INF}; + double c[] = {1, 1, -1, 3, 2}; + + Settings *stgs = default_settings(); + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + remove_ston_cols(prob); + problem_clean(prob, true); + + mu_assert("error", + CHECK_ROW_SIZES(constraints->A, constraints->state->row_sizes)); + mu_assert("error", + CHECK_COL_SIZES(constraints->AT, constraints->state->col_sizes)); + + // check that new A is correct + double Ax_correct[] = {2, 1, 1, 1, 1, 4, 3, 1}; + int Ai_correct[] = {0, 1, 2, 3, 0, 1, 2, 3}; + int Ap_correct[] = {0, 4, 8}; + mu_assert("error Ax", ARRAYS_EQUAL(Ax_correct, A->x, 8)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai_correct, A->i, 8)); + CHECK_ROW_STARTS(A, Ap_correct); + + // check new AT + DEBUG(mu_assert("error AT", verify_A_and_AT_consistency(A, constraints->AT))); + + // check new rowtags + RowTag rowtags_correct[] = {R_TAG_EQ, R_TAG_EQ}; + mu_assert("error rowtags", + ARRAYS_EQUAL(rowtags_correct, constraints->row_tags, 2)); + + // check new coltags + ColTag coltags_correct[] = {C_TAG_UB_INF, C_TAG_UB_INF, C_TAG_UB_INF, + C_TAG_UB_INF}; + mu_assert("error coltags", + ARRAYS_EQUAL(coltags_correct, constraints->col_tags, 4)); + + // check that new variable bounds are correct + double lbs_correct[] = {0, 0, 0, 0}; + double ubs_correct[] = {INF, INF, INF, INF}; + mu_assert("error bounds", + CHECK_BOUNDS(constraints->bounds, lbs_correct, ubs_correct, 4)); + + // check that the objective function is correct + double obj_correct[] = {0.5, -1, 0, -2}; + mu_assert("error obj", ARRAYS_EQUAL(obj_correct, prob->obj->c, 4)); + mu_assert("error obj", prob->obj->offset == 0); + + // check that lhs and rhs are correct + double lhs_correct[] = {2, 4}; + double rhs_correct[] = {2, 4}; + mu_assert("error lhs", ARRAYS_EQUAL(lhs_correct, constraints->lhs, 2)); + mu_assert("error rhs", ARRAYS_EQUAL(rhs_correct, constraints->rhs, 2)); + + PS_FREE(stgs); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + + return 0; +} + +/* Two free column singletons in different equality constraints + + min. [1 1 -1 3 2]x + s.t.[-1, 0, 2, 3, 4 ] [2] + [ 0, -1, 5, 6, 7 ] x = [4] + [ 0, 0, 8, 9, 10 ] [6] + [ 0, 0, 11, 12, 13 ] [8] + x3, x4, x5 >= 0 + x1, x2 free +*/ +static char *test_03_ston() +{ + double Ax[] = {-1, 2, 3, 4, -1, 5, 6, 7, 8, 9, 10, 11, 12, 13}; + int Ai[] = {0, 2, 3, 4, 1, 2, 3, 4, 2, 3, 4, 2, 3, 4}; + int Ap[] = {0, 4, 8, 11, 14}; + int nnz = 14; + int n_rows = 4; + int n_cols = 5; + + double lhs[] = {2, 4, 6, 8}; + double rhs[] = {2, 4, 6, 8}; + double lbs[] = {-INF, -INF, 0, 0, 0}; + double ubs[] = {INF, INF, INF, INF, INF}; + double c[] = {1, 1, -1, 3, 2}; + + Settings *stgs = default_settings(); + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + remove_ston_cols(prob); + problem_clean(prob, true); + + mu_assert("error", + CHECK_ROW_SIZES(constraints->A, constraints->state->row_sizes)); + mu_assert("error", + CHECK_COL_SIZES(constraints->AT, constraints->state->col_sizes)); + + // check that new A is correct + double Ax_correct[] = {8, 9, 10, 11, 12, 13}; + int Ai_correct[] = {0, 1, 2, 0, 1, 2}; + int Ap_correct[] = {0, 3, 6}; + mu_assert("error Ax", ARRAYS_EQUAL(Ax_correct, A->x, 6)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai_correct, A->i, 6)); + CHECK_ROW_STARTS(A, Ap_correct); + + // check new AT + DEBUG(mu_assert("error AT", verify_A_and_AT_consistency(A, constraints->AT))); + + // check new rowtags + RowTag rowtags_correct[] = {R_TAG_EQ, R_TAG_EQ}; + mu_assert("error rowtags", + ARRAYS_EQUAL(rowtags_correct, constraints->row_tags, 2)); + + // check new coltags + ColTag coltags_correct[] = {C_TAG_UB_INF, C_TAG_UB_INF, C_TAG_UB_INF}; + mu_assert("error coltags", + ARRAYS_EQUAL(coltags_correct, constraints->col_tags, 3)); + + // check that new variable bounds are correct + double lbs_correct[] = {0, 0, 0}; + double ubs_correct[] = {INF, INF, INF}; + mu_assert("error bounds", + CHECK_BOUNDS(constraints->bounds, lbs_correct, ubs_correct, 3)); + + // check that the objective function is correct + double obj_correct[] = {6, 12, 13}; + mu_assert("error obj", ARRAYS_EQUAL(obj_correct, prob->obj->c, 3)); + mu_assert("error obj", prob->obj->offset == -6); + + // check that lhs and rhs are correct + double lhs_correct[] = {6, 8}; + double rhs_correct[] = {6, 8}; + mu_assert("error lhs", ARRAYS_EQUAL(lhs_correct, constraints->lhs, 2)); + mu_assert("error rhs", ARRAYS_EQUAL(rhs_correct, constraints->rhs, 2)); + + PS_FREE(stgs); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + + return 0; +} + +/* One free column singleton and one non-free column singleton in + the same equality constraint (very interesting!) + + min. [1 2 -1 3 2]x + s.t.[-1, -1, 2, 3, 4 ] [2] + [ 0, 0, 5, 6, 7 ] x = [4] + [ 0, 0, 8, 9, 10 ] [6] + [ 0, 0, 11, 12, 13 ] [8] + x3, x4, x5 >= 0 + -1 <= x2 <= 5 + x1 free +*/ +static char *test_04_ston() +{ + double Ax[] = {-1, -1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13}; + int Ai[] = {0, 1, 2, 3, 4, 2, 3, 4, 2, 3, 4, 2, 3, 4}; + int Ap[] = {0, 5, 8, 11, 14}; + int nnz = 14; + int n_rows = 4; + int n_cols = 5; + + double lhs[] = {2, 4, 6, 8}; + double rhs[] = {2, 4, 6, 8}; + double lbs[] = {-INF, -1, 0, 0, 0}; + double ubs[] = {INF, 5, INF, INF, INF}; + double c[] = {1, 2, -1, 3, 2}; + + Settings *stgs = default_settings(); + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + remove_ston_cols(prob); + problem_clean(prob, true); + + mu_assert("error", + CHECK_ROW_SIZES(constraints->A, constraints->state->row_sizes)); + mu_assert("error", + CHECK_COL_SIZES(constraints->AT, constraints->state->col_sizes)); + + // check that new A is correct (it should containt the empty column) + double Ax_correct[] = {5, 6, 7, 8, 9, 10, 11, 12, 13}; + int Ai_correct[] = {1, 2, 3, 1, 2, 3, 1, 2, 3}; + int Ap_correct[] = {0, 3, 6, 9}; + mu_assert("error Ax", ARRAYS_EQUAL(Ax_correct, A->x, 9)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai_correct, A->i, 9)); + CHECK_ROW_STARTS(A, Ap_correct); + + // check new AT + DEBUG(mu_assert("error AT", verify_A_and_AT_consistency(A, constraints->AT))); + + // check new rowtags + RowTag rowtags_correct[] = {R_TAG_EQ, R_TAG_EQ, R_TAG_EQ}; + mu_assert("error rowtags", + ARRAYS_EQUAL(rowtags_correct, constraints->row_tags, 3)); + + // check new coltags + ColTag coltags_correct[] = {C_TAG_NONE, C_TAG_UB_INF, C_TAG_UB_INF, + C_TAG_UB_INF}; + mu_assert("error coltags", + ARRAYS_EQUAL(coltags_correct, constraints->col_tags, 4)); + + // check that new variable bounds are correct + double lbs_correct[] = {-1, 0, 0, 0}; + double ubs_correct[] = {5, INF, INF, INF}; + mu_assert("error bounds", + CHECK_BOUNDS(constraints->bounds, lbs_correct, ubs_correct, 3)); + + // check that the objective function is correct + double obj_correct[] = {1, 1, 6, 6}; + mu_assert("error obj", ARRAYS_EQUAL(obj_correct, prob->obj->c, 4)); + mu_assert("error obj", prob->obj->offset == -2); + + // check that lhs and rhs are correct + double lhs_correct[] = {4, 6, 8}; + double rhs_correct[] = {4, 6, 8}; + mu_assert("error lhs", ARRAYS_EQUAL(lhs_correct, constraints->lhs, 3)); + mu_assert("error rhs", ARRAYS_EQUAL(rhs_correct, constraints->rhs, 3)); + + PS_FREE(stgs); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + + return 0; +} + +/* Column singleton unbounded from below in equality constraint + (negative coefficient) + + min. [1 1 -1 3 2]x + s.t. [2 1 0 1 0] [2] + [1 4 -2 6 8] x = [1] + [1 4 0 3 1] [4] + [2 0 0 0 1] >= 6 + x1, x2, x4, x5 >= 0 + x3 <= 4 +*/ +static char *test_05_ston() +{ + double Ax[] = {2, 1, 1, 1, 4, -2, 6, 8, 1, 4, 3, 1, 2, 1}; + int Ai[] = {0, 1, 3, 0, 1, 2, 3, 4, 0, 1, 3, 4, 0, 4}; + int Ap[] = {0, 3, 8, 12, 14}; + int nnz = 14; + int n_rows = 4; + int n_cols = 5; + + double lhs[] = {2, 1, 4, 6}; + double rhs[] = {2, 1, 4, INF}; + double lbs[] = {0, 0, -INF, 0, 0}; + double ubs[] = {INF, INF, 4, INF, INF}; + double c[] = {1, 1, -1, 3, 2}; + + Settings *stgs = default_settings(); + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + remove_ston_cols(prob); + problem_clean(prob, true); + + mu_assert("error", + CHECK_ROW_SIZES(constraints->A, constraints->state->row_sizes)); + mu_assert("error", + CHECK_COL_SIZES(constraints->AT, constraints->state->col_sizes)); + + // check that new A is correct + double Ax_correct[] = {2, 1, 1, 1, 4, 6, 8, 1, 4, 3, 1, 2, 1}; + int Ai_correct[] = {0, 1, 2, 0, 1, 2, 3, 0, 1, 2, 3, 0, 3}; + int Ap_correct[] = {0, 3, 7, 11, 13}; + mu_assert("error Ax", ARRAYS_EQUAL(Ax_correct, A->x, 13)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai_correct, A->i, 13)); + CHECK_ROW_STARTS(A, Ap_correct); + + // check new AT + DEBUG(mu_assert("error AT", verify_A_and_AT_consistency(A, constraints->AT))); + + // check new rowtags + RowTag rowtags_correct[] = {R_TAG_EQ, R_TAG_LHS_INF, R_TAG_EQ, R_TAG_RHS_INF}; + mu_assert("error rowtags", + ARRAYS_EQUAL(rowtags_correct, constraints->row_tags, 4)); + + // check new coltags + ColTag coltags_correct[] = {C_TAG_UB_INF, C_TAG_UB_INF, C_TAG_UB_INF, + C_TAG_UB_INF}; + mu_assert("error coltags", + ARRAYS_EQUAL(coltags_correct, constraints->col_tags, 4)); + + // check that new variable bounds are correct + double lbs_correct[] = {0, 0, 0, 0}; + double ubs_correct[] = {INF, INF, INF, INF}; + mu_assert("error bounds", + CHECK_BOUNDS(constraints->bounds, lbs_correct, ubs_correct, 4)); + + // check that the objective function is correct + double obj_correct[] = {0.5, -1, 0, -2}; + mu_assert("error obj", ARRAYS_EQUAL(obj_correct, prob->obj->c, 4)); + mu_assert("error obj", prob->obj->offset == 0.5); + + // check that lhs and rhs are correct + double lhs_correct[] = {2, -INF, 4, 6}; + double rhs_correct[] = {2, 9, 4, INF}; + mu_assert("error lhs", ARRAYS_EQUAL(lhs_correct, constraints->lhs, 4)); + mu_assert("error rhs", ARRAYS_EQUAL(rhs_correct, constraints->rhs, 4)); + + PS_FREE(stgs); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + + return 0; +} + +/* Column singleton unbounded from above in equality constraint + (negative coefficient) + + min. [1 1 -1 3 2]x + s.t. [2 1 0 1 0] [2] + [1 4 -2 6 8] x = [1] + [1 4 0 3 1] [4] + [2 0 0 0 1] >= 6 + x1, x2, x4, x5 >= 0 + x3 >= 4 +*/ +static char *test_06_ston() +{ + double Ax[] = {2, 1, 1, 1, 4, -2, 6, 8, 1, 4, 3, 1, 2, 1}; + int Ai[] = {0, 1, 3, 0, 1, 2, 3, 4, 0, 1, 3, 4, 0, 4}; + int Ap[] = {0, 3, 8, 12, 14}; + int nnz = 14; + int n_rows = 4; + int n_cols = 5; + + double lhs[] = {2, 1, 4, 6}; + double rhs[] = {2, 1, 4, INF}; + double lbs[] = {0, 0, 4, 0, 0}; + double ubs[] = {INF, INF, INF, INF, INF}; + double c[] = {1, 1, -1, 3, 2}; + + Settings *stgs = default_settings(); + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + remove_ston_cols(prob); + problem_clean(prob, true); + + mu_assert("error", + CHECK_ROW_SIZES(constraints->A, constraints->state->row_sizes)); + mu_assert("error", + CHECK_COL_SIZES(constraints->AT, constraints->state->col_sizes)); + + // check that new A is correct + double Ax_correct[] = {2, 1, 1, 1, 4, 6, 8, 1, 4, 3, 1, 2, 1}; + int Ai_correct[] = {0, 1, 2, 0, 1, 2, 3, 0, 1, 2, 3, 0, 3}; + int Ap_correct[] = {0, 3, 7, 11, 13}; + mu_assert("error Ax", ARRAYS_EQUAL(Ax_correct, A->x, 13)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai_correct, A->i, 13)); + CHECK_ROW_STARTS(A, Ap_correct); + + // check new AT + DEBUG(mu_assert("error AT", verify_A_and_AT_consistency(A, constraints->AT))); + + // check new rowtags + RowTag rowtags_correct[] = {R_TAG_EQ, R_TAG_RHS_INF, R_TAG_EQ, R_TAG_RHS_INF}; + mu_assert("error rowtags", + ARRAYS_EQUAL(rowtags_correct, constraints->row_tags, 4)); + + // check new coltags + ColTag coltags_correct[] = {C_TAG_UB_INF, C_TAG_UB_INF, C_TAG_UB_INF, + C_TAG_UB_INF}; + mu_assert("error coltags", + ARRAYS_EQUAL(coltags_correct, constraints->col_tags, 4)); + + // check that new variable bounds are correct + double lbs_correct[] = {0, 0, 0, 0}; + double ubs_correct[] = {INF, INF, INF, INF}; + mu_assert("error bounds", + CHECK_BOUNDS(constraints->bounds, lbs_correct, ubs_correct, 4)); + + // check that the objective function is correct + double obj_correct[] = {0.5, -1, 0, -2}; + mu_assert("error obj", ARRAYS_EQUAL(obj_correct, prob->obj->c, 4)); + mu_assert("error obj", prob->obj->offset == 0.5); + + // check that lhs and rhs are correct + double lhs_correct[] = {2, 9, 4, 6}; + double rhs_correct[] = {2, INF, 4, INF}; + mu_assert("error lhs", ARRAYS_EQUAL(lhs_correct, constraints->lhs, 4)); + mu_assert("error rhs", ARRAYS_EQUAL(rhs_correct, constraints->rhs, 4)); + + PS_FREE(stgs); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + + return 0; +} + +/* Column singleton unbounded from below in equality constraint + (positive coefficient) + + min. [1 1 -1 3 2]x + s.t. [2 1 0 1 0] [2] + [1 4 2 6 8] x = [1] + [1 4 0 3 0] [4] + [2 0 0 0 1] >= 6 + x1, x2, x4 >= 0 + x5 >= -10 + x3 <= 4 +*/ +static char *test_07_ston() +{ + double Ax[] = {2, 1, 1, 1, 4, 2, 6, 8, 1, 4, 3, 1, 2, 1}; + int Ai[] = {0, 1, 3, 0, 1, 2, 3, 4, 0, 1, 3, 4, 0, 4}; + int Ap[] = {0, 3, 8, 12, 14}; + int nnz = 14; + int n_rows = 4; + int n_cols = 5; + + double lhs[] = {2, 1, 4, 6}; + double rhs[] = {2, 1, 4, INF}; + double lbs[] = {0, 0, -INF, 0, -10}; + double ubs[] = {INF, INF, 4, INF, INF}; + double c[] = {1, 1, -1, 3, 2}; + + Settings *stgs = default_settings(); + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + remove_ston_cols(prob); + problem_clean(prob, true); + + mu_assert("error", + CHECK_ROW_SIZES(constraints->A, constraints->state->row_sizes)); + mu_assert("error", + CHECK_COL_SIZES(constraints->AT, constraints->state->col_sizes)); + + // check that new A is correct + double Ax_correct[] = {2, 1, 1, 1, 4, 6, 8, 1, 4, 3, 1, 2, 1}; + int Ai_correct[] = {0, 1, 2, 0, 1, 2, 3, 0, 1, 2, 3, 0, 3}; + int Ap_correct[] = {0, 3, 7, 11, 13}; + mu_assert("error Ax", ARRAYS_EQUAL(Ax_correct, A->x, 13)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai_correct, A->i, 13)); + CHECK_ROW_STARTS(A, Ap_correct); + + // check new AT + DEBUG(mu_assert("error AT", verify_A_and_AT_consistency(A, constraints->AT))); + + // check new rowtags + RowTag rowtags_correct[] = {R_TAG_EQ, R_TAG_RHS_INF, R_TAG_EQ, R_TAG_RHS_INF}; + mu_assert("error rowtags", + ARRAYS_EQUAL(rowtags_correct, constraints->row_tags, 4)); + + // check new coltags + ColTag coltags_correct[] = {C_TAG_UB_INF, C_TAG_UB_INF, C_TAG_UB_INF, + C_TAG_UB_INF}; + mu_assert("error coltags", + ARRAYS_EQUAL(coltags_correct, constraints->col_tags, 4)); + + // check that new variable bounds are correct + double lbs_correct[] = {0, 0, 0, -10}; + double ubs_correct[] = {INF, INF, INF, INF}; + mu_assert("error bounds", + CHECK_BOUNDS(constraints->bounds, lbs_correct, ubs_correct, 4)); + + // check that the objective function is correct + double obj_correct[] = {1.5, 3, 6, 6}; + mu_assert("error obj", ARRAYS_EQUAL(obj_correct, prob->obj->c, 4)); + mu_assert("error obj", prob->obj->offset == -0.5); + + // check that lhs and rhs are correct + double lhs_correct[] = {2, -7, 4, 6}; + double rhs_correct[] = {2, INF, 4, INF}; + mu_assert("error lhs", ARRAYS_EQUAL(lhs_correct, constraints->lhs, 4)); + mu_assert("error rhs", ARRAYS_EQUAL(rhs_correct, constraints->rhs, 4)); + + PS_FREE(stgs); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + + return 0; +} + +/* Column singleton unbounded from above in equality constraint + (positive coefficient) + + min. [1 1 -1 3 2]x + s.t. [2 1 0 1 0] [2] + [1 4 2 6 -8] x = [1] + [1 4 0 3 0] [4] + [2 0 0 0 1] >= 2 + x1, x2, x4, x5 >= 0 + x3 >= 4 +*/ +static char *test_08_ston() +{ + double Ax[] = {2, 1, 1, 1, 4, 2, 6, -8, 1, 4, 3, 1, 2, 1}; + int Ai[] = {0, 1, 3, 0, 1, 2, 3, 4, 0, 1, 3, 4, 0, 4}; + int Ap[] = {0, 3, 8, 12, 14}; + int nnz = 14; + int n_rows = 4; + int n_cols = 5; + + double lhs[] = {2, 1, 4, 2}; + double rhs[] = {2, 1, 4, INF}; + double lbs[] = {0, 0, 4, 0, 0}; + double ubs[] = {INF, INF, INF, INF, INF}; + double c[] = {1, 1, -1, 3, 2}; + + Settings *stgs = default_settings(); + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + remove_ston_cols(prob); + problem_clean(prob, true); + + mu_assert("error", + CHECK_ROW_SIZES(constraints->A, constraints->state->row_sizes)); + mu_assert("error", + CHECK_COL_SIZES(constraints->AT, constraints->state->col_sizes)); + + // check that new A is correct + double Ax_correct[] = {2, 1, 1, 1, 4, 6, -8, 1, 4, 3, 1, 2, 1}; + int Ai_correct[] = {0, 1, 2, 0, 1, 2, 3, 0, 1, 2, 3, 0, 3}; + int Ap_correct[] = {0, 3, 7, 11, 13}; + mu_assert("error Ax", ARRAYS_EQUAL(Ax_correct, A->x, 13)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai_correct, A->i, 13)); + CHECK_ROW_STARTS(A, Ap_correct); + + // check new AT + DEBUG(mu_assert("error AT", verify_A_and_AT_consistency(A, constraints->AT))); + + // check new rowtags + RowTag rowtags_correct[] = {R_TAG_EQ, R_TAG_LHS_INF, R_TAG_EQ, R_TAG_RHS_INF}; + mu_assert("error rowtags", + ARRAYS_EQUAL(rowtags_correct, constraints->row_tags, 4)); + + // check new coltags + ColTag coltags_correct[] = {C_TAG_UB_INF, C_TAG_UB_INF, C_TAG_UB_INF, + C_TAG_UB_INF}; + mu_assert("error coltags", + ARRAYS_EQUAL(coltags_correct, constraints->col_tags, 4)); + + // check that new variable bounds are correct + double lbs_correct[] = {0, 0, 0, 0}; + double ubs_correct[] = {INF, INF, INF, INF}; + mu_assert("error bounds", + CHECK_BOUNDS(constraints->bounds, lbs_correct, ubs_correct, 4)); + + // check that the objective function is correct + double obj_correct[] = {1.5, 3, 6, -2}; + mu_assert("error obj", ARRAYS_EQUAL(obj_correct, prob->obj->c, 4)); + mu_assert("error obj", prob->obj->offset == -0.5); + + PS_FREE(stgs); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + + return 0; +} + +/* Simple chain of column singletons: + + min. [1 1 1 1 1 1 1 1 1 1 1]x + + s.t [1 2 2 2 2 2 2 2 2 2 2] [50] + [0 1 3 3 3 3 3 3 3 3 3] [40] + [0 0 1 4 4 4 4 4 4 4 4] x = [30] + [0 0 0 1 1 1 1 1 1 1 1] [20] + [0 0 0 2 1 1 1 1 1 1 1] [10] + + x1, x2, x3 free + x4 up to x10 >= 0, <= [1, 2, 3, 4, 5, 6, 7] + x11 <= 8 +*/ +static char *test_09_ston() +{ + double Ax[] = {1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 1, 4, 4, 4, 4, 4, 4, 4, 4, 1, 1, + 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1}; + int Ai[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, + 6, 7, 8, 9, 10, 2, 3, 4, 5, 6, 7, 8, 9, 10, 3, 4, + 5, 6, 7, 8, 9, 10, 3, 4, 5, 6, 7, 8, 9, 10}; + int Ap[] = {0, 11, 21, 30, 38, 46}; + int nnz = 46; + int n_rows = 5; + int n_cols = 11; + + double lhs[] = {50, 40, 30, 20, 10}; + double rhs[] = {50, 40, 30, 20, 10}; + double lbs[] = {-INF, -INF, -INF, 0, 0, 0, 0, 0, 0, 0, -INF}; + double ubs[] = {INF, INF, INF, 1, 2, 3, 4, 5, 6, 7, 8}; + double c[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; + + Settings *stgs = default_settings(); + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + remove_ston_cols(prob); + problem_clean(prob, true); + + mu_assert("error", + CHECK_ROW_SIZES(constraints->A, constraints->state->row_sizes)); + mu_assert("error", + CHECK_COL_SIZES(constraints->AT, constraints->state->col_sizes)); + + // check that new A is correct + double Ax_correct[] = {1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1}; + int Ai_correct[] = {0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7}; + int Ap_correct[] = {0, 8, 16}; + mu_assert("error Ax", ARRAYS_EQUAL(Ax_correct, A->x, 16)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai_correct, A->i, 16)); + CHECK_ROW_STARTS(A, Ap_correct); + + // check new AT + DEBUG(mu_assert("error AT", verify_A_and_AT_consistency(A, constraints->AT))); + + // check new rowtags + RowTag rowtags_correct[] = {R_TAG_EQ, R_TAG_EQ}; + mu_assert("error rowtags", + ARRAYS_EQUAL(rowtags_correct, constraints->row_tags, 2)); + + // check new coltags + ColTag coltags_correct[] = {C_TAG_NONE, C_TAG_NONE, C_TAG_NONE, C_TAG_NONE, + C_TAG_NONE, C_TAG_NONE, C_TAG_NONE, C_TAG_LB_INF}; + mu_assert("error coltags", + ARRAYS_EQUAL(coltags_correct, constraints->col_tags, 8)); + + // check that new variable bounds are correct + double lbs_correct[] = {0, 0, 0, 0, 0, 0, 0, -INF}; + double ubs_correct[] = {1, 2, 3, 4, 5, 6, 7, 8}; + mu_assert("error bounds", + CHECK_BOUNDS(constraints->bounds, lbs_correct, ubs_correct, 8)); + + // check that the objective function is correct + double obj_correct[] = {-6, -6, -6, -6, -6, -6, -6, -6}; + mu_assert("error obj", ARRAYS_EQUAL(obj_correct, prob->obj->c, 8)); + mu_assert("error obj", prob->obj->offset == 70); + + // check that lhs and rhs are correct + double lhs_correct[] = {20, 10}; + double rhs_correct[] = {20, 10}; + mu_assert("error lhs", ARRAYS_EQUAL(lhs_correct, constraints->lhs, 2)); + mu_assert("error rhs", ARRAYS_EQUAL(rhs_correct, constraints->rhs, 2)); + + PS_FREE(stgs); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + + return 0; +} + +/* Implied free column singleton in two-sided inequality constraint + min. x1 + 2x2 - 4x3 - 3x4 + s.t. [3 0 5 2] <= [5] + 10 >= [-1 1 0 -3] x >= [8] + [-2 0 -2 1] >= [6] + x2 >= 8 + x1, x3, x4 >= 0 +*/ +static char *test_10_ston() +{ + double Ax[] = {3, 5, 2, -1, 1, -3, -2, -2, 1}; + int Ai[] = {0, 2, 3, 0, 1, 3, 0, 2, 3}; + int Ap[] = {0, 3, 6, 9}; + int nnz = 9; + int n_rows = 3; + int n_cols = 4; + + double lhs[] = {-INF, 8, 6}; + double rhs[] = {5, 10, INF}; + double lbs[] = {0, 8, 0, 0}; + double ubs[] = {INF, INF, INF, INF}; + double c[] = {1, 2, -4, -3}; + + Settings *stgs = default_settings(); + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + remove_ston_cols(prob); + problem_clean(prob, true); + + mu_assert("error", + CHECK_ROW_SIZES(constraints->A, constraints->state->row_sizes)); + mu_assert("error", + CHECK_COL_SIZES(constraints->AT, constraints->state->col_sizes)); + + // check that new A is correct + double Ax_correct[] = {3, 5, 2, -2, -2, 1}; + int Ai_correct[] = {0, 1, 2, 0, 1, 2}; + int Ap_correct[] = {0, 3, 6}; + mu_assert("error Ax", ARRAYS_EQUAL(Ax_correct, A->x, 6)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai_correct, A->i, 6)); + CHECK_ROW_STARTS(A, Ap_correct); + + // check new AT + DEBUG(mu_assert("error AT", verify_A_and_AT_consistency(A, constraints->AT))); + + // check new rowtags + RowTag rowtags_correct[] = {R_TAG_LHS_INF, R_TAG_RHS_INF}; + mu_assert("error rowtags", + ARRAYS_EQUAL(rowtags_correct, constraints->row_tags, 2)); + + // check new coltags + ColTag coltags_correct[] = {C_TAG_UB_INF, C_TAG_UB_INF, C_TAG_UB_INF}; + mu_assert("error coltags", + ARRAYS_EQUAL(coltags_correct, constraints->col_tags, 3)); + + // check that new variable bounds are correct + double lbs_correct[] = {0, 0, 0}; + double ubs_correct[] = {INF, INF, INF}; + mu_assert("error bounds", + CHECK_BOUNDS(constraints->bounds, lbs_correct, ubs_correct, 3)); + + // check that the objective function is correct + double obj_correct[] = {3, -4, 3}; + mu_assert("error obj", ARRAYS_EQUAL(obj_correct, prob->obj->c, 3)); + mu_assert("error obj", prob->obj->offset == 16); + + // check that lhs and rhs are correct + double lhs_correct[] = {-INF, 6}; + double rhs_correct[] = {5, INF}; + mu_assert("error lhs", ARRAYS_EQUAL(lhs_correct, constraints->lhs, 2)); + mu_assert("error rhs", ARRAYS_EQUAL(rhs_correct, constraints->rhs, 2)); + + PS_FREE(stgs); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + + return 0; +} + +/* Dual fix to lower bound + min. 4x1 + x2 - 2x3 + 7x4 + s.t 3x1 - x3 - 6x4 <= 0 + 3x1 - 2x2 + 5x3 - x4 >= 0 + 4x1 + 3x3 + 4x4 <= 5 + x2 >= 1 + x1, x3, x4 >= 0 +*/ +static char *test_12_ston() +{ + double Ax[] = {3, -1, -6, 3, -2, 5, -1, 4, 3, 4}; + int Ai[] = {0, 2, 3, 0, 1, 2, 3, 0, 2, 3}; + int Ap[] = {0, 3, 7, 10}; + int nnz = 10; + int n_rows = 3; + int n_cols = 4; + + double lhs[] = {-INF, 0, -INF}; + double rhs[] = {0, INF, 5}; + double lbs[] = {0, 1, 0, 0}; + double ubs[] = {INF, INF, INF, INF}; + double c[] = {4, 1, -2, 7}; + + Settings *stgs = default_settings(); + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + remove_ston_cols(prob); + problem_clean(prob, true); + + mu_assert("error", + CHECK_ROW_SIZES(constraints->A, constraints->state->row_sizes)); + mu_assert("error", + CHECK_COL_SIZES(constraints->AT, constraints->state->col_sizes)); + + // check that new A is correct + double Ax_correct[] = {3, -1, -6, 3, 5, -1, 4, 3, 4}; + int Ai_correct[] = {0, 1, 2, 0, 1, 2, 0, 1, 2}; + int Ap_correct[] = {0, 3, 6, 9}; + mu_assert("error Ax", ARRAYS_EQUAL(Ax_correct, A->x, 9)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai_correct, A->i, 9)); + CHECK_ROW_STARTS(A, Ap_correct); + + // check new AT + DEBUG(mu_assert("error AT", verify_A_and_AT_consistency(A, constraints->AT))); + + // check new rowtags + RowTag rowtags_correct[] = {R_TAG_LHS_INF, R_TAG_RHS_INF, R_TAG_LHS_INF}; + mu_assert("error rowtags", + ARRAYS_EQUAL(rowtags_correct, constraints->row_tags, 3)); + + // check new coltags + ColTag coltags_correct[] = {C_TAG_UB_INF, C_TAG_UB_INF, C_TAG_UB_INF}; + mu_assert("error coltags", + ARRAYS_EQUAL(coltags_correct, constraints->col_tags, 3)); + + // check that new variable bounds are correct + double lbs_correct[] = {0, 0, 0}; + double ubs_correct[] = {INF, INF, INF}; + mu_assert("error bounds", + CHECK_BOUNDS(constraints->bounds, lbs_correct, ubs_correct, 3)); + + // check that the objective function is correct + double obj_correct[] = {4, -2, 7}; + mu_assert("error obj", ARRAYS_EQUAL(obj_correct, prob->obj->c, 3)); + mu_assert("error obj", prob->obj->offset == 1); + + // check that lhs and rhs are correct + double lhs_correct[] = {-INF, 2, -INF}; + double rhs_correct[] = {0, INF, 5}; + mu_assert("error lhs", ARRAYS_EQUAL(lhs_correct, constraints->lhs, 3)); + mu_assert("error rhs", ARRAYS_EQUAL(rhs_correct, constraints->rhs, 3)); + + PS_FREE(stgs); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + + return 0; +} + +/* Dual fix to upper bound + min. 4x1 - x2 - 2x3 + 7x4 + s.t 3x1 - x3 - 6x4 <= 0 + -3x1 - 2x2 + 5x3 - x4 <= 0 + 4x1 + 3x3 + 4x4 <= 5 + 0 <= x2 <= 5 + x1, x3, x4 >= 0 +*/ +static char *test_13_ston() +{ + double Ax[] = {3, -1, -6, -3, -2, 5, -1, 4, 3, 4}; + int Ai[] = {0, 2, 3, 0, 1, 2, 3, 0, 2, 3}; + int Ap[] = {0, 3, 7, 10}; + int nnz = 10; + int n_rows = 3; + int n_cols = 4; + + double lhs[] = {-INF, -INF, -INF}; + double rhs[] = {0, 0, 5}; + double lbs[] = {0, 0, 0, 0}; + double ubs[] = {INF, 5, INF, INF}; + double c[] = {4, -1, -2, 7}; + + Settings *stgs = default_settings(); + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + + Problem *prob = presolver->prob; + Constraints *constraints = prob->constraints; + Matrix *A = constraints->A; + remove_ston_cols(prob); + problem_clean(prob, true); + + mu_assert("error", + CHECK_ROW_SIZES(constraints->A, constraints->state->row_sizes)); + mu_assert("error", + CHECK_COL_SIZES(constraints->AT, constraints->state->col_sizes)); + + // check that new A is correct + double Ax_correct[] = {3, -1, -6, -3, 5, -1, 4, 3, 4}; + int Ai_correct[] = {0, 1, 2, 0, 1, 2, 0, 1, 2}; + int Ap_correct[] = {0, 3, 6, 9}; + mu_assert("error Ax", ARRAYS_EQUAL(Ax_correct, A->x, 9)); + mu_assert("error Ai", ARRAYS_EQUAL(Ai_correct, A->i, 9)); + CHECK_ROW_STARTS(A, Ap_correct); + + // check new AT + DEBUG(mu_assert("error AT", verify_A_and_AT_consistency(A, constraints->AT))); + + // check new rowtags + RowTag rowtags_correct[] = {R_TAG_LHS_INF, R_TAG_LHS_INF, R_TAG_LHS_INF}; + mu_assert("error rowtags", + ARRAYS_EQUAL(rowtags_correct, constraints->row_tags, 3)); + + // check new coltags + ColTag coltags_correct[] = {C_TAG_UB_INF, C_TAG_UB_INF, C_TAG_UB_INF}; + mu_assert("error coltags", + ARRAYS_EQUAL(coltags_correct, constraints->col_tags, 3)); + + // check that new variable bounds are correct + double lbs_correct[] = {0, 0, 0}; + double ubs_correct[] = {INF, INF, INF}; + mu_assert("error bounds", + CHECK_BOUNDS(constraints->bounds, lbs_correct, ubs_correct, 3)); + + // check that the objective function is correct + double obj_correct[] = {4, -2, 7}; + mu_assert("error obj", ARRAYS_EQUAL(obj_correct, prob->obj->c, 3)); + mu_assert("error obj", prob->obj->offset == -5); + + // check that lhs and rhs are correct + double lhs_correct[] = {-INF, -INF, -INF}; + double rhs_correct[] = {0, 10, 5}; + mu_assert("error lhs", ARRAYS_EQUAL(lhs_correct, constraints->lhs, 3)); + mu_assert("error rhs", ARRAYS_EQUAL(rhs_correct, constraints->rhs, 3)); + + PS_FREE(stgs); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + + return 0; +} + +static const char *all_tests_ston() +{ + mu_run_test(test_01_ston, counter_ston); // (✓) + mu_run_test(test_02_ston, counter_ston); // (✓) + mu_run_test(test_03_ston, counter_ston); // (✓) + mu_run_test(test_04_ston, counter_ston); // (✓) + mu_run_test(test_05_ston, counter_ston); // (✓) + mu_run_test(test_06_ston, counter_ston); // (✓) + mu_run_test(test_07_ston, counter_ston); // (✓) + mu_run_test(test_08_ston, counter_ston); // (✓) + mu_run_test(test_09_ston, counter_ston); // (✓) + mu_run_test(test_10_ston, counter_ston); // (✓) + mu_run_test(test_12_ston, counter_ston); // (✓) + mu_run_test(test_13_ston, counter_ston); // (✓) + return 0; +} + +// not clear what should happen for test 14 and 15 + +int test_ston() +{ + const char *result = all_tests_ston(); + if (result != 0) + { + printf("%s\n", result); + printf("ston: TEST FAILED!\n"); + } + else + { + printf("ston: ALL TESTS PASSED\n"); + } + printf("ston: Tests run: %d\n", counter_ston); + return result == 0; +} + +#endif // TEST_ston_H From a5ba67352293055244e8d82db3b068f26a6edc6c Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 17 Nov 2025 03:32:09 -0800 Subject: [PATCH 2/3] workflows --- .github/{ => workflows}/cmake.yml | 0 .github/{ => workflows}/formatting.yml | 0 .github/{ => workflows}/sanitizer.yml | 0 .github/{ => workflows}/valgrind.yml | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename .github/{ => workflows}/cmake.yml (100%) rename .github/{ => workflows}/formatting.yml (100%) rename .github/{ => workflows}/sanitizer.yml (100%) rename .github/{ => workflows}/valgrind.yml (100%) diff --git a/.github/cmake.yml b/.github/workflows/cmake.yml similarity index 100% rename from .github/cmake.yml rename to .github/workflows/cmake.yml diff --git a/.github/formatting.yml b/.github/workflows/formatting.yml similarity index 100% rename from .github/formatting.yml rename to .github/workflows/formatting.yml diff --git a/.github/sanitizer.yml b/.github/workflows/sanitizer.yml similarity index 100% rename from .github/sanitizer.yml rename to .github/workflows/sanitizer.yml diff --git a/.github/valgrind.yml b/.github/workflows/valgrind.yml similarity index 100% rename from .github/valgrind.yml rename to .github/workflows/valgrind.yml From 81d895c198ad74a8460a179167de4875db2089b3 Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 17 Nov 2025 03:50:41 -0800 Subject: [PATCH 3/3] ran formatter --- src/explorers/DtonsEq.c | 6 +++--- src/explorers/Parallel_rows.c | 7 ++++--- tests/test_Constraints.h | 4 ++-- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/explorers/DtonsEq.c b/src/explorers/DtonsEq.c index 7b9a992f..e218cc4a 100644 --- a/src/explorers/DtonsEq.c +++ b/src/explorers/DtonsEq.c @@ -236,9 +236,9 @@ static inline void modify_bounds(Constraints *constraints, int i, double aij, #ifndef TESTING static inline #endif - Old_and_new_coeff update_row_A_dton(Matrix *A, int i, int q, int j, int k, - double aij, double aik, int *row_size, - PostsolveInfo *postsolve_info) + Old_and_new_coeff + update_row_A_dton(Matrix *A, int i, int q, int j, int k, double aij, double aik, + int *row_size, PostsolveInfo *postsolve_info) { int ii, start, end, insertion; double old_val = 0.0; diff --git a/src/explorers/Parallel_rows.c b/src/explorers/Parallel_rows.c index 6db6f11b..14d0a90a 100644 --- a/src/explorers/Parallel_rows.c +++ b/src/explorers/Parallel_rows.c @@ -78,9 +78,10 @@ static inline uint32_t hash_double_array_with_scale(const double *arr, int size) #ifndef TESTING static inline #endif - void compute_supp_and_coeff_hash(const Matrix *A, const RowTag *rowtags, - int *sparsity_IDs, int *coeff_hashes, - RowTag INACTIVE_TAG) + void + compute_supp_and_coeff_hash(const Matrix *A, const RowTag *rowtags, + int *sparsity_IDs, int *coeff_hashes, + RowTag INACTIVE_TAG) { for (int i = 0; i < A->m; i++) diff --git a/tests/test_Constraints.h b/tests/test_Constraints.h index 903a61ab..8a5d4ac3 100644 --- a/tests/test_Constraints.h +++ b/tests/test_Constraints.h @@ -130,7 +130,7 @@ static char *test_2_constraints() constraints_new(A, AT, lhs, rhs, bounds, data, row_tags, col_tags); // remove rows 1, 3, 5 - iVec_append_array(data->rows_to_delete, (int[]) {1, 3, 5}, 3); + iVec_append_array(data->rows_to_delete, (int[]){1, 3, 5}, 3); delete_inactive_rows(constraints); // test for correctness @@ -286,7 +286,7 @@ static char *test_4_constraints() constraints_new(A, AT, lhs, rhs, bounds, data, row_tags, col_tags); // remove cols 1, 3, 5 - iVec_append_array(data->fixed_cols_to_delete, (int[]) {1, 3, 5}, 3); + iVec_append_array(data->fixed_cols_to_delete, (int[]){1, 3, 5}, 3); delete_inactive_cols_from_A_and_AT(constraints); // test for correctness