From 73d160e4d60c696f61357aa1da3e3105de9f5bd4 Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 17 Nov 2025 03:26:02 -0800 Subject: [PATCH 01/17] 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 02/17] 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 03/17] 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 From 8fc1360f51f0c8f97326973876fe303f13fb7950 Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 17 Nov 2025 03:54:42 -0800 Subject: [PATCH 04/17] readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0986003c..19413535 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # PSLP — A Lightweight C Presolver for Linear Programs [![License: Apache 2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) -[![Build Status](https://img.shields.io/github/actions/workflow/status/yourusername/PSLP/ci.yml?branch=main)](https://github.com/yourusername/PSLP/actions) +[![Build Status](https://img.shields.io/github/actions/workflow/status/dance858/PSLP/ci.yml?branch=main)](https://github.com/dance858/PSLP/actions) [![Language: C](https://img.shields.io/badge/Language-C-blue.svg)](https://en.wikipedia.org/wiki/C_(programming_language)) [![Coverage Status](https://coveralls.io/repos/github/XXX/badge.svg?branch=master)](https://coveralls.io/github/XXXbranch=master) From 2f43e3f12de8086e59b1d527eb0fae9d9cd7be4e Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 17 Nov 2025 04:01:12 -0800 Subject: [PATCH 05/17] workflow --- .github/workflows/cmake.yml | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 3b434251..00fa6d4d 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -1,4 +1,4 @@ -name: CI +name: CMake Build and Test on: push: diff --git a/README.md b/README.md index 19413535..6ae6a3bb 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # PSLP — A Lightweight C Presolver for Linear Programs [![License: Apache 2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) -[![Build Status](https://img.shields.io/github/actions/workflow/status/dance858/PSLP/ci.yml?branch=main)](https://github.com/dance858/PSLP/actions) +[![Build Status](https://img.shields.io/github/actions/workflow/status/dance858/PSLP/cmake.yml?branch=main)](https://github.com/dance858/PSLP/actions) [![Language: C](https://img.shields.io/badge/Language-C-blue.svg)](https://en.wikipedia.org/wiki/C_(programming_language)) [![Coverage Status](https://coveralls.io/repos/github/XXX/badge.svg?branch=master)](https://coveralls.io/github/XXXbranch=master) From 93e11b57816167e08754772a311af0174c5758b7 Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 17 Nov 2025 04:13:23 -0800 Subject: [PATCH 06/17] removed old workflows --- .github/cmake.yml | 27 --------------------------- .github/formatting.yml | 38 -------------------------------------- .github/sanitizer.yml | 33 --------------------------------- .github/valgrind.yml | 26 -------------------------- README.md | 2 +- 5 files changed, 1 insertion(+), 125 deletions(-) delete mode 100644 .github/cmake.yml delete mode 100644 .github/formatting.yml delete mode 100644 .github/sanitizer.yml delete mode 100644 .github/valgrind.yml diff --git a/.github/cmake.yml b/.github/cmake.yml deleted file mode 100644 index 3b434251..00000000 --- a/.github/cmake.yml +++ /dev/null @@ -1,27 +0,0 @@ -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 deleted file mode 100644 index 98ba1036..00000000 --- a/.github/formatting.yml +++ /dev/null @@ -1,38 +0,0 @@ -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 " Date: Mon, 17 Nov 2025 04:17:00 -0800 Subject: [PATCH 07/17] new workflow --- .github/workflows/buildcpp.yml | 50 ++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 .github/workflows/buildcpp.yml diff --git a/.github/workflows/buildcpp.yml b/.github/workflows/buildcpp.yml new file mode 100644 index 00000000..bddbb4c8 --- /dev/null +++ b/.github/workflows/buildcpp.yml @@ -0,0 +1,50 @@ +name: Build C++ # Test PSLP with a C++ compiler (compatibility check) + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + linux: + name: Linux (C++ compiler) + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + # Use g++ as the C compiler for compatibility test + - name: Configure with g++ + run: | + export CC=g++ + cmake -B build_cpp -S . \ + -DCMAKE_C_COMPILER=g++ \ + -DCMAKE_BUILD_TYPE=Release + + - name: Build (g++) + run: cmake --build build_cpp --config Release + + - name: Run tests (g++) + run: cd build_cpp && ctest --output-on-failure + + macos: + name: macOS (C++ compiler) + runs-on: macos-latest + + steps: + - uses: actions/checkout@v4 + + # Use clang++ as the C compiler for compatibility test + - name: Configure with clang++ + run: | + export CC=clang++ + cmake -B build_cpp -S . \ + -DCMAKE_C_COMPILER=clang++ \ + -DCMAKE_BUILD_TYPE=Release + + - name: Build (clang++) + run: cmake --build build_cpp --config Release + + - name: Run tests (clang++) + run: cd build_cpp && ctest --output-on-failure From 288c5eec6437ce2ce27c8c7722064cce904fe5e1 Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 17 Nov 2025 05:25:19 -0800 Subject: [PATCH 08/17] c++ workflow --- .github/workflows/buildcpp.yml | 51 +++++++------------------- CMakeLists.txt | 9 +++-- include/PSLP/{API.h => PSLP_API.h} | 3 +- include/PSLP/PSLP_infs.h | 18 +++++++++ include/core/glbopts.h | 10 ++--- src/core/Presolver.c | 9 ++++- src/core/State.c | 2 +- tests/test_Constraints.h | 6 +-- tests/test_Parallel_cols.h | 2 +- tests/test_Parallel_rows.h | 2 +- tests/test_Presolver.h | 52 +++++++++++++++++++++++++- tests/test_SimpleReductions.h | 2 +- tests/test_cpp.cpp | 59 ++++++++++++++++++++++++++++++ tests/test_dton.h | 2 +- tests/test_postsolve.h | 34 ++++++++--------- tests/test_ston.h | 2 +- 16 files changed, 187 insertions(+), 76 deletions(-) rename include/PSLP/{API.h => PSLP_API.h} (97%) create mode 100644 include/PSLP/PSLP_infs.h create mode 100644 tests/test_cpp.cpp diff --git a/.github/workflows/buildcpp.yml b/.github/workflows/buildcpp.yml index bddbb4c8..6ba35e49 100644 --- a/.github/workflows/buildcpp.yml +++ b/.github/workflows/buildcpp.yml @@ -1,4 +1,4 @@ -name: Build C++ # Test PSLP with a C++ compiler (compatibility check) +name: Build C++ Test on: push: @@ -7,44 +7,21 @@ on: branches: [ main ] jobs: - linux: - name: Linux (C++ compiler) - runs-on: ubuntu-latest + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest] + build_type: [Release, Debug] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v3 - # Use g++ as the C compiler for compatibility test - - name: Configure with g++ - run: | - export CC=g++ - cmake -B build_cpp -S . \ - -DCMAKE_C_COMPILER=g++ \ - -DCMAKE_BUILD_TYPE=Release + - name: Configure CMake + run: cmake -B build_cpp -S . -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} - - name: Build (g++) - run: cmake --build build_cpp --config Release + - name: Build C++ test + run: cmake --build build_cpp --target test_cpp --config ${{ matrix.build_type }} - - name: Run tests (g++) - run: cd build_cpp && ctest --output-on-failure - - macos: - name: macOS (C++ compiler) - runs-on: macos-latest - - steps: - - uses: actions/checkout@v4 - - # Use clang++ as the C compiler for compatibility test - - name: Configure with clang++ - run: | - export CC=clang++ - cmake -B build_cpp -S . \ - -DCMAKE_C_COMPILER=clang++ \ - -DCMAKE_BUILD_TYPE=Release - - - name: Build (clang++) - run: cmake --build build_cpp --config Release - - - name: Run tests (clang++) - run: cd build_cpp && ctest --output-on-failure + - name: Run C++ test + run: cd build_cpp && ./test_cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 19ce54c6..afcec394 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ #CMakeLists.txt file for cvx_presolver cmake_minimum_required(VERSION 3.20) project(PSLP - LANGUAGES C + LANGUAGES C CXX VERSION 0.1.0 DESCRIPTION "PSLP — A Lightweight C Presolver for Linear Programs" ) @@ -56,7 +56,7 @@ if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo") endif() -#Add uninstall target +# Add uninstall target include(AddUninstallTarget) #Specify the C standard @@ -71,7 +71,7 @@ file(GLOB CORE_SOURCES CONFIGURE_DEPENDS # distinguish between public and private headers set(PUBLIC_HEADERS - include/PSLP/"API.h" + include/PSLP/API.h ) @@ -158,6 +158,9 @@ else() message(STATUS "Testing disabled") endif() +add_executable(test_cpp EXCLUDE_FROM_ALL tests/test_cpp.cpp) +target_link_libraries(test_cpp PRIVATE PSLP) + # Installation and export install(TARGETS PSLP EXPORT PSLPTargets diff --git a/include/PSLP/API.h b/include/PSLP/PSLP_API.h similarity index 97% rename from include/PSLP/API.h rename to include/PSLP/PSLP_API.h index 17917dac..c1d13544 100644 --- a/include/PSLP/API.h +++ b/include/PSLP/PSLP_API.h @@ -87,6 +87,7 @@ extern "C" /* The user is responsible for freeing the settings struct using standard free. */ Settings *default_settings(); + void free_settings(Settings *stgs); void set_settings_true(Settings *stgs); void set_settings_false(Settings *stgs); @@ -104,7 +105,7 @@ extern "C" /* Runs the presolver. At completion, the 'problem' field of the presolver contains the presolved problem. */ - PresolveStatus presolver_run(Presolver *presolver); + PresolveStatus run_presolver(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'. diff --git a/include/PSLP/PSLP_infs.h b/include/PSLP/PSLP_infs.h new file mode 100644 index 00000000..ac83533e --- /dev/null +++ b/include/PSLP/PSLP_infs.h @@ -0,0 +1,18 @@ +#ifndef PSLP_PSLP_INFS_H +#define PSLP_PSLP_INFS_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +#define PSLP_INF 1e20 +#define IS_POS_INF(x) ((x) >= PSLP_INF) +#define IS_NEG_INF(x) ((x) <= -PSLP_INF) +#define IS_ABS_INF(x) (IS_POS_INF(x) || IS_NEG_INF(x)) + +#ifdef __cplusplus +} +#endif + +#endif /* PSLP_PSLP_INFS_H */ \ No newline at end of file diff --git a/include/core/glbopts.h b/include/core/glbopts.h index eaacda0f..1050d8bf 100644 --- a/include/core/glbopts.h +++ b/include/core/glbopts.h @@ -19,6 +19,10 @@ #ifndef GLB_H_GUARD #define GLB_H_GUARD +#include "PSLP_infs.h" + +#define INF PSLP_INF + #ifdef TESTING #define EXTRA_ROW_SPACE 2 #define EXTRA_MEMORY_RATIO 1 @@ -38,12 +42,6 @@ #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/src/core/Presolver.c b/src/core/Presolver.c index 68e57e35..1ded9651 100644 --- a/src/core/Presolver.c +++ b/src/core/Presolver.c @@ -16,7 +16,6 @@ * limitations under the License. */ -#include "API.h" #include "Activity.h" #include "Constraints.h" #include "CoreTransformations.h" @@ -26,6 +25,7 @@ #include "Matrix.h" #include "Memory_wrapper.h" #include "Numerics.h" +#include "PSLP_API.h" #include "Parallel_cols.h" #include "Parallel_rows.h" #include "Postsolver.h" @@ -66,6 +66,11 @@ PresolveStats *init_stats(int n_rows, int n_cols, int nnz) return stats; } +void free_settings(Settings *stgs) +{ + PS_FREE(stgs); +} + Settings *default_settings() { Settings *stgs = (Settings *) ps_malloc(1, sizeof(Settings)); @@ -532,7 +537,7 @@ static inline void print_end_message(const Matrix *A, const PresolveStats *stats stats->presolve_total_time); } -PresolveStatus presolver_run(Presolver *presolver) +PresolveStatus run_presolver(Presolver *presolver) { Timer inner_timer, outer_timer; int nnz_before_cycle, nnz_after_cycle; diff --git a/src/core/State.c b/src/core/State.c index ce555ab6..8d0f6e2f 100644 --- a/src/core/State.c +++ b/src/core/State.c @@ -17,10 +17,10 @@ */ #include "State.h" -#include "API.h" #include "Activity.h" #include "Locks.h" #include "Numerics.h" +#include "PSLP_API.h" #include "Workspace.h" #include "utils.h" diff --git a/tests/test_Constraints.h b/tests/test_Constraints.h index 8a5d4ac3..071eaae6 100644 --- a/tests/test_Constraints.h +++ b/tests/test_Constraints.h @@ -1,10 +1,10 @@ #ifndef TEST_CONSTRAINTS_H #define TEST_CONSTRAINTS_H -#include "API.h" #include "Activity.h" #include "Constraints.h" #include "Locks.h" +#include "PSLP_API.h" #include "State.h" #include "glbopts.h" #include "minunit.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 diff --git a/tests/test_Parallel_cols.h b/tests/test_Parallel_cols.h index b11de4c9..822123a4 100644 --- a/tests/test_Parallel_cols.h +++ b/tests/test_Parallel_cols.h @@ -1,9 +1,9 @@ #ifndef TEST_PARALLEL_COLS_H #define TEST_PARALLEL_COLS_H -#include "API.h" #include "Debugger.h" #include "Matrix.h" +#include "PSLP_API.h" #include "Parallel_cols.h" #include "Problem.h" diff --git a/tests/test_Parallel_rows.h b/tests/test_Parallel_rows.h index 9e9f1234..b669b8d1 100644 --- a/tests/test_Parallel_rows.h +++ b/tests/test_Parallel_rows.h @@ -1,9 +1,9 @@ #ifndef TEST_PARALLEL_ROWS_H #define TEST_PARALLEL_ROWS_H -#include "API.h" #include "Debugger.h" #include "Matrix.h" +#include "PSLP_API.h" #include "Parallel_rows.h" #include "math.h" diff --git a/tests/test_Presolver.h b/tests/test_Presolver.h index 02646063..181665b8 100644 --- a/tests/test_Presolver.h +++ b/tests/test_Presolver.h @@ -1,8 +1,8 @@ #ifndef TEST_PRESOLVER_H #define TEST_PRESOLVER_H -#include "API.h" #include "Debugger.h" +#include "PSLP_API.h" #include "Problem.h" #include "Workspace.h" @@ -39,9 +39,59 @@ static char *test_00_presolver() return 0; } +// afiro - just test that it runs (we implement this so we can have a similar +// C++ test later) +static char *test_01_presolver() +{ + double Ax[] = { + -1., 1., 1., -1.06, 1., 1., -1., 1.4, -1., -1., -1., + -1., 1., 1., -1.06, -1.06, -0.96, -0.86, 1., 1., -1., 1., + -1., 1., -1., 1., -1., -1., 1., 1., 1., -0.43, 1., + 1., -1., 1.4, -0.43, -0.43, -0.39, -0.37, 1., 1., 1., 1., + 1., -1., 1., 1., 1., -1., 1., -1., 1., -1., 1., + -1., 2.364, 2.386, 2.408, 2.429, -1., 2.191, 2.219, 2.249, 2.279, -1., + 0.109, -1., 0.109, 0.108, 0.108, 0.107, 0.301, -1., 0.301, 0.313, 0.313, + 0.326, -1., 1., 1., 1., 1.}; + int Ai[] = {0, 1, 2, 0, 3, 0, 1, 12, 4, 5, 6, 7, 12, 13, 4, 5, 6, + 7, 14, 4, 8, 5, 9, 6, 10, 7, 11, 15, 16, 17, 18, 15, 19, 15, + 16, 28, 20, 21, 22, 23, 30, 20, 21, 22, 23, 28, 29, 31, 20, 24, 21, + 25, 22, 26, 23, 27, 8, 9, 10, 11, 18, 24, 25, 26, 27, 2, 15, 13, + 20, 21, 22, 23, 0, 17, 4, 5, 6, 7, 29, 3, 19, 14, 30}; + int Ap[] = {0, 3, 5, 6, 8, 14, 19, 21, 23, 25, 27, 31, 33, 34, + 36, 41, 48, 50, 52, 54, 56, 65, 67, 72, 74, 79, 81, 83}; + int nnz = 83; + int n_rows = 27; + int n_cols = 32; + double lhs[] = {0., 0., -INF, -INF, 0., 0., -INF, -INF, -INF, + -INF, 0., 0., -INF, -INF, 0., 44., -INF, -INF, + -INF, -INF, -INF, -INF, -INF, -INF, -INF, -INF, -INF}; + double rhs[] = {0., 0., 80., 0., 0., 0., 80., 0., 0., 0., 0., 0., 500., 0., + 0., 44., 500., 0., 0., 0., 0., 0., 0., 0., 0., 310., 300.}; + double lbs[] = {0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., + 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.}; + double ubs[] = {INF, INF, INF, INF, INF, INF, INF, INF, INF, INF, INF, + INF, INF, INF, INF, INF, INF, INF, INF, INF, INF, INF, + INF, INF, INF, INF, INF, INF, INF, INF, INF, INF}; + double c[] = {0., -0.4, 0., 0., 0., 0., 0., 0., 0., 0., 0., + 0., -0.32, 0., 0., 0., -0.6, 0., 0., 0., 0., 0., + 0., 0., 0., 0., 0., 0., -0.48, 0., 0., 10.}; + + 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); + + run_presolver(presolver); + + PS_FREE(stgs); + free_presolver(presolver); + return 0; +} + static const char *all_tests_presolver() { mu_run_test(test_00_presolver, counter_presolver); + mu_run_test(test_01_presolver, counter_presolver); return 0; } diff --git a/tests/test_SimpleReductions.h b/tests/test_SimpleReductions.h index 0cdb569d..b996c35f 100644 --- a/tests/test_SimpleReductions.h +++ b/tests/test_SimpleReductions.h @@ -1,8 +1,8 @@ #ifndef TEST_SIMPLEREDUCTIONS_H #define TEST_SIMPLEREDUCTIONS_H -#include "API.h" #include "Debugger.h" +#include "PSLP_API.h" #include "minunit.h" #include "test_SimpleReductions.h" diff --git a/tests/test_cpp.cpp b/tests/test_cpp.cpp new file mode 100644 index 00000000..abc8ea27 --- /dev/null +++ b/tests/test_cpp.cpp @@ -0,0 +1,59 @@ +#include "PSLP_API.h" +#include "PSLP_infs.h" +#include +#include + +// run affiro presolver test in C++ +int main() +{ + double Ax[] = { + -1., 1., 1., -1.06, 1., 1., -1., 1.4, -1., -1., -1., + -1., 1., 1., -1.06, -1.06, -0.96, -0.86, 1., 1., -1., 1., + -1., 1., -1., 1., -1., -1., 1., 1., 1., -0.43, 1., + 1., -1., 1.4, -0.43, -0.43, -0.39, -0.37, 1., 1., 1., 1., + 1., -1., 1., 1., 1., -1., 1., -1., 1., -1., 1., + -1., 2.364, 2.386, 2.408, 2.429, -1., 2.191, 2.219, 2.249, 2.279, -1., + 0.109, -1., 0.109, 0.108, 0.108, 0.107, 0.301, -1., 0.301, 0.313, 0.313, + 0.326, -1., 1., 1., 1., 1.}; + int Ai[] = {0, 1, 2, 0, 3, 0, 1, 12, 4, 5, 6, 7, 12, 13, 4, 5, 6, + 7, 14, 4, 8, 5, 9, 6, 10, 7, 11, 15, 16, 17, 18, 15, 19, 15, + 16, 28, 20, 21, 22, 23, 30, 20, 21, 22, 23, 28, 29, 31, 20, 24, 21, + 25, 22, 26, 23, 27, 8, 9, 10, 11, 18, 24, 25, 26, 27, 2, 15, 13, + 20, 21, 22, 23, 0, 17, 4, 5, 6, 7, 29, 3, 19, 14, 30}; + int Ap[] = {0, 3, 5, 6, 8, 14, 19, 21, 23, 25, 27, 31, 33, 34, + 36, 41, 48, 50, 52, 54, 56, 65, 67, 72, 74, 79, 81, 83}; + int nnz = 83; + int n_rows = 27; + int n_cols = 32; + double lhs[] = {0., 0., -PSLP_INF, -PSLP_INF, 0., 0., + -PSLP_INF, -PSLP_INF, -PSLP_INF, -PSLP_INF, 0., 0., + -PSLP_INF, -PSLP_INF, 0., 44., -PSLP_INF, -PSLP_INF, + -PSLP_INF, -PSLP_INF, -PSLP_INF, -PSLP_INF, -PSLP_INF, -PSLP_INF, + -PSLP_INF, -PSLP_INF, -PSLP_INF}; + double rhs[] = {0., 0., 80., 0., 0., 0., 80., 0., 0., 0., 0., 0., 500., 0., + 0., 44., 500., 0., 0., 0., 0., 0., 0., 0., 0., 310., 300.}; + double lbs[] = {0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., + 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.}; + double ubs[] = {PSLP_INF, PSLP_INF, PSLP_INF, PSLP_INF, PSLP_INF, PSLP_INF, + PSLP_INF, PSLP_INF, PSLP_INF, PSLP_INF, PSLP_INF, PSLP_INF, + PSLP_INF, PSLP_INF, PSLP_INF, PSLP_INF, PSLP_INF, PSLP_INF, + PSLP_INF, PSLP_INF, PSLP_INF, PSLP_INF, PSLP_INF, PSLP_INF, + PSLP_INF, PSLP_INF, PSLP_INF, PSLP_INF, PSLP_INF, PSLP_INF, + PSLP_INF, PSLP_INF}; + double c[] = {0., -0.4, 0., 0., 0., 0., 0., 0., 0., 0., 0., + 0., -0.32, 0., 0., 0., -0.6, 0., 0., 0., 0., 0., + 0., 0., 0., 0., 0., 0., -0.48, 0., 0., 10.}; + + Settings *stgs = default_settings(); + Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, + lbs, ubs, c, stgs, true); + assert(presolver != nullptr && "Presolver initialization failed"); + + run_presolver(presolver); + + free_settings(stgs); + free_presolver(presolver); + + std::cout << "Presolver ran succesfully in C++ build" << std::endl; + return 0; +} diff --git a/tests/test_dton.h b/tests/test_dton.h index affbe62c..0bf9bcf8 100644 --- a/tests/test_dton.h +++ b/tests/test_dton.h @@ -1,9 +1,9 @@ #ifndef TEST_DTON_H #define TEST_DTON_H -#include "API.h" #include "DTonsEq.h" #include "Debugger.h" +#include "PSLP_API.h" #include "Problem.h" #include "Workspace.h" diff --git a/tests/test_postsolve.h b/tests/test_postsolve.h index 12046ebe..08821b49 100644 --- a/tests/test_postsolve.h +++ b/tests/test_postsolve.h @@ -1,9 +1,9 @@ #ifndef TEST_POSTSOLVE_H #define TEST_POSTSOLVE_H -#include "API.h" #include "CoreTransformations.h" #include "Numerics.h" +#include "PSLP_API.h" #include "SimpleReductions.h" #include "minunit.h" @@ -45,7 +45,7 @@ static char *test_0_postsolve() Constraints *constraints = prob->constraints; Matrix *A = constraints->A; - presolver_run(presolver); + run_presolver(presolver); Mapping *maps = prob->constraints->state->work->mappings; int *rows_map = maps->rows; @@ -108,7 +108,7 @@ static char *test_1_postsolve() Constraints *constraints = prob->constraints; Matrix *A = constraints->A; - presolver_run(presolver); + run_presolver(presolver); Mapping *maps = prob->constraints->state->work->mappings; int *rows_map = maps->rows; @@ -162,7 +162,7 @@ static char *test_singleton_eq() Constraints *constraints = prob->constraints; Matrix *A = constraints->A; - presolver_run(presolver); + run_presolver(presolver); Mapping *maps = prob->constraints->state->work->mappings; int *rows_map = maps->rows; @@ -223,7 +223,7 @@ static char *test_1_postsolve_implied_bound_active() Constraints *constraints = prob->constraints; Matrix *A = constraints->A; - presolver_run(presolver); + run_presolver(presolver); Mapping *maps = prob->constraints->state->work->mappings; int *rows_map = maps->rows; @@ -276,7 +276,7 @@ static char *test_singleton_ineq_row() Constraints *constraints = prob->constraints; Matrix *A = constraints->A; - presolver_run(presolver); + run_presolver(presolver); Mapping *maps = prob->constraints->state->work->mappings; int *rows_map = maps->rows; @@ -338,7 +338,7 @@ static char *test_2_postsolve() Constraints *constraints = prob->constraints; Matrix *A = constraints->A; - presolver_run(presolver); + run_presolver(presolver); // construct optimal primal solution to reduced problem (computed offline) double x[] = {0.33333333, 0.0, 0.33333333}; @@ -389,7 +389,7 @@ static char *test_implied_free_col_ston_in_inequality_postsolve() Constraints *constraints = prob->constraints; Matrix *A = constraints->A; - presolver_run(presolver); + run_presolver(presolver); // construct optimal primal solution to reduced problem (computed offline) double x[] = {0., 1., 0.}; @@ -441,7 +441,7 @@ static char *test_col_ston_dual_fix() Constraints *constraints = prob->constraints; Matrix *A = constraints->A; - presolver_run(presolver); + run_presolver(presolver); // construct optimal primal solution to reduced problem (computed offline) double x[] = {0., 1.66666667, 0.}; @@ -496,7 +496,7 @@ static char *test_3_postsolve() Constraints *constraints = prob->constraints; Matrix *A = constraints->A; - presolver_run(presolver); + run_presolver(presolver); // construct optimal primal solution to reduced problem (computed offline) double x[] = {1., 0., 0., 4., 0., 6., 0., -10.}; @@ -555,7 +555,7 @@ static char *test_4_postsolve() Constraints *constraints = prob->constraints; Matrix *A = constraints->A; - presolver_run(presolver); + run_presolver(presolver); // construct optimal primal solution to reduced problem (computed offline) double x[] = {-10, 10, -6.75, -10}; @@ -627,7 +627,7 @@ static char *test_6_postsolve() Constraints *constraints = prob->constraints; Matrix *A = constraints->A; - presolver_run(presolver); + run_presolver(presolver); // construct optimal primal solution to reduced problem (computed offline). // on linux vs mac the parallel column kept is different, leading to @@ -693,7 +693,7 @@ static char *test_7_postsolve() Constraints *constraints = prob->constraints; Matrix *A = constraints->A; - presolver_run(presolver); + run_presolver(presolver); // construct optimal primal solution to reduced problem (computed offline) #ifdef __linux__ @@ -764,7 +764,7 @@ static char *test_8_postsolve() Constraints *constraints = prob->constraints; Matrix *A = constraints->A; - presolver_run(presolver); + run_presolver(presolver); Mapping *maps = prob->constraints->state->work->mappings; int *rows_map = maps->rows; @@ -824,7 +824,7 @@ static char *test_9_postsolve() Constraints *constraints = prob->constraints; Matrix *A = constraints->A; - presolver_run(presolver); + run_presolver(presolver); Mapping *maps = prob->constraints->state->work->mappings; int *rows_map = maps->rows; @@ -878,7 +878,7 @@ static char *test_pathological_ston_one() Constraints *constraints = prob->constraints; Matrix *A = constraints->A; - presolver_run(presolver); + run_presolver(presolver); Mapping *maps = prob->constraints->state->work->mappings; int *rows_map = maps->rows; @@ -932,7 +932,7 @@ static char *test_pathological_ston_two() Constraints *constraints = prob->constraints; Matrix *A = constraints->A; - presolver_run(presolver); + run_presolver(presolver); Mapping *maps = prob->constraints->state->work->mappings; int *rows_map = maps->rows; diff --git a/tests/test_ston.h b/tests/test_ston.h index dcc1f55d..ad791c55 100644 --- a/tests/test_ston.h +++ b/tests/test_ston.h @@ -1,8 +1,8 @@ #ifndef TEST_ston_H #define TEST_ston_H -#include "API.h" #include "Debugger.h" +#include "PSLP_API.h" #include "StonCols.h" #include "debug_macros.h" From 44d962a1a0c33169519200b8318033b097dc5189 Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 17 Nov 2025 05:54:40 -0800 Subject: [PATCH 09/17] C++ workflow --- .github/workflows/buildcpp.yml | 2 +- include/core/debug_macros.h | 49 +++++++--- src/explorers/Parallel_rows.c | 2 +- tests/test_Constraints.h | 52 +++++----- tests/test_Matrix.h | 98 ++++++++++--------- tests/test_Parallel_cols.h | 54 +++++------ tests/test_Parallel_rows.h | 146 +++++++++++++++------------- tests/test_SimpleReductions.h | 58 +++++------ tests/test_domain_propagation.h | 64 ++++++------ tests/test_dton.h | 166 ++++++++++++++++---------------- tests/test_macros.h | 4 +- tests/test_ston.h | 164 +++++++++++++++---------------- 12 files changed, 449 insertions(+), 410 deletions(-) diff --git a/.github/workflows/buildcpp.yml b/.github/workflows/buildcpp.yml index 6ba35e49..46cf4f7d 100644 --- a/.github/workflows/buildcpp.yml +++ b/.github/workflows/buildcpp.yml @@ -24,4 +24,4 @@ jobs: run: cmake --build build_cpp --target test_cpp --config ${{ matrix.build_type }} - name: Run C++ test - run: cd build_cpp && ./test_cpp + run: cd build_cpp && ./bin/test_cpp diff --git a/include/core/debug_macros.h b/include/core/debug_macros.h index 85cd48b6..6a67618e 100644 --- a/include/core/debug_macros.h +++ b/include/core/debug_macros.h @@ -19,6 +19,7 @@ #ifndef DEBUG_MACROS_H #define DEBUG_MACROS_H +#include "Tags.h" #include #include @@ -49,19 +50,41 @@ } 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; \ - }) +static inline bool ARRAYS_EQUAL_INT(int *arr1, int *arr2, int size) +{ + for (int i = 0; i < size; i++) + { + if (arr1[i] != arr2[i]) return false; + } + return true; +} + +static inline bool ARRAYS_EQUAL_DOUBLE(double *arr1, double *arr2, int size) +{ + for (int i = 0; i < size; i++) + { + if (arr1[i] != arr2[i]) return false; + } + return true; +} + +static inline bool ARRAYS_EQUAL_ROWTAG(RowTag *arr1, const RowTag *arr2, int size) +{ + for (int i = 0; i < size; i++) + { + if (arr1[i] != arr2[i]) return false; + } + return true; +} + +static inline bool ARRAYS_EQUAL_COLTAG(ColTag *arr1, const ColTag *arr2, int size) +{ + for (int i = 0; i < size; i++) + { + if (arr1[i] != arr2[i]) return false; + } + return true; +} #define ASSERT_NO_INACTIVE_ROWS(rows, row_tags, len) \ do \ diff --git a/src/explorers/Parallel_rows.c b/src/explorers/Parallel_rows.c index 14d0a90a..6b851946 100644 --- a/src/explorers/Parallel_rows.c +++ b/src/explorers/Parallel_rows.c @@ -204,7 +204,7 @@ void VERIFY_PARALLEL_ROWS(const Matrix *A, const RowTag *rows_tags, { 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); + ARRAYS_EQUAL_INT(cols1, A->i + A->p[parallel_rows[j]].start, len1); } // check that all rows have same normalized coefficients diff --git a/tests/test_Constraints.h b/tests/test_Constraints.h index 071eaae6..3de6db60 100644 --- a/tests/test_Constraints.h +++ b/tests/test_Constraints.h @@ -57,8 +57,8 @@ static char *test_1_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)); + mu_assert("error col_sizes", ARRAYS_EQUAL_INT(col_sizes, correct_col_sizes, 4)); + mu_assert("error row_sizes", ARRAYS_EQUAL_INT(row_sizes, correct_row_sizes, 3)); int work_map[4] = {0}; remove_extra_space(A, row_sizes, col_sizes, true, work_map); @@ -67,15 +67,15 @@ static char *test_1_constraints() 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)); + mu_assert("error A", ARRAYS_EQUAL_DOUBLE(A->x, A_vals_correct, 6)); + mu_assert("error A", ARRAYS_EQUAL_INT(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)); + mu_assert("error AT_vals", ARRAYS_EQUAL_DOUBLE(AT->x, AT_vals_correct, 6)); + mu_assert("error AT_cols", ARRAYS_EQUAL_INT(AT->i, AT_cols_correct, 6)); CHECK_ROW_STARTS(AT, AT_row_starts_correct); int correct_up_locks[] = {2, 1, 1, 2}; @@ -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 @@ -138,8 +138,8 @@ static char *test_2_constraints() 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)); + mu_assert("error col_sizes", ARRAYS_EQUAL_INT(col_sizes, correct_col_sizes, 10)); + mu_assert("error row_sizes", ARRAYS_EQUAL_INT(row_sizes, correct_row_sizes, 10)); int work_map[10] = {0}; remove_extra_space(A, row_sizes, col_sizes, true, work_map); @@ -153,8 +153,8 @@ static char *test_2_constraints() 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)); + mu_assert("error A_vals", ARRAYS_EQUAL_DOUBLE(A->x, A_vals_correct, 29)); + mu_assert("error A_cols", ARRAYS_EQUAL_INT(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, @@ -164,8 +164,8 @@ static char *test_2_constraints() 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)); + mu_assert("error AT_vals", ARRAYS_EQUAL_DOUBLE(AT->x, AT_vals_correct, 27)); + mu_assert("error AT_cols", ARRAYS_EQUAL_INT(AT->i, AT_cols_correct, 27)); CHECK_ROW_STARTS(AT, AT_row_starts_correct); // deallocate memory @@ -217,8 +217,8 @@ static char *test_3_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)); + mu_assert("error col_sizes", ARRAYS_EQUAL_INT(col_sizes, correct_col_sizes, 4)); + mu_assert("error row_sizes", ARRAYS_EQUAL_INT(row_sizes, correct_row_sizes, 3)); int work_map[4] = {0}; remove_extra_space(A, row_sizes, col_sizes, true, work_map); @@ -227,15 +227,15 @@ static char *test_3_constraints() 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)); + mu_assert("error A", ARRAYS_EQUAL_DOUBLE(A->x, A_vals_correct, 7)); + mu_assert("error A", ARRAYS_EQUAL_INT(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)); + mu_assert("error AT_vals", ARRAYS_EQUAL_DOUBLE(AT->x, AT_vals_correct, 7)); + mu_assert("error AT_cols", ARRAYS_EQUAL_INT(AT->i, AT_cols_correct, 7)); CHECK_ROW_STARTS(AT, AT_row_starts_correct); // deallocate memory @@ -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 @@ -294,8 +294,8 @@ static char *test_4_constraints() 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)); + mu_assert("error col_sizes", ARRAYS_EQUAL_INT(col_sizes, correct_col_sizes, 10)); + mu_assert("error row_sizes", ARRAYS_EQUAL_INT(row_sizes, correct_row_sizes, 10)); int work_map[10] = {0}; remove_extra_space(A, row_sizes, col_sizes, true, work_map); @@ -309,8 +309,8 @@ static char *test_4_constraints() 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)); + mu_assert("error A_vals", ARRAYS_EQUAL_DOUBLE(A->x, A_vals_correct, 29)); + mu_assert("error A_cols", ARRAYS_EQUAL_INT(A->i, A_cols_correct, 29)); CHECK_ROW_STARTS(A, A_row_starts_correct); double AT_vals_correct[] = { @@ -320,8 +320,8 @@ static char *test_4_constraints() 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)); + mu_assert("error AT_vals", ARRAYS_EQUAL_DOUBLE(AT->x, AT_vals_correct, 29)); + mu_assert("error AT_cols", ARRAYS_EQUAL_INT(AT->i, AT_cols_correct, 29)); CHECK_ROW_STARTS(AT, AT_row_starts_correct); // deallocate memory diff --git a/tests/test_Matrix.h b/tests/test_Matrix.h index 4560d089..8e0dfa9c 100644 --- a/tests/test_Matrix.h +++ b/tests/test_Matrix.h @@ -61,8 +61,9 @@ static char *test_1_matrix() 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)); + mu_assert("error, vals not equal", + ARRAYS_EQUAL_DOUBLE(AT_vals_correct, AT->x, 11)); + mu_assert("error, cols not equal", ARRAYS_EQUAL_INT(AT_cols_correct, AT->i, 11)); CHECK_ROW_STARTS(AT, AT_row_starts_correct); free_matrix(A); @@ -118,7 +119,7 @@ static char *test_3_matrix() // 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)); + mu_assert("error, vals not equal", ARRAYS_EQUAL_DOUBLE(vals_correct, A->x, 18)); free_matrix(A); return 0; @@ -146,7 +147,7 @@ static char *test_4_matrix() // 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)); + mu_assert("error, vals not equal", ARRAYS_EQUAL_DOUBLE(vals_correct, A->x, 18)); int row_starts_correct[] = {0, 8, 11, 14, 18}; int row_ends_correct[] = {3, 11, 13, 16, 18}; @@ -203,10 +204,10 @@ static char *test_6_matrix() // 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}; + int 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)); + mu_assert("error, vals not equal", ARRAYS_EQUAL_DOUBLE(vals_correct, A->x, 18)); + mu_assert("error, cols not equal", ARRAYS_EQUAL_INT(cols_correct, A->i, 18)); int row_starts_correct[] = {0, 5, 9, 11, 18}; int row_ends_correct[] = {3, 8, 11, 13, 18}; @@ -242,11 +243,11 @@ static char *test_7_matrix() // 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}; + int 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)); + mu_assert("error, vals not equal", ARRAYS_EQUAL_DOUBLE(vals_correct, A->x, 18)); + mu_assert("error, cols not equal", ARRAYS_EQUAL_INT(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}; @@ -282,11 +283,11 @@ static char *test_8_matrix() // 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}; + int 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)); + mu_assert("error, vals not equal", ARRAYS_EQUAL_DOUBLE(vals_correct, A->x, 18)); + mu_assert("error, cols not equal", ARRAYS_EQUAL_INT(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}; @@ -326,12 +327,12 @@ static char *test_9_matrix() 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, + int 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)); + mu_assert("error, vals not equal", ARRAYS_EQUAL_DOUBLE(vals_correct, A->x, 18)); + mu_assert("error, cols not equal", ARRAYS_EQUAL_INT(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}; @@ -373,11 +374,11 @@ static char *test_10_matrix() // 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}; + int 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)); + mu_assert("error, vals not equal", ARRAYS_EQUAL_DOUBLE(vals_correct, A->x, 18)); + mu_assert("error, cols not equal", ARRAYS_EQUAL_INT(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}; @@ -419,11 +420,11 @@ static char *test_11_matrix() // 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}; + int 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)); + mu_assert("error, vals not equal", ARRAYS_EQUAL_DOUBLE(vals_correct, A->x, 18)); + mu_assert("error, cols not equal", ARRAYS_EQUAL_INT(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}; @@ -461,11 +462,11 @@ static char *test_12_matrix() // 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}; + int 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)); + mu_assert("error, vals not equal", ARRAYS_EQUAL_DOUBLE(vals_correct, A->x, 18)); + mu_assert("error, cols not equal", ARRAYS_EQUAL_INT(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}; @@ -503,11 +504,11 @@ static char *test_13_matrix() // 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}; + int 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)); + mu_assert("error, vals not equal", ARRAYS_EQUAL_DOUBLE(vals_correct, A->x, 18)); + mu_assert("error, cols not equal", ARRAYS_EQUAL_INT(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}; @@ -566,7 +567,7 @@ static char *test_14_matrix() 20, 0, 0, 21, 22, 0, 0}; - mu_assert("error, vals not equal", ARRAYS_EQUAL(vals_correct, A->x, 18)); + mu_assert("error, vals not equal", ARRAYS_EQUAL_DOUBLE(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}; @@ -622,10 +623,11 @@ static char *test_15_matrix() // 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)); + int cols_correct[] = {0, 1, 2, 0, 2, 0, 2, 1, 2, 3}; + mu_assert("error, vals not equal", + ARRAYS_EQUAL_DOUBLE(vals_correct, A->x, A->nnz)); - mu_assert("error, cols not equal", ARRAYS_EQUAL(cols_correct, A->i, A->nnz)); + mu_assert("error, cols not equal", ARRAYS_EQUAL_INT(cols_correct, A->i, A->nnz)); int row_starts_correct[] = {0, 3, 5, 7, 10}; int row_ends_correct[] = {3, 5, 7, 10, 10}; @@ -667,10 +669,11 @@ static char *test_16_matrix() // 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)); + int cols_correct[] = {0, 1, 2, 0, 1, 2, 0, 2, 3}; + mu_assert("error, vals not equal", + ARRAYS_EQUAL_DOUBLE(vals_correct, A->x, A->nnz)); - mu_assert("error, cols not equal", ARRAYS_EQUAL(cols_correct, A->i, A->nnz)); + mu_assert("error, cols not equal", ARRAYS_EQUAL_INT(cols_correct, A->i, A->nnz)); int row_starts_correct[] = {0, 3, 6, 7, 9}; int row_ends_correct[] = {3, 6, 7, 9, 9}; @@ -709,10 +712,11 @@ static char *test_17_matrix() 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)); + int cols_correct[] = {0, 2, 3, 0, 1, 0, 3}; + mu_assert("error, vals not equal", + ARRAYS_EQUAL_DOUBLE(vals_correct, A->x, A->nnz)); - mu_assert("error, cols not equal", ARRAYS_EQUAL(cols_correct, A->i, A->nnz)); + mu_assert("error, cols not equal", ARRAYS_EQUAL_INT(cols_correct, A->i, A->nnz)); int row_starts_correct[] = {0, 3, 5, 7}; int row_ends_correct[] = {3, 5, 7, 7}; @@ -754,9 +758,9 @@ static char *test_18_matrix() 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, vals not equal", ARRAYS_EQUAL_DOUBLE(vals, A->x, A->nnz)); - mu_assert("error, cols not equal", ARRAYS_EQUAL(cols, A->i, A->nnz)); + mu_assert("error, cols not equal", ARRAYS_EQUAL_INT(cols, A->i, A->nnz)); int row_ends_correct[] = {3, 6, 8, 8}; diff --git a/tests/test_Parallel_cols.h b/tests/test_Parallel_cols.h index 822123a4..0397f468 100644 --- a/tests/test_Parallel_cols.h +++ b/tests/test_Parallel_cols.h @@ -44,8 +44,8 @@ static char *test_1_parallel_cols() 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax_correct, A->x, 2)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(Ai_correct, A->i, 2)); CHECK_ROW_STARTS(A, Ap_correct); // check that new variable bounds are correct @@ -56,7 +56,7 @@ static char *test_1_parallel_cols() // 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 obj", ARRAYS_EQUAL_DOUBLE(obj_correct, prob->obj->c, 2)); mu_assert("error offset", prob->obj->offset == 0); PS_FREE(stgs); @@ -99,8 +99,8 @@ static char *test_2_parallel_cols() 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax_correct, A->x, 2)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(Ai_correct, A->i, 2)); CHECK_ROW_STARTS(A, Ap_correct); // check that new variable bounds are correct @@ -111,7 +111,7 @@ static char *test_2_parallel_cols() // 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 obj", ARRAYS_EQUAL_DOUBLE(obj_correct, prob->obj->c, 2)); mu_assert("error offset", prob->obj->offset == 0); PS_FREE(stgs); @@ -154,8 +154,8 @@ static char *test_3_parallel_cols() 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax_correct, A->x, 2)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(Ai_correct, A->i, 2)); CHECK_ROW_STARTS(A, Ap_correct); // check that new variable bounds are correct @@ -166,7 +166,7 @@ static char *test_3_parallel_cols() // 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 obj", ARRAYS_EQUAL_DOUBLE(obj_correct, prob->obj->c, 2)); mu_assert("error offset", prob->obj->offset == 0); PS_FREE(stgs); @@ -209,8 +209,8 @@ static char *test_4_parallel_cols() 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax_correct, A->x, 2)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(Ai_correct, A->i, 2)); CHECK_ROW_STARTS(A, Ap_correct); // check that new variable bounds are correct @@ -221,7 +221,7 @@ static char *test_4_parallel_cols() // 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 obj", ARRAYS_EQUAL_DOUBLE(obj_correct, prob->obj->c, 2)); mu_assert("error offset", prob->obj->offset == 0); PS_FREE(stgs); @@ -264,8 +264,8 @@ static char *test_5_parallel_cols() 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax_correct, A->x, 2)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(Ai_correct, A->i, 2)); CHECK_ROW_STARTS(A, Ap_correct); // check that new variable bounds are correct @@ -276,7 +276,7 @@ static char *test_5_parallel_cols() // 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 obj", ARRAYS_EQUAL_DOUBLE(obj_correct, prob->obj->c, 2)); mu_assert("error offset", prob->obj->offset == 0); PS_FREE(stgs); @@ -319,8 +319,8 @@ static char *test_6_parallel_cols() 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax_correct, A->x, 1)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(Ai_correct, A->i, 1)); CHECK_ROW_STARTS(A, Ap_correct); // check that new variable bounds are correct @@ -331,7 +331,7 @@ static char *test_6_parallel_cols() // check that the objective function is correct double obj_correct[] = {2}; - mu_assert("error obj", ARRAYS_EQUAL(obj_correct, prob->obj->c, 1)); + mu_assert("error obj", ARRAYS_EQUAL_DOUBLE(obj_correct, prob->obj->c, 1)); PS_FREE(stgs); DEBUG(run_debugger(constraints, false)); @@ -373,8 +373,8 @@ static char *test_5_negated_parallel_cols() 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax_correct, A->x, 2)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(Ai_correct, A->i, 2)); CHECK_ROW_STARTS(A, Ap_correct); // check that new variable bounds are correct @@ -385,7 +385,7 @@ static char *test_5_negated_parallel_cols() // 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 obj", ARRAYS_EQUAL_DOUBLE(obj_correct, prob->obj->c, 2)); mu_assert("error offset", prob->obj->offset == 0); PS_FREE(stgs); @@ -428,8 +428,8 @@ static char *test_6_negated_parallel_cols() 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax_correct, A->x, 2)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(Ai_correct, A->i, 2)); CHECK_ROW_STARTS(A, Ap_correct); // check that new variable bounds are correct @@ -440,7 +440,7 @@ static char *test_6_negated_parallel_cols() // 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 obj", ARRAYS_EQUAL_DOUBLE(obj_correct, prob->obj->c, 2)); mu_assert("error offset", prob->obj->offset == 0); PS_FREE(stgs); @@ -552,8 +552,8 @@ static char *test_8_parallel_cols() // 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax_correct, A->x, A->nnz)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(Ai_correct, A->i, A->nnz)); CHECK_ROW_STARTS(A, Ap_correct); // check that new variable bounds are correct @@ -564,7 +564,7 @@ static char *test_8_parallel_cols() // 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 obj", ARRAYS_EQUAL_DOUBLE(obj_correct, prob->obj->c, 6)); mu_assert("error offset", prob->obj->offset == 0); #endif // __linux__ diff --git a/tests/test_Parallel_rows.h b/tests/test_Parallel_rows.h index b669b8d1..1d970529 100644 --- a/tests/test_Parallel_rows.h +++ b/tests/test_Parallel_rows.h @@ -107,9 +107,9 @@ static char *test_3_parallel_rows() 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", ARRAYS_EQUAL_INT(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)); + mu_assert("error", ARRAYS_EQUAL_INT(group_start_correct, group_start->data, 3)); free_matrix(A); iVec_free(group_start); @@ -207,7 +207,7 @@ static char *test_4_parallel_rows() 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)); + ARRAYS_EQUAL_INT(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 @@ -228,17 +228,19 @@ static char *test_4_parallel_rows() if (sorted[0] == 0) { - mu_assert("error first group", ARRAYS_EQUAL(sorted, first_group_correct, 8)); + mu_assert("error first group", + ARRAYS_EQUAL_INT(sorted, first_group_correct, 8)); } else if (sorted[0] == 10) { mu_assert("error first group", - ARRAYS_EQUAL(sorted, second_group_correct, 8)); + ARRAYS_EQUAL_INT(sorted, second_group_correct, 8)); } else { assert(sorted[0] == 20); - mu_assert("error first group", ARRAYS_EQUAL(sorted, third_group_correct, 8)); + mu_assert("error first group", + ARRAYS_EQUAL_INT(sorted, third_group_correct, 8)); } // check that next 8 entries of parallell rows corresponds to one of the @@ -251,17 +253,19 @@ static char *test_4_parallel_rows() if (sorted[0] == 0) { - mu_assert("error first group", ARRAYS_EQUAL(sorted, first_group_correct, 8)); + mu_assert("error first group", + ARRAYS_EQUAL_INT(sorted, first_group_correct, 8)); } else if (sorted[0] == 10) { mu_assert("error first group", - ARRAYS_EQUAL(sorted, second_group_correct, 8)); + ARRAYS_EQUAL_INT(sorted, second_group_correct, 8)); } else { assert(sorted[0] == 20); - mu_assert("error first group", ARRAYS_EQUAL(sorted, third_group_correct, 8)); + mu_assert("error first group", + ARRAYS_EQUAL_INT(sorted, third_group_correct, 8)); } // check that next 8 entries of parallell rows corresponds to one of the @@ -274,17 +278,19 @@ static char *test_4_parallel_rows() if (sorted[0] == 0) { - mu_assert("error first group", ARRAYS_EQUAL(sorted, first_group_correct, 8)); + mu_assert("error first group", + ARRAYS_EQUAL_INT(sorted, first_group_correct, 8)); } else if (sorted[0] == 10) { mu_assert("error first group", - ARRAYS_EQUAL(sorted, second_group_correct, 8)); + ARRAYS_EQUAL_INT(sorted, second_group_correct, 8)); } else { assert(sorted[0] == 20); - mu_assert("error first group", ARRAYS_EQUAL(sorted, third_group_correct, 8)); + mu_assert("error first group", + ARRAYS_EQUAL_INT(sorted, third_group_correct, 8)); } free_matrix(A); @@ -372,7 +378,7 @@ static char *test_5_parallel_rows() 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)); + // ARRAYS_EQUAL_INT(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}; @@ -395,17 +401,19 @@ static char *test_5_parallel_rows() if (sorted[0] == 10) { - mu_assert("error first group", ARRAYS_EQUAL(sorted, first_group_correct, 6)); + mu_assert("error first group", + ARRAYS_EQUAL_INT(sorted, first_group_correct, 6)); } else if (sorted[0] == 20) { mu_assert("error first group", - ARRAYS_EQUAL(sorted, second_group_correct, 8)); + ARRAYS_EQUAL_INT(sorted, second_group_correct, 8)); } else { assert(sorted[0] == 50); - mu_assert("error first group", ARRAYS_EQUAL(sorted, third_group_correct, 6)); + mu_assert("error first group", + ARRAYS_EQUAL_INT(sorted, third_group_correct, 6)); } // check that next entries of parallell rows corresponds to one of the @@ -418,17 +426,19 @@ static char *test_5_parallel_rows() if (sorted[0] == 10) { - mu_assert("error first group", ARRAYS_EQUAL(sorted, first_group_correct, 6)); + mu_assert("error first group", + ARRAYS_EQUAL_INT(sorted, first_group_correct, 6)); } else if (sorted[0] == 20) { mu_assert("error first group", - ARRAYS_EQUAL(sorted, second_group_correct, 8)); + ARRAYS_EQUAL_INT(sorted, second_group_correct, 8)); } else { assert(sorted[0] == 50); - mu_assert("error first group", ARRAYS_EQUAL(sorted, third_group_correct, 6)); + mu_assert("error first group", + ARRAYS_EQUAL_INT(sorted, third_group_correct, 6)); } // check that next entries of parallell rows corresponds to one of the @@ -441,17 +451,19 @@ static char *test_5_parallel_rows() if (sorted[0] == 10) { - mu_assert("error first group", ARRAYS_EQUAL(sorted, first_group_correct, 6)); + mu_assert("error first group", + ARRAYS_EQUAL_INT(sorted, first_group_correct, 6)); } else if (sorted[0] == 20) { mu_assert("error first group", - ARRAYS_EQUAL(sorted, second_group_correct, 8)); + ARRAYS_EQUAL_INT(sorted, second_group_correct, 8)); } else { assert(sorted[0] == 50); - mu_assert("error first group", ARRAYS_EQUAL(sorted, third_group_correct, 6)); + mu_assert("error first group", + ARRAYS_EQUAL_INT(sorted, third_group_correct, 6)); } free_matrix(A); @@ -491,8 +503,8 @@ static char *test_6_parallel_rows() 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax, A->x, nnz)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(Ai, A->i, nnz)); CHECK_ROW_STARTS(A, Ap); PS_FREE(stgs); @@ -533,8 +545,8 @@ static char *test_7_parallel_rows() 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax, A->x, nnz)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(Ai, A->i, nnz)); CHECK_ROW_STARTS(A, Ap); PS_FREE(stgs); @@ -575,8 +587,8 @@ static char *test_8_parallel_rows() 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax, A->x, nnz)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(Ai, A->i, nnz)); CHECK_ROW_STARTS(A, Ap); PS_FREE(stgs); @@ -617,8 +629,8 @@ static char *test_9_parallel_rows() 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax, A->x, nnz)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(Ai, A->i, nnz)); CHECK_ROW_STARTS(A, Ap); PS_FREE(stgs); @@ -659,8 +671,8 @@ static char *test_10_parallel_rows() 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax, A->x, nnz)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(Ai, A->i, nnz)); CHECK_ROW_STARTS(A, Ap); PS_FREE(stgs); @@ -701,8 +713,8 @@ static char *test_11_parallel_rows() 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax, A->x, nnz)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(Ai, A->i, nnz)); CHECK_ROW_STARTS(A, Ap); PS_FREE(stgs); @@ -743,8 +755,8 @@ static char *test_12_parallel_rows() 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax, A->x, nnz)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(Ai, A->i, nnz)); CHECK_ROW_STARTS(A, Ap); PS_FREE(stgs); @@ -785,8 +797,8 @@ static char *test_13_parallel_rows() 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax, A->x, nnz)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(Ai, A->i, nnz)); CHECK_ROW_STARTS(A, Ap); PS_FREE(stgs); @@ -918,25 +930,25 @@ static char *test_14_parallel_rows() 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}; + int 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]}; + int 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax_correct, A->x, 12)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(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)); + ARRAYS_EQUAL_ROWTAG(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)); + mu_assert("error lhs", ARRAYS_EQUAL_DOUBLE(lhs_correct, constraints->lhs, 3)); + mu_assert("error rhs", ARRAYS_EQUAL_DOUBLE(rhs_correct, constraints->rhs, 3)); PS_FREE(stgs); DEBUG(run_debugger(constraints, false)); @@ -1070,26 +1082,26 @@ static char *test_15_parallel_rows() 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}; + int 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]}; + int 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax_correct, A->x, 15)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(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)); + ARRAYS_EQUAL_ROWTAG(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)); + mu_assert("error lhs", ARRAYS_EQUAL_DOUBLE(lhs_correct, constraints->lhs, 4)); + mu_assert("error rhs", ARRAYS_EQUAL_DOUBLE(rhs_correct, constraints->rhs, 4)); PS_FREE(stgs); DEBUG(run_debugger(constraints, false)); @@ -1219,26 +1231,26 @@ static char *test_16_parallel_rows() -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}; + int 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]}; + int 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax_correct, A->x, 15)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(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)); + ARRAYS_EQUAL_ROWTAG(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)); + mu_assert("error lhs", ARRAYS_EQUAL_DOUBLE(lhs_correct, constraints->lhs, 4)); + mu_assert("error rhs", ARRAYS_EQUAL_DOUBLE(rhs_correct, constraints->rhs, 4)); PS_FREE(stgs); DEBUG(run_debugger(constraints, false)); diff --git a/tests/test_SimpleReductions.h b/tests/test_SimpleReductions.h index b996c35f..5ec90d58 100644 --- a/tests/test_SimpleReductions.h +++ b/tests/test_SimpleReductions.h @@ -84,15 +84,15 @@ static char *test_4_simple() 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax_correct, A->x, 10)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(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)); + mu_assert("error lhs", ARRAYS_EQUAL_DOUBLE(lhs_correct, constraints->lhs, 3)); + mu_assert("error rhs", ARRAYS_EQUAL_DOUBLE(rhs_correct, constraints->rhs, 3)); PS_FREE(stgs); DEBUG(run_debugger(constraints, false)); @@ -133,20 +133,20 @@ static char *test_11_simple() 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax, A->x, 11)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(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)); + ARRAYS_EQUAL_ROWTAG(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)); + mu_assert("error lhs", ARRAYS_EQUAL_DOUBLE(lhs_correct, constraints->lhs, 3)); + mu_assert("error rhs", ARRAYS_EQUAL_DOUBLE(rhs_correct, constraints->rhs, 3)); PS_FREE(stgs); DEBUG(run_debugger(constraints, false)); @@ -187,20 +187,20 @@ static char *test_13_simple() 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax, A->x, 11)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(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)); + ARRAYS_EQUAL_ROWTAG(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)); + mu_assert("error lhs", ARRAYS_EQUAL_DOUBLE(lhs_correct, constraints->lhs, 3)); + mu_assert("error rhs", ARRAYS_EQUAL_DOUBLE(rhs_correct, constraints->rhs, 3)); PS_FREE(stgs); DEBUG(run_debugger(constraints, false)); @@ -245,20 +245,20 @@ static char *test_14_simple() 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax_correct, A->x, 8)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(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)); + ARRAYS_EQUAL_ROWTAG(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)); + mu_assert("error lhs", ARRAYS_EQUAL_DOUBLE(lhs_correct, constraints->lhs, 2)); + mu_assert("error rhs", ARRAYS_EQUAL_DOUBLE(rhs_correct, constraints->rhs, 2)); PS_FREE(stgs); DEBUG(run_debugger(constraints, false)); @@ -303,20 +303,20 @@ static char *test_15_simple() 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax_correct, A->x, 8)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(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)); + ARRAYS_EQUAL_ROWTAG(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)); + mu_assert("error lhs", ARRAYS_EQUAL_DOUBLE(lhs_correct, constraints->lhs, 2)); + mu_assert("error rhs", ARRAYS_EQUAL_DOUBLE(rhs_correct, constraints->rhs, 2)); PS_FREE(stgs); DEBUG(run_debugger(constraints, false)); @@ -361,20 +361,20 @@ static char *test_16_simple() 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax_correct, A->x, 8)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(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)); + ARRAYS_EQUAL_ROWTAG(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)); + mu_assert("error lhs", ARRAYS_EQUAL_DOUBLE(lhs_correct, constraints->lhs, 2)); + mu_assert("error rhs", ARRAYS_EQUAL_DOUBLE(rhs_correct, constraints->rhs, 2)); PS_FREE(stgs); DEBUG(run_debugger(constraints, false)); diff --git a/tests/test_domain_propagation.h b/tests/test_domain_propagation.h index 4e6b46aa..4f9f40df 100644 --- a/tests/test_domain_propagation.h +++ b/tests/test_domain_propagation.h @@ -75,8 +75,8 @@ static char *test_1_domain() 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax_correct, A->x, 8)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(Ai_correct, A->i, 8)); CHECK_ROW_STARTS(A, Ap_correct); // check that new variable bounds are correct @@ -88,8 +88,8 @@ static char *test_1_domain() // 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)); + mu_assert("error lhs", ARRAYS_EQUAL_DOUBLE(lhs_correct, constraints->lhs, 4)); + mu_assert("error rhs", ARRAYS_EQUAL_DOUBLE(rhs_correct, constraints->rhs, 4)); PS_FREE(stgs); DEBUG(run_debugger(constraints, false)); @@ -139,8 +139,8 @@ static char *test_2_domain() 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax_correct, A->x, 6)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(Ai_correct, A->i, 6)); CHECK_ROW_STARTS(A, Ap_correct); // check that new variable bounds are correct @@ -155,8 +155,8 @@ static char *test_2_domain() // 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)); + mu_assert("error lhs", ARRAYS_EQUAL_DOUBLE(lhs_correct, constraints->lhs, 3)); + mu_assert("error rhs", ARRAYS_EQUAL_DOUBLE(rhs_correct, constraints->rhs, 3)); PS_FREE(stgs); DEBUG(run_debugger(constraints, false)); @@ -205,8 +205,8 @@ static char *test_3_domain() 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax_correct, A->x, 6)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(Ai_correct, A->i, 6)); CHECK_ROW_STARTS(A, Ap_correct); // check that new variable bounds are correct @@ -221,8 +221,8 @@ static char *test_3_domain() // 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)); + mu_assert("error lhs", ARRAYS_EQUAL_DOUBLE(lhs_correct, constraints->lhs, 3)); + mu_assert("error rhs", ARRAYS_EQUAL_DOUBLE(rhs_correct, constraints->rhs, 3)); PS_FREE(stgs); DEBUG(run_debugger(constraints, false)); @@ -272,8 +272,8 @@ static char *test_4_domain() 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax_correct, A->x, 6)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(Ai_correct, A->i, 6)); CHECK_ROW_STARTS(A, Ap_correct); // check that new variable bounds are correct @@ -288,8 +288,8 @@ static char *test_4_domain() // 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)); + mu_assert("error lhs", ARRAYS_EQUAL_DOUBLE(lhs_correct, constraints->lhs, 3)); + mu_assert("error rhs", ARRAYS_EQUAL_DOUBLE(rhs_correct, constraints->rhs, 3)); PS_FREE(stgs); DEBUG(run_debugger(constraints, false)); @@ -338,8 +338,8 @@ static char *test_5_domain() 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax_correct, A->x, 6)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(Ai_correct, A->i, 6)); CHECK_ROW_STARTS(A, Ap_correct); // check that new variable bounds are correct @@ -354,8 +354,8 @@ static char *test_5_domain() // 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)); + mu_assert("error lhs", ARRAYS_EQUAL_DOUBLE(lhs_correct, constraints->lhs, 3)); + mu_assert("error rhs", ARRAYS_EQUAL_DOUBLE(rhs_correct, constraints->rhs, 3)); PS_FREE(stgs); DEBUG(run_debugger(constraints, false)); @@ -405,8 +405,8 @@ static char *test_6_domain() 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax_correct, A->x, 6)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(Ai_correct, A->i, 6)); CHECK_ROW_STARTS(A, Ap_correct); // check that new variable bounds are correct @@ -421,8 +421,8 @@ static char *test_6_domain() // 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)); + mu_assert("error lhs", ARRAYS_EQUAL_DOUBLE(lhs_correct, constraints->lhs, 3)); + mu_assert("error rhs", ARRAYS_EQUAL_DOUBLE(rhs_correct, constraints->rhs, 3)); PS_FREE(stgs); DEBUG(run_debugger(constraints, false)); @@ -471,8 +471,8 @@ static char *test_7_domain() 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax_correct, A->x, 6)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(Ai_correct, A->i, 6)); CHECK_ROW_STARTS(A, Ap_correct); // check that new variable bounds are correct @@ -487,8 +487,8 @@ static char *test_7_domain() // 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)); + mu_assert("error lhs", ARRAYS_EQUAL_DOUBLE(lhs_correct, constraints->lhs, 3)); + mu_assert("error rhs", ARRAYS_EQUAL_DOUBLE(rhs_correct, constraints->rhs, 3)); PS_FREE(stgs); DEBUG(run_debugger(constraints, false)); @@ -537,8 +537,8 @@ static char *test_8_domain() 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax_correct, A->x, 6)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(Ai_correct, A->i, 6)); CHECK_ROW_STARTS(A, Ap_correct); // check that new variable bounds are correct @@ -553,8 +553,8 @@ static char *test_8_domain() // 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)); + mu_assert("error lhs", ARRAYS_EQUAL_DOUBLE(lhs_correct, constraints->lhs, 3)); + mu_assert("error rhs", ARRAYS_EQUAL_DOUBLE(rhs_correct, constraints->rhs, 3)); PS_FREE(stgs); DEBUG(run_debugger(constraints, false)); diff --git a/tests/test_dton.h b/tests/test_dton.h index 0bf9bcf8..f9173a9e 100644 --- a/tests/test_dton.h +++ b/tests/test_dton.h @@ -41,7 +41,7 @@ static char *test_00_dton() // 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)); + mu_assert("error row_sizes", ARRAYS_EQUAL_INT(row_sizes_correct, row_sizes, 3)); int col_sizes[] = {SIZE_INACTIVE_COL, 2, 3, 3}; int map[4] = {0}; @@ -51,8 +51,8 @@ static char *test_00_dton() 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax_correct, A->x, 8)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(Ai_correct, A->i, 8)); CHECK_ROW_STARTS(A, Ap_correct); mu_assert("wrong nnz", A->nnz == 8); @@ -90,7 +90,7 @@ static char *test_01_dton() // 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)); + mu_assert("error row_sizes", ARRAYS_EQUAL_INT(row_sizes_correct, row_sizes, 3)); int col_sizes[] = {3, 3, SIZE_INACTIVE_COL, 2, 3, 3}; int map[6] = {0}; @@ -100,8 +100,8 @@ static char *test_01_dton() 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax_correct, A->x, 14)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(Ai_correct, A->i, 14)); CHECK_ROW_STARTS(A, Ap_correct); mu_assert("wrong nnz", A->nnz == 14); @@ -139,7 +139,7 @@ static char *test_02_dton() // 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)); + mu_assert("error row_sizes", ARRAYS_EQUAL_INT(row_sizes_correct, row_sizes, 3)); int col_sizes[] = {3, 3, 3, 3, SIZE_INACTIVE_COL, 2}; int map[6] = {0}; @@ -149,8 +149,8 @@ static char *test_02_dton() 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax_correct, A->x, 14)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(Ai_correct, A->i, 14)); CHECK_ROW_STARTS(A, Ap_correct); mu_assert("wrong nnz", A->nnz == 14); @@ -187,7 +187,7 @@ static char *test_03_dton() // 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)); + mu_assert("error row_sizes", ARRAYS_EQUAL_INT(row_sizes_correct, row_sizes, 3)); int col_sizes[] = {2, SIZE_INACTIVE_COL, 3, 3}; int map[4] = {0}; @@ -197,8 +197,8 @@ static char *test_03_dton() 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax_correct, A->x, 8)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(Ai_correct, A->i, 8)); CHECK_ROW_STARTS(A, Ap_correct); mu_assert("wrong nnz", A->nnz == 8); @@ -235,7 +235,7 @@ static char *test_04_dton() // 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)); + mu_assert("error row_sizes", ARRAYS_EQUAL_INT(row_sizes_correct, row_sizes, 3)); int col_sizes[] = {3, 3, 2, SIZE_INACTIVE_COL, 3, 3}; int map[6] = {0}; @@ -245,8 +245,8 @@ static char *test_04_dton() 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax_correct, A->x, 14)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(Ai_correct, A->i, 14)); CHECK_ROW_STARTS(A, Ap_correct); mu_assert("wrong nnz", A->nnz == 14); @@ -283,7 +283,7 @@ static char *test_05_dton() // 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)); + mu_assert("error row_sizes", ARRAYS_EQUAL_INT(row_sizes_correct, row_sizes, 3)); int col_sizes[] = {3, 3, 3, 3, 2, SIZE_INACTIVE_COL}; int map[6] = {0}; @@ -293,8 +293,8 @@ static char *test_05_dton() 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax_correct, A->x, 14)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(Ai_correct, A->i, 14)); CHECK_ROW_STARTS(A, Ap_correct); mu_assert("wrong nnz", A->nnz == 14); @@ -335,7 +335,7 @@ static char *test_06_dton() // 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)); + mu_assert("error row_sizes", ARRAYS_EQUAL_INT(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); @@ -344,8 +344,8 @@ static char *test_06_dton() 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax_correct, A->x, 8)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(Ai_correct, A->i, 8)); CHECK_ROW_STARTS(A, Ap_correct); mu_assert("wrong nnz", A->nnz == 8); @@ -397,8 +397,8 @@ static char *test_1_dton() 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax_correct, A->x, 7)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(Ai_correct, A->i, 7)); CHECK_ROW_STARTS(A, Ap_correct); // check new AT @@ -408,7 +408,7 @@ static char *test_1_dton() 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)); + ARRAYS_EQUAL_COLTAG(col_tags_correct, constraints->col_tags, 4)); // check that new variable bounds are correct double lbs_correct[] = {0.7, 0, 0, 0}; @@ -418,14 +418,14 @@ static char *test_1_dton() // 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", ARRAYS_EQUAL_DOUBLE(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)); + mu_assert("error lhs", ARRAYS_EQUAL_DOUBLE(lhs_correct, constraints->lhs, 2)); + mu_assert("error rhs", ARRAYS_EQUAL_DOUBLE(rhs_correct, constraints->rhs, 2)); PS_FREE(stgs); DEBUG(run_debugger(constraints, false)); @@ -483,8 +483,8 @@ static char *test_2_dton() 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax_correct, A->x, 7)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(Ai_correct, A->i, 7)); CHECK_ROW_STARTS(A, Ap_correct); // check new AT @@ -498,14 +498,14 @@ static char *test_2_dton() // 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", ARRAYS_EQUAL_DOUBLE(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)); + mu_assert("error lhs", ARRAYS_EQUAL_DOUBLE(lhs_correct, constraints->lhs, 2)); + mu_assert("error rhs", ARRAYS_EQUAL_DOUBLE(rhs_correct, constraints->rhs, 2)); */ // PS_FREE(stgs); @@ -559,8 +559,8 @@ static char *test_3_dton() 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax_correct, A->x, 6)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(Ai_correct, A->i, 6)); CHECK_ROW_STARTS(A, Ap_correct); // check new AT @@ -574,14 +574,14 @@ static char *test_3_dton() // 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", ARRAYS_EQUAL_DOUBLE(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)); + mu_assert("error lhs", ARRAYS_EQUAL_DOUBLE(lhs_correct, constraints->lhs, 2)); + mu_assert("error rhs", ARRAYS_EQUAL_DOUBLE(rhs_correct, constraints->rhs, 2)); PS_FREE(stgs); DEBUG(run_debugger(constraints, false)); @@ -635,8 +635,8 @@ static char *test_004_dton() 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax_correct, A->x, 7)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(Ai_correct, A->i, 7)); CHECK_ROW_STARTS(A, Ap_correct); // check new AT @@ -650,14 +650,14 @@ static char *test_004_dton() // 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", ARRAYS_EQUAL_DOUBLE(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)); + mu_assert("error lhs", ARRAYS_EQUAL_DOUBLE(lhs_correct, constraints->lhs, 3)); + mu_assert("error rhs", ARRAYS_EQUAL_DOUBLE(rhs_correct, constraints->rhs, 3)); PS_FREE(stgs); DEBUG(run_debugger(constraints, false)); @@ -711,8 +711,8 @@ static char *test_4_dton() 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax_correct, A->x, 6)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(Ai_correct, A->i, 6)); CHECK_ROW_STARTS(A, Ap_correct); // check new AT @@ -726,14 +726,14 @@ static char *test_4_dton() // 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", ARRAYS_EQUAL_DOUBLE(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)); + mu_assert("error lhs", ARRAYS_EQUAL_DOUBLE(lhs_correct, constraints->lhs, 4)); + mu_assert("error rhs", ARRAYS_EQUAL_DOUBLE(rhs_correct, constraints->rhs, 4)); PS_FREE(stgs); DEBUG(run_debugger(constraints, false)); @@ -827,8 +827,8 @@ static char *test_6_dton() 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax_correct, A->x, 5)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(Ai_correct, A->i, 5)); CHECK_ROW_STARTS(A, Ap_correct); // check new AT @@ -842,14 +842,14 @@ static char *test_6_dton() // 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 obj", ARRAYS_EQUAL_DOUBLE(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)); + mu_assert("error lhs", ARRAYS_EQUAL_DOUBLE(lhs_correct, constraints->lhs, 2)); + mu_assert("error rhs", ARRAYS_EQUAL_DOUBLE(rhs_correct, constraints->rhs, 2)); PS_FREE(stgs); DEBUG(run_debugger(constraints, false)); @@ -903,8 +903,8 @@ static char *test_7_dton() 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax_correct, A->x, 8)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(Ai_correct, A->i, 8)); CHECK_ROW_STARTS(A, Ap_correct); // check new AT @@ -918,14 +918,14 @@ static char *test_7_dton() // 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", ARRAYS_EQUAL_DOUBLE(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)); + mu_assert("error lhs", ARRAYS_EQUAL_DOUBLE(lhs_correct, constraints->lhs, 2)); + mu_assert("error rhs", ARRAYS_EQUAL_DOUBLE(rhs_correct, constraints->rhs, 2)); PS_FREE(stgs); DEBUG(run_debugger(constraints, false)); @@ -983,8 +983,8 @@ static char *test_8_dton() 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax_correct, A->x, 17)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(Ai_correct, A->i, 17)); CHECK_ROW_STARTS(A, Ap_correct); // check new AT @@ -999,8 +999,8 @@ static char *test_8_dton() // 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)); + mu_assert("error lhs", ARRAYS_EQUAL_DOUBLE(lhs_correct, constraints->lhs, 8)); + mu_assert("error rhs", ARRAYS_EQUAL_DOUBLE(rhs_correct, constraints->rhs, 8)); PS_FREE(stgs); DEBUG(run_debugger(constraints, false)); @@ -1057,8 +1057,8 @@ static char *test_9_dton() 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax_correct, A->x, 18)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(Ai_correct, A->i, 18)); CHECK_ROW_STARTS(A, Ap_correct); // check new AT @@ -1073,8 +1073,8 @@ static char *test_9_dton() // 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)); + mu_assert("error lhs", ARRAYS_EQUAL_DOUBLE(lhs_correct, constraints->lhs, 8)); + mu_assert("error rhs", ARRAYS_EQUAL_DOUBLE(rhs_correct, constraints->rhs, 8)); PS_FREE(stgs); DEBUG(run_debugger(constraints, false)); @@ -1133,8 +1133,8 @@ static char *test_10_dton() 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax_correct, A->x, 18)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(Ai_correct, A->i, 18)); CHECK_ROW_STARTS(A, Ap_correct); // check new AT @@ -1149,8 +1149,8 @@ static char *test_10_dton() // 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)); + mu_assert("error lhs", ARRAYS_EQUAL_DOUBLE(lhs_correct, constraints->lhs, 8)); + mu_assert("error rhs", ARRAYS_EQUAL_DOUBLE(rhs_correct, constraints->rhs, 8)); PS_FREE(stgs); DEBUG(run_debugger(constraints, false)); @@ -1209,8 +1209,8 @@ static char *test_11_dton() // 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax, A->x, nnz)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(Ai, A->i, nnz)); CHECK_ROW_STARTS(A, Ap); PS_FREE(stgs); @@ -1326,8 +1326,8 @@ static char *test_13_dton() 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax_correct, A->x, 30)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(Ai_correct, A->i, 30)); CHECK_ROW_STARTS(A, Ap_correct); PS_FREE(stgs); @@ -1383,8 +1383,8 @@ static char *test_14_dton() 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax_correct, A->x, 32)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(Ai_correct, A->i, 32)); CHECK_ROW_STARTS(A, Ap_correct); // check new AT @@ -1441,8 +1441,8 @@ static char *test_15_dton() 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax_correct, A->x, 30)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(Ai_correct, A->i, 30)); CHECK_ROW_STARTS(A, Ap_correct); // check new AT @@ -1498,8 +1498,8 @@ static char *test_16_dton() 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax_correct, A->x, 32)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(Ai_correct, A->i, 32)); CHECK_ROW_STARTS(A, Ap_correct); PS_FREE(stgs); // check new AT @@ -1557,8 +1557,8 @@ static char *test_17_dton() 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax_correct, A->x, 10)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(Ai_correct, A->i, 10)); CHECK_ROW_STARTS(A, Ap_correct); PS_FREE(stgs); @@ -1616,8 +1616,8 @@ static char *test_18_dton() 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax_correct, A->x, 6)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(Ai_correct, A->i, 6)); CHECK_ROW_STARTS(A, Ap_correct); PS_FREE(stgs); diff --git a/tests/test_macros.h b/tests/test_macros.h index 00c1defa..8e055ec2 100644 --- a/tests/test_macros.h +++ b/tests/test_macros.h @@ -24,7 +24,7 @@ row_starts[i] = A->p[i].start; \ } \ mu_assert("error, row_starts not equal", \ - ARRAYS_EQUAL(row_starts, row_starts_correct, A->m + 1)); \ + ARRAYS_EQUAL_INT(row_starts, row_starts_correct, A->m + 1)); \ } while (0) #define CHECK_ROW_ENDS(A, row_ends_correct) \ @@ -36,7 +36,7 @@ row_ends[i] = A->p[i].end; \ } \ mu_assert("error, row_starts not equal", \ - ARRAYS_EQUAL(row_ends, row_ends_correct, A->m + 1)); \ + ARRAYS_EQUAL_INT(row_ends, row_ends_correct, A->m + 1)); \ } while (0) #define CHECK_LOCKS(locks, correct_up, correct_down, size) \ diff --git a/tests/test_ston.h b/tests/test_ston.h index ad791c55..90398c96 100644 --- a/tests/test_ston.h +++ b/tests/test_ston.h @@ -80,8 +80,8 @@ static char *test_01_ston() 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax_correct, A->x, 8)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(Ai_correct, A->i, 8)); CHECK_ROW_STARTS(A, Ap_correct); // check new AT @@ -90,14 +90,14 @@ static char *test_01_ston() // 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)); + ARRAYS_EQUAL_ROWTAG(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)); + ARRAYS_EQUAL_COLTAG(coltags_correct, constraints->col_tags, 4)); // check that new variable bounds are correct double lbs_correct[] = {0, 0, -INF, 0}; @@ -107,14 +107,14 @@ static char *test_01_ston() // 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", ARRAYS_EQUAL_DOUBLE(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)); + mu_assert("error lhs", ARRAYS_EQUAL_DOUBLE(lhs_correct, constraints->lhs, 3)); + mu_assert("error rhs", ARRAYS_EQUAL_DOUBLE(rhs_correct, constraints->rhs, 3)); PS_FREE(stgs); DEBUG(run_debugger(constraints, false)); @@ -165,8 +165,8 @@ static char *test_02_ston() 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax_correct, A->x, 8)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(Ai_correct, A->i, 8)); CHECK_ROW_STARTS(A, Ap_correct); // check new AT @@ -175,13 +175,13 @@ static char *test_02_ston() // check new rowtags RowTag rowtags_correct[] = {R_TAG_EQ, R_TAG_EQ}; mu_assert("error rowtags", - ARRAYS_EQUAL(rowtags_correct, constraints->row_tags, 2)); + ARRAYS_EQUAL_ROWTAG(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)); + ARRAYS_EQUAL_COLTAG(coltags_correct, constraints->col_tags, 4)); // check that new variable bounds are correct double lbs_correct[] = {0, 0, 0, 0}; @@ -191,14 +191,14 @@ static char *test_02_ston() // 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", ARRAYS_EQUAL_DOUBLE(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)); + mu_assert("error lhs", ARRAYS_EQUAL_DOUBLE(lhs_correct, constraints->lhs, 2)); + mu_assert("error rhs", ARRAYS_EQUAL_DOUBLE(rhs_correct, constraints->rhs, 2)); PS_FREE(stgs); DEBUG(run_debugger(constraints, false)); @@ -251,8 +251,8 @@ static char *test_03_ston() 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax_correct, A->x, 6)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(Ai_correct, A->i, 6)); CHECK_ROW_STARTS(A, Ap_correct); // check new AT @@ -261,12 +261,12 @@ static char *test_03_ston() // check new rowtags RowTag rowtags_correct[] = {R_TAG_EQ, R_TAG_EQ}; mu_assert("error rowtags", - ARRAYS_EQUAL(rowtags_correct, constraints->row_tags, 2)); + ARRAYS_EQUAL_ROWTAG(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)); + ARRAYS_EQUAL_COLTAG(coltags_correct, constraints->col_tags, 3)); // check that new variable bounds are correct double lbs_correct[] = {0, 0, 0}; @@ -276,14 +276,14 @@ static char *test_03_ston() // 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", ARRAYS_EQUAL_DOUBLE(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)); + mu_assert("error lhs", ARRAYS_EQUAL_DOUBLE(lhs_correct, constraints->lhs, 2)); + mu_assert("error rhs", ARRAYS_EQUAL_DOUBLE(rhs_correct, constraints->rhs, 2)); PS_FREE(stgs); DEBUG(run_debugger(constraints, false)); @@ -338,8 +338,8 @@ static char *test_04_ston() 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax_correct, A->x, 9)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(Ai_correct, A->i, 9)); CHECK_ROW_STARTS(A, Ap_correct); // check new AT @@ -348,13 +348,13 @@ static char *test_04_ston() // 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)); + ARRAYS_EQUAL_ROWTAG(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)); + ARRAYS_EQUAL_COLTAG(coltags_correct, constraints->col_tags, 4)); // check that new variable bounds are correct double lbs_correct[] = {-1, 0, 0, 0}; @@ -364,14 +364,14 @@ static char *test_04_ston() // 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", ARRAYS_EQUAL_DOUBLE(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)); + mu_assert("error lhs", ARRAYS_EQUAL_DOUBLE(lhs_correct, constraints->lhs, 3)); + mu_assert("error rhs", ARRAYS_EQUAL_DOUBLE(rhs_correct, constraints->rhs, 3)); PS_FREE(stgs); DEBUG(run_debugger(constraints, false)); @@ -425,8 +425,8 @@ static char *test_05_ston() 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax_correct, A->x, 13)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(Ai_correct, A->i, 13)); CHECK_ROW_STARTS(A, Ap_correct); // check new AT @@ -435,13 +435,13 @@ static char *test_05_ston() // 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)); + ARRAYS_EQUAL_ROWTAG(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)); + ARRAYS_EQUAL_COLTAG(coltags_correct, constraints->col_tags, 4)); // check that new variable bounds are correct double lbs_correct[] = {0, 0, 0, 0}; @@ -451,14 +451,14 @@ static char *test_05_ston() // 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", ARRAYS_EQUAL_DOUBLE(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)); + mu_assert("error lhs", ARRAYS_EQUAL_DOUBLE(lhs_correct, constraints->lhs, 4)); + mu_assert("error rhs", ARRAYS_EQUAL_DOUBLE(rhs_correct, constraints->rhs, 4)); PS_FREE(stgs); DEBUG(run_debugger(constraints, false)); @@ -512,8 +512,8 @@ static char *test_06_ston() 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax_correct, A->x, 13)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(Ai_correct, A->i, 13)); CHECK_ROW_STARTS(A, Ap_correct); // check new AT @@ -522,13 +522,13 @@ static char *test_06_ston() // 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)); + ARRAYS_EQUAL_ROWTAG(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)); + ARRAYS_EQUAL_COLTAG(coltags_correct, constraints->col_tags, 4)); // check that new variable bounds are correct double lbs_correct[] = {0, 0, 0, 0}; @@ -538,14 +538,14 @@ static char *test_06_ston() // 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", ARRAYS_EQUAL_DOUBLE(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)); + mu_assert("error lhs", ARRAYS_EQUAL_DOUBLE(lhs_correct, constraints->lhs, 4)); + mu_assert("error rhs", ARRAYS_EQUAL_DOUBLE(rhs_correct, constraints->rhs, 4)); PS_FREE(stgs); DEBUG(run_debugger(constraints, false)); @@ -600,8 +600,8 @@ static char *test_07_ston() 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax_correct, A->x, 13)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(Ai_correct, A->i, 13)); CHECK_ROW_STARTS(A, Ap_correct); // check new AT @@ -610,13 +610,13 @@ static char *test_07_ston() // 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)); + ARRAYS_EQUAL_ROWTAG(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)); + ARRAYS_EQUAL_COLTAG(coltags_correct, constraints->col_tags, 4)); // check that new variable bounds are correct double lbs_correct[] = {0, 0, 0, -10}; @@ -626,14 +626,14 @@ static char *test_07_ston() // 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", ARRAYS_EQUAL_DOUBLE(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)); + mu_assert("error lhs", ARRAYS_EQUAL_DOUBLE(lhs_correct, constraints->lhs, 4)); + mu_assert("error rhs", ARRAYS_EQUAL_DOUBLE(rhs_correct, constraints->rhs, 4)); PS_FREE(stgs); DEBUG(run_debugger(constraints, false)); @@ -687,8 +687,8 @@ static char *test_08_ston() 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax_correct, A->x, 13)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(Ai_correct, A->i, 13)); CHECK_ROW_STARTS(A, Ap_correct); // check new AT @@ -697,13 +697,13 @@ static char *test_08_ston() // 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)); + ARRAYS_EQUAL_ROWTAG(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)); + ARRAYS_EQUAL_COLTAG(coltags_correct, constraints->col_tags, 4)); // check that new variable bounds are correct double lbs_correct[] = {0, 0, 0, 0}; @@ -713,7 +713,7 @@ static char *test_08_ston() // 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", ARRAYS_EQUAL_DOUBLE(obj_correct, prob->obj->c, 4)); mu_assert("error obj", prob->obj->offset == -0.5); PS_FREE(stgs); @@ -775,8 +775,8 @@ static char *test_09_ston() 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax_correct, A->x, 16)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(Ai_correct, A->i, 16)); CHECK_ROW_STARTS(A, Ap_correct); // check new AT @@ -785,13 +785,13 @@ static char *test_09_ston() // check new rowtags RowTag rowtags_correct[] = {R_TAG_EQ, R_TAG_EQ}; mu_assert("error rowtags", - ARRAYS_EQUAL(rowtags_correct, constraints->row_tags, 2)); + ARRAYS_EQUAL_ROWTAG(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)); + ARRAYS_EQUAL_COLTAG(coltags_correct, constraints->col_tags, 8)); // check that new variable bounds are correct double lbs_correct[] = {0, 0, 0, 0, 0, 0, 0, -INF}; @@ -801,14 +801,14 @@ static char *test_09_ston() // 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", ARRAYS_EQUAL_DOUBLE(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)); + mu_assert("error lhs", ARRAYS_EQUAL_DOUBLE(lhs_correct, constraints->lhs, 2)); + mu_assert("error rhs", ARRAYS_EQUAL_DOUBLE(rhs_correct, constraints->rhs, 2)); PS_FREE(stgs); DEBUG(run_debugger(constraints, false)); @@ -859,8 +859,8 @@ static char *test_10_ston() 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax_correct, A->x, 6)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(Ai_correct, A->i, 6)); CHECK_ROW_STARTS(A, Ap_correct); // check new AT @@ -869,12 +869,12 @@ static char *test_10_ston() // 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)); + ARRAYS_EQUAL_ROWTAG(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)); + ARRAYS_EQUAL_COLTAG(coltags_correct, constraints->col_tags, 3)); // check that new variable bounds are correct double lbs_correct[] = {0, 0, 0}; @@ -884,14 +884,14 @@ static char *test_10_ston() // 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", ARRAYS_EQUAL_DOUBLE(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)); + mu_assert("error lhs", ARRAYS_EQUAL_DOUBLE(lhs_correct, constraints->lhs, 2)); + mu_assert("error rhs", ARRAYS_EQUAL_DOUBLE(rhs_correct, constraints->rhs, 2)); PS_FREE(stgs); DEBUG(run_debugger(constraints, false)); @@ -942,8 +942,8 @@ static char *test_12_ston() 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax_correct, A->x, 9)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(Ai_correct, A->i, 9)); CHECK_ROW_STARTS(A, Ap_correct); // check new AT @@ -952,12 +952,12 @@ static char *test_12_ston() // 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)); + ARRAYS_EQUAL_ROWTAG(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)); + ARRAYS_EQUAL_COLTAG(coltags_correct, constraints->col_tags, 3)); // check that new variable bounds are correct double lbs_correct[] = {0, 0, 0}; @@ -967,14 +967,14 @@ static char *test_12_ston() // 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", ARRAYS_EQUAL_DOUBLE(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)); + mu_assert("error lhs", ARRAYS_EQUAL_DOUBLE(lhs_correct, constraints->lhs, 3)); + mu_assert("error rhs", ARRAYS_EQUAL_DOUBLE(rhs_correct, constraints->rhs, 3)); PS_FREE(stgs); DEBUG(run_debugger(constraints, false)); @@ -1025,8 +1025,8 @@ static char *test_13_ston() 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)); + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax_correct, A->x, 9)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(Ai_correct, A->i, 9)); CHECK_ROW_STARTS(A, Ap_correct); // check new AT @@ -1035,12 +1035,12 @@ static char *test_13_ston() // 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)); + ARRAYS_EQUAL_ROWTAG(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)); + ARRAYS_EQUAL_COLTAG(coltags_correct, constraints->col_tags, 3)); // check that new variable bounds are correct double lbs_correct[] = {0, 0, 0}; @@ -1050,14 +1050,14 @@ static char *test_13_ston() // 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", ARRAYS_EQUAL_DOUBLE(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)); + mu_assert("error lhs", ARRAYS_EQUAL_DOUBLE(lhs_correct, constraints->lhs, 3)); + mu_assert("error rhs", ARRAYS_EQUAL_DOUBLE(rhs_correct, constraints->rhs, 3)); PS_FREE(stgs); DEBUG(run_debugger(constraints, false)); From 8b2320974a75112282410f97953cc1311a87da39 Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 17 Nov 2025 06:01:28 -0800 Subject: [PATCH 10/17] new line at eof to formatter --- .clang-format | 1 + include/PSLP/PSLP_API.h | 2 +- include/PSLP/PSLP_infs.h | 2 +- include/PSLP/PresolveStatus.h | 2 +- include/PSLP/Sol.h | 2 +- include/core/Activity.h | 2 +- include/core/Bounds.h | 2 +- include/core/Constraints.h | 2 +- include/core/Debugger.h | 2 +- include/core/Locks.h | 2 +- include/core/Matrix.h | 2 +- include/core/Numerics.h | 2 +- include/core/Postsolver.h | 2 +- include/core/PresolveStats.h | 2 +- include/core/Problem.h | 2 +- include/core/RowColViews.h | 2 +- include/core/State.h | 2 +- include/core/Workspace.h | 2 +- include/core/debug_macros.h | 2 +- include/core/glbopts.h | 2 +- include/core/utils.h | 2 +- include/data_structures/dVec.h | 2 +- include/data_structures/iVec.h | 2 +- include/data_structures/u16Vec.h | 2 +- include/explorers/DTonsEq.h | 2 +- include/explorers/Parallel_cols.h | 2 +- include/explorers/Parallel_rows.h | 2 +- include/explorers/Primal_propagation.h | 2 +- include/explorers/SimpleReductions.h | 2 +- include/explorers/Simple_dual_fix.h | 2 +- include/explorers/StonCols.h | 2 +- include/explorers/Timer.h | 2 +- src/core/Activity.c | 2 +- src/core/Constraints.c | 2 +- src/core/CoreTransformation.c | 2 +- src/core/Debugger.c | 2 +- src/core/Matrix.c | 2 +- src/core/Postsolver.c | 2 +- src/core/Presolver.c | 2 +- src/core/Problem.c | 2 +- src/core/State.c | 2 +- src/core/utils.c | 2 +- src/explorers/Parallel_cols.c | 2 +- src/explorers/Parallel_rows.c | 2 +- src/explorers/Primal_propagation.c | 2 +- src/explorers/Simple_dual_fix.c | 2 +- src/explorers/StonCols.c | 2 +- tests/all_tests.c | 2 +- tests/minunit.h | 2 +- tests/test_CoreTransformations.h | 2 +- tests/test_domain_propagation.h | 2 +- tests/test_iVec.h | 2 +- tests/test_macros.h | 2 +- tests/test_postsolve.h | 2 +- 54 files changed, 54 insertions(+), 53 deletions(-) diff --git a/.clang-format b/.clang-format index 780c5366..611ab934 100644 --- a/.clang-format +++ b/.clang-format @@ -42,3 +42,4 @@ AllowAllParametersOfDeclarationOnNextLine: true ReflowComments: true IndentCaseLabels: true AlignOperands: true +InsertNewlineAtEOF: true \ No newline at end of file diff --git a/include/PSLP/PSLP_API.h b/include/PSLP/PSLP_API.h index c1d13544..ad8acc9e 100644 --- a/include/PSLP/PSLP_API.h +++ b/include/PSLP/PSLP_API.h @@ -117,4 +117,4 @@ extern "C" } #endif -#endif // PRESOLVER_H \ No newline at end of file +#endif // PRESOLVER_H diff --git a/include/PSLP/PSLP_infs.h b/include/PSLP/PSLP_infs.h index ac83533e..6fe4a7d2 100644 --- a/include/PSLP/PSLP_infs.h +++ b/include/PSLP/PSLP_infs.h @@ -15,4 +15,4 @@ extern "C" } #endif -#endif /* PSLP_PSLP_INFS_H */ \ No newline at end of file +#endif /* PSLP_PSLP_INFS_H */ diff --git a/include/PSLP/PresolveStatus.h b/include/PSLP/PresolveStatus.h index 644af9c3..450bc26c 100644 --- a/include/PSLP/PresolveStatus.h +++ b/include/PSLP/PresolveStatus.h @@ -23,4 +23,4 @@ extern "C" } #endif -#endif // PSLP_PresolveStatus_H \ No newline at end of file +#endif // PSLP_PresolveStatus_H diff --git a/include/PSLP/Sol.h b/include/PSLP/Sol.h index 5f76a8d3..28a3c89f 100644 --- a/include/PSLP/Sol.h +++ b/include/PSLP/Sol.h @@ -39,4 +39,4 @@ extern "C" } #endif -#endif // CORE_SOL_H \ No newline at end of file +#endif // CORE_SOL_H diff --git a/include/core/Activity.h b/include/core/Activity.h index 54b539a2..598af0eb 100644 --- a/include/core/Activity.h +++ b/include/core/Activity.h @@ -162,4 +162,4 @@ void remove_finite_lb_from_activities(const struct ConstColView *col, Activity * void remove_finite_ub_from_activities(const struct ConstColView *col, Activity *acts, double bound); -#endif // ACTIVITY_H \ No newline at end of file +#endif // ACTIVITY_H diff --git a/include/core/Bounds.h b/include/core/Bounds.h index f1890819..f2bbbf36 100644 --- a/include/core/Bounds.h +++ b/include/core/Bounds.h @@ -25,4 +25,4 @@ typedef struct Bound double ub; } Bound; -#endif // CORE_BOUNDS_H \ No newline at end of file +#endif // CORE_BOUNDS_H diff --git a/include/core/Constraints.h b/include/core/Constraints.h index 207e4a0d..323fc09b 100644 --- a/include/core/Constraints.h +++ b/include/core/Constraints.h @@ -89,4 +89,4 @@ void delete_inactive_rows(Constraints *constraints); */ void delete_inactive_cols_from_A_and_AT(Constraints *constraints); -#endif // CONSTRAINTS_H \ No newline at end of file +#endif // CONSTRAINTS_H diff --git a/include/core/Debugger.h b/include/core/Debugger.h index 3f277ea7..3c793d0d 100644 --- a/include/core/Debugger.h +++ b/include/core/Debugger.h @@ -127,4 +127,4 @@ 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 +#endif // CORE_PRESOLVER_DEBUGGER_H diff --git a/include/core/Locks.h b/include/core/Locks.h index 6e191443..7ea9ab23 100644 --- a/include/core/Locks.h +++ b/include/core/Locks.h @@ -78,4 +78,4 @@ static inline void update_lock_coeff_deletion(struct Lock *lock, double val, } } -#endif // LOCKS_H \ No newline at end of file +#endif // LOCKS_H diff --git a/include/core/Matrix.h b/include/core/Matrix.h index 696ba936..72696a12 100644 --- a/include/core/Matrix.h +++ b/include/core/Matrix.h @@ -122,4 +122,4 @@ void replace_row_A(Matrix *A, int row, double ratio, double *new_vals, int *cols int new_len); #endif -#endif // SPARSE_LA_MATRIX_H \ No newline at end of file +#endif // SPARSE_LA_MATRIX_H diff --git a/include/core/Numerics.h b/include/core/Numerics.h index 459d4c91..215fcce0 100644 --- a/include/core/Numerics.h +++ b/include/core/Numerics.h @@ -57,4 +57,4 @@ if (fabs(x) < ZERO_TOL_DUAL_POSTSOLVE) x = 0.0; \ } while (0) -#endif // CORE_NUMERICS_H \ No newline at end of file +#endif // CORE_NUMERICS_H diff --git a/include/core/Postsolver.h b/include/core/Postsolver.h index 3c2c12e5..785b5fc2 100644 --- a/include/core/Postsolver.h +++ b/include/core/Postsolver.h @@ -185,4 +185,4 @@ void save_retrieval_bound_change_the_row(PostsolveInfo *info, int i, const int * */ void save_retrieval_eq_to_ineq(PostsolveInfo *info, int row, double val); -#endif // CORE_POSTSOLVER_H \ No newline at end of file +#endif // CORE_POSTSOLVER_H diff --git a/include/core/PresolveStats.h b/include/core/PresolveStats.h index 31c0f5e1..e26a8ac1 100644 --- a/include/core/PresolveStats.h +++ b/include/core/PresolveStats.h @@ -47,4 +47,4 @@ typedef struct PresolveStats } PresolveStats; -#endif // PRESOLVE_STATS_H \ No newline at end of file +#endif // PRESOLVE_STATS_H diff --git a/include/core/Problem.h b/include/core/Problem.h index adea14f5..ccc027e0 100644 --- a/include/core/Problem.h +++ b/include/core/Problem.h @@ -57,4 +57,4 @@ void fix_var_in_obj(Objective *obj, int col, double value); 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 +#endif // CORE_PROBLEM_H diff --git a/include/core/RowColViews.h b/include/core/RowColViews.h index 8e9b8b19..44657555 100644 --- a/include/core/RowColViews.h +++ b/include/core/RowColViews.h @@ -133,4 +133,4 @@ static inline ConstColView new_const_colview(const double *vals, const int *rows 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 index cc92c4d9..44a37560 100644 --- a/include/core/State.h +++ b/include/core/State.h @@ -61,4 +61,4 @@ void free_state(State *data); 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 +#endif // CORE_INTERNALDATA_H diff --git a/include/core/Workspace.h b/include/core/Workspace.h index 7aee187f..dad840f6 100644 --- a/include/core/Workspace.h +++ b/include/core/Workspace.h @@ -52,4 +52,4 @@ typedef struct 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 +#endif // CORE_WORKSPACE_H diff --git a/include/core/debug_macros.h b/include/core/debug_macros.h index 6a67618e..49abe928 100644 --- a/include/core/debug_macros.h +++ b/include/core/debug_macros.h @@ -95,4 +95,4 @@ static inline bool ARRAYS_EQUAL_COLTAG(ColTag *arr1, const ColTag *arr2, int siz } \ } while (0) -#endif // DEBUG_MACROS_H \ No newline at end of file +#endif // DEBUG_MACROS_H diff --git a/include/core/glbopts.h b/include/core/glbopts.h index 1050d8bf..afceb436 100644 --- a/include/core/glbopts.h +++ b/include/core/glbopts.h @@ -44,4 +44,4 @@ #define MAX_RATIO_PIVOT 1e3 #define CVX_PRESOLVE_VERSION "0.0.1" -#endif \ No newline at end of file +#endif diff --git a/include/core/utils.h b/include/core/utils.h index 9f44d649..62eeb9b9 100644 --- a/include/core/utils.h +++ b/include/core/utils.h @@ -32,4 +32,4 @@ 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 +#endif // CORE_UTILS_H diff --git a/include/data_structures/dVec.h b/include/data_structures/dVec.h index dd22e4c1..22cd9bc6 100644 --- a/include/data_structures/dVec.h +++ b/include/data_structures/dVec.h @@ -23,4 +23,4 @@ DEFINE_VECTOR(double, d) -#endif // DVEC_H \ No newline at end of file +#endif // DVEC_H diff --git a/include/data_structures/iVec.h b/include/data_structures/iVec.h index c75dd6db..ffe780bf 100644 --- a/include/data_structures/iVec.h +++ b/include/data_structures/iVec.h @@ -35,4 +35,4 @@ __attribute__((unused)) static void print_ivec(iVec *vec) printf("\n"); } -#endif // IVEC_H \ No newline at end of file +#endif // IVEC_H diff --git a/include/data_structures/u16Vec.h b/include/data_structures/u16Vec.h index 88a72268..b85c150e 100644 --- a/include/data_structures/u16Vec.h +++ b/include/data_structures/u16Vec.h @@ -28,4 +28,4 @@ __attribute__((unused)) static void print_u16Vec(const u16Vec *vec) 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 index 4d08c6b3..b6088f7f 100644 --- a/include/explorers/DTonsEq.h +++ b/include/explorers/DTonsEq.h @@ -84,4 +84,4 @@ Old_and_new_coeff update_row_A_dton(struct Matrix *A, int i, int q, int j, int k struct PostsolveInfo *postsolve_info); #endif -#endif \ No newline at end of file +#endif diff --git a/include/explorers/Parallel_cols.h b/include/explorers/Parallel_cols.h index b7ba23e7..ded88080 100644 --- a/include/explorers/Parallel_cols.h +++ b/include/explorers/Parallel_cols.h @@ -30,4 +30,4 @@ struct Problem; */ PresolveStatus remove_parallel_cols(struct Problem *prob); -#endif // PARALLEL_COLS_H \ No newline at end of file +#endif // PARALLEL_COLS_H diff --git a/include/explorers/Parallel_rows.h b/include/explorers/Parallel_rows.h index e8af35a8..4dac7bad 100644 --- a/include/explorers/Parallel_rows.h +++ b/include/explorers/Parallel_rows.h @@ -67,4 +67,4 @@ void find_parallel_rows(const struct Matrix *A, const RowTag *r_Tags, */ PresolveStatus remove_parallel_rows(struct Constraints *constraints); -#endif // PARALLEL_ROWS_H \ No newline at end of file +#endif // PARALLEL_ROWS_H diff --git a/include/explorers/Primal_propagation.h b/include/explorers/Primal_propagation.h index 610b15a2..9c82f2af 100644 --- a/include/explorers/Primal_propagation.h +++ b/include/explorers/Primal_propagation.h @@ -77,4 +77,4 @@ PresolveStatus bound_tightening_single_row(const struct ConstRowView *row, BoundUpdater updater_ub); void remove_redundant_bounds(struct Constraints *constraints); -#endif // PRIMAL_PROPAGATION_H \ No newline at end of file +#endif // PRIMAL_PROPAGATION_H diff --git a/include/explorers/SimpleReductions.h b/include/explorers/SimpleReductions.h index c0bfe661..87d8f327 100644 --- a/include/explorers/SimpleReductions.h +++ b/include/explorers/SimpleReductions.h @@ -113,4 +113,4 @@ 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 +#endif // SIMPLEREDUCTIONS_H diff --git a/include/explorers/Simple_dual_fix.h b/include/explorers/Simple_dual_fix.h index 67355a3d..98805745 100644 --- a/include/explorers/Simple_dual_fix.h +++ b/include/explorers/Simple_dual_fix.h @@ -32,4 +32,4 @@ struct Problem; synchronization is needed. */ PresolveStatus simple_dual_fix(struct Problem *prob); -#endif // SIMPLE_DUAL_FIX_H \ No newline at end of file +#endif // SIMPLE_DUAL_FIX_H diff --git a/include/explorers/StonCols.h b/include/explorers/StonCols.h index eae396e0..37b8a7fa 100644 --- a/include/explorers/StonCols.h +++ b/include/explorers/StonCols.h @@ -66,4 +66,4 @@ struct Problem; */ PresolveStatus remove_ston_cols(struct Problem *prob); -#endif // STONCOLS_HPP \ No newline at end of file +#endif // STONCOLS_HPP diff --git a/include/explorers/Timer.h b/include/explorers/Timer.h index 52d7af8d..a7042669 100644 --- a/include/explorers/Timer.h +++ b/include/explorers/Timer.h @@ -40,4 +40,4 @@ typedef struct (time_variable) += GET_ELAPSED_SECONDS(timer); \ } while (0) -#endif // TIMER_H \ No newline at end of file +#endif // TIMER_H diff --git a/src/core/Activity.c b/src/core/Activity.c index a380e785..e89296d4 100644 --- a/src/core/Activity.c +++ b/src/core/Activity.c @@ -831,4 +831,4 @@ void recompute_n_infs(Activity *act, const double *vals, const int *cols, int le 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 index f67b553c..282979e2 100644 --- a/src/core/Constraints.c +++ b/src/core/Constraints.c @@ -319,4 +319,4 @@ void constraints_clean(Constraints *constraints, Mapping *map, bool remove_all) 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 index db3b97ad..bb606ae6 100644 --- a/src/core/CoreTransformation.c +++ b/src/core/CoreTransformation.c @@ -530,4 +530,4 @@ bool is_lb_implied_free(const ConstColView *col, Activity *acts, const double *l } return false; -} \ No newline at end of file +} diff --git a/src/core/Debugger.c b/src/core/Debugger.c index d1e0ef92..4d9773fe 100644 --- a/src/core/Debugger.c +++ b/src/core/Debugger.c @@ -808,4 +808,4 @@ void run_debugger_stats_consistency_check(const PresolveStats *stats) 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/Matrix.c b/src/core/Matrix.c index 26e85c18..052421f1 100644 --- a/src/core/Matrix.c +++ b/src/core/Matrix.c @@ -608,4 +608,4 @@ void replace_row_A(Matrix *A, int row, double ratio, double *new_vals, int *cols assert(A->p[row].end <= A->p[row + 1].start); } -#endif \ No newline at end of file +#endif diff --git a/src/core/Postsolver.c b/src/core/Postsolver.c index e0b2b7c7..bebbbbfd 100644 --- a/src/core/Postsolver.c +++ b/src/core/Postsolver.c @@ -841,4 +841,4 @@ void save_retrieval_eq_to_ineq(PostsolveInfo *info, int row, double 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 index 1ded9651..e4ab5ce0 100644 --- a/src/core/Presolver.c +++ b/src/core/Presolver.c @@ -670,4 +670,4 @@ void free_presolver(Presolver *presolver) } PS_FREE(presolver); -} \ No newline at end of file +} diff --git a/src/core/Problem.c b/src/core/Problem.c index 05c054b7..7be83fe4 100644 --- a/src/core/Problem.c +++ b/src/core/Problem.c @@ -107,4 +107,4 @@ void sub_var_in_obj(Objective *obj, const double *vals, const int *cols, int len } obj->offset += rhs * ratio; -} \ No newline at end of file +} diff --git a/src/core/State.c b/src/core/State.c index 8d0f6e2f..c47eef21 100644 --- a/src/core/State.c +++ b/src/core/State.c @@ -205,4 +205,4 @@ void clean_state(State *data, const Mapping *maps, int n_rows, int n_cols) 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/utils.c b/src/core/utils.c index 5ac80c22..61f7b6e7 100644 --- a/src/core/utils.c +++ b/src/core/utils.c @@ -97,4 +97,4 @@ double get_max_abs(const double *vals, int len) max_abs = MAX(max_abs, abs_val); } return max_abs; -} \ No newline at end of file +} diff --git a/src/explorers/Parallel_cols.c b/src/explorers/Parallel_cols.c index 6908f8a3..7fe822b7 100644 --- a/src/explorers/Parallel_cols.c +++ b/src/explorers/Parallel_cols.c @@ -340,4 +340,4 @@ PresolveStatus remove_parallel_cols(Problem *prob) 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 index 6b851946..e1271e3f 100644 --- a/src/explorers/Parallel_rows.c +++ b/src/explorers/Parallel_rows.c @@ -610,4 +610,4 @@ PresolveStatus remove_parallel_rows(Constraints *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 index cd36d3a3..be51e0c1 100644 --- a/src/explorers/Primal_propagation.c +++ b/src/explorers/Primal_propagation.c @@ -883,4 +883,4 @@ void remove_redundant_bounds(Constraints *constraints) } DEBUG(verify_activities(constraints)); -} \ No newline at end of file +} diff --git a/src/explorers/Simple_dual_fix.c b/src/explorers/Simple_dual_fix.c index ddb79b06..d6f6c2a9 100644 --- a/src/explorers/Simple_dual_fix.c +++ b/src/explorers/Simple_dual_fix.c @@ -175,4 +175,4 @@ PresolveStatus simple_dual_fix(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 index a0b6c536..01943d47 100644 --- a/src/explorers/StonCols.c +++ b/src/explorers/StonCols.c @@ -713,4 +713,4 @@ PresolveStatus remove_ston_cols(Problem *prob) // 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 index a9f713f0..2ae1f45b 100644 --- a/tests/all_tests.c +++ b/tests/all_tests.c @@ -38,4 +38,4 @@ int main() } printf("All tests passed!\n"); return 0; -} \ No newline at end of file +} diff --git a/tests/minunit.h b/tests/minunit.h index 2412802d..853cecbb 100644 --- a/tests/minunit.h +++ b/tests/minunit.h @@ -31,4 +31,4 @@ if (message) return message; \ } while (0) -#endif \ No newline at end of file +#endif diff --git a/tests/test_CoreTransformations.h b/tests/test_CoreTransformations.h index 0972fc69..1bbf47c4 100644 --- a/tests/test_CoreTransformations.h +++ b/tests/test_CoreTransformations.h @@ -83,4 +83,4 @@ int test_core() return result == 0; } -#endif // TEST_core_H \ No newline at end of file +#endif // TEST_core_H diff --git a/tests/test_domain_propagation.h b/tests/test_domain_propagation.h index 4f9f40df..b0e10786 100644 --- a/tests/test_domain_propagation.h +++ b/tests/test_domain_propagation.h @@ -682,4 +682,4 @@ int test_domain() return result == 0; } -#endif \ No newline at end of file +#endif diff --git a/tests/test_iVec.h b/tests/test_iVec.h index f10aa71e..7b163245 100644 --- a/tests/test_iVec.h +++ b/tests/test_iVec.h @@ -87,4 +87,4 @@ int test_iVec() return result == 0; } -#endif // TEST_IVEC_H \ No newline at end of file +#endif // TEST_IVEC_H diff --git a/tests/test_macros.h b/tests/test_macros.h index 8e055ec2..c5867384 100644 --- a/tests/test_macros.h +++ b/tests/test_macros.h @@ -151,4 +151,4 @@ bool is_solution_correct(const double *x, const double *correct_x, const double return true; } -#endif // TEST_MACROS_H \ No newline at end of file +#endif // TEST_MACROS_H diff --git a/tests/test_postsolve.h b/tests/test_postsolve.h index 08821b49..29eb8835 100644 --- a/tests/test_postsolve.h +++ b/tests/test_postsolve.h @@ -999,4 +999,4 @@ int test_postsolve() return result == 0; } -#endif \ No newline at end of file +#endif From 40f2419f5acd5a92a7d3cb92b74b07b64e3d4a1a Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 17 Nov 2025 06:53:18 -0800 Subject: [PATCH 11/17] improved interface --- README.md | 15 +++------ include/PSLP/PSLP_API.h | 4 +-- include/PSLP/{PSLP_infs.h => PSLP_inf.h} | 3 -- include/PSLP/Sol.h | 1 - include/core/glbopts.h | 5 ++- src/core/Constraints.c | 2 -- src/core/Presolver.c | 42 ++++++++++++++++++++++++ src/core/State.c | 4 --- src/core/utils.c | 8 ----- src/explorers/Primal_propagation.c | 3 -- tests/test_cpp.cpp | 7 ++-- 11 files changed, 56 insertions(+), 38 deletions(-) rename include/PSLP/{PSLP_infs.h => PSLP_inf.h} (55%) diff --git a/README.md b/README.md index 51134c38..53267982 100644 --- a/README.md +++ b/README.md @@ -23,29 +23,22 @@ 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. -After installing PSLP, you can include it in your project with: -```c -#include -``` -The API consists of three main operations: +The public C API is defined in the header file `PSLP/PSLP_API.h`. The other files +in that folder contain some trivial public data structures. The API consists of three main operations: 1. **Initialization** — performed using `new_presolver()`, which creates and initializes internal data structures used by the presolver. 2. **Presolve** — performed using `run_presolver()`, which executes the presolve routines to reduce the LP. The reduced LP is available in the struct - `reduced_problem` of the presolver object. + `reduced_prob` of the presolver struct. 4. **Postsolve** — performed using `postsolve()`, which recovers a primal-dual solution to the original LP from a primal-dual solution to the reduced problem. (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 the `PSLP` folder. A video tutorial is available at XXX. --- ## Citation diff --git a/include/PSLP/PSLP_API.h b/include/PSLP/PSLP_API.h index ad8acc9e..ce2292e5 100644 --- a/include/PSLP/PSLP_API.h +++ b/include/PSLP/PSLP_API.h @@ -69,8 +69,7 @@ extern "C" double *rhs; double *c; - // variable bounds bounds[k].lb <= x_k <= bounds[k].ub - // Bound *bounds; + // variable bounds lbs <= x_k <= ubs double *lbs; double *ubs; } PresolvedProblem; @@ -81,6 +80,7 @@ extern "C" struct PresolveStats *stats; const Settings *stgs; struct Problem *prob; + PresolvedProblem *reduced_prob; Solution *sol; } Presolver; diff --git a/include/PSLP/PSLP_infs.h b/include/PSLP/PSLP_inf.h similarity index 55% rename from include/PSLP/PSLP_infs.h rename to include/PSLP/PSLP_inf.h index 6fe4a7d2..4c09bab6 100644 --- a/include/PSLP/PSLP_infs.h +++ b/include/PSLP/PSLP_inf.h @@ -7,9 +7,6 @@ extern "C" #endif #define PSLP_INF 1e20 -#define IS_POS_INF(x) ((x) >= PSLP_INF) -#define IS_NEG_INF(x) ((x) <= -PSLP_INF) -#define IS_ABS_INF(x) (IS_POS_INF(x) || IS_NEG_INF(x)) #ifdef __cplusplus } diff --git a/include/PSLP/Sol.h b/include/PSLP/Sol.h index 28a3c89f..dee3ada2 100644 --- a/include/PSLP/Sol.h +++ b/include/PSLP/Sol.h @@ -32,7 +32,6 @@ extern "C" double obj; int dim_x; int dim_y; - int status; } Solution; #ifdef __cplusplus diff --git a/include/core/glbopts.h b/include/core/glbopts.h index afceb436..30bd5da6 100644 --- a/include/core/glbopts.h +++ b/include/core/glbopts.h @@ -19,9 +19,12 @@ #ifndef GLB_H_GUARD #define GLB_H_GUARD -#include "PSLP_infs.h" +#include "PSLP_inf.h" #define INF PSLP_INF +#define IS_POS_INF(x) ((x) >= PSLP_INF) +#define IS_NEG_INF(x) ((x) <= -PSLP_INF) +#define IS_ABS_INF(x) (IS_POS_INF(x) || IS_NEG_INF(x)) #ifdef TESTING #define EXTRA_ROW_SPACE 2 diff --git a/src/core/Constraints.c b/src/core/Constraints.c index 282979e2..652f0c27 100644 --- a/src/core/Constraints.c +++ b/src/core/Constraints.c @@ -293,12 +293,10 @@ void delete_inactive_cols_from_A_and_AT(Constraints *constraints) 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]; } } diff --git a/src/core/Presolver.c b/src/core/Presolver.c index e4ab5ce0..e38ff971 100644 --- a/src/core/Presolver.c +++ b/src/core/Presolver.c @@ -278,6 +278,8 @@ Presolver *new_presolver(const double *Ax, const int *Ai, const int *Ap, int m, 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); + presolver->reduced_prob = + (PresolvedProblem *) ps_calloc(1, sizeof(PresolvedProblem)); DEBUG(run_debugger(constraints, false)); // --------------------------------------------------------------------------- @@ -520,6 +522,37 @@ run_medium_explorers(Problem *prob, const Settings *stgs, PresolveStats *stats) return status; } +void populate_presolved_problem(Presolver *presolver) +{ + PresolvedProblem *ps_prob = presolver->reduced_prob; + Constraints *constraints = presolver->prob->constraints; + Matrix *A = constraints->A; + ps_prob->m = A->m; + ps_prob->n = A->n; + ps_prob->nnz = A->nnz; + ps_prob->Ax = A->x; + ps_prob->Ai = A->i; + ps_prob->rhs = constraints->rhs; + ps_prob->lhs = constraints->lhs; + ps_prob->c = presolver->prob->obj->c; + + // create bounds arrays + ps_prob->lbs = (double *) malloc(A->n * sizeof(double)); + ps_prob->ubs = (double *) malloc(A->n * sizeof(double)); + for (int i = 0; i < A->n; i++) + { + ps_prob->lbs[i] = constraints->bounds[i].lb; + ps_prob->ubs[i] = constraints->bounds[i].ub; + } + + // create row pointers + ps_prob->Ap = (int *) malloc((A->m + 1) * sizeof(int)); + for (int i = 0; i < A->m + 1; i++) + { + ps_prob->Ap[i] = A->p[i].start; + } +} + static inline void print_start_message(const PresolveStats *stats) { printf("\n\t PSLP v%s - LP presolver \n\t(c) Daniel " @@ -620,6 +653,7 @@ PresolveStatus run_presolver(Presolver *presolver) stats->nnz_reduced = A->nnz; stats->presolve_total_time = GET_ELAPSED_SECONDS(outer_timer); DEBUG(run_debugger_stats_consistency_check(stats)); + populate_presolved_problem(presolver); if (stgs->verbose) { @@ -661,6 +695,14 @@ void free_presolver(Presolver *presolver) free_problem(presolver->prob); PS_FREE(presolver->stats); + if (presolver->reduced_prob) + { + PS_FREE(presolver->reduced_prob->Ap); + PS_FREE(presolver->reduced_prob->lbs); + PS_FREE(presolver->reduced_prob->ubs); + PS_FREE(presolver->reduced_prob); + } + if (presolver->sol) { PS_FREE(presolver->sol->x); diff --git a/src/core/State.c b/src/core/State.c index c47eef21..f5221320 100644 --- a/src/core/State.c +++ b/src/core/State.c @@ -168,12 +168,10 @@ void free_state(State *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]; } } @@ -181,12 +179,10 @@ static void shrink_locks(Lock *ptr, const int *map, int len) 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]; } } diff --git a/src/core/utils.c b/src/core/utils.c index 61f7b6e7..af98dbdf 100644 --- a/src/core/utils.c +++ b/src/core/utils.c @@ -21,12 +21,10 @@ 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]; } } @@ -34,12 +32,10 @@ void dPtr_shrink(double *ptr, const int *map, int len) 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]; } } @@ -47,12 +43,10 @@ void iPtr_shrink(int *ptr, const int *map, int len) 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]; } } @@ -60,12 +54,10 @@ void rowTagPtr_shrink(RowTag *ptr, const int *map, int len) 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]; } } diff --git a/src/explorers/Primal_propagation.c b/src/explorers/Primal_propagation.c index be51e0c1..bd9a22ab 100644 --- a/src/explorers/Primal_propagation.c +++ b/src/explorers/Primal_propagation.c @@ -816,7 +816,6 @@ void remove_redundant_bounds(Constraints *constraints) 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 @@ -862,7 +861,6 @@ void remove_redundant_bounds(Constraints *constraints) 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); } @@ -875,7 +873,6 @@ void remove_redundant_bounds(Constraints *constraints) 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); } diff --git a/tests/test_cpp.cpp b/tests/test_cpp.cpp index abc8ea27..1feebbbd 100644 --- a/tests/test_cpp.cpp +++ b/tests/test_cpp.cpp @@ -1,11 +1,12 @@ #include "PSLP_API.h" -#include "PSLP_infs.h" +#include "PSLP_inf.h" #include #include // run affiro presolver test in C++ int main() { + double CSR = true; double Ax[] = { -1., 1., 1., -1.06, 1., 1., -1., 1.4, -1., -1., -1., -1., 1., 1., -1.06, -1.06, -0.96, -0.86, 1., 1., -1., 1., @@ -46,10 +47,10 @@ int main() Settings *stgs = default_settings(); Presolver *presolver = new_presolver(Ax, Ai, Ap, n_rows, n_cols, nnz, lhs, rhs, - lbs, ubs, c, stgs, true); + lbs, ubs, c, stgs, CSR); assert(presolver != nullptr && "Presolver initialization failed"); - run_presolver(presolver); + PresolveStatus status = run_presolver(presolver); free_settings(stgs); free_presolver(presolver); From 933769169cd77c4e6338e8e37d119f3bbbb98291 Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 17 Nov 2025 11:05:05 -0800 Subject: [PATCH 12/17] new workflow --- .github/workflows/coverage.yml | 48 ++++++++++++++++++++++++++++++++++ CMakeLists.txt | 16 +++++++++--- include/core/debug_macros.h | 2 -- 3 files changed, 60 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/coverage.yml diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml new file mode 100644 index 00000000..ebab5dfa --- /dev/null +++ b/.github/workflows/coverage.yml @@ -0,0 +1,48 @@ +name: Coverage + +on: + push: + branches: [ main ] + pull_request: + +jobs: + coverage: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Install tools + run: sudo apt-get update && sudo apt-get install -y lcov gcovr + + # ---- BUILD ---- + - name: Configure + run: cmake -DCOVERAGE=ON -DCMAKE_BUILD_TYPE=Debug . + + - name: Build + run: make -j + + # ---- RUN TESTS ---- + - name: Run tests + run: | + if [ -f "CTestTestfile.cmake" ]; then + ctest --output-on-failure + else + ./tests # replace with your test binary + fi + + # ---- GENERATE COVERAGE ---- + - name: Generate coverage + run: | + gcovr \ + --root . \ + --xml-pretty coverage.xml \ + --exclude 'test/*' \ + --exclude 'build/*' + + # ---- SEND TO COVERALLS ---- + - name: Upload to Coveralls + uses: coverallsapp/github-action@v2 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + path-to-lcov: coverage.xml diff --git a/CMakeLists.txt b/CMakeLists.txt index afcec394..9e4a5afa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,6 @@ -#CMakeLists.txt file for cvx_presolver cmake_minimum_required(VERSION 3.20) project(PSLP - LANGUAGES C CXX + LANGUAGES C CXX # The library is written in C, but we also have some C++ test files VERSION 0.1.0 DESCRIPTION "PSLP — A Lightweight C Presolver for Linear Programs" ) @@ -95,7 +94,7 @@ else() endif() # Create PSLP library -add_library(PSLP ${CORE_SOURCES})# ${PRIVATE_HEADERS}) +add_library(PSLP ${CORE_SOURCES}) target_include_directories(PSLP PUBLIC $ @@ -126,7 +125,7 @@ else() endif() # Testing -option(BUILD_TESTING "Build tests" ON) +option(BUILD_TESTING "Build tests" OFF) if(BUILD_TESTING) include(CTest) enable_testing() @@ -158,6 +157,15 @@ else() message(STATUS "Testing disabled") endif() +option(COVERAGE "Enable coverage reporting" OFF) + +if(COVERAGE) + message(STATUS "Building with coverage flags") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O0 -g --coverage") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage") + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} --coverage") +endif() + add_executable(test_cpp EXCLUDE_FROM_ALL tests/test_cpp.cpp) target_link_libraries(test_cpp PRIVATE PSLP) diff --git a/include/core/debug_macros.h b/include/core/debug_macros.h index 49abe928..748c5a2d 100644 --- a/include/core/debug_macros.h +++ b/include/core/debug_macros.h @@ -35,8 +35,6 @@ #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 \ From 9f5cd683b587e6021cb375c7cc641fd0f3891824 Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 17 Nov 2025 11:11:15 -0800 Subject: [PATCH 13/17] new workflow --- .github/workflows/coverage.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index ebab5dfa..8c97d4bc 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -17,7 +17,7 @@ jobs: # ---- BUILD ---- - name: Configure - run: cmake -DCOVERAGE=ON -DCMAKE_BUILD_TYPE=Debug . + run: cmake -DCOVERAGE=ON -DCMAKE_BUILD_TYPE=Debug -DBUILD_TESTING=ON . - name: Build run: make -j @@ -28,7 +28,7 @@ jobs: if [ -f "CTestTestfile.cmake" ]; then ctest --output-on-failure else - ./tests # replace with your test binary + bin/all_tests fi # ---- GENERATE COVERAGE ---- From 11f17c7e2a5d1885074aad2fd63cc8e191926cef Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 17 Nov 2025 11:13:55 -0800 Subject: [PATCH 14/17] new workflow --- .github/workflows/coverage.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 8c97d4bc..296d0727 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -36,7 +36,8 @@ jobs: run: | gcovr \ --root . \ - --xml-pretty coverage.xml \ + --xml-pretty \ + --output coverage.xml \ --exclude 'test/*' \ --exclude 'build/*' From 80940630b6d27e182cdef35dd00a0f004e393f8a Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 18 Nov 2025 09:12:53 -0800 Subject: [PATCH 15/17] updated default settings --- include/core/glbopts.h | 2 +- src/core/Presolver.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/core/glbopts.h b/include/core/glbopts.h index 30bd5da6..1ce87f1f 100644 --- a/include/core/glbopts.h +++ b/include/core/glbopts.h @@ -45,6 +45,6 @@ #define SIZE_INACTIVE_ROW -1 #define SIZE_INACTIVE_COL -1 #define MAX_RATIO_PIVOT 1e3 -#define CVX_PRESOLVE_VERSION "0.0.1" +#define PSLP_presolve_VERSION "0.0.1" #endif diff --git a/src/core/Presolver.c b/src/core/Presolver.c index e38ff971..c3002694 100644 --- a/src/core/Presolver.c +++ b/src/core/Presolver.c @@ -81,7 +81,7 @@ Settings *default_settings() stgs->parallel_cols = true; stgs->primal_propagation = true; stgs->dual_fix = true; - stgs->clean_small_coeff = true; + stgs->clean_small_coeff = false; stgs->finite_bound_tightening = true; stgs->relax_bounds = true; stgs->max_shift = 10; @@ -557,7 +557,7 @@ 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); + PSLP_presolve_VERSION); printf("Original problem: %d rows, %d columns, %d nnz\n", stats->n_rows_original, stats->n_cols_original, stats->nnz_original); } From df92d45c437a119185241ddd46f2fd32c95b14bf Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 18 Nov 2025 09:41:42 -0800 Subject: [PATCH 16/17] small changes to API --- include/PSLP/PSLP_API.h | 38 +++++++++++-------- include/PSLP/PSLP_infs.h | 18 --------- include/PSLP/{Sol.h => PSLP_sol.h} | 0 .../PSLP/{PresolveStatus.h => PSLP_status.h} | 0 include/core/CoreTransformations.h | 2 +- include/core/Postsolver.h | 2 +- include/explorers/DTonsEq.h | 2 +- include/explorers/Parallel_cols.h | 2 +- include/explorers/Parallel_rows.h | 2 +- include/explorers/Primal_propagation.h | 2 +- include/explorers/SimpleReductions.h | 2 +- include/explorers/Simple_dual_fix.h | 2 +- include/explorers/StonCols.h | 2 +- src/core/Presolver.c | 5 +-- src/explorers/DtonsEq.c | 6 +-- 15 files changed, 36 insertions(+), 49 deletions(-) delete mode 100644 include/PSLP/PSLP_infs.h rename include/PSLP/{Sol.h => PSLP_sol.h} (100%) rename include/PSLP/{PresolveStatus.h => PSLP_status.h} (100%) diff --git a/include/PSLP/PSLP_API.h b/include/PSLP/PSLP_API.h index b9c348f3..4dcd65d6 100644 --- a/include/PSLP/PSLP_API.h +++ b/include/PSLP/PSLP_API.h @@ -16,11 +16,9 @@ * 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. */ +/* Public header containing the outward facing API. Together with the other + files in the folder "PSLP", it includes the input/output data structs + and the API functions. */ #ifndef PRESOLVER_H #define PRESOLVER_H @@ -29,8 +27,8 @@ extern "C" { #endif -#include "PresolveStatus.h" -#include "Sol.h" +#include "PSLP_sol.h" +#include "PSLP_status.h" #include /* forward declaration */ @@ -69,13 +67,21 @@ extern "C" double *rhs; double *c; - // variable bounds bounds[k].lb <= x_k <= bounds[k].ub - // Bound *bounds; + // variable bounds lbs <= x <= ubs double *lbs; double *ubs; } PresolvedProblem; - /* struct corresponding to the presolver*/ + /* struct corresponding to the presolver: + - 'stats' contains statistics about the presolving process + - 'stgs' contains the settings used for presolving + - 'prob' contains the internal problem representation used during + presolving + - 'reduced_prob' contains the presolved problem after running + 'run_presolver' + - 'sol' contains the solution to the original problem after running + 'postsolve' + */ typedef struct { struct PresolveStats *stats; @@ -85,8 +91,7 @@ extern "C" Solution *sol; } Presolver; - /* The user is responsible for freeing the settings struct using standard free. - */ + /* The user is responsible for freeing the settings using 'free_settings'. */ Settings *default_settings(); void free_settings(Settings *stgs); void set_settings_true(Settings *stgs); @@ -94,7 +99,7 @@ extern "C" /* 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 + ubs, and c. The user is responsible for freeing the presolver 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, @@ -104,13 +109,14 @@ extern "C" /* 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. */ + /* Runs the presolver. At completion, the 'reduced_prob' field of the + presolver contains the presolved problem. */ PresolveStatus run_presolver(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. */ + The function populates presolver->sol, so if you're looking for the + solution to the original problem, you should look there. */ void postsolve(Presolver *presolver, const double *x, const double *y, const double *z, double obj); diff --git a/include/PSLP/PSLP_infs.h b/include/PSLP/PSLP_infs.h deleted file mode 100644 index 6fe4a7d2..00000000 --- a/include/PSLP/PSLP_infs.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef PSLP_PSLP_INFS_H -#define PSLP_PSLP_INFS_H - -#ifdef __cplusplus -extern "C" -{ -#endif - -#define PSLP_INF 1e20 -#define IS_POS_INF(x) ((x) >= PSLP_INF) -#define IS_NEG_INF(x) ((x) <= -PSLP_INF) -#define IS_ABS_INF(x) (IS_POS_INF(x) || IS_NEG_INF(x)) - -#ifdef __cplusplus -} -#endif - -#endif /* PSLP_PSLP_INFS_H */ diff --git a/include/PSLP/Sol.h b/include/PSLP/PSLP_sol.h similarity index 100% rename from include/PSLP/Sol.h rename to include/PSLP/PSLP_sol.h diff --git a/include/PSLP/PresolveStatus.h b/include/PSLP/PSLP_status.h similarity index 100% rename from include/PSLP/PresolveStatus.h rename to include/PSLP/PSLP_status.h diff --git a/include/core/CoreTransformations.h b/include/core/CoreTransformations.h index 40996369..3904c4c8 100644 --- a/include/core/CoreTransformations.h +++ b/include/core/CoreTransformations.h @@ -19,8 +19,8 @@ #ifndef CORE_TRANSFORMATIONS_H #define CORE_TRANSFORMATIONS_H +#include "PSLP_status.h" #include "Postsolver.h" -#include "PresolveStatus.h" #include "Tags.h" #include "debug_macros.h" #include "glbopts.h" diff --git a/include/core/Postsolver.h b/include/core/Postsolver.h index 785b5fc2..e045bc09 100644 --- a/include/core/Postsolver.h +++ b/include/core/Postsolver.h @@ -22,7 +22,7 @@ #include #include -#include "Sol.h" +#include "PSLP_sol.h" #include "Tags.h" struct u16Vec; diff --git a/include/explorers/DTonsEq.h b/include/explorers/DTonsEq.h index b6088f7f..77ddf1af 100644 --- a/include/explorers/DTonsEq.h +++ b/include/explorers/DTonsEq.h @@ -19,7 +19,7 @@ #ifndef DTONS_EQ_H #define DTONS_EQ_H -#include "PresolveStatus.h" +#include "PSLP_status.h" struct Matrix; struct Problem; struct PostsolveInfo; diff --git a/include/explorers/Parallel_cols.h b/include/explorers/Parallel_cols.h index ded88080..a12a5cc9 100644 --- a/include/explorers/Parallel_cols.h +++ b/include/explorers/Parallel_cols.h @@ -19,7 +19,7 @@ #ifndef PARALLEL_COLS_H #define PARALLEL_COLS_H -#include "PresolveStatus.h" +#include "PSLP_status.h" // forward declaration struct Problem; diff --git a/include/explorers/Parallel_rows.h b/include/explorers/Parallel_rows.h index 4dac7bad..3350c5d7 100644 --- a/include/explorers/Parallel_rows.h +++ b/include/explorers/Parallel_rows.h @@ -19,7 +19,7 @@ #ifndef PARALLEL_ROWS_H #define PARALLEL_ROWS_H -#include "PresolveStatus.h" +#include "PSLP_status.h" #include "Tags.h" // forward declarations diff --git a/include/explorers/Primal_propagation.h b/include/explorers/Primal_propagation.h index 9c82f2af..a11e49ed 100644 --- a/include/explorers/Primal_propagation.h +++ b/include/explorers/Primal_propagation.h @@ -21,7 +21,7 @@ #include -#include "PresolveStatus.h" +#include "PSLP_status.h" #include "Tags.h" struct Constraints; diff --git a/include/explorers/SimpleReductions.h b/include/explorers/SimpleReductions.h index 87d8f327..927ab09c 100644 --- a/include/explorers/SimpleReductions.h +++ b/include/explorers/SimpleReductions.h @@ -19,7 +19,7 @@ #ifndef SIMPLEREDUCTIONS_H #define SIMPLEREDUCTIONS_H -#include "PresolveStatus.h" +#include "PSLP_status.h" #include "Tags.h" // forward declarations diff --git a/include/explorers/Simple_dual_fix.h b/include/explorers/Simple_dual_fix.h index 98805745..6493a367 100644 --- a/include/explorers/Simple_dual_fix.h +++ b/include/explorers/Simple_dual_fix.h @@ -19,7 +19,7 @@ #ifndef SIMPLE_DUAL_FIX_H #define SIMPLE_DUAL_FIX_H -#include "PresolveStatus.h" +#include "PSLP_status.h" // forward declaration struct Problem; diff --git a/include/explorers/StonCols.h b/include/explorers/StonCols.h index 37b8a7fa..c57de077 100644 --- a/include/explorers/StonCols.h +++ b/include/explorers/StonCols.h @@ -19,7 +19,7 @@ #ifndef STONCOLS_HPP #define STONCOLS_HPP -#include "PresolveStatus.h" +#include "PSLP_status.h" // forward declaration struct Problem; diff --git a/src/core/Presolver.c b/src/core/Presolver.c index c3002694..5fc4cd21 100644 --- a/src/core/Presolver.c +++ b/src/core/Presolver.c @@ -336,6 +336,7 @@ static inline bool update_termination(int nnz_after_cycle, int nnz_before_cycle, if (GET_ELAPSED_SECONDS(outer_timer) >= max_time) { + printf("Maximum time limit of %.2f seconds reached.\n", max_time); return true; } @@ -370,6 +371,7 @@ static inline Complexity update_complexity(Complexity curr_complexity, { assert(false); } + return FAST; // to suppress compiler warning } @@ -409,7 +411,6 @@ static inline PresolveStatus run_trivial_explorers(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); - return UNCHANGED; } @@ -437,12 +438,10 @@ static inline PresolveStatus run_fast_explorers(Problem *prob, const Settings *s 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; } diff --git a/src/explorers/DtonsEq.c b/src/explorers/DtonsEq.c index e218cc4a..7b9a992f 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; From 528be6d415432769e3a81e5a06ab54b52e070c3b Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 18 Nov 2025 10:51:06 -0800 Subject: [PATCH 17/17] added one test --- include/data_structures/iVec.h | 3 +- include/data_structures/u16Vec.h | 2 + src/explorers/StonCols.c | 2 - tests/test_ston.h | 92 ++++++++++++++++++++++++++++++++ 4 files changed, 96 insertions(+), 3 deletions(-) diff --git a/include/data_structures/iVec.h b/include/data_structures/iVec.h index ffe780bf..1760090d 100644 --- a/include/data_structures/iVec.h +++ b/include/data_structures/iVec.h @@ -26,6 +26,7 @@ // 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) @@ -34,5 +35,5 @@ __attribute__((unused)) static void print_ivec(iVec *vec) } printf("\n"); } - +*/ #endif // IVEC_H diff --git a/include/data_structures/u16Vec.h b/include/data_structures/u16Vec.h index b85c150e..bc5b1461 100644 --- a/include/data_structures/u16Vec.h +++ b/include/data_structures/u16Vec.h @@ -21,6 +21,7 @@ // 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) @@ -29,3 +30,4 @@ __attribute__((unused)) static void print_u16Vec(const u16Vec *vec) } printf("\n"); } +*/ diff --git a/src/explorers/StonCols.c b/src/explorers/StonCols.c index 01943d47..c58630f3 100644 --- a/src/explorers/StonCols.c +++ b/src/explorers/StonCols.c @@ -617,8 +617,6 @@ PresolveStatus remove_ston_cols__(Problem *prob) { 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 diff --git a/tests/test_ston.h b/tests/test_ston.h index 90398c96..9fa8ec62 100644 --- a/tests/test_ston.h +++ b/tests/test_ston.h @@ -1066,6 +1066,96 @@ static char *test_13_ston() return 0; } +static char *test_14_ston() +{ + double Ax[] = {1, 1, 2, 1, 1}; + int Ai[] = {0, 1, 2, 1, 2}; + int Ap[] = {0, 3, 5}; + int nnz = 5; + int n_rows = 2; + int n_cols = 3; + + double lhs[] = {1, -INF}; + double rhs[] = {6, 5}; + double lbs[] = {0, 0, 0}; + double ubs[] = {INF, INF, INF}; + double c[] = {-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, 2, 1, 1}; + int Ai_correct[] = {0, 1, 0, 1}; + int Ap_correct[] = {0, 2, 4}; + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax_correct, A->x, 4)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(Ai_correct, A->i, 4)); + CHECK_ROW_STARTS(A, Ap_correct); + + PS_FREE(stgs); + DEBUG(run_debugger(constraints, false)); + free_presolver(presolver); + + return 0; +} + +static char *test_15_ston() +{ + double Ax[] = {-1, 1, 2, 1, 1}; + int Ai[] = {0, 1, 2, 1, 2}; + int Ap[] = {0, 3, 5}; + int nnz = 5; + int n_rows = 2; + int n_cols = 3; + + double lhs[] = {1, -INF}; + double rhs[] = {6, 5}; + double lbs[] = {0, 0, 0}; + double ubs[] = {INF, INF, INF}; + double c[] = {-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, 2, 1, 1}; + int Ai_correct[] = {0, 1, 0, 1}; + int Ap_correct[] = {0, 2, 4}; + mu_assert("error Ax", ARRAYS_EQUAL_DOUBLE(Ax_correct, A->x, 4)); + mu_assert("error Ai", ARRAYS_EQUAL_INT(Ai_correct, A->i, 4)); + CHECK_ROW_STARTS(A, Ap_correct); + + 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); // (✓) @@ -1080,6 +1170,8 @@ static const char *all_tests_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); // (✓) + mu_run_test(test_14_ston, counter_ston); // (✓) + mu_run_test(test_15_ston, counter_ston); // (✓) return 0; }