From 90c858ff2794e087a59ea6b2bbedb4b407575d2c Mon Sep 17 00:00:00 2001 From: Ivet Galabova Date: Fri, 27 Sep 2024 20:36:54 +0300 Subject: [PATCH 01/75] option added --- CMakeLists.txt | 1 + src/parallel/HighsTaskExecutor.h | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index aa59302874..506298c906 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,6 +43,7 @@ option(BUILD_CXX "Build C++ library" ON) message(STATUS "Build C++ library: ${BUILD_CXX}") option(BUILD_TESTING "Build Tests" ON) +option(BUILD_EXTRA_UNIT_TESTS "Build extra unit tests" ON) option(FORTRAN "Build Fortran interface" OFF) message(STATUS "Build Fortran: ${FORTRAN}") diff --git a/src/parallel/HighsTaskExecutor.h b/src/parallel/HighsTaskExecutor.h index c0acab9e69..d8cfddb2f3 100644 --- a/src/parallel/HighsTaskExecutor.h +++ b/src/parallel/HighsTaskExecutor.h @@ -136,7 +136,7 @@ class HighsTaskExecutor { return threadLocalWorkerDeque()->getNumWorkers(); } - static void initialize(int numThreads) { + static void ilatestnitialize(int numThreads) { auto& executorHandle = threadLocalExecutorHandle(); if (!executorHandle.ptr) { executorHandle.ptr = From 4c4ce1793a676783a108fd059fd0100f4e1f95d1 Mon Sep 17 00:00:00 2001 From: Ivet Galabova Date: Fri, 27 Sep 2024 20:37:50 +0300 Subject: [PATCH 02/75] typo --- src/parallel/HighsTaskExecutor.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parallel/HighsTaskExecutor.h b/src/parallel/HighsTaskExecutor.h index 9b620b0520..3e97183bf1 100644 --- a/src/parallel/HighsTaskExecutor.h +++ b/src/parallel/HighsTaskExecutor.h @@ -165,7 +165,7 @@ class HighsTaskExecutor { return threadLocalWorkerDeque()->getNumWorkers(); } - static void ilatestnitialize(int numThreads) { + static void initialize(int numThreads) { auto& executorHandle = threadLocalExecutorHandle(); if (executorHandle.ptr == nullptr) { executorHandle.isMain = true; From e35a241f4fa313d6e1e0fd5c461db91547836bc3 Mon Sep 17 00:00:00 2001 From: Ivet Galabova Date: Fri, 27 Sep 2024 21:38:32 +0300 Subject: [PATCH 03/75] wip --- .gitignore | 3 +++ CMakeLists.txt | 20 ++++++++++++++++++++ check/CMakeLists.txt | 10 ++++++++++ 3 files changed, 33 insertions(+) diff --git a/.gitignore b/.gitignore index ee3e66a161..5be7e2e246 100644 --- a/.gitignore +++ b/.gitignore @@ -283,3 +283,6 @@ CMakeSettings.json # Nix .direnv/ result + +# Extra unit tests +highs-unit-tests \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 0f5a3f7b0a..a63e11effc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -95,6 +95,26 @@ endif() # See below for RelWithDeb info, todo test wip set(DEBUG_MEMORY "Off" CACHE STRING "Sanitizers") +if((GIT) AND(EXISTS ${HIGHS_SOURCE_DIR}/.git)) + option(GIT_SUBMODULE "Check submodules during build" ON) + if(GIT_SUBMODULE) + message(STATUS "Submodule update") + execute_process(COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + RESULT_VARIABLE GIT_SUBMOD_RESULT) + if(NOT GIT_SUBMOD_RESULT EQUAL "0") + message(FATAL_ERROR "git submodule update --init --recursive failed with ${GIT_SUBMOD_RESULT}, please checkout submodules") + endif() + endif() +endif() + +if(NOT EXISTS "${PROJECT_SOURCE_DIR}/extern/repo/CMakeLists.txt") + message(FATAL_ERROR "The submodules were not downloaded! GIT_SUBMODULE was turned off or failed. Please update submodules and try again.") +endif() + + + + # emscripten option(EMSCRIPTEN_HTML "Emscripten HTML output" OFF) diff --git a/check/CMakeLists.txt b/check/CMakeLists.txt index f258f4c514..837ff8b7c6 100644 --- a/check/CMakeLists.txt +++ b/check/CMakeLists.txt @@ -1,5 +1,10 @@ include(CTest) +if (BUILD_EXTRA_UNIT_TESTS) + add_subdirectory(highs-unit-tests) +endif() + + if (FORTRAN) set(CMAKE_Fortran_MODULE_DIRECTORY ${HIGHS_BINARY_DIR}/modules) add_executable(fortrantest TestFortranAPI.f90) @@ -87,6 +92,11 @@ if (NOT FAST_BUILD OR ALL_TESTS) endif() add_executable(unit_tests ${TEST_SOURCES}) + + if (BUILD_EXTRA_UNIT_TESTS) + set(TEST_SOURCES ${TEST_SOURCES} ${HIGHS_EXTRA_UNIT_TESTS}) + endif() + if (UNIX) target_compile_options(unit_tests PRIVATE "-Wno-unused-variable") target_compile_options(unit_tests PRIVATE "-Wno-unused-const-variable") From 082f7ebc719b43773209375ef53cb17560823718 Mon Sep 17 00:00:00 2001 From: Ivet Galabova Date: Wed, 9 Oct 2024 13:22:32 +0200 Subject: [PATCH 04/75] clean subsolutions --- CMakeLists.txt | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a63e11effc..0f5a3f7b0a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -95,26 +95,6 @@ endif() # See below for RelWithDeb info, todo test wip set(DEBUG_MEMORY "Off" CACHE STRING "Sanitizers") -if((GIT) AND(EXISTS ${HIGHS_SOURCE_DIR}/.git)) - option(GIT_SUBMODULE "Check submodules during build" ON) - if(GIT_SUBMODULE) - message(STATUS "Submodule update") - execute_process(COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - RESULT_VARIABLE GIT_SUBMOD_RESULT) - if(NOT GIT_SUBMOD_RESULT EQUAL "0") - message(FATAL_ERROR "git submodule update --init --recursive failed with ${GIT_SUBMOD_RESULT}, please checkout submodules") - endif() - endif() -endif() - -if(NOT EXISTS "${PROJECT_SOURCE_DIR}/extern/repo/CMakeLists.txt") - message(FATAL_ERROR "The submodules were not downloaded! GIT_SUBMODULE was turned off or failed. Please update submodules and try again.") -endif() - - - - # emscripten option(EMSCRIPTEN_HTML "Emscripten HTML output" OFF) From 548d28a52e3d4241623f69c53dd1700c50bf1229 Mon Sep 17 00:00:00 2001 From: Ivet Galabova Date: Wed, 9 Oct 2024 14:06:00 +0200 Subject: [PATCH 05/75] move add_aubdir for extra tests --- .gitignore | 3 ++- CMakeLists.txt | 5 +++++ check/CMakeLists.txt | 3 --- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 5be7e2e246..096bc893e1 100644 --- a/.gitignore +++ b/.gitignore @@ -285,4 +285,5 @@ CMakeSettings.json result # Extra unit tests -highs-unit-tests \ No newline at end of file +highs-unit-tests +highs-problem-set \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 0f5a3f7b0a..ca841e13e9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -585,6 +585,11 @@ else(FAST_BUILD) add_subdirectory(app) if(BUILD_TESTING) enable_testing() + + if (BUILD_EXTRA_UNIT_TESTS) + add_subdirectory(highs-unit-tests) + endif() + add_subdirectory(check) endif() # Add tests in examples/tests diff --git a/check/CMakeLists.txt b/check/CMakeLists.txt index 837ff8b7c6..ab8419a26f 100644 --- a/check/CMakeLists.txt +++ b/check/CMakeLists.txt @@ -1,8 +1,5 @@ include(CTest) -if (BUILD_EXTRA_UNIT_TESTS) - add_subdirectory(highs-unit-tests) -endif() if (FORTRAN) From d4a102f7c60db7ec683fbcbf3a4a1da586f0f7b9 Mon Sep 17 00:00:00 2001 From: Ivet Galabova Date: Wed, 9 Oct 2024 18:00:15 +0200 Subject: [PATCH 06/75] just unit --- CMakeLists.txt | 4 ---- check/CMakeLists.txt | 2 -- 2 files changed, 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ca841e13e9..a1eeeca6ab 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -586,10 +586,6 @@ else(FAST_BUILD) if(BUILD_TESTING) enable_testing() - if (BUILD_EXTRA_UNIT_TESTS) - add_subdirectory(highs-unit-tests) - endif() - add_subdirectory(check) endif() # Add tests in examples/tests diff --git a/check/CMakeLists.txt b/check/CMakeLists.txt index ab8419a26f..300f71dc54 100644 --- a/check/CMakeLists.txt +++ b/check/CMakeLists.txt @@ -1,7 +1,5 @@ include(CTest) - - if (FORTRAN) set(CMAKE_Fortran_MODULE_DIRECTORY ${HIGHS_BINARY_DIR}/modules) add_executable(fortrantest TestFortranAPI.f90) From 723264ca92143e88bff9b5ecdadb77cae8f2569f Mon Sep 17 00:00:00 2001 From: Ivet Galabova Date: Wed, 9 Oct 2024 18:03:18 +0200 Subject: [PATCH 07/75] clean up --- .gitignore | 3 +-- CMakeLists.txt | 9 +++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 096bc893e1..5be7e2e246 100644 --- a/.gitignore +++ b/.gitignore @@ -285,5 +285,4 @@ CMakeSettings.json result # Extra unit tests -highs-unit-tests -highs-problem-set \ No newline at end of file +highs-unit-tests \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index a1eeeca6ab..ddd5d6bb69 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -60,7 +60,7 @@ option(BUILD_CXX "Build C++ library" ON) message(STATUS "Build C++ library: ${BUILD_CXX}") option(BUILD_TESTING "Build Tests" ON) -option(BUILD_EXTRA_UNIT_TESTS "Build extra unit tests" ON) + option(FORTRAN "Build Fortran interface" OFF) message(STATUS "Build Fortran: ${FORTRAN}") @@ -83,6 +83,12 @@ include(CMakeDependentOption) CMAKE_DEPENDENT_OPTION(ALL_TESTS "Build all tests" OFF "BUILD_TESTING;BUILD_CXX" OFF) message(STATUS "Build all tests: ${ALL_TESTS}") + +CMAKE_DEPENDENT_OPTION(BUILD_EXTRA_UNIT_TESTS "Build extra unit tests" OFF "BUILD_TESTING;BUILD_CXX;ALL_TESTS" OFF) +if (BUILD_EXTRA_UNIT_TESTS) + message(STATUS "Build extra unit tests: ON") +endif() + option(ZLIB "ZLIB" ON) message(STATUS "ZLIB: ${ZLIB}") if (PYTHON_BUILD_SETUP) @@ -585,7 +591,6 @@ else(FAST_BUILD) add_subdirectory(app) if(BUILD_TESTING) enable_testing() - add_subdirectory(check) endif() # Add tests in examples/tests From dbc4db9b936b38ec9c0b4667590135c1b4d0c074 Mon Sep 17 00:00:00 2001 From: Ivet Galabova Date: Wed, 9 Oct 2024 18:09:58 +0200 Subject: [PATCH 08/75] extra unit --- CMakeLists.txt | 2 -- check/CMakeLists.txt | 4 +++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ddd5d6bb69..307bf2620a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -61,7 +61,6 @@ message(STATUS "Build C++ library: ${BUILD_CXX}") option(BUILD_TESTING "Build Tests" ON) - option(FORTRAN "Build Fortran interface" OFF) message(STATUS "Build Fortran: ${FORTRAN}") option(CSHARP "Build CSharp interface" OFF) @@ -83,7 +82,6 @@ include(CMakeDependentOption) CMAKE_DEPENDENT_OPTION(ALL_TESTS "Build all tests" OFF "BUILD_TESTING;BUILD_CXX" OFF) message(STATUS "Build all tests: ${ALL_TESTS}") - CMAKE_DEPENDENT_OPTION(BUILD_EXTRA_UNIT_TESTS "Build extra unit tests" OFF "BUILD_TESTING;BUILD_CXX;ALL_TESTS" OFF) if (BUILD_EXTRA_UNIT_TESTS) message(STATUS "Build extra unit tests: ON") diff --git a/check/CMakeLists.txt b/check/CMakeLists.txt index 300f71dc54..f0eff157d6 100644 --- a/check/CMakeLists.txt +++ b/check/CMakeLists.txt @@ -89,7 +89,9 @@ if (NOT FAST_BUILD OR ALL_TESTS) add_executable(unit_tests ${TEST_SOURCES}) if (BUILD_EXTRA_UNIT_TESTS) - set(TEST_SOURCES ${TEST_SOURCES} ${HIGHS_EXTRA_UNIT_TESTS}) + list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/highs-unit-tests") + include(extra-tests) + set(TEST_SOURCES ${TEST_SOURCES} ${HIGHS_EXTRA_UNIT_TESTS}) endif() if (UNIX) From d53797b6c5bb9fe6349d9ec27508efc0a8a82489 Mon Sep 17 00:00:00 2001 From: Ivet Galabova Date: Fri, 11 Oct 2024 20:59:32 +0200 Subject: [PATCH 09/75] unit tests --- .../workflows/build-unit-tests-external.yml | 416 ++++++++++++++++++ check/CMakeLists.txt | 3 +- 2 files changed, 418 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/build-unit-tests-external.yml diff --git a/.github/workflows/build-unit-tests-external.yml b/.github/workflows/build-unit-tests-external.yml new file mode 100644 index 0000000000..5ba1d9126c --- /dev/null +++ b/.github/workflows/build-unit-tests-external.yml @@ -0,0 +1,416 @@ +name: build-unit-tests-external + +on: [push, pull_request] + +jobs: + fast_build_release: + runs-on: windows-2019 + + steps: + - uses: actions/checkout@v4 + + - name: Create Build Environment + # Some projects don't allow in-source building, so create a separate build directory + # We'll use this as our working directory for all subsequent commands + run: cmake -E make_directory ${{runner.workspace}}/build + + - name: Configure CMake + # Use a bash shell so we can use the same syntax for environment variable + # access regardless of the host operating system + shell: bash + working-directory: ${{runner.workspace}}/build + # Note the current convention is to use the -S and -B options here to specify source + # and build directories, but this is only available with CMake 3.13 and higher. + # The CMake binaries on the Github Actions machines are (as of this writing) 3.12 + run: cmake $GITHUB_WORKSPACE -DFAST_BUILD=ON -DALL_TESTS=ON -DBUILD_EXTRA_UNIT_TESTS -DCMAKE_BUILD_TYPE=Release + + - name: Build + working-directory: ${{runner.workspace}}/build + shell: bash + # Execute the build. You can specify a specific target with "--target " + run: cmake --build . --config Release --parallel + + - name: Test + working-directory: ${{runner.workspace}}/build + shell: bash + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest --timeout 300 --output-on-failure -C Release + + - name: Configure CMake All + shell: bash + working-directory: ${{runner.workspace}}/build + run: cmake $GITHUB_WORKSPACE -DALL_TESTS=ON + + fast_build_release_all_tests: + runs-on: windows-2019 + + steps: + - uses: actions/checkout@v4 + + - name: Create Build Environment + # Some projects don't allow in-source building, so create a separate build directory + # We'll use this as our working directory for all subsequent commands + run: cmake -E make_directory ${{runner.workspace}}/build + + - name: Configure CMake All + shell: bash + working-directory: ${{runner.workspace}}/build + run: cmake $GITHUB_WORKSPACE -DALL_TESTS=ON + + - name: Build All + working-directory: ${{runner.workspace}}/build + shell: bash + # Execute the build. You can specify a specific target with "--target " + run: | + cmake --build . --parallel --config Release + + - name: Test All + working-directory: ${{runner.workspace}}/build + shell: bash + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest --parallel --timeout 300 --output-on-failure -C Release + + - name: Install + working-directory: ${{runner.workspace}}/build + shell: bash + # Execute the build. You can specify a specific target with "--target " + run: cmake --install . + + windows_release_fb_off: + runs-on: windows-2019 + + steps: + - uses: actions/checkout@v4 + + - name: Create Build Environment + # Some projects don't allow in-source building, so create a separate build directory + # We'll use this as our working directory for all subsequent commands + run: cmake -E make_directory ${{runner.workspace}}/build + + - name: Configure CMake + # Use a bash shell so we can use the same syntax for environment variable + # access regardless of the host operating system + shell: bash + working-directory: ${{runner.workspace}}/build + # Note the current convention is to use the -S and -B options here to specify source + # and build directories, but this is only available with CMake 3.13 and higher. + # The CMake binaries on the Github Actions machines are (as of this writing) 3.12 + run: cmake $GITHUB_WORKSPACE -DFAST_BUILD=OFF -DHIGHS_NO_DEFAULT_THREADS=ON + + - name: Build + working-directory: ${{runner.workspace}}/build + shell: bash + # Execute the build. You can specify a specific target with "--target " + run: cmake --build . --config Release --parallel + + - name: Test + working-directory: ${{runner.workspace}}/build + shell: bash + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest --timeout 10000 --output-on-failure -C Release + + fast_build_debug: + runs-on: windows-latest + + steps: + - uses: actions/checkout@v4 + + - name: Create Build Environment + # Some projects don't allow in-source building, so create a separate build directory + # We'll use this as our working directory for all subsequent commands + run: cmake -E make_directory ${{runner.workspace}}/build + + - name: Configure CMake + # Use a bash shell so we can use the same syntax for environment variable + # access regardless of the host operating system + shell: bash + working-directory: ${{runner.workspace}}/build + # Note the current convention is to use the -S and -B options here to specify source + # and build directories, but this is only available with CMake 3.13 and higher. + # The CMake binaries on the Github Actions machines are (as of this writing) 3.12 + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=Debug -DFAST_BUILD=ON -DHIGHS_NO_DEFAULT_THREADS=ON + + - name: Build + working-directory: ${{runner.workspace}}/build + shell: bash + # Execute the build. You can specify a specific target with "--target " + run: cmake --build . --config Debug --parallel + + - name: Test + working-directory: ${{runner.workspace}}/build + shell: bash + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest --output-on-failure -C Debug + + fast_build_debug_all_tests: + runs-on: windows-latest + + steps: + - uses: actions/checkout@v4 + + - name: Create Build Environment + # Some projects don't allow in-source building, so create a separate build directory + # We'll use this as our working directory for all subsequent commands + run: cmake -E make_directory ${{runner.workspace}}/build + + - name: Configure CMake + # Use a bash shell so we can use the same syntax for environment variable + # access regardless of the host operating system + shell: bash + working-directory: ${{runner.workspace}}/build + # Note the current convention is to use the -S and -B options here to specify source + # and build directories, but this is only available with CMake 3.13 and higher. + # The CMake binaries on the Github Actions machines are (as of this writing) 3.12 + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=Debug -DALL_TESTS=ON -DHIGHS_NO_DEFAULT_THREADS=ON + + - name: Build + working-directory: ${{runner.workspace}}/build + shell: bash + # Execute the build. You can specify a specific target with "--target " + run: cmake --build . --config Debug --parallel + + - name: Test + working-directory: ${{runner.workspace}}/build + shell: bash + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest --output-on-failure -C Debug + + windows_debug_fb_off: + runs-on: windows-latest + + steps: + - uses: actions/checkout@v4 + + - name: Create Build Environment + # Some projects don't allow in-source building, so create a separate build directory + # We'll use this as our working directory for all subsequent commands + run: cmake -E make_directory ${{runner.workspace}}/build + + - name: Configure CMake All + shell: bash + working-directory: ${{runner.workspace}}/build + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=Debug -DFAST_BUILD=OFF + + - name: Build All + working-directory: ${{runner.workspace}}/build + shell: bash + # Execute the build. You can specify a specific target with "--target " + run: | + cmake --build . --parallel --config Debug + + - name: Test All + working-directory: ${{runner.workspace}}/build + shell: bash + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest --parallel --timeout 300 --output-on-failure -C Debug + + windows_release64: + runs-on: windows-2019 + + steps: + - uses: actions/checkout@v4 + + - name: Create Build Environment + # Some projects don't allow in-source building, so create a separate build directory + # We'll use this as our working directory for all subsequent commands + run: cmake -E make_directory ${{runner.workspace}}/build + + - name: Configure CMake + # Use a bash shell so we can use the same syntax for environment variable + # access regardless of the host operating system + shell: bash + working-directory: ${{runner.workspace}}/build + # Note the current convention is to use the -S and -B options here to specify source + # and build directories, but this is only available with CMake 3.13 and higher. + # The CMake binaries on the Github Actions machines are (as of this writing) 3.12 + run: cmake $GITHUB_WORKSPACE -DFAST_BUILD=OFF -DHIGHSINT64=on -DHIGHS_NO_DEFAULT_THREADS=ON + + - name: Build + working-directory: ${{runner.workspace}}/build + shell: bash + # Execute the build. You can specify a specific target with "--target " + run: cmake --build . --config Release --parallel + + - name: Test + working-directory: ${{runner.workspace}}/build + shell: bash + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest --timeout 300 --output-on-failure -C Release + + windows_release64_all_tests: + runs-on: windows-2019 + + steps: + - uses: actions/checkout@v4 + + - name: Create Build Environment + # Some projects don't allow in-source building, so create a separate build directory + # We'll use this as our working directory for all subsequent commands + run: cmake -E make_directory ${{runner.workspace}}/build + + - name: Configure CMake + # Use a bash shell so we can use the same syntax for environment variable + # access regardless of the host operating system + shell: bash + working-directory: ${{runner.workspace}}/build + # Note the current convention is to use the -S and -B options here to specify source + # and build directories, but this is only available with CMake 3.13 and higher. + # The CMake binaries on the Github Actions machines are (as of this writing) 3.12 + run: cmake $GITHUB_WORKSPACE -DFAST_BUILD=OFF -DHIGHSINT64=on -DHIGHS_NO_DEFAULT_THREADS=ON + + - name: Build + working-directory: ${{runner.workspace}}/build + shell: bash + # Execute the build. You can specify a specific target with "--target " + run: cmake --build . --config Release --parallel + + - name: Test + working-directory: ${{runner.workspace}}/build + shell: bash + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest --timeout 300 --output-on-failure -C Release + + + windows_release64_fb_off: + runs-on: windows-2019 + + steps: + - uses: actions/checkout@v4 + + - name: Create Build Environment + # Some projects don't allow in-source building, so create a separate build directory + # We'll use this as our working directory for all subsequent commands + run: cmake -E make_directory ${{runner.workspace}}/build + + - name: Configure CMake + # Use a bash shell so we can use the same syntax for environment variable + # access regardless of the host operating system + shell: bash + working-directory: ${{runner.workspace}}/build + # Note the current convention is to use the -S and -B options here to specify source + # and build directories, but this is only available with CMake 3.13 and higher. + # The CMake binaries on the Github Actions machines are (as of this writing) 3.12 + run: cmake $GITHUB_WORKSPACE -DFAST_BUILD=OFF -DHIGHSINT64=on -DHIGHS_NO_DEFAULT_THREADS=ON + + - name: Build + working-directory: ${{runner.workspace}}/build + shell: bash + # Execute the build. You can specify a specific target with "--target " + run: cmake --build . --config Release --parallel + + - name: Test + working-directory: ${{runner.workspace}}/build + shell: bash + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest --timeout 300 --output-on-failure -C Release + + windows_debug64: + runs-on: windows-latest + + steps: + - uses: actions/checkout@v4 + + - name: Create Build Environment + # Some projects don't allow in-source building, so create a separate build directory + # We'll use this as our working directory for all subsequent commands + run: cmake -E make_directory ${{runner.workspace}}/build + + - name: Configure CMake + # Use a bash shell so we can use the same syntax for environment variable + # access regardless of the host operating system + shell: bash + working-directory: ${{runner.workspace}}/build + # Note the current convention is to use the -S and -B options here to specify source + # and build directories, but this is only available with CMake 3.13 and higher. + # The CMake binaries on the Github Actions machines are (as of this writing) 3.12 + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=Debug -DHIGHSINT64=on -DHIGHS_NO_DEFAULT_THREADS=ON + + - name: Build + working-directory: ${{runner.workspace}}/build + shell: bash + # Execute the build. You can specify a specific target with "--target " + run: cmake --build . --config Debug --parallel + + - name: Test + working-directory: ${{runner.workspace}}/build + shell: bash + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest --output-on-failure -C Debug + + windows_debug64_all_tests: + runs-on: windows-latest + + steps: + - uses: actions/checkout@v4 + + - name: Create Build Environment + # Some projects don't allow in-source building, so create a separate build directory + # We'll use this as our working directory for all subsequent commands + run: cmake -E make_directory ${{runner.workspace}}/build + + - name: Configure CMake + # Use a bash shell so we can use the same syntax for environment variable + # access regardless of the host operating system + shell: bash + working-directory: ${{runner.workspace}}/build + # Note the current convention is to use the -S and -B options here to specify source + # and build directories, but this is only available with CMake 3.13 and higher. + # The CMake binaries on the Github Actions machines are (as of this writing) 3.12 + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=Debug -DALL_TESTS=ON -DHIGHSINT64=on -DHIGHS_NO_DEFAULT_THREADS=ON + + - name: Build + working-directory: ${{runner.workspace}}/build + shell: bash + # Execute the build. You can specify a specific target with "--target " + run: cmake --build . --config Debug --parallel + + - name: Test + working-directory: ${{runner.workspace}}/build + shell: bash + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest --output-on-failure -C Debug + + windows_debug64_fb_off: + runs-on: windows-latest + + steps: + - uses: actions/checkout@v4 + + - name: Create Build Environment + # Some projects don't allow in-source building, so create a separate build directory + # We'll use this as our working directory for all subsequent commands + run: cmake -E make_directory ${{runner.workspace}}/build + + - name: Configure CMake + # Use a bash shell so we can use the same syntax for environment variable + # access regardless of the host operating system + shell: bash + working-directory: ${{runner.workspace}}/build + # Note the current convention is to use the -S and -B options here to specify source + # and build directories, but this is only available with CMake 3.13 and higher. + # The CMake binaries on the Github Actions machines are (as of this writing) 3.12 + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=Debug -DFAST_BUILD=OFF -DHIGHSINT64=on -DHIGHS_NO_DEFAULT_THREADS=ON + + - name: Build + working-directory: ${{runner.workspace}}/build + shell: bash + # Execute the build. You can specify a specific target with "--target " + run: cmake --build . --config Debug --parallel + + - name: Test + working-directory: ${{runner.workspace}}/build + shell: bash + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest --output-on-failure -C Debug \ No newline at end of file diff --git a/check/CMakeLists.txt b/check/CMakeLists.txt index f0eff157d6..1da4017262 100644 --- a/check/CMakeLists.txt +++ b/check/CMakeLists.txt @@ -91,7 +91,8 @@ if (NOT FAST_BUILD OR ALL_TESTS) if (BUILD_EXTRA_UNIT_TESTS) list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/highs-unit-tests") include(extra-tests) - set(TEST_SOURCES ${TEST_SOURCES} ${HIGHS_EXTRA_UNIT_TESTS}) + message(STATUS "EXTRA TESTS: ${HIGHS_EXTRA_UNIT_TESTS}") + set(TEST_SOURCES ${HIGHS_EXTRA_UNIT_TESTS}) endif() if (UNIX) From 4cc4b74ed8c2392a2eb7c56995fb5e13e25995bb Mon Sep 17 00:00:00 2001 From: Ivet Galabova Date: Mon, 14 Oct 2024 15:13:26 +0300 Subject: [PATCH 10/75] gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 5be7e2e246..096bc893e1 100644 --- a/.gitignore +++ b/.gitignore @@ -285,4 +285,5 @@ CMakeSettings.json result # Extra unit tests -highs-unit-tests \ No newline at end of file +highs-unit-tests +highs-problem-set \ No newline at end of file From 31e21e6a4068044cd6553c3a443e380f128d382e Mon Sep 17 00:00:00 2001 From: Ivet Galabova Date: Tue, 15 Oct 2024 13:31:01 +0300 Subject: [PATCH 11/75] gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 5be7e2e246..096bc893e1 100644 --- a/.gitignore +++ b/.gitignore @@ -285,4 +285,5 @@ CMakeSettings.json result # Extra unit tests -highs-unit-tests \ No newline at end of file +highs-unit-tests +highs-problem-set \ No newline at end of file From 612dedb8ce98463ec1382aaaca99094034d8b0df Mon Sep 17 00:00:00 2001 From: Ivet Galabova Date: Tue, 15 Oct 2024 17:05:07 +0300 Subject: [PATCH 12/75] extra problem set in ctest OK --- CMakeLists.txt | 7 ++++++- check/CMakeLists.txt | 22 +++++++++++++++------- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 307bf2620a..e0504cb518 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -82,11 +82,16 @@ include(CMakeDependentOption) CMAKE_DEPENDENT_OPTION(ALL_TESTS "Build all tests" OFF "BUILD_TESTING;BUILD_CXX" OFF) message(STATUS "Build all tests: ${ALL_TESTS}") -CMAKE_DEPENDENT_OPTION(BUILD_EXTRA_UNIT_TESTS "Build extra unit tests" OFF "BUILD_TESTING;BUILD_CXX;ALL_TESTS" OFF) +CMAKE_DEPENDENT_OPTION(BUILD_EXTRA_UNIT_TESTS "Build extra unit tests" OFF "ALL_TESTS" OFF) if (BUILD_EXTRA_UNIT_TESTS) message(STATUS "Build extra unit tests: ON") endif() +CMAKE_DEPENDENT_OPTION(BUILD_EXTRA_PROBLEM_SET "Build extra instance tests" OFF "BUILD_TESTING" OFF) +if (BUILD_EXTRA_PROBLEM_SET) + message(STATUS "Build extra instance tests: ON") +endif() + option(ZLIB "ZLIB" ON) message(STATUS "ZLIB: ${ZLIB}") if (PYTHON_BUILD_SETUP) diff --git a/check/CMakeLists.txt b/check/CMakeLists.txt index 1da4017262..9aa5db34a4 100644 --- a/check/CMakeLists.txt +++ b/check/CMakeLists.txt @@ -86,14 +86,16 @@ if (NOT FAST_BUILD OR ALL_TESTS) set(TEST_SOURCES ${TEST_SOURCES} TestSpecialLps.cpp TestLpSolvers.cpp TestMipSolver.cpp) endif() - add_executable(unit_tests ${TEST_SOURCES}) + if (BUILD_EXTRA_UNIT_TESTS) + list(APPEND CMAKE_MODULE_PATH "${HIGHS_SOURCE_DIR}/check/highs-unit-tests") + message(STATUS "${HIGHS_SOURCE_DIR}/check/highs-unit-tests") + include(highs-unit-tests) - if (BUILD_EXTRA_UNIT_TESTS) - list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/highs-unit-tests") - include(extra-tests) - message(STATUS "EXTRA TESTS: ${HIGHS_EXTRA_UNIT_TESTS}") - set(TEST_SOURCES ${HIGHS_EXTRA_UNIT_TESTS}) - endif() + set(TEST_SOURCES ${TEST_SOURCES} ${HIGHS_EXTRA_UNIT_TESTS}) + message(STATUS ${TEST_SOURCES}) + endif() + + add_executable(unit_tests ${TEST_SOURCES}) if (UNIX) target_compile_options(unit_tests PRIVATE "-Wno-unused-variable") @@ -331,3 +333,9 @@ if (NOT FAST_BUILD OR ALL_TESTS) endforeach() endif() + +if (BUILD_EXTRA_PROBLEM_SET) + list(APPEND CMAKE_MODULE_PATH "${HIGHS_SOURCE_DIR}/check/highs-problem-set") + message(STATUS "${HIGHS_SOURCE_DIR}/check/highs-problem-set") + include(highs-problem-set) +endif() From 5c52ccd79abe36136131d2655075a6110a341c3d Mon Sep 17 00:00:00 2001 From: Ivet Galabova Date: Tue, 15 Oct 2024 17:16:28 +0300 Subject: [PATCH 13/75] workflow windows --- .../workflows/build-unit-tests-external.yml | 385 +----------------- 1 file changed, 9 insertions(+), 376 deletions(-) diff --git a/.github/workflows/build-unit-tests-external.yml b/.github/workflows/build-unit-tests-external.yml index 5ba1d9126c..d596a985b4 100644 --- a/.github/workflows/build-unit-tests-external.yml +++ b/.github/workflows/build-unit-tests-external.yml @@ -3,91 +3,22 @@ name: build-unit-tests-external on: [push, pull_request] jobs: - fast_build_release: + release_extra_unit_tests: runs-on: windows-2019 steps: - uses: actions/checkout@v4 - name: Create Build Environment - # Some projects don't allow in-source building, so create a separate build directory - # We'll use this as our working directory for all subsequent commands run: cmake -E make_directory ${{runner.workspace}}/build - - name: Configure CMake - # Use a bash shell so we can use the same syntax for environment variable - # access regardless of the host operating system - shell: bash + - name: Clone extra unit tests repo working-directory: ${{runner.workspace}}/build - # Note the current convention is to use the -S and -B options here to specify source - # and build directories, but this is only available with CMake 3.13 and higher. - # The CMake binaries on the Github Actions machines are (as of this writing) 3.12 - run: cmake $GITHUB_WORKSPACE -DFAST_BUILD=ON -DALL_TESTS=ON -DBUILD_EXTRA_UNIT_TESTS -DCMAKE_BUILD_TYPE=Release + run: git clone https://github.com/galabovaa/highs-unit-tests.git - - name: Build - working-directory: ${{runner.workspace}}/build - shell: bash - # Execute the build. You can specify a specific target with "--target " - run: cmake --build . --config Release --parallel - - - name: Test - working-directory: ${{runner.workspace}}/build - shell: bash - # Execute tests defined by the CMake configuration. - # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail - run: ctest --timeout 300 --output-on-failure -C Release - - - name: Configure CMake All - shell: bash - working-directory: ${{runner.workspace}}/build - run: cmake $GITHUB_WORKSPACE -DALL_TESTS=ON - - fast_build_release_all_tests: - runs-on: windows-2019 - - steps: - - uses: actions/checkout@v4 - - - name: Create Build Environment - # Some projects don't allow in-source building, so create a separate build directory - # We'll use this as our working directory for all subsequent commands - run: cmake -E make_directory ${{runner.workspace}}/build - - - name: Configure CMake All - shell: bash - working-directory: ${{runner.workspace}}/build - run: cmake $GITHUB_WORKSPACE -DALL_TESTS=ON - - - name: Build All - working-directory: ${{runner.workspace}}/build - shell: bash - # Execute the build. You can specify a specific target with "--target " - run: | - cmake --build . --parallel --config Release - - - name: Test All - working-directory: ${{runner.workspace}}/build - shell: bash - # Execute tests defined by the CMake configuration. - # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail - run: ctest --parallel --timeout 300 --output-on-failure -C Release - - - name: Install - working-directory: ${{runner.workspace}}/build - shell: bash - # Execute the build. You can specify a specific target with "--target " - run: cmake --install . - - windows_release_fb_off: - runs-on: windows-2019 - - steps: - - uses: actions/checkout@v4 - - - name: Create Build Environment - # Some projects don't allow in-source building, so create a separate build directory - # We'll use this as our working directory for all subsequent commands - run: cmake -E make_directory ${{runner.workspace}}/build + - name: Create symlink + working-directory: $GITHUB_WORKSPACE/check + run: mklink /d highs-unit-tests ${{runner.workspace}}/highs-unit-tests - name: Configure CMake # Use a bash shell so we can use the same syntax for environment variable @@ -97,7 +28,7 @@ jobs: # Note the current convention is to use the -S and -B options here to specify source # and build directories, but this is only available with CMake 3.13 and higher. # The CMake binaries on the Github Actions machines are (as of this writing) 3.12 - run: cmake $GITHUB_WORKSPACE -DFAST_BUILD=OFF -DHIGHS_NO_DEFAULT_THREADS=ON + run: cmake $GITHUB_WORKSPACE -DALL_TESTS=ON -DBUILD_EXTRA_UNIT_TESTS=ON - name: Build working-directory: ${{runner.workspace}}/build @@ -105,206 +36,10 @@ jobs: # Execute the build. You can specify a specific target with "--target " run: cmake --build . --config Release --parallel - - name: Test + - name: Unit Test Extra working-directory: ${{runner.workspace}}/build shell: bash - # Execute tests defined by the CMake configuration. - # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail - run: ctest --timeout 10000 --output-on-failure -C Release - - fast_build_debug: - runs-on: windows-latest - - steps: - - uses: actions/checkout@v4 - - - name: Create Build Environment - # Some projects don't allow in-source building, so create a separate build directory - # We'll use this as our working directory for all subsequent commands - run: cmake -E make_directory ${{runner.workspace}}/build - - - name: Configure CMake - # Use a bash shell so we can use the same syntax for environment variable - # access regardless of the host operating system - shell: bash - working-directory: ${{runner.workspace}}/build - # Note the current convention is to use the -S and -B options here to specify source - # and build directories, but this is only available with CMake 3.13 and higher. - # The CMake binaries on the Github Actions machines are (as of this writing) 3.12 - run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=Debug -DFAST_BUILD=ON -DHIGHS_NO_DEFAULT_THREADS=ON - - - name: Build - working-directory: ${{runner.workspace}}/build - shell: bash - # Execute the build. You can specify a specific target with "--target " - run: cmake --build . --config Debug --parallel - - - name: Test - working-directory: ${{runner.workspace}}/build - shell: bash - # Execute tests defined by the CMake configuration. - # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail - run: ctest --output-on-failure -C Debug - - fast_build_debug_all_tests: - runs-on: windows-latest - - steps: - - uses: actions/checkout@v4 - - - name: Create Build Environment - # Some projects don't allow in-source building, so create a separate build directory - # We'll use this as our working directory for all subsequent commands - run: cmake -E make_directory ${{runner.workspace}}/build - - - name: Configure CMake - # Use a bash shell so we can use the same syntax for environment variable - # access regardless of the host operating system - shell: bash - working-directory: ${{runner.workspace}}/build - # Note the current convention is to use the -S and -B options here to specify source - # and build directories, but this is only available with CMake 3.13 and higher. - # The CMake binaries on the Github Actions machines are (as of this writing) 3.12 - run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=Debug -DALL_TESTS=ON -DHIGHS_NO_DEFAULT_THREADS=ON - - - name: Build - working-directory: ${{runner.workspace}}/build - shell: bash - # Execute the build. You can specify a specific target with "--target " - run: cmake --build . --config Debug --parallel - - - name: Test - working-directory: ${{runner.workspace}}/build - shell: bash - # Execute tests defined by the CMake configuration. - # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail - run: ctest --output-on-failure -C Debug - - windows_debug_fb_off: - runs-on: windows-latest - - steps: - - uses: actions/checkout@v4 - - - name: Create Build Environment - # Some projects don't allow in-source building, so create a separate build directory - # We'll use this as our working directory for all subsequent commands - run: cmake -E make_directory ${{runner.workspace}}/build - - - name: Configure CMake All - shell: bash - working-directory: ${{runner.workspace}}/build - run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=Debug -DFAST_BUILD=OFF - - - name: Build All - working-directory: ${{runner.workspace}}/build - shell: bash - # Execute the build. You can specify a specific target with "--target " - run: | - cmake --build . --parallel --config Debug - - - name: Test All - working-directory: ${{runner.workspace}}/build - shell: bash - # Execute tests defined by the CMake configuration. - # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail - run: ctest --parallel --timeout 300 --output-on-failure -C Debug - - windows_release64: - runs-on: windows-2019 - - steps: - - uses: actions/checkout@v4 - - - name: Create Build Environment - # Some projects don't allow in-source building, so create a separate build directory - # We'll use this as our working directory for all subsequent commands - run: cmake -E make_directory ${{runner.workspace}}/build - - - name: Configure CMake - # Use a bash shell so we can use the same syntax for environment variable - # access regardless of the host operating system - shell: bash - working-directory: ${{runner.workspace}}/build - # Note the current convention is to use the -S and -B options here to specify source - # and build directories, but this is only available with CMake 3.13 and higher. - # The CMake binaries on the Github Actions machines are (as of this writing) 3.12 - run: cmake $GITHUB_WORKSPACE -DFAST_BUILD=OFF -DHIGHSINT64=on -DHIGHS_NO_DEFAULT_THREADS=ON - - - name: Build - working-directory: ${{runner.workspace}}/build - shell: bash - # Execute the build. You can specify a specific target with "--target " - run: cmake --build . --config Release --parallel - - - name: Test - working-directory: ${{runner.workspace}}/build - shell: bash - # Execute tests defined by the CMake configuration. - # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail - run: ctest --timeout 300 --output-on-failure -C Release - - windows_release64_all_tests: - runs-on: windows-2019 - - steps: - - uses: actions/checkout@v4 - - - name: Create Build Environment - # Some projects don't allow in-source building, so create a separate build directory - # We'll use this as our working directory for all subsequent commands - run: cmake -E make_directory ${{runner.workspace}}/build - - - name: Configure CMake - # Use a bash shell so we can use the same syntax for environment variable - # access regardless of the host operating system - shell: bash - working-directory: ${{runner.workspace}}/build - # Note the current convention is to use the -S and -B options here to specify source - # and build directories, but this is only available with CMake 3.13 and higher. - # The CMake binaries on the Github Actions machines are (as of this writing) 3.12 - run: cmake $GITHUB_WORKSPACE -DFAST_BUILD=OFF -DHIGHSINT64=on -DHIGHS_NO_DEFAULT_THREADS=ON - - - name: Build - working-directory: ${{runner.workspace}}/build - shell: bash - # Execute the build. You can specify a specific target with "--target " - run: cmake --build . --config Release --parallel - - - name: Test - working-directory: ${{runner.workspace}}/build - shell: bash - # Execute tests defined by the CMake configuration. - # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail - run: ctest --timeout 300 --output-on-failure -C Release - - - windows_release64_fb_off: - runs-on: windows-2019 - - steps: - - uses: actions/checkout@v4 - - - name: Create Build Environment - # Some projects don't allow in-source building, so create a separate build directory - # We'll use this as our working directory for all subsequent commands - run: cmake -E make_directory ${{runner.workspace}}/build - - - name: Configure CMake - # Use a bash shell so we can use the same syntax for environment variable - # access regardless of the host operating system - shell: bash - working-directory: ${{runner.workspace}}/build - # Note the current convention is to use the -S and -B options here to specify source - # and build directories, but this is only available with CMake 3.13 and higher. - # The CMake binaries on the Github Actions machines are (as of this writing) 3.12 - run: cmake $GITHUB_WORKSPACE -DFAST_BUILD=OFF -DHIGHSINT64=on -DHIGHS_NO_DEFAULT_THREADS=ON - - - name: Build - working-directory: ${{runner.workspace}}/build - shell: bash - # Execute the build. You can specify a specific target with "--target " - run: cmake --build . --config Release --parallel + run: ./bin/Release/unit_tests.exe highs-names-extra - name: Test working-directory: ${{runner.workspace}}/build @@ -312,105 +47,3 @@ jobs: # Execute tests defined by the CMake configuration. # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail run: ctest --timeout 300 --output-on-failure -C Release - - windows_debug64: - runs-on: windows-latest - - steps: - - uses: actions/checkout@v4 - - - name: Create Build Environment - # Some projects don't allow in-source building, so create a separate build directory - # We'll use this as our working directory for all subsequent commands - run: cmake -E make_directory ${{runner.workspace}}/build - - - name: Configure CMake - # Use a bash shell so we can use the same syntax for environment variable - # access regardless of the host operating system - shell: bash - working-directory: ${{runner.workspace}}/build - # Note the current convention is to use the -S and -B options here to specify source - # and build directories, but this is only available with CMake 3.13 and higher. - # The CMake binaries on the Github Actions machines are (as of this writing) 3.12 - run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=Debug -DHIGHSINT64=on -DHIGHS_NO_DEFAULT_THREADS=ON - - - name: Build - working-directory: ${{runner.workspace}}/build - shell: bash - # Execute the build. You can specify a specific target with "--target " - run: cmake --build . --config Debug --parallel - - - name: Test - working-directory: ${{runner.workspace}}/build - shell: bash - # Execute tests defined by the CMake configuration. - # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail - run: ctest --output-on-failure -C Debug - - windows_debug64_all_tests: - runs-on: windows-latest - - steps: - - uses: actions/checkout@v4 - - - name: Create Build Environment - # Some projects don't allow in-source building, so create a separate build directory - # We'll use this as our working directory for all subsequent commands - run: cmake -E make_directory ${{runner.workspace}}/build - - - name: Configure CMake - # Use a bash shell so we can use the same syntax for environment variable - # access regardless of the host operating system - shell: bash - working-directory: ${{runner.workspace}}/build - # Note the current convention is to use the -S and -B options here to specify source - # and build directories, but this is only available with CMake 3.13 and higher. - # The CMake binaries on the Github Actions machines are (as of this writing) 3.12 - run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=Debug -DALL_TESTS=ON -DHIGHSINT64=on -DHIGHS_NO_DEFAULT_THREADS=ON - - - name: Build - working-directory: ${{runner.workspace}}/build - shell: bash - # Execute the build. You can specify a specific target with "--target " - run: cmake --build . --config Debug --parallel - - - name: Test - working-directory: ${{runner.workspace}}/build - shell: bash - # Execute tests defined by the CMake configuration. - # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail - run: ctest --output-on-failure -C Debug - - windows_debug64_fb_off: - runs-on: windows-latest - - steps: - - uses: actions/checkout@v4 - - - name: Create Build Environment - # Some projects don't allow in-source building, so create a separate build directory - # We'll use this as our working directory for all subsequent commands - run: cmake -E make_directory ${{runner.workspace}}/build - - - name: Configure CMake - # Use a bash shell so we can use the same syntax for environment variable - # access regardless of the host operating system - shell: bash - working-directory: ${{runner.workspace}}/build - # Note the current convention is to use the -S and -B options here to specify source - # and build directories, but this is only available with CMake 3.13 and higher. - # The CMake binaries on the Github Actions machines are (as of this writing) 3.12 - run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=Debug -DFAST_BUILD=OFF -DHIGHSINT64=on -DHIGHS_NO_DEFAULT_THREADS=ON - - - name: Build - working-directory: ${{runner.workspace}}/build - shell: bash - # Execute the build. You can specify a specific target with "--target " - run: cmake --build . --config Debug --parallel - - - name: Test - working-directory: ${{runner.workspace}}/build - shell: bash - # Execute tests defined by the CMake configuration. - # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail - run: ctest --output-on-failure -C Debug \ No newline at end of file From 5ded22eea2c10feef8f34dc5c017f69ffd04de6b Mon Sep 17 00:00:00 2001 From: Ivet Galabova Date: Tue, 15 Oct 2024 17:24:37 +0300 Subject: [PATCH 14/75] bash shell --- .github/workflows/build-unit-tests-external.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build-unit-tests-external.yml b/.github/workflows/build-unit-tests-external.yml index d596a985b4..0423062c16 100644 --- a/.github/workflows/build-unit-tests-external.yml +++ b/.github/workflows/build-unit-tests-external.yml @@ -17,6 +17,7 @@ jobs: run: git clone https://github.com/galabovaa/highs-unit-tests.git - name: Create symlink + shell: bash working-directory: $GITHUB_WORKSPACE/check run: mklink /d highs-unit-tests ${{runner.workspace}}/highs-unit-tests From 6e5297e714ec9708c9a538e78966ef94333abe08 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Fri, 25 Oct 2024 13:32:41 +0100 Subject: [PATCH 15/75] Added presolve-slacks test case to TestPresolve.cpp --- check/TestPresolve.cpp | 38 +++++++++++++++++++++++++++++++++ src/qpsolver/dantzigpricing.hpp | 3 ++- src/qpsolver/devexpricing.hpp | 2 ++ 3 files changed, 42 insertions(+), 1 deletion(-) diff --git a/check/TestPresolve.cpp b/check/TestPresolve.cpp index 86c5dfc469..b0a4dbc95b 100644 --- a/check/TestPresolve.cpp +++ b/check/TestPresolve.cpp @@ -601,3 +601,41 @@ TEST_CASE("write-presolved-model", "[highs_test_presolve]") { REQUIRE(highs.getInfo().simplex_iteration_count == -1); std::remove(presolved_model_file.c_str()); } + +TEST_CASE("presolve-slacks", "[highs_test_presolve]") { + // This LP reduces to empty, because the equation is a doubleton + HighsLp lp; + lp.num_col_ = 2; + lp.num_row_ = 1; + lp.col_cost_ = {1, 0}; + lp.col_lower_ = {0, 0}; + lp.col_upper_ = {kHighsInf, kHighsInf}; + lp.row_lower_ = {1}; + lp.row_upper_ = {1}; + lp.a_matrix_.start_ = {0, 1, 2}; + lp.a_matrix_.index_ = {0, 0}; + lp.a_matrix_.value_ = {1, 1}; + Highs h; + h.setOptionValue("output_flag", dev_run); + REQUIRE(h.passModel(lp) == HighsStatus::kOk); + REQUIRE(h.presolve() == HighsStatus::kOk); + REQUIRE(h.getPresolvedLp().num_col_ == 0); + REQUIRE(h.getPresolvedLp().num_row_ == 0); + + lp.num_col_ = 4; + lp.num_row_ = 2; + lp.col_cost_ = {-10, -25, 0, 0}; + lp.col_lower_ = {0, 0, 0, 0}; + lp.col_upper_ = {kHighsInf, kHighsInf, kHighsInf, kHighsInf}; + lp.row_lower_ = {80, 120}; + lp.row_upper_ = {80, 120}; + lp.a_matrix_.start_ = {0, 2, 4, 5, 6}; + lp.a_matrix_.index_ = {0, 1, 0, 1, 0, 1}; + lp.a_matrix_.value_ = {1, 1, 2, 4, 1, 1}; + + REQUIRE(h.passModel(lp) == HighsStatus::kOk); + REQUIRE(h.run() == HighsStatus::kOk); + REQUIRE(h.presolve() == HighsStatus::kOk); + // REQUIRE(h.getPresolvedLp().num_col_ == 0); + // REQUIRE(h.getPresolvedLp().num_row_ == 0); +} diff --git a/src/qpsolver/dantzigpricing.hpp b/src/qpsolver/dantzigpricing.hpp index 89b109e5c1..5a62dce7fb 100644 --- a/src/qpsolver/dantzigpricing.hpp +++ b/src/qpsolver/dantzigpricing.hpp @@ -52,8 +52,9 @@ class DantzigPricing : public Pricing { public: DantzigPricing(Runtime& rt, Basis& bas, ReducedCosts& rc) + // clang-format off : runtime(rt), basis(bas), redcosts(rc) {}; - + // clang-format on HighsInt price(const QpVector& x, const QpVector& gradient) { HighsInt minidx = chooseconstrainttodrop(redcosts.getReducedCosts()); return minidx; diff --git a/src/qpsolver/devexpricing.hpp b/src/qpsolver/devexpricing.hpp index a88f3ecea5..014d8ce283 100644 --- a/src/qpsolver/devexpricing.hpp +++ b/src/qpsolver/devexpricing.hpp @@ -58,7 +58,9 @@ class DevexPricing : public Pricing { : runtime(rt), basis(bas), redcosts(rc), + // clang-format off weights(std::vector(rt.instance.num_var, 1.0)) {}; + // clang-format on // B lambda = g // lambda = inv(B)g From 0999bf230ca6f686485b9f321f22102bff038e59 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Wed, 6 Nov 2024 14:34:35 +0000 Subject: [PATCH 16/75] Corrected use of solve_clock in mip/HighsMipSolverData.cpp --- FEATURES.md | 15 ++++++++++++++- src/mip/HighsMipSolverData.cpp | 4 ++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/FEATURES.md b/FEATURES.md index 6ba63e7068..708e928092 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -8,9 +8,22 @@ The method `Highs::getDualObjectiveValue` now exitsts to compute the dual object The method `Highs::getStandardFormLp` now exists to return the incumbent LP in standard form - overlooking any integrality or Hessian. To determine the sizes of the vectors of data, the method is called without specifying pointers to the data arrays. - Added documentation on the use of presolve when solving an incumbent model, and clarifying the use of the method `Highs::presolve`. +HiGHS will now read a `MIPLIB` solution file + +Added time limit check to `HPresolve::strengthenInequalities` + +Added `getColIntegrality` to `highspy` + +Now computing the primal-dual integral, reporting it, and making it available as `HighsInfo::primal_dual_integral` + +Trivial primal heuristics "all zero", "all lower bound", "all upper bound", and "lock point" added to the MIP solver + + + + + diff --git a/src/mip/HighsMipSolverData.cpp b/src/mip/HighsMipSolverData.cpp index 6516dc3e89..d8896524d3 100644 --- a/src/mip/HighsMipSolverData.cpp +++ b/src/mip/HighsMipSolverData.cpp @@ -1096,7 +1096,7 @@ double HighsMipSolverData::transformNewIntegerFeasibleSolution( this->total_repair_lp++; double time_available = std::max(mipsolver.options_mip_->time_limit - - mipsolver.timer_.read(mipsolver.timer_.solve_clock), + mipsolver.timer_.read(mipsolver.timer_.total_clock), 0.1); Highs tmpSolver; const bool debug_report = false; @@ -2617,7 +2617,7 @@ void HighsMipSolverData::updatePrimalDualIntegral(const double from_lower_bound, assert(gap_consistent); } if (to_gap < kHighsInf) { - double time = mipsolver.timer_.read(mipsolver.timer_.solve_clock); + double time = mipsolver.timer_.read(mipsolver.timer_.total_clock); if (from_gap < kHighsInf) { // Need to update the P-D integral double time_diff = time - pdi.prev_time; From 9e57225a6f8e0932104b32b6d103ef55fa1bbe34 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Wed, 6 Nov 2024 14:42:47 +0000 Subject: [PATCH 17/75] Switched off report on trivial heuristic --- src/mip/HighsMipSolverData.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mip/HighsMipSolverData.cpp b/src/mip/HighsMipSolverData.cpp index d8896524d3..e84f9b5396 100644 --- a/src/mip/HighsMipSolverData.cpp +++ b/src/mip/HighsMipSolverData.cpp @@ -283,7 +283,7 @@ HighsModelStatus HighsMipSolverData::trivialHeuristics() { const double save_upper_bound = upper_bound; const bool new_incumbent = addIncumbent(solution, obj, heuristic_source[try_heuristic]); - const bool lc_report = true; + const bool lc_report = false; if (lc_report) { printf("Trivial heuristic %d has succeeded: objective = %g", int(try_heuristic), obj); From 7aede2a4ed2732f6003c15e9550274464d1a5e13 Mon Sep 17 00:00:00 2001 From: Julian Hall Date: Thu, 7 Nov 2024 15:22:24 +0000 Subject: [PATCH 18/75] Added comments to IP-infeasible-unbounded test case --- check/TestMipSolver.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/check/TestMipSolver.cpp b/check/TestMipSolver.cpp index 0249725857..cfbe1ffbf2 100644 --- a/check/TestMipSolver.cpp +++ b/check/TestMipSolver.cpp @@ -679,12 +679,16 @@ TEST_CASE("IP-infeasible-unbounded", "[highs_test_mip_solver]") { highs.run(); HighsModelStatus required_model_status; if (k == 0) { + // Presolve off if (l == 0) { + // MIP solver proves infeasiblilty required_model_status = HighsModelStatus::kInfeasible; } else { + // Relaxation is unbounded, but origin is feasible required_model_status = HighsModelStatus::kUnbounded; } } else { + // Presolve on, and identifies primal infeasible or unbounded required_model_status = HighsModelStatus::kUnboundedOrInfeasible; } REQUIRE(highs.getModelStatus() == required_model_status); From fe88b22201e1fef5a88ded9b11d29865db6163c6 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Fri, 8 Nov 2024 13:42:57 +0000 Subject: [PATCH 19/75] Added skeleton SlackColSubstitution as presolve/postsolve action --- check/TestPresolve.cpp | 6 ++-- src/lp_data/Highs.cpp | 36 +++++++++++++++++++- src/lp_data/HighsOptions.h | 7 ++++ src/presolve/HighsPostsolveStack.cpp | 48 ++++++++++++++++++++++++++ src/presolve/HighsPostsolveStack.h | 50 ++++++++++++++++++++++++++++ 5 files changed, 143 insertions(+), 4 deletions(-) diff --git a/check/TestPresolve.cpp b/check/TestPresolve.cpp index b0a4dbc95b..7b676dc011 100644 --- a/check/TestPresolve.cpp +++ b/check/TestPresolve.cpp @@ -616,7 +616,7 @@ TEST_CASE("presolve-slacks", "[highs_test_presolve]") { lp.a_matrix_.index_ = {0, 0}; lp.a_matrix_.value_ = {1, 1}; Highs h; - h.setOptionValue("output_flag", dev_run); + // h.setOptionValue("output_flag", dev_run); REQUIRE(h.passModel(lp) == HighsStatus::kOk); REQUIRE(h.presolve() == HighsStatus::kOk); REQUIRE(h.getPresolvedLp().num_col_ == 0); @@ -636,6 +636,6 @@ TEST_CASE("presolve-slacks", "[highs_test_presolve]") { REQUIRE(h.passModel(lp) == HighsStatus::kOk); REQUIRE(h.run() == HighsStatus::kOk); REQUIRE(h.presolve() == HighsStatus::kOk); - // REQUIRE(h.getPresolvedLp().num_col_ == 0); - // REQUIRE(h.getPresolvedLp().num_row_ == 0); + REQUIRE(h.getPresolvedLp().num_col_ == 2); + REQUIRE(h.getPresolvedLp().num_row_ == 2); } diff --git a/src/lp_data/Highs.cpp b/src/lp_data/Highs.cpp index c1f1f48b72..87d6309d6f 100644 --- a/src/lp_data/Highs.cpp +++ b/src/lp_data/Highs.cpp @@ -1251,6 +1251,40 @@ HighsStatus Highs::run() { // Run solver. bool have_optimal_solution = false; // ToDo Put solution of presolved problem in a separate method + + if (!this->options_.presolve_remove_slacks) { + HighsLp& reduced_lp = presolve_.getReducedProblem(); + HighsInt num_double_slack = 0; + HighsInt num_slack = 0; + HighsInt num_zero_cost_slack = 0; + HighsInt num_unit_coeff_slack = 0; + double min_slack_coeff = kHighsInf; + double max_slack_coeff = -kHighsInf; + std::vector found_slack; + found_slack.assign(reduced_lp.num_row_, false); + for (HighsInt iCol = 0; iCol < reduced_lp.num_col_; iCol++) { + HighsInt nnz = reduced_lp.a_matrix_.start_[iCol+1] - reduced_lp.a_matrix_.start_[iCol]; + if (nnz != 1) continue; + HighsInt iRow = reduced_lp.a_matrix_.index_[reduced_lp.a_matrix_.start_[iCol]]; + if (found_slack[iRow]) { + num_double_slack++; + continue; + } + if (reduced_lp.row_lower_[iRow] != reduced_lp.row_upper_[iRow]) continue; + double coeff = std::fabs(reduced_lp.a_matrix_.value_[reduced_lp.a_matrix_.start_[iCol]]); + if (coeff == 1.0) num_unit_coeff_slack++; + min_slack_coeff = std::min(coeff,min_slack_coeff); + max_slack_coeff = std::max(coeff,max_slack_coeff); + found_slack[iRow] = true; + num_slack++; + if (reduced_lp.col_cost_[iCol] == 0) num_zero_cost_slack++; + } + printf("grepSlack,model,col,slack,unit coeff,zero_cost,double,min coeff, max_coeff\n"); + printf("grepSlack,%s,%d, %d, %d, %d, %d, %g, %g\n", this->model_.lp_.model_name_.c_str(), + int(reduced_lp.num_col_), int(num_slack), int(num_unit_coeff_slack), int(num_zero_cost_slack), int(num_double_slack), + min_slack_coeff, max_slack_coeff); + } + switch (model_presolve_status_) { case HighsPresolveStatus::kNotPresolved: { ekk_instance_.lp_name_ = "Original LP"; @@ -1275,7 +1309,7 @@ HighsStatus Highs::run() { break; } case HighsPresolveStatus::kReduced: { - HighsLp& reduced_lp = presolve_.getReducedProblem(); + HighsLp& reduced_lp = presolve_.getReducedProblem(); reduced_lp.setMatrixDimensions(); if (kAllowDeveloperAssert) { // Validate the reduced LP diff --git a/src/lp_data/HighsOptions.h b/src/lp_data/HighsOptions.h index 6e2be6cc78..8103f4db7b 100644 --- a/src/lp_data/HighsOptions.h +++ b/src/lp_data/HighsOptions.h @@ -372,6 +372,7 @@ struct HighsOptionsStruct { HighsInt presolve_substitution_maxfillin; HighsInt presolve_rule_off; bool presolve_rule_logging; + bool presolve_remove_slacks; bool simplex_initial_condition_check; bool no_unnecessary_rebuild_refactor; double simplex_initial_condition_tolerance; @@ -507,6 +508,7 @@ struct HighsOptionsStruct { presolve_substitution_maxfillin(0), presolve_rule_off(0), presolve_rule_logging(false), + presolve_remove_slacks(false), simplex_initial_condition_check(false), no_unnecessary_rebuild_refactor(false), simplex_initial_condition_tolerance(0.0), @@ -1324,6 +1326,11 @@ class HighsOptions : public HighsOptionsStruct { advanced, &presolve_rule_logging, false); records.push_back(record_bool); + record_bool = new OptionRecordBool( + "presolve_remove_slacks", "Remove slacks after presolve", + advanced, &presolve_remove_slacks, true);//false); + records.push_back(record_bool); + record_int = new OptionRecordInt( "presolve_substitution_maxfillin", "Maximal fillin allowed for substitutions in presolve", advanced, diff --git a/src/presolve/HighsPostsolveStack.cpp b/src/presolve/HighsPostsolveStack.cpp index bec3e4945f..79d582a7f4 100644 --- a/src/presolve/HighsPostsolveStack.cpp +++ b/src/presolve/HighsPostsolveStack.cpp @@ -1351,4 +1351,52 @@ void HighsPostsolveStack::DuplicateColumn::transformToPresolvedSpace( primalSol[col] = primalSol[col] + colScale * primalSol[duplicateCol]; } +void HighsPostsolveStack::SlackColSubstitution::undo( + const HighsOptions& options, const std::vector& rowValues, + const std::vector& colValues, HighsSolution& solution, + HighsBasis& basis) { + // a (removed) cut may have been used in this reduction. + bool isModelRow = static_cast(row) < solution.row_value.size(); + + // compute primal values + double colCoef = 0; + HighsCDouble rowValue = 0; + for (const auto& rowVal : rowValues) { + if (rowVal.index == col) + colCoef = rowVal.value; + else + rowValue += rowVal.value * solution.col_value[rowVal.index]; + } + + assert(colCoef != 0); + // Row values aren't fully postsolved, so why do this? + if (isModelRow) + solution.row_value[row] = + double(rowValue + colCoef * solution.col_value[col]); + solution.col_value[col] = double((rhs - rowValue) / colCoef); + + // if no dual values requested, return here + if (!solution.dual_valid) return; + + // compute the row dual value such that reduced cost of basic column is 0 + if (isModelRow) { + solution.row_dual[row] = 0; + HighsCDouble dualval = colCost; + for (const auto& colVal : colValues) { + if (static_cast(colVal.index) < solution.row_dual.size()) + dualval -= colVal.value * solution.row_dual[colVal.index]; + } + solution.row_dual[row] = double(dualval / colCoef); + } + + solution.col_dual[col] = 0; + + // set basis status if necessary + if (!basis.valid) return; + + basis.col_status[col] = HighsBasisStatus::kBasic; + if (isModelRow) + basis.row_status[row] = computeRowStatus(solution.row_dual[row], rowType); +} + } // namespace presolve diff --git a/src/presolve/HighsPostsolveStack.h b/src/presolve/HighsPostsolveStack.h index 2200977b5b..79a658f977 100644 --- a/src/presolve/HighsPostsolveStack.h +++ b/src/presolve/HighsPostsolveStack.h @@ -219,6 +219,19 @@ class HighsPostsolveStack { void transformToPresolvedSpace(std::vector& primalSol) const; }; + struct SlackColSubstitution { + double rhs; + double colCost; + HighsInt row; + HighsInt col; + RowType rowType; + + void undo(const HighsOptions& options, + const std::vector& rowValues, + const std::vector& colValues, HighsSolution& solution, + HighsBasis& basis); + }; + /// tags for reduction enum class ReductionType : uint8_t { kLinearTransform, @@ -234,6 +247,7 @@ class HighsPostsolveStack { kForcingColumnRemovedRow, kDuplicateRow, kDuplicateColumn, + kSlackColSubstitution, }; HighsDataStack reductionValues; @@ -323,6 +337,26 @@ class HighsPostsolveStack { reductionAdded(ReductionType::kFreeColSubstitution); } + template + void slackColSubstitution(HighsInt row, HighsInt col, double rhs, + double colCost, RowType rowType, + const HighsMatrixSlice& rowVec, + const HighsMatrixSlice& colVec) { + rowValues.clear(); + for (const HighsSliceNonzero& rowVal : rowVec) + rowValues.emplace_back(origColIndex[rowVal.index()], rowVal.value()); + + colValues.clear(); + for (const HighsSliceNonzero& colVal : colVec) + colValues.emplace_back(origRowIndex[colVal.index()], colVal.value()); + + reductionValues.push(SlackColSubstitution{rhs, colCost, origRowIndex[row], + origColIndex[col], rowType}); + reductionValues.push(rowValues); + reductionValues.push(colValues); + reductionAdded(ReductionType::kSlackColSubstitution); + } + template void doubletonEquation(HighsInt row, HighsInt colSubst, HighsInt col, double coefSubst, double coef, double rhs, @@ -710,6 +744,14 @@ class HighsPostsolveStack { reduction.undo(options, solution, basis); break; } + case ReductionType::kSlackColSubstitution: { + SlackColSubstitution reduction; + reductionValues.pop(colValues); + reductionValues.pop(rowValues); + reductionValues.pop(reduction); + reduction.undo(options, rowValues, colValues, solution, basis); + break; + } default: printf("Reduction case %d not handled\n", int(reductions[i - 1].first)); @@ -887,6 +929,14 @@ class HighsPostsolveStack { reductionValues.pop(reduction); reduction.undo(options, solution, basis); } + case ReductionType::kSlackColSubstitution: { + SlackColSubstitution reduction; + reductionValues.pop(colValues); + reductionValues.pop(rowValues); + reductionValues.pop(reduction); + reduction.undo(options, rowValues, colValues, solution, basis); + break; + } } } #ifdef DEBUG_EXTRA From 14c13c979d66dac55a5745361c1594c0b63e180a Mon Sep 17 00:00:00 2001 From: JAJHall Date: Fri, 8 Nov 2024 13:44:13 +0000 Subject: [PATCH 20/75] Switched off presolve-slacks check for elimination of slacks --- check/TestPresolve.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/check/TestPresolve.cpp b/check/TestPresolve.cpp index 7b676dc011..40d1ea900c 100644 --- a/check/TestPresolve.cpp +++ b/check/TestPresolve.cpp @@ -636,6 +636,6 @@ TEST_CASE("presolve-slacks", "[highs_test_presolve]") { REQUIRE(h.passModel(lp) == HighsStatus::kOk); REQUIRE(h.run() == HighsStatus::kOk); REQUIRE(h.presolve() == HighsStatus::kOk); - REQUIRE(h.getPresolvedLp().num_col_ == 2); - REQUIRE(h.getPresolvedLp().num_row_ == 2); + // REQUIRE(h.getPresolvedLp().num_col_ == 2); + // REQUIRE(h.getPresolvedLp().num_row_ == 2); } From d5ffacc534dc52201b1aee0ed87f5e819814cd48 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Fri, 8 Nov 2024 15:46:11 +0000 Subject: [PATCH 21/75] Now getting through to postsolve after eliminating a slack in presolve for adlittle --- src/lp_data/Highs.cpp | 7 ++-- src/presolve/HPresolve.cpp | 61 ++++++++++++++++++++++++++++ src/presolve/HPresolve.h | 2 + src/presolve/HighsPostsolveStack.cpp | 7 +++- src/presolve/HighsPostsolveStack.h | 19 ++++----- 5 files changed, 81 insertions(+), 15 deletions(-) diff --git a/src/lp_data/Highs.cpp b/src/lp_data/Highs.cpp index 87d6309d6f..05d89e3ca4 100644 --- a/src/lp_data/Highs.cpp +++ b/src/lp_data/Highs.cpp @@ -1252,7 +1252,7 @@ HighsStatus Highs::run() { bool have_optimal_solution = false; // ToDo Put solution of presolved problem in a separate method - if (!this->options_.presolve_remove_slacks) { + // if (!this->options_.presolve_remove_slacks) { HighsLp& reduced_lp = presolve_.getReducedProblem(); HighsInt num_double_slack = 0; HighsInt num_slack = 0; @@ -1271,19 +1271,20 @@ HighsStatus Highs::run() { continue; } if (reduced_lp.row_lower_[iRow] != reduced_lp.row_upper_[iRow]) continue; + num_slack++; + printf("Column %d is slack\n", int(iCol)); double coeff = std::fabs(reduced_lp.a_matrix_.value_[reduced_lp.a_matrix_.start_[iCol]]); if (coeff == 1.0) num_unit_coeff_slack++; min_slack_coeff = std::min(coeff,min_slack_coeff); max_slack_coeff = std::max(coeff,max_slack_coeff); found_slack[iRow] = true; - num_slack++; if (reduced_lp.col_cost_[iCol] == 0) num_zero_cost_slack++; } printf("grepSlack,model,col,slack,unit coeff,zero_cost,double,min coeff, max_coeff\n"); printf("grepSlack,%s,%d, %d, %d, %d, %d, %g, %g\n", this->model_.lp_.model_name_.c_str(), int(reduced_lp.num_col_), int(num_slack), int(num_unit_coeff_slack), int(num_zero_cost_slack), int(num_double_slack), min_slack_coeff, max_slack_coeff); - } + // } switch (model_presolve_status_) { case HighsPresolveStatus::kNotPresolved: { diff --git a/src/presolve/HPresolve.cpp b/src/presolve/HPresolve.cpp index 5551ef0f20..18f6df36c8 100644 --- a/src/presolve/HPresolve.cpp +++ b/src/presolve/HPresolve.cpp @@ -4360,6 +4360,11 @@ HPresolve::Result HPresolve::presolve(HighsPostsolveStack& postsolve_stack) { break; } + // Now consider removing slacks + if (options->presolve_remove_slacks) { + HPRESOLVE_CHECKED_CALL(removeSlacks(postsolve_stack)); + } + report(); } else { highsLogUser(options->log_options, HighsLogType::kInfo, @@ -4375,6 +4380,62 @@ HPresolve::Result HPresolve::presolve(HighsPostsolveStack& postsolve_stack) { return Result::kOk; } +HPresolve::Result HPresolve::removeSlacks(HighsPostsolveStack& postsolve_stack) { + // singletonColumns data structure appears not to be retained + // throughout presolve + // + bool unit_coeff_only = true; + for (HighsInt iCol = 0; iCol != model->num_col_; ++iCol) { + if (colDeleted[iCol]) continue; + if (colsize[iCol] != 1) continue; + if (model->integrality_[iCol] == HighsVarType::kInteger) continue; + HighsInt coliter = colhead[iCol]; + HighsInt iRow = Arow[coliter]; + assert(Acol[coliter] == iCol); + assert(!rowDeleted[iRow]); + if (model->row_lower_[iRow] != model->row_upper_[iRow]) continue; + double lower = model->col_lower_[iCol]; + double upper = model->col_upper_[iCol]; + double cost = model->col_cost_[iCol]; + double rhs = model->row_lower_[iRow]; + double coeff = Avalue[coliter]; + printf("Col %d is continuous and is singleton in equality row %d with cost %g, bounds [%g, %g], coeff %g and RHS = %g\n", int(iCol), int(iRow), cost, lower, upper, coeff, rhs); + if (unit_coeff_only && std::fabs(coeff) != 1.0) continue; + assert(coeff); + // Slack is s = (rhs - a^Tx)/coeff + // + if (coeff > 0) { + // Constraint bounds become [rhs - coeff * lower, rhs - coeff * + // upper] + model->col_lower_[iCol] = rhs - coeff * lower; + model->col_upper_[iCol] = rhs - coeff * upper; + } else { + // Constraint bounds become [rhs - coeff * upper, rhs - coeff * + // lower] + model->col_lower_[iCol] = rhs - coeff * upper; + model->col_upper_[iCol] = rhs - coeff * lower; + } + if (cost) { + // Cost is (cost * rhs / coeff) + (col_cost - (cost/coeff) row_values)^Tx + double multiplier = cost / coeff; + for (const HighsSliceNonzero& nonzero : getRowVector(iRow)) { + HighsInt local_iCol = nonzero.index(); + double local_value = nonzero.value(); + model->col_cost_[local_iCol] -= multiplier * local_value; + } + model->offset_ += multiplier * rhs; + } + // + postsolve_stack.slackColSubstitution(iRow, iCol, rhs, cost, lower, upper, coeff, + getColumnVector(iCol)); + markColDeleted(iCol); + + unlink(coliter); + + } + return Result::kOk; +} + HPresolve::Result HPresolve::checkLimits(HighsPostsolveStack& postsolve_stack) { size_t numreductions = postsolve_stack.numReductions(); diff --git a/src/presolve/HPresolve.h b/src/presolve/HPresolve.h index 2a5b3427c8..28ac2bbe47 100644 --- a/src/presolve/HPresolve.h +++ b/src/presolve/HPresolve.h @@ -269,6 +269,8 @@ class HPresolve { Result presolve(HighsPostsolveStack& postsolve_stack); + Result removeSlacks(HighsPostsolveStack& postsolve_stack); + Result checkLimits(HighsPostsolveStack& postsolve_stack); void storeCurrentProblemSize(); diff --git a/src/presolve/HighsPostsolveStack.cpp b/src/presolve/HighsPostsolveStack.cpp index 79d582a7f4..a2bc80f2e2 100644 --- a/src/presolve/HighsPostsolveStack.cpp +++ b/src/presolve/HighsPostsolveStack.cpp @@ -1355,6 +1355,11 @@ void HighsPostsolveStack::SlackColSubstitution::undo( const HighsOptions& options, const std::vector& rowValues, const std::vector& colValues, HighsSolution& solution, HighsBasis& basis) { + + assert(111==222); + + + // a (removed) cut may have been used in this reduction. bool isModelRow = static_cast(row) < solution.row_value.size(); @@ -1396,7 +1401,7 @@ void HighsPostsolveStack::SlackColSubstitution::undo( basis.col_status[col] = HighsBasisStatus::kBasic; if (isModelRow) - basis.row_status[row] = computeRowStatus(solution.row_dual[row], rowType); + basis.row_status[row] = computeRowStatus(solution.row_dual[row], RowType::kEq); } } // namespace presolve diff --git a/src/presolve/HighsPostsolveStack.h b/src/presolve/HighsPostsolveStack.h index 79a658f977..c4160dd226 100644 --- a/src/presolve/HighsPostsolveStack.h +++ b/src/presolve/HighsPostsolveStack.h @@ -222,9 +222,11 @@ class HighsPostsolveStack { struct SlackColSubstitution { double rhs; double colCost; + double colLower; + double colUpper; + double colCoeff; HighsInt row; HighsInt col; - RowType rowType; void undo(const HighsOptions& options, const std::vector& rowValues, @@ -337,21 +339,16 @@ class HighsPostsolveStack { reductionAdded(ReductionType::kFreeColSubstitution); } - template + template void slackColSubstitution(HighsInt row, HighsInt col, double rhs, - double colCost, RowType rowType, - const HighsMatrixSlice& rowVec, - const HighsMatrixSlice& colVec) { - rowValues.clear(); - for (const HighsSliceNonzero& rowVal : rowVec) - rowValues.emplace_back(origColIndex[rowVal.index()], rowVal.value()); - + double colCost, double colLower, double colUpper, double colCoeff, + const HighsMatrixSlice& colVec) { colValues.clear(); for (const HighsSliceNonzero& colVal : colVec) colValues.emplace_back(origRowIndex[colVal.index()], colVal.value()); - reductionValues.push(SlackColSubstitution{rhs, colCost, origRowIndex[row], - origColIndex[col], rowType}); + reductionValues.push(SlackColSubstitution{rhs, colCost, colLower, colUpper, colCoeff, origRowIndex[row], + origColIndex[col]}); reductionValues.push(rowValues); reductionValues.push(colValues); reductionAdded(ReductionType::kSlackColSubstitution); From b14bbe1a49fb08bc10dc4a7dc86b93792650e15d Mon Sep 17 00:00:00 2001 From: JAJHall Date: Fri, 8 Nov 2024 17:43:16 +0000 Subject: [PATCH 22/75] Solves adlittle with slack removed --- src/presolve/HPresolve.cpp | 23 ++++++++++------------- src/presolve/HighsPostsolveStack.cpp | 15 +++++++++------ src/presolve/HighsPostsolveStack.h | 16 +++++++++++----- 3 files changed, 30 insertions(+), 24 deletions(-) diff --git a/src/presolve/HPresolve.cpp b/src/presolve/HPresolve.cpp index 18f6df36c8..e1e721347c 100644 --- a/src/presolve/HPresolve.cpp +++ b/src/presolve/HPresolve.cpp @@ -4404,17 +4404,13 @@ HPresolve::Result HPresolve::removeSlacks(HighsPostsolveStack& postsolve_stack) assert(coeff); // Slack is s = (rhs - a^Tx)/coeff // - if (coeff > 0) { - // Constraint bounds become [rhs - coeff * lower, rhs - coeff * - // upper] - model->col_lower_[iCol] = rhs - coeff * lower; - model->col_upper_[iCol] = rhs - coeff * upper; - } else { - // Constraint bounds become [rhs - coeff * upper, rhs - coeff * - // lower] - model->col_lower_[iCol] = rhs - coeff * upper; - model->col_upper_[iCol] = rhs - coeff * lower; - } + // Constraint bounds become: + // + // For coeff > 0 [rhs - coeff * upper, rhs - coeff * lower] + // + // For coeff < 0 [rhs - coeff * lower, rhs - coeff * upper] + model->row_lower_[iRow] = coeff > 0 ? rhs - coeff * upper : rhs - coeff * lower; + model->row_upper_[iRow] = coeff > 0 ? rhs - coeff * lower : rhs - coeff * upper; if (cost) { // Cost is (cost * rhs / coeff) + (col_cost - (cost/coeff) row_values)^Tx double multiplier = cost / coeff; @@ -4426,8 +4422,9 @@ HPresolve::Result HPresolve::removeSlacks(HighsPostsolveStack& postsolve_stack) model->offset_ += multiplier * rhs; } // - postsolve_stack.slackColSubstitution(iRow, iCol, rhs, cost, lower, upper, coeff, - getColumnVector(iCol)); + postsolve_stack.slackColSubstitution(iRow, iCol, rhs, cost, lower, upper, //coeff, + getRowVector(iRow), + getColumnVector(iCol)); markColDeleted(iCol); unlink(coliter); diff --git a/src/presolve/HighsPostsolveStack.cpp b/src/presolve/HighsPostsolveStack.cpp index a2bc80f2e2..9a976187f9 100644 --- a/src/presolve/HighsPostsolveStack.cpp +++ b/src/presolve/HighsPostsolveStack.cpp @@ -1356,11 +1356,14 @@ void HighsPostsolveStack::SlackColSubstitution::undo( const std::vector& colValues, HighsSolution& solution, HighsBasis& basis) { - assert(111==222); - - - + // Taken from HighsPostsolveStack::FreeColSubstitution::undo( + // // a (removed) cut may have been used in this reduction. + // + // May have to determine row dual and basis status unless doing + // primal-only transformation in MIP solver, in which case row may + // no longer exist if it corresponds to a removed cut, so have to + // avoid exceeding array bounds of solution.row_value bool isModelRow = static_cast(row) < solution.row_value.size(); // compute primal values @@ -1375,9 +1378,9 @@ void HighsPostsolveStack::SlackColSubstitution::undo( assert(colCoef != 0); // Row values aren't fully postsolved, so why do this? - if (isModelRow) - solution.row_value[row] = + if (isModelRow) solution.row_value[row] = double(rowValue + colCoef * solution.col_value[col]); + printf("HighsPostsolveStack::SlackColSubstitution::undo rowValue = %g\n", double(rowValue)); solution.col_value[col] = double((rhs - rowValue) / colCoef); // if no dual values requested, return here diff --git a/src/presolve/HighsPostsolveStack.h b/src/presolve/HighsPostsolveStack.h index c4160dd226..954ffd2732 100644 --- a/src/presolve/HighsPostsolveStack.h +++ b/src/presolve/HighsPostsolveStack.h @@ -224,7 +224,7 @@ class HighsPostsolveStack { double colCost; double colLower; double colUpper; - double colCoeff; + // double colCoeff; HighsInt row; HighsInt col; @@ -339,16 +339,22 @@ class HighsPostsolveStack { reductionAdded(ReductionType::kFreeColSubstitution); } - template + template void slackColSubstitution(HighsInt row, HighsInt col, double rhs, - double colCost, double colLower, double colUpper, double colCoeff, + double colCost, double colLower, double colUpper, //double colCoeff, + const HighsMatrixSlice& rowVec, const HighsMatrixSlice& colVec) { + rowValues.clear(); + for (const HighsSliceNonzero& rowVal : rowVec) + rowValues.emplace_back(origColIndex[rowVal.index()], rowVal.value()); + colValues.clear(); for (const HighsSliceNonzero& colVal : colVec) colValues.emplace_back(origRowIndex[colVal.index()], colVal.value()); - reductionValues.push(SlackColSubstitution{rhs, colCost, colLower, colUpper, colCoeff, origRowIndex[row], - origColIndex[col]}); + reductionValues.push(SlackColSubstitution{rhs, colCost, colLower, colUpper, //colCoeff, + origRowIndex[row], + origColIndex[col]}); reductionValues.push(rowValues); reductionValues.push(colValues); reductionAdded(ReductionType::kSlackColSubstitution); From b32cfd0892d48b55ff58afbd799e28b052aff118 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Fri, 8 Nov 2024 18:21:37 +0000 Subject: [PATCH 23/75] Tidied and formatted --- check/TestPresolve.cpp | 8 ++-- src/lp_data/Highs.cpp | 72 +++++++++++++++------------- src/lp_data/HighsOptions.h | 8 ++-- src/presolve/HPresolve.cpp | 29 ++++++----- src/presolve/HPresolve.h | 2 +- src/presolve/HighsPostsolveStack.cpp | 34 ++++++++----- src/presolve/HighsPostsolveStack.h | 29 ++++------- src/qpsolver/dantzigpricing.hpp | 2 +- src/qpsolver/devexpricing.hpp | 2 +- 9 files changed, 98 insertions(+), 88 deletions(-) diff --git a/check/TestPresolve.cpp b/check/TestPresolve.cpp index 40d1ea900c..7d2e5d9604 100644 --- a/check/TestPresolve.cpp +++ b/check/TestPresolve.cpp @@ -616,7 +616,7 @@ TEST_CASE("presolve-slacks", "[highs_test_presolve]") { lp.a_matrix_.index_ = {0, 0}; lp.a_matrix_.value_ = {1, 1}; Highs h; - // h.setOptionValue("output_flag", dev_run); + h.setOptionValue("output_flag", dev_run); REQUIRE(h.passModel(lp) == HighsStatus::kOk); REQUIRE(h.presolve() == HighsStatus::kOk); REQUIRE(h.getPresolvedLp().num_col_ == 0); @@ -632,10 +632,10 @@ TEST_CASE("presolve-slacks", "[highs_test_presolve]") { lp.a_matrix_.start_ = {0, 2, 4, 5, 6}; lp.a_matrix_.index_ = {0, 1, 0, 1, 0, 1}; lp.a_matrix_.value_ = {1, 1, 2, 4, 1, 1}; - + REQUIRE(h.setOptionValue("presolve_remove_slacks", true) == HighsStatus::kOk); REQUIRE(h.passModel(lp) == HighsStatus::kOk); REQUIRE(h.run() == HighsStatus::kOk); REQUIRE(h.presolve() == HighsStatus::kOk); - // REQUIRE(h.getPresolvedLp().num_col_ == 2); - // REQUIRE(h.getPresolvedLp().num_row_ == 2); + REQUIRE(h.getPresolvedLp().num_col_ == 2); + REQUIRE(h.getPresolvedLp().num_row_ == 2); } diff --git a/src/lp_data/Highs.cpp b/src/lp_data/Highs.cpp index 05d89e3ca4..894b703aac 100644 --- a/src/lp_data/Highs.cpp +++ b/src/lp_data/Highs.cpp @@ -1251,40 +1251,46 @@ HighsStatus Highs::run() { // Run solver. bool have_optimal_solution = false; // ToDo Put solution of presolved problem in a separate method - + // if (!this->options_.presolve_remove_slacks) { - HighsLp& reduced_lp = presolve_.getReducedProblem(); - HighsInt num_double_slack = 0; - HighsInt num_slack = 0; - HighsInt num_zero_cost_slack = 0; - HighsInt num_unit_coeff_slack = 0; - double min_slack_coeff = kHighsInf; - double max_slack_coeff = -kHighsInf; - std::vector found_slack; - found_slack.assign(reduced_lp.num_row_, false); - for (HighsInt iCol = 0; iCol < reduced_lp.num_col_; iCol++) { - HighsInt nnz = reduced_lp.a_matrix_.start_[iCol+1] - reduced_lp.a_matrix_.start_[iCol]; - if (nnz != 1) continue; - HighsInt iRow = reduced_lp.a_matrix_.index_[reduced_lp.a_matrix_.start_[iCol]]; - if (found_slack[iRow]) { - num_double_slack++; - continue; - } - if (reduced_lp.row_lower_[iRow] != reduced_lp.row_upper_[iRow]) continue; - num_slack++; - printf("Column %d is slack\n", int(iCol)); - double coeff = std::fabs(reduced_lp.a_matrix_.value_[reduced_lp.a_matrix_.start_[iCol]]); - if (coeff == 1.0) num_unit_coeff_slack++; - min_slack_coeff = std::min(coeff,min_slack_coeff); - max_slack_coeff = std::max(coeff,max_slack_coeff); - found_slack[iRow] = true; - if (reduced_lp.col_cost_[iCol] == 0) num_zero_cost_slack++; + HighsLp& reduced_lp = presolve_.getReducedProblem(); + HighsInt num_double_slack = 0; + HighsInt num_slack = 0; + HighsInt num_zero_cost_slack = 0; + HighsInt num_unit_coeff_slack = 0; + double min_slack_coeff = kHighsInf; + double max_slack_coeff = -kHighsInf; + std::vector found_slack; + found_slack.assign(reduced_lp.num_row_, false); + for (HighsInt iCol = 0; iCol < reduced_lp.num_col_; iCol++) { + HighsInt nnz = reduced_lp.a_matrix_.start_[iCol + 1] - + reduced_lp.a_matrix_.start_[iCol]; + if (nnz != 1) continue; + HighsInt iRow = + reduced_lp.a_matrix_.index_[reduced_lp.a_matrix_.start_[iCol]]; + if (found_slack[iRow]) { + num_double_slack++; + continue; } - printf("grepSlack,model,col,slack,unit coeff,zero_cost,double,min coeff, max_coeff\n"); - printf("grepSlack,%s,%d, %d, %d, %d, %d, %g, %g\n", this->model_.lp_.model_name_.c_str(), - int(reduced_lp.num_col_), int(num_slack), int(num_unit_coeff_slack), int(num_zero_cost_slack), int(num_double_slack), - min_slack_coeff, max_slack_coeff); - // } + if (reduced_lp.row_lower_[iRow] != reduced_lp.row_upper_[iRow]) continue; + num_slack++; + printf("Column %d is slack\n", int(iCol)); + double coeff = std::fabs( + reduced_lp.a_matrix_.value_[reduced_lp.a_matrix_.start_[iCol]]); + if (coeff == 1.0) num_unit_coeff_slack++; + min_slack_coeff = std::min(coeff, min_slack_coeff); + max_slack_coeff = std::max(coeff, max_slack_coeff); + found_slack[iRow] = true; + if (reduced_lp.col_cost_[iCol] == 0) num_zero_cost_slack++; + } + printf( + "grepSlack,model,col,slack,unit coeff,zero_cost,double,min coeff, " + "max_coeff\n"); + printf("grepSlack,%s,%d, %d, %d, %d, %d, %g, %g\n", + this->model_.lp_.model_name_.c_str(), int(reduced_lp.num_col_), + int(num_slack), int(num_unit_coeff_slack), int(num_zero_cost_slack), + int(num_double_slack), min_slack_coeff, max_slack_coeff); + // } switch (model_presolve_status_) { case HighsPresolveStatus::kNotPresolved: { @@ -1310,7 +1316,7 @@ HighsStatus Highs::run() { break; } case HighsPresolveStatus::kReduced: { - HighsLp& reduced_lp = presolve_.getReducedProblem(); + HighsLp& reduced_lp = presolve_.getReducedProblem(); reduced_lp.setMatrixDimensions(); if (kAllowDeveloperAssert) { // Validate the reduced LP diff --git a/src/lp_data/HighsOptions.h b/src/lp_data/HighsOptions.h index 8103f4db7b..64d565014e 100644 --- a/src/lp_data/HighsOptions.h +++ b/src/lp_data/HighsOptions.h @@ -508,7 +508,7 @@ struct HighsOptionsStruct { presolve_substitution_maxfillin(0), presolve_rule_off(0), presolve_rule_logging(false), - presolve_remove_slacks(false), + presolve_remove_slacks(false), simplex_initial_condition_check(false), no_unnecessary_rebuild_refactor(false), simplex_initial_condition_tolerance(0.0), @@ -1326,9 +1326,9 @@ class HighsOptions : public HighsOptionsStruct { advanced, &presolve_rule_logging, false); records.push_back(record_bool); - record_bool = new OptionRecordBool( - "presolve_remove_slacks", "Remove slacks after presolve", - advanced, &presolve_remove_slacks, true);//false); + record_bool = new OptionRecordBool("presolve_remove_slacks", + "Remove slacks after presolve", advanced, + &presolve_remove_slacks, false); records.push_back(record_bool); record_int = new OptionRecordInt( diff --git a/src/presolve/HPresolve.cpp b/src/presolve/HPresolve.cpp index e1e721347c..de5a26204e 100644 --- a/src/presolve/HPresolve.cpp +++ b/src/presolve/HPresolve.cpp @@ -4380,7 +4380,8 @@ HPresolve::Result HPresolve::presolve(HighsPostsolveStack& postsolve_stack) { return Result::kOk; } -HPresolve::Result HPresolve::removeSlacks(HighsPostsolveStack& postsolve_stack) { +HPresolve::Result HPresolve::removeSlacks( + HighsPostsolveStack& postsolve_stack) { // singletonColumns data structure appears not to be retained // throughout presolve // @@ -4399,7 +4400,10 @@ HPresolve::Result HPresolve::removeSlacks(HighsPostsolveStack& postsolve_stack) double cost = model->col_cost_[iCol]; double rhs = model->row_lower_[iRow]; double coeff = Avalue[coliter]; - printf("Col %d is continuous and is singleton in equality row %d with cost %g, bounds [%g, %g], coeff %g and RHS = %g\n", int(iCol), int(iRow), cost, lower, upper, coeff, rhs); + printf( + "Col %d is continuous and is singleton in equality row %d with cost " + "%g, bounds [%g, %g], coeff %g and RHS = %g\n", + int(iCol), int(iRow), cost, lower, upper, coeff, rhs); if (unit_coeff_only && std::fabs(coeff) != 1.0) continue; assert(coeff); // Slack is s = (rhs - a^Tx)/coeff @@ -4409,26 +4413,27 @@ HPresolve::Result HPresolve::removeSlacks(HighsPostsolveStack& postsolve_stack) // For coeff > 0 [rhs - coeff * upper, rhs - coeff * lower] // // For coeff < 0 [rhs - coeff * lower, rhs - coeff * upper] - model->row_lower_[iRow] = coeff > 0 ? rhs - coeff * upper : rhs - coeff * lower; - model->row_upper_[iRow] = coeff > 0 ? rhs - coeff * lower : rhs - coeff * upper; + model->row_lower_[iRow] = + coeff > 0 ? rhs - coeff * upper : rhs - coeff * lower; + model->row_upper_[iRow] = + coeff > 0 ? rhs - coeff * lower : rhs - coeff * upper; if (cost) { // Cost is (cost * rhs / coeff) + (col_cost - (cost/coeff) row_values)^Tx double multiplier = cost / coeff; for (const HighsSliceNonzero& nonzero : getRowVector(iRow)) { - HighsInt local_iCol = nonzero.index(); - double local_value = nonzero.value(); - model->col_cost_[local_iCol] -= multiplier * local_value; + HighsInt local_iCol = nonzero.index(); + double local_value = nonzero.value(); + model->col_cost_[local_iCol] -= multiplier * local_value; } model->offset_ += multiplier * rhs; } - // - postsolve_stack.slackColSubstitution(iRow, iCol, rhs, cost, lower, upper, //coeff, - getRowVector(iRow), - getColumnVector(iCol)); + // + postsolve_stack.slackColSubstitution(iRow, iCol, rhs, cost, + getRowVector(iRow)); + markColDeleted(iCol); unlink(coliter); - } return Result::kOk; } diff --git a/src/presolve/HPresolve.h b/src/presolve/HPresolve.h index 28ac2bbe47..3699d23399 100644 --- a/src/presolve/HPresolve.h +++ b/src/presolve/HPresolve.h @@ -270,7 +270,7 @@ class HPresolve { Result presolve(HighsPostsolveStack& postsolve_stack); Result removeSlacks(HighsPostsolveStack& postsolve_stack); - + Result checkLimits(HighsPostsolveStack& postsolve_stack); void storeCurrentProblemSize(); diff --git a/src/presolve/HighsPostsolveStack.cpp b/src/presolve/HighsPostsolveStack.cpp index 9a976187f9..43485006ea 100644 --- a/src/presolve/HighsPostsolveStack.cpp +++ b/src/presolve/HighsPostsolveStack.cpp @@ -1353,9 +1353,7 @@ void HighsPostsolveStack::DuplicateColumn::transformToPresolvedSpace( void HighsPostsolveStack::SlackColSubstitution::undo( const HighsOptions& options, const std::vector& rowValues, - const std::vector& colValues, HighsSolution& solution, - HighsBasis& basis) { - + HighsSolution& solution, HighsBasis& basis) { // Taken from HighsPostsolveStack::FreeColSubstitution::undo( // // a (removed) cut may have been used in this reduction. @@ -1378,33 +1376,47 @@ void HighsPostsolveStack::SlackColSubstitution::undo( assert(colCoef != 0); // Row values aren't fully postsolved, so why do this? - if (isModelRow) solution.row_value[row] = + if (isModelRow) + solution.row_value[row] = double(rowValue + colCoef * solution.col_value[col]); - printf("HighsPostsolveStack::SlackColSubstitution::undo rowValue = %g\n", double(rowValue)); + solution.col_value[col] = double((rhs - rowValue) / colCoef); + printf( + "\nHighsPostsolveStack::SlackColSubstitution::undo rowValue = %g; " + "colValue = %g\n", + double(rowValue), solution.col_value[col]); // if no dual values requested, return here if (!solution.dual_valid) return; // compute the row dual value such that reduced cost of basic column is 0 + double save_row_dual = solution.row_dual[row]; if (isModelRow) { solution.row_dual[row] = 0; - HighsCDouble dualval = colCost; - for (const auto& colVal : colValues) { - if (static_cast(colVal.index) < solution.row_dual.size()) - dualval -= colVal.value * solution.row_dual[colVal.index]; - } + HighsCDouble dualval = HighsCDouble(colCost); + dualval = -colCoef * solution.row_dual[row]; solution.row_dual[row] = double(dualval / colCoef); } solution.col_dual[col] = 0; + printf( + "HighsPostsolveStack::SlackColSubstitution::undo OgRowDual = %g; rowDual " + "= %g; colDual = %g\n", + save_row_dual, solution.row_dual[row], solution.col_dual[col]); // set basis status if necessary if (!basis.valid) return; basis.col_status[col] = HighsBasisStatus::kBasic; + HighsBasisStatus save_row_basis_status = basis.row_status[row]; if (isModelRow) - basis.row_status[row] = computeRowStatus(solution.row_dual[row], RowType::kEq); + basis.row_status[row] = + computeRowStatus(solution.row_dual[row], RowType::kEq); + printf( + "HighsPostsolveStack::SlackColSubstitution::undo OgRowStatus = %d; " + "RowStatus = %d; ColStatus = %d\n", + int(save_row_basis_status), int(basis.row_status[row]), + int(basis.col_status[col])); } } // namespace presolve diff --git a/src/presolve/HighsPostsolveStack.h b/src/presolve/HighsPostsolveStack.h index 954ffd2732..418462103c 100644 --- a/src/presolve/HighsPostsolveStack.h +++ b/src/presolve/HighsPostsolveStack.h @@ -222,15 +222,11 @@ class HighsPostsolveStack { struct SlackColSubstitution { double rhs; double colCost; - double colLower; - double colUpper; - // double colCoeff; HighsInt row; HighsInt col; void undo(const HighsOptions& options, - const std::vector& rowValues, - const std::vector& colValues, HighsSolution& solution, + const std::vector& rowValues, HighsSolution& solution, HighsBasis& basis); }; @@ -339,24 +335,17 @@ class HighsPostsolveStack { reductionAdded(ReductionType::kFreeColSubstitution); } - template + template void slackColSubstitution(HighsInt row, HighsInt col, double rhs, - double colCost, double colLower, double colUpper, //double colCoeff, - const HighsMatrixSlice& rowVec, - const HighsMatrixSlice& colVec) { + double colCost, + const HighsMatrixSlice& rowVec) { rowValues.clear(); for (const HighsSliceNonzero& rowVal : rowVec) rowValues.emplace_back(origColIndex[rowVal.index()], rowVal.value()); - colValues.clear(); - for (const HighsSliceNonzero& colVal : colVec) - colValues.emplace_back(origRowIndex[colVal.index()], colVal.value()); - - reductionValues.push(SlackColSubstitution{rhs, colCost, colLower, colUpper, //colCoeff, - origRowIndex[row], - origColIndex[col]}); + reductionValues.push(SlackColSubstitution{rhs, colCost, origRowIndex[row], + origColIndex[col]}); reductionValues.push(rowValues); - reductionValues.push(colValues); reductionAdded(ReductionType::kSlackColSubstitution); } @@ -749,10 +738,9 @@ class HighsPostsolveStack { } case ReductionType::kSlackColSubstitution: { SlackColSubstitution reduction; - reductionValues.pop(colValues); reductionValues.pop(rowValues); reductionValues.pop(reduction); - reduction.undo(options, rowValues, colValues, solution, basis); + reduction.undo(options, rowValues, solution, basis); break; } default: @@ -934,10 +922,9 @@ class HighsPostsolveStack { } case ReductionType::kSlackColSubstitution: { SlackColSubstitution reduction; - reductionValues.pop(colValues); reductionValues.pop(rowValues); reductionValues.pop(reduction); - reduction.undo(options, rowValues, colValues, solution, basis); + reduction.undo(options, rowValues, solution, basis); break; } } diff --git a/src/qpsolver/dantzigpricing.hpp b/src/qpsolver/dantzigpricing.hpp index 5a62dce7fb..3c305529b2 100644 --- a/src/qpsolver/dantzigpricing.hpp +++ b/src/qpsolver/dantzigpricing.hpp @@ -52,7 +52,7 @@ class DantzigPricing : public Pricing { public: DantzigPricing(Runtime& rt, Basis& bas, ReducedCosts& rc) - // clang-format off + // clang-format off : runtime(rt), basis(bas), redcosts(rc) {}; // clang-format on HighsInt price(const QpVector& x, const QpVector& gradient) { diff --git a/src/qpsolver/devexpricing.hpp b/src/qpsolver/devexpricing.hpp index 014d8ce283..6664a98611 100644 --- a/src/qpsolver/devexpricing.hpp +++ b/src/qpsolver/devexpricing.hpp @@ -58,7 +58,7 @@ class DevexPricing : public Pricing { : runtime(rt), basis(bas), redcosts(rc), - // clang-format off + // clang-format off weights(std::vector(rt.instance.num_var, 1.0)) {}; // clang-format on From d0744ed76d717ebc886bf8511fbef477dec1a167 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Fri, 8 Nov 2024 22:40:53 +0000 Subject: [PATCH 24/75] Gets e226 right --- src/presolve/HPresolve.cpp | 2 + src/presolve/HighsPostsolveStack.cpp | 67 +++++++++++++++++----------- src/presolve/HighsPostsolveStack.h | 7 ++- 3 files changed, 48 insertions(+), 28 deletions(-) diff --git a/src/presolve/HPresolve.cpp b/src/presolve/HPresolve.cpp index de5a26204e..8e99f91023 100644 --- a/src/presolve/HPresolve.cpp +++ b/src/presolve/HPresolve.cpp @@ -4429,6 +4429,8 @@ HPresolve::Result HPresolve::removeSlacks( } // postsolve_stack.slackColSubstitution(iRow, iCol, rhs, cost, + lower, + upper, getRowVector(iRow)); markColDeleted(iCol); diff --git a/src/presolve/HighsPostsolveStack.cpp b/src/presolve/HighsPostsolveStack.cpp index 43485006ea..94f7cd0e0c 100644 --- a/src/presolve/HighsPostsolveStack.cpp +++ b/src/presolve/HighsPostsolveStack.cpp @@ -13,6 +13,7 @@ #include #include "lp_data/HConst.h" +#include "lp_data/HighsModelUtils.h" // For debugging #include "lp_data/HighsOptions.h" #include "util/HighsCDouble.h" #include "util/HighsUtils.h" @@ -1381,42 +1382,54 @@ void HighsPostsolveStack::SlackColSubstitution::undo( double(rowValue + colCoef * solution.col_value[col]); solution.col_value[col] = double((rhs - rowValue) / colCoef); + double rowLower = colCoef > 0 ? rhs - colCoef * slackUpper : rhs - colCoef * slackLower; + double rowUpper = colCoef > 0 ? rhs - colCoef * slackLower : rhs - colCoef * slackUpper; + printf( - "\nHighsPostsolveStack::SlackColSubstitution::undo rowValue = %g; " - "colValue = %g\n", - double(rowValue), solution.col_value[col]); + "\nHighsPostsolveStack::SlackColSubstitution::undo rowValue = %11.5g; " + "bounds [%11.5g, %11.5g] colCoef = %11.6g\n", + double(rowValue), rowLower, rowUpper, colCoef); + printf( + "HighsPostsolveStack::SlackColSubstitution::undo colValue = %11.5g, bounds [%11.5g, %11.5g]\n", + solution.col_value[col], slackLower, slackUpper); - // if no dual values requested, return here + // If no dual values requested, return here if (!solution.dual_valid) return; - // compute the row dual value such that reduced cost of basic column is 0 - double save_row_dual = solution.row_dual[row]; + // Row retains its dual value, and column has this dual value scaled by coeff if (isModelRow) { - solution.row_dual[row] = 0; - HighsCDouble dualval = HighsCDouble(colCost); - dualval = -colCoef * solution.row_dual[row]; - solution.row_dual[row] = double(dualval / colCoef); + solution.col_dual[col] = - solution.row_dual[row] / colCoef; + printf( + "HighsPostsolveStack::SlackColSubstitution::undo rowDual = %11.5g; colDual = %11.5g\n", + solution.row_dual[row], solution.col_dual[col]); } - solution.col_dual[col] = 0; - printf( - "HighsPostsolveStack::SlackColSubstitution::undo OgRowDual = %g; rowDual " - "= %g; colDual = %g\n", - save_row_dual, solution.row_dual[row], solution.col_dual[col]); - - // set basis status if necessary + // Set basis status if necessary if (!basis.valid) return; - basis.col_status[col] = HighsBasisStatus::kBasic; - HighsBasisStatus save_row_basis_status = basis.row_status[row]; - if (isModelRow) - basis.row_status[row] = - computeRowStatus(solution.row_dual[row], RowType::kEq); - printf( - "HighsPostsolveStack::SlackColSubstitution::undo OgRowStatus = %d; " - "RowStatus = %d; ColStatus = %d\n", - int(save_row_basis_status), int(basis.row_status[row]), - int(basis.col_status[col])); + // If row is basic, then slack is basic, otherwise row retains its status + if (isModelRow) { + HighsBasisStatus save_row_basis_status = basis.row_status[row]; + if (basis.row_status[row] == HighsBasisStatus::kBasic) { + basis.col_status[col] = HighsBasisStatus::kBasic; + basis.row_status[row] = computeRowStatus(solution.row_dual[row], RowType::kEq); + } else if (basis.row_status[row] == HighsBasisStatus::kLower) { + basis.col_status[col] = colCoef > 0 ? HighsBasisStatus::kUpper : HighsBasisStatus::kLower; + } else { + basis.col_status[col] = colCoef > 0 ? HighsBasisStatus::kLower : HighsBasisStatus::kUpper; + } + printf("HighsPostsolveStack::SlackColSubstitution::undo OgRowStatus = %s; " + "RowStatus = %s; ColStatus = %s\n", + utilBasisStatusToString(save_row_basis_status).c_str(), utilBasisStatusToString(basis.row_status[row]).c_str(), + utilBasisStatusToString(basis.col_status[col]).c_str()); + if (basis.col_status[col] == HighsBasisStatus::kLower) { + assert(solution.col_dual[col] > 0); + } else if (basis.col_status[col] == HighsBasisStatus::kUpper) { + assert(solution.col_dual[col] < 0); + } + } else { + basis.col_status[col] = HighsBasisStatus::kNonbasic; + } } } // namespace presolve diff --git a/src/presolve/HighsPostsolveStack.h b/src/presolve/HighsPostsolveStack.h index 418462103c..8b3f97200e 100644 --- a/src/presolve/HighsPostsolveStack.h +++ b/src/presolve/HighsPostsolveStack.h @@ -222,6 +222,8 @@ class HighsPostsolveStack { struct SlackColSubstitution { double rhs; double colCost; + double slackLower; + double slackUpper; HighsInt row; HighsInt col; @@ -338,12 +340,15 @@ class HighsPostsolveStack { template void slackColSubstitution(HighsInt row, HighsInt col, double rhs, double colCost, + double slackLower, double slackUpper, const HighsMatrixSlice& rowVec) { rowValues.clear(); for (const HighsSliceNonzero& rowVal : rowVec) rowValues.emplace_back(origColIndex[rowVal.index()], rowVal.value()); - reductionValues.push(SlackColSubstitution{rhs, colCost, origRowIndex[row], + reductionValues.push(SlackColSubstitution{rhs, colCost, + slackLower, slackUpper, + origRowIndex[row], origColIndex[col]}); reductionValues.push(rowValues); reductionAdded(ReductionType::kSlackColSubstitution); From f769209b9df6eeb3f86e6464b4778288789c4acd Mon Sep 17 00:00:00 2001 From: JAJHall Date: Sun, 10 Nov 2024 10:31:36 +0000 Subject: [PATCH 25/75] Cleaned up HPresolve::removeSlacks but left off by default since not advantageous on average --- check/TestPresolve.cpp | 2 +- src/lp_data/Highs.cpp | 44 +++--------------------- src/presolve/HPresolve.cpp | 17 ++-------- src/presolve/HighsPostsolveStack.cpp | 50 +++++++++++----------------- src/presolve/HighsPostsolveStack.h | 11 ++---- 5 files changed, 29 insertions(+), 95 deletions(-) diff --git a/check/TestPresolve.cpp b/check/TestPresolve.cpp index 7d2e5d9604..e4263c010e 100644 --- a/check/TestPresolve.cpp +++ b/check/TestPresolve.cpp @@ -616,7 +616,7 @@ TEST_CASE("presolve-slacks", "[highs_test_presolve]") { lp.a_matrix_.index_ = {0, 0}; lp.a_matrix_.value_ = {1, 1}; Highs h; - h.setOptionValue("output_flag", dev_run); + // h.setOptionValue("output_flag", dev_run); REQUIRE(h.passModel(lp) == HighsStatus::kOk); REQUIRE(h.presolve() == HighsStatus::kOk); REQUIRE(h.getPresolvedLp().num_col_ == 0); diff --git a/src/lp_data/Highs.cpp b/src/lp_data/Highs.cpp index 894b703aac..f408807367 100644 --- a/src/lp_data/Highs.cpp +++ b/src/lp_data/Highs.cpp @@ -1252,46 +1252,6 @@ HighsStatus Highs::run() { bool have_optimal_solution = false; // ToDo Put solution of presolved problem in a separate method - // if (!this->options_.presolve_remove_slacks) { - HighsLp& reduced_lp = presolve_.getReducedProblem(); - HighsInt num_double_slack = 0; - HighsInt num_slack = 0; - HighsInt num_zero_cost_slack = 0; - HighsInt num_unit_coeff_slack = 0; - double min_slack_coeff = kHighsInf; - double max_slack_coeff = -kHighsInf; - std::vector found_slack; - found_slack.assign(reduced_lp.num_row_, false); - for (HighsInt iCol = 0; iCol < reduced_lp.num_col_; iCol++) { - HighsInt nnz = reduced_lp.a_matrix_.start_[iCol + 1] - - reduced_lp.a_matrix_.start_[iCol]; - if (nnz != 1) continue; - HighsInt iRow = - reduced_lp.a_matrix_.index_[reduced_lp.a_matrix_.start_[iCol]]; - if (found_slack[iRow]) { - num_double_slack++; - continue; - } - if (reduced_lp.row_lower_[iRow] != reduced_lp.row_upper_[iRow]) continue; - num_slack++; - printf("Column %d is slack\n", int(iCol)); - double coeff = std::fabs( - reduced_lp.a_matrix_.value_[reduced_lp.a_matrix_.start_[iCol]]); - if (coeff == 1.0) num_unit_coeff_slack++; - min_slack_coeff = std::min(coeff, min_slack_coeff); - max_slack_coeff = std::max(coeff, max_slack_coeff); - found_slack[iRow] = true; - if (reduced_lp.col_cost_[iCol] == 0) num_zero_cost_slack++; - } - printf( - "grepSlack,model,col,slack,unit coeff,zero_cost,double,min coeff, " - "max_coeff\n"); - printf("grepSlack,%s,%d, %d, %d, %d, %d, %g, %g\n", - this->model_.lp_.model_name_.c_str(), int(reduced_lp.num_col_), - int(num_slack), int(num_unit_coeff_slack), int(num_zero_cost_slack), - int(num_double_slack), min_slack_coeff, max_slack_coeff); - // } - switch (model_presolve_status_) { case HighsPresolveStatus::kNotPresolved: { ekk_instance_.lp_name_ = "Original LP"; @@ -1565,6 +1525,10 @@ HighsStatus Highs::run() { options_ = save_options; if (return_status == HighsStatus::kError) return returnFromRun(return_status, undo_mods); + if (postsolve_iteration_count > 0) + highsLogUser(options_.log_options, HighsLogType::kInfo, + "Required %d simplex iterations after postsolve\n", + int(postsolve_iteration_count)); } } else { highsLogUser(log_options, HighsLogType::kError, diff --git a/src/presolve/HPresolve.cpp b/src/presolve/HPresolve.cpp index 8e99f91023..61d18979f0 100644 --- a/src/presolve/HPresolve.cpp +++ b/src/presolve/HPresolve.cpp @@ -4361,9 +4361,8 @@ HPresolve::Result HPresolve::presolve(HighsPostsolveStack& postsolve_stack) { } // Now consider removing slacks - if (options->presolve_remove_slacks) { + if (options->presolve_remove_slacks) HPRESOLVE_CHECKED_CALL(removeSlacks(postsolve_stack)); - } report(); } else { @@ -4382,10 +4381,8 @@ HPresolve::Result HPresolve::presolve(HighsPostsolveStack& postsolve_stack) { HPresolve::Result HPresolve::removeSlacks( HighsPostsolveStack& postsolve_stack) { - // singletonColumns data structure appears not to be retained + // SingletonColumns data structure appears not to be retained // throughout presolve - // - bool unit_coeff_only = true; for (HighsInt iCol = 0; iCol != model->num_col_; ++iCol) { if (colDeleted[iCol]) continue; if (colsize[iCol] != 1) continue; @@ -4400,11 +4397,6 @@ HPresolve::Result HPresolve::removeSlacks( double cost = model->col_cost_[iCol]; double rhs = model->row_lower_[iRow]; double coeff = Avalue[coliter]; - printf( - "Col %d is continuous and is singleton in equality row %d with cost " - "%g, bounds [%g, %g], coeff %g and RHS = %g\n", - int(iCol), int(iRow), cost, lower, upper, coeff, rhs); - if (unit_coeff_only && std::fabs(coeff) != 1.0) continue; assert(coeff); // Slack is s = (rhs - a^Tx)/coeff // @@ -4428,10 +4420,7 @@ HPresolve::Result HPresolve::removeSlacks( model->offset_ += multiplier * rhs; } // - postsolve_stack.slackColSubstitution(iRow, iCol, rhs, cost, - lower, - upper, - getRowVector(iRow)); + postsolve_stack.slackColSubstitution(iRow, iCol, rhs, getRowVector(iRow)); markColDeleted(iCol); diff --git a/src/presolve/HighsPostsolveStack.cpp b/src/presolve/HighsPostsolveStack.cpp index 94f7cd0e0c..2e78d9a1a7 100644 --- a/src/presolve/HighsPostsolveStack.cpp +++ b/src/presolve/HighsPostsolveStack.cpp @@ -13,7 +13,7 @@ #include #include "lp_data/HConst.h" -#include "lp_data/HighsModelUtils.h" // For debugging +#include "lp_data/HighsModelUtils.h" // For debugging #2001 #include "lp_data/HighsOptions.h" #include "util/HighsCDouble.h" #include "util/HighsUtils.h" @@ -1355,10 +1355,7 @@ void HighsPostsolveStack::DuplicateColumn::transformToPresolvedSpace( void HighsPostsolveStack::SlackColSubstitution::undo( const HighsOptions& options, const std::vector& rowValues, HighsSolution& solution, HighsBasis& basis) { - // Taken from HighsPostsolveStack::FreeColSubstitution::undo( - // - // a (removed) cut may have been used in this reduction. - // + bool debug_print = false; // May have to determine row dual and basis status unless doing // primal-only transformation in MIP solver, in which case row may // no longer exist if it corresponds to a removed cut, so have to @@ -1382,50 +1379,41 @@ void HighsPostsolveStack::SlackColSubstitution::undo( double(rowValue + colCoef * solution.col_value[col]); solution.col_value[col] = double((rhs - rowValue) / colCoef); - double rowLower = colCoef > 0 ? rhs - colCoef * slackUpper : rhs - colCoef * slackLower; - double rowUpper = colCoef > 0 ? rhs - colCoef * slackLower : rhs - colCoef * slackUpper; - - printf( - "\nHighsPostsolveStack::SlackColSubstitution::undo rowValue = %11.5g; " - "bounds [%11.5g, %11.5g] colCoef = %11.6g\n", - double(rowValue), rowLower, rowUpper, colCoef); - printf( - "HighsPostsolveStack::SlackColSubstitution::undo colValue = %11.5g, bounds [%11.5g, %11.5g]\n", - solution.col_value[col], slackLower, slackUpper); // If no dual values requested, return here if (!solution.dual_valid) return; // Row retains its dual value, and column has this dual value scaled by coeff - if (isModelRow) { - solution.col_dual[col] = - solution.row_dual[row] / colCoef; - printf( - "HighsPostsolveStack::SlackColSubstitution::undo rowDual = %11.5g; colDual = %11.5g\n", - solution.row_dual[row], solution.col_dual[col]); - } + if (isModelRow) solution.col_dual[col] = -solution.row_dual[row] / colCoef; // Set basis status if necessary if (!basis.valid) return; - // If row is basic, then slack is basic, otherwise row retains its status + // If row is basic, then slack is basic, otherwise row retains its status if (isModelRow) { HighsBasisStatus save_row_basis_status = basis.row_status[row]; if (basis.row_status[row] == HighsBasisStatus::kBasic) { basis.col_status[col] = HighsBasisStatus::kBasic; - basis.row_status[row] = computeRowStatus(solution.row_dual[row], RowType::kEq); + basis.row_status[row] = + computeRowStatus(solution.row_dual[row], RowType::kEq); } else if (basis.row_status[row] == HighsBasisStatus::kLower) { - basis.col_status[col] = colCoef > 0 ? HighsBasisStatus::kUpper : HighsBasisStatus::kLower; + basis.col_status[col] = + colCoef > 0 ? HighsBasisStatus::kUpper : HighsBasisStatus::kLower; } else { - basis.col_status[col] = colCoef > 0 ? HighsBasisStatus::kLower : HighsBasisStatus::kUpper; + basis.col_status[col] = + colCoef > 0 ? HighsBasisStatus::kLower : HighsBasisStatus::kUpper; } - printf("HighsPostsolveStack::SlackColSubstitution::undo OgRowStatus = %s; " - "RowStatus = %s; ColStatus = %s\n", - utilBasisStatusToString(save_row_basis_status).c_str(), utilBasisStatusToString(basis.row_status[row]).c_str(), - utilBasisStatusToString(basis.col_status[col]).c_str()); + if (debug_print) + printf( + "HighsPostsolveStack::SlackColSubstitution::undo OgRowStatus = %s; " + "RowStatus = %s; ColStatus = %s\n", + utilBasisStatusToString(save_row_basis_status).c_str(), + utilBasisStatusToString(basis.row_status[row]).c_str(), + utilBasisStatusToString(basis.col_status[col]).c_str()); if (basis.col_status[col] == HighsBasisStatus::kLower) { - assert(solution.col_dual[col] > 0); + assert(solution.col_dual[col] > -options.dual_feasibility_tolerance); } else if (basis.col_status[col] == HighsBasisStatus::kUpper) { - assert(solution.col_dual[col] < 0); + assert(solution.col_dual[col] < options.dual_feasibility_tolerance); } } else { basis.col_status[col] = HighsBasisStatus::kNonbasic; diff --git a/src/presolve/HighsPostsolveStack.h b/src/presolve/HighsPostsolveStack.h index 8b3f97200e..f3409d074c 100644 --- a/src/presolve/HighsPostsolveStack.h +++ b/src/presolve/HighsPostsolveStack.h @@ -221,9 +221,6 @@ class HighsPostsolveStack { struct SlackColSubstitution { double rhs; - double colCost; - double slackLower; - double slackUpper; HighsInt row; HighsInt col; @@ -339,17 +336,13 @@ class HighsPostsolveStack { template void slackColSubstitution(HighsInt row, HighsInt col, double rhs, - double colCost, - double slackLower, double slackUpper, const HighsMatrixSlice& rowVec) { rowValues.clear(); for (const HighsSliceNonzero& rowVal : rowVec) rowValues.emplace_back(origColIndex[rowVal.index()], rowVal.value()); - reductionValues.push(SlackColSubstitution{rhs, colCost, - slackLower, slackUpper, - origRowIndex[row], - origColIndex[col]}); + reductionValues.push( + SlackColSubstitution{rhs, origRowIndex[row], origColIndex[col]}); reductionValues.push(rowValues); reductionAdded(ReductionType::kSlackColSubstitution); } From 835b87e2421d8428cc602aa0bba174e663e49984 Mon Sep 17 00:00:00 2001 From: fwesselm Date: Mon, 11 Nov 2024 11:25:23 +0100 Subject: [PATCH 26/75] Add utilities --- src/presolve/HPresolve.cpp | 90 +++++++++++++++++++------------------- src/presolve/HPresolve.h | 4 ++ 2 files changed, 49 insertions(+), 45 deletions(-) diff --git a/src/presolve/HPresolve.cpp b/src/presolve/HPresolve.cpp index 5360c9e092..a6f0776410 100644 --- a/src/presolve/HPresolve.cpp +++ b/src/presolve/HPresolve.cpp @@ -222,6 +222,26 @@ void HPresolve::dualImpliedFreeGetRhsAndRowType( } } +bool HPresolve::isImpliedEquationAtLower(HighsInt row) const { + // if the implied lower bound on a row dual is strictly positive then the row + // is an implied equation (using its lower bound) due to complementary + // slackness + bool isLbndPositive = + implRowDualLower[row] > options->dual_feasibility_tolerance; + assert(!isLbndPositive || model->row_lower_[row] != -kHighsInf); + return isLbndPositive; +} + +bool HPresolve::isImpliedEquationAtUpper(HighsInt row) const { + // if the implied upper bound on a row dual is strictly negative then the row + // is an implied equation (using its upper bound) due to complementary + // slackness + bool isUbndNegative = + implRowDualUpper[row] < -options->dual_feasibility_tolerance; + assert(!isUbndNegative || model->row_upper_[row] != kHighsInf); + return isUbndNegative; +} + bool HPresolve::isImpliedIntegral(HighsInt col) { bool runDualDetection = true; @@ -236,15 +256,13 @@ bool HPresolve::isImpliedIntegral(HighsInt col) { continue; } - double rowLower = - implRowDualUpper[nz.index()] < -options->dual_feasibility_tolerance - ? model->row_upper_[nz.index()] - : model->row_lower_[nz.index()]; + double rowLower = isImpliedEquationAtUpper(nz.index()) + ? model->row_upper_[nz.index()] + : model->row_lower_[nz.index()]; - double rowUpper = - implRowDualLower[nz.index()] > options->dual_feasibility_tolerance - ? model->row_lower_[nz.index()] - : model->row_upper_[nz.index()]; + double rowUpper = isImpliedEquationAtLower(nz.index()) + ? model->row_lower_[nz.index()] + : model->row_upper_[nz.index()]; if (rowUpper == rowLower) { // if there is an equation the dual detection does not need to be tried @@ -308,15 +326,13 @@ bool HPresolve::isImpliedInteger(HighsInt col) { continue; } - double rowLower = - implRowDualUpper[nz.index()] < -options->dual_feasibility_tolerance - ? model->row_upper_[nz.index()] - : model->row_lower_[nz.index()]; + double rowLower = isImpliedEquationAtUpper(nz.index()) + ? model->row_upper_[nz.index()] + : model->row_lower_[nz.index()]; - double rowUpper = - implRowDualLower[nz.index()] > options->dual_feasibility_tolerance - ? model->row_lower_[nz.index()] - : model->row_upper_[nz.index()]; + double rowUpper = isImpliedEquationAtLower(nz.index()) + ? model->row_lower_[nz.index()] + : model->row_upper_[nz.index()]; if (rowUpper == rowLower) { // if there is an equation the dual detection does not need to be tried @@ -564,12 +580,10 @@ void HPresolve::updateRowDualImpliedBounds(HighsInt row, HighsInt col, void HPresolve::updateColImpliedBounds(HighsInt row, HighsInt col, double val) { // propagate implied column bound upper bound if row has an upper bound - double rowUpper = implRowDualLower[row] > options->dual_feasibility_tolerance - ? model->row_lower_[row] - : model->row_upper_[row]; - double rowLower = implRowDualUpper[row] < -options->dual_feasibility_tolerance - ? model->row_upper_[row] - : model->row_lower_[row]; + double rowUpper = isImpliedEquationAtLower(row) ? model->row_lower_[row] + : model->row_upper_[row]; + double rowLower = isImpliedEquationAtUpper(row) ? model->row_upper_[row] + : model->row_lower_[row]; assert(rowLower != kHighsInf); assert(rowUpper != -kHighsInf); @@ -3067,7 +3081,7 @@ HPresolve::Result HPresolve::rowPresolve(HighsPostsolveStack& postsolve_stack, double origRowLower = model->row_lower_[row]; if (model->row_lower_[row] != model->row_upper_[row]) { - if (implRowDualLower[row] > options->dual_feasibility_tolerance) { + if (isImpliedEquationAtLower(row)) { // Convert to equality constraint (note that currently postsolve will not // know about this conversion) model->row_upper_[row] = model->row_lower_[row]; @@ -3076,7 +3090,7 @@ HPresolve::Result HPresolve::rowPresolve(HighsPostsolveStack& postsolve_stack, changeRowDualLower(row, -kHighsInf); if (mipsolver == nullptr) checkRedundantBounds(rowDualLowerSource[row], row); - } else if (implRowDualUpper[row] < -options->dual_feasibility_tolerance) { + } else if (isImpliedEquationAtUpper(row)) { // Convert to equality constraint (note that currently postsolve will not // know about this conversion) model->row_lower_[row] = model->row_upper_[row]; @@ -3769,27 +3783,13 @@ HPresolve::Result HPresolve::rowPresolve(HighsPostsolveStack& postsolve_stack, } } - bool hasRowUpper = - model->row_upper_[row] != kHighsInf || - implRowDualUpper[row] < -options->dual_feasibility_tolerance; - // #1711: This looks very dodgy: surely model->row_lower_[row] != - // -kHighsInf: dates from before 25/02/21 - // - // bool hasRowLower = - // model->row_lower_[row] != kHighsInf || - // implRowDualLower[row] > options->dual_feasibility_tolerance; - // - // Using this corrected line will reduce the number of calls to - // updateColImpliedBounds, as model->row_lower_[row] != kHighsInf is - // never false - bool hasRowLower = - model->row_lower_[row] != -kHighsInf || - implRowDualLower[row] > options->dual_feasibility_tolerance; - // #1711: Unsurprisingly, the assert is triggered very frequently - // assert(true_hasRowLower == hasRowLower); - - if ((hasRowUpper && impliedRowBounds.getNumInfSumLowerOrig(row) <= 1) || - (hasRowLower && impliedRowBounds.getNumInfSumUpperOrig(row) <= 1)) { + // implied bounds can only be computed when row bounds are available and + // bounds on activity contain at most one infinite bound + if (((model->row_upper_[row] != kHighsInf || isImpliedEquationAtLower(row)) && + impliedRowBounds.getNumInfSumLowerOrig(row) <= 1) || + ((model->row_lower_[row] != -kHighsInf || + isImpliedEquationAtUpper(row)) && + impliedRowBounds.getNumInfSumUpperOrig(row) <= 1)) { for (const HighsSliceNonzero& nonzero : getRowVector(row)) updateColImpliedBounds(row, nonzero.index(), nonzero.value()); } diff --git a/src/presolve/HPresolve.h b/src/presolve/HPresolve.h index 2be1b24218..75625e22a2 100644 --- a/src/presolve/HPresolve.h +++ b/src/presolve/HPresolve.h @@ -173,6 +173,10 @@ class HPresolve { HighsPostsolveStack::RowType& rowType, bool relaxRowDualBounds = false); + bool isImpliedEquationAtLower(HighsInt row) const; + + bool isImpliedEquationAtUpper(HighsInt row) const; + bool isImpliedIntegral(HighsInt col); bool isImpliedInteger(HighsInt col); From e7e421b393561201481a782245f1df58d0a2a42c Mon Sep 17 00:00:00 2001 From: Ivet Galabova Date: Mon, 11 Nov 2024 13:46:54 +0200 Subject: [PATCH 27/75] extra unit tests infrastructure and first workflow job to test it --- .../workflows/build-unit-tests-external.yml | 124 ++++++++++++++---- CMakeLists.txt | 21 +-- check/CMakeLists.txt | 47 ++++++- 3 files changed, 154 insertions(+), 38 deletions(-) diff --git a/.github/workflows/build-unit-tests-external.yml b/.github/workflows/build-unit-tests-external.yml index 0423062c16..a458289de3 100644 --- a/.github/workflows/build-unit-tests-external.yml +++ b/.github/workflows/build-unit-tests-external.yml @@ -2,49 +2,117 @@ name: build-unit-tests-external on: [push, pull_request] -jobs: - release_extra_unit_tests: - runs-on: windows-2019 +jobs: + release_extra_only: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest] steps: - uses: actions/checkout@v4 - - name: Create Build Environment - run: cmake -E make_directory ${{runner.workspace}}/build - - name: Clone extra unit tests repo - working-directory: ${{runner.workspace}}/build - run: git clone https://github.com/galabovaa/highs-unit-tests.git + shell: bash + working-directory: ${{runner.workspace}} + run: | + git clone https://github.com/galabovaa/highs-unit-tests.git - name: Create symlink shell: bash - working-directory: $GITHUB_WORKSPACE/check - run: mklink /d highs-unit-tests ${{runner.workspace}}/highs-unit-tests + working-directory: ${{runner.workspace}} + working-directory: + run: ln -s ${{runner.workspace}}/highs-unit-tests $GITHUB_WORKSPACE/check + + - name: Create Build Environment + run: cmake -E make_directory ${{runner.workspace}}/build - - name: Configure CMake - # Use a bash shell so we can use the same syntax for environment variable - # access regardless of the host operating system + - name: Configure CMake All shell: bash working-directory: ${{runner.workspace}}/build - # Note the current convention is to use the -S and -B options here to specify source - # and build directories, but this is only available with CMake 3.13 and higher. - # The CMake binaries on the Github Actions machines are (as of this writing) 3.12 - run: cmake $GITHUB_WORKSPACE -DALL_TESTS=ON -DBUILD_EXTRA_UNIT_TESTS=ON + run: cmake $GITHUB_WORKSPACE -DBUILD_EXTRA_UNIT_TESTS=ON -DBUILD_EXTRA_UNIT_ONLY=ON -DBUILD_CXX=OFF - - name: Build + - name: Build All working-directory: ${{runner.workspace}}/build shell: bash - # Execute the build. You can specify a specific target with "--target " - run: cmake --build . --config Release --parallel + run: | + cmake --build . --parallel - - name: Unit Test Extra + - name: Test Extra Only working-directory: ${{runner.workspace}}/build shell: bash - run: ./bin/Release/unit_tests.exe highs-names-extra + run: ctest --parallel --timeout 300 --output-on-failure - - name: Test - working-directory: ${{runner.workspace}}/build - shell: bash - # Execute tests defined by the CMake configuration. - # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail - run: ctest --timeout 300 --output-on-failure -C Release + # release_all_tests: + # runs-on: ${{ matrix.os }} + # strategy: + # matrix: + # os: [ubuntu-latest] + + # steps: + # - uses: actions/checkout@v4 + + # - name: Create Build Environment + # run: cmake -E make_directory ${{runner.workspace}}/build + + # - name: Configure CMake All + # shell: bash + # working-directory: ${{runner.workspace}}/build + # run: cmake $GITHUB_WORKSPACE -DALL_TESTS=ON -DBUILD_EXTRA_UNIT_TESTS=ON + + # - name: Build All + # working-directory: ${{runner.workspace}}/build + # shell: bash + # run: | + # cmake --build . --parallel + + # - name: Test All + # working-directory: ${{runner.workspace}}/build + # shell: bash + # run: ctest --parallel --timeout 300 --output-on-failure + + # release__windows_extra_unit_tests: + # runs-on: windows-2019 + + # steps: + # - uses: actions/checkout@v4 + + # - name: Create Build Environment + # run: cmake -E make_directory ${{runner.workspace}}/build + + # - name: Clone extra unit tests repo + # working-directory: ${{runner.workspace}}/build + # run: git clone https://github.com/galabovaa/highs-unit-tests.git + + # - name: Create symlink + # shell: bash + # working-directory: $GITHUB_WORKSPACE/check + # run: mklink /d highs-unit-tests ${{runner.workspace}}/highs-unit-tests + + # - name: Configure CMake + # # Use a bash shell so we can use the same syntax for environment variable + # # access regardless of the host operating system + # shell: bash + # working-directory: ${{runner.workspace}}/build + # # Note the current convention is to use the -S and -B options here to specify source + # # and build directories, but this is only available with CMake 3.13 and higher. + # # The CMake binaries on the Github Actions machines are (as of this writing) 3.12 + # run: cmake $GITHUB_WORKSPACE -DALL_TESTS=ON -DBUILD_EXTRA_UNIT_TESTS=ON + + # - name: Build + # working-directory: ${{runner.workspace}}/build + # shell: bash + # # Execute the build. You can specify a specific target with "--target " + # run: cmake --build . --config Release --parallel + + # - name: Unit Test Extra + # working-directory: ${{runner.workspace}}/build + # shell: bash + # run: ./bin/Release/unit_tests.exe highs-names-extra + + # - name: Test + # working-directory: ${{runner.workspace}}/build + # shell: bash + # # Execute tests defined by the CMake configuration. + # # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + # run: ctest --timeout 300 --output-on-failure -C Release diff --git a/CMakeLists.txt b/CMakeLists.txt index e0504cb518..c8deeee559 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,11 +9,11 @@ cmake_minimum_required(VERSION 3.15...3.27) # set preference for clang compiler and intel compiler over gcc and other compilers include(Platform/${CMAKE_SYSTEM_NAME}-Determine-C OPTIONAL) include(Platform/${CMAKE_SYSTEM_NAME}-C OPTIONAL) -set(CMAKE_C_COMPILER_NAMES clang icc cc ${CMAKE_C_COMPILER_NAMES}) +set(CMAKE_C_COMPILER_NAMES clang gcc icx cc ${CMAKE_C_COMPILER_NAMES}) include(Platform/${CMAKE_SYSTEM_NAME}-Determine-CXX OPTIONAL) include(Platform/${CMAKE_SYSTEM_NAME}-CXX OPTIONAL) -set(CMAKE_CXX_COMPILER_NAMES clang++ icpc c++ ${CMAKE_CXX_COMPILER_NAMES}) +set(CMAKE_CXX_COMPILER_NAMES clang++ g++ icpx c++ ${CMAKE_CXX_COMPILER_NAMES}) list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") @@ -82,11 +82,16 @@ include(CMakeDependentOption) CMAKE_DEPENDENT_OPTION(ALL_TESTS "Build all tests" OFF "BUILD_TESTING;BUILD_CXX" OFF) message(STATUS "Build all tests: ${ALL_TESTS}") -CMAKE_DEPENDENT_OPTION(BUILD_EXTRA_UNIT_TESTS "Build extra unit tests" OFF "ALL_TESTS" OFF) +option(BUILD_EXTRA_UNIT_TESTS "Build extra unit tests" OFF) if (BUILD_EXTRA_UNIT_TESTS) message(STATUS "Build extra unit tests: ON") endif() +CMAKE_DEPENDENT_OPTION(BUILD_EXTRA_UNIT_ONLY "Build extra unit tests ONLY" OFF "BUILD_EXTRA_UNIT_TESTS" OFF) +if (BUILD_EXTRA_UNIT_ONLY) + message(STATUS "Build only extra unit tests: ON") +endif() + CMAKE_DEPENDENT_OPTION(BUILD_EXTRA_PROBLEM_SET "Build extra instance tests" OFF "BUILD_TESTING" OFF) if (BUILD_EXTRA_PROBLEM_SET) message(STATUS "Build extra instance tests: ON") @@ -592,14 +597,14 @@ else(FAST_BUILD) if(BUILD_CXX) add_subdirectory(app) - if(BUILD_TESTING) - enable_testing() - add_subdirectory(check) - endif() - # Add tests in examples/tests add_subdirectory(examples) endif() + if(BUILD_TESTING) + enable_testing() + add_subdirectory(check) + endif() + include(python-highs) option(USE_DOTNET_STD_21 "Use .Net Standard 2.1 support" ON) diff --git a/check/CMakeLists.txt b/check/CMakeLists.txt index 428532c323..051b09623a 100644 --- a/check/CMakeLists.txt +++ b/check/CMakeLists.txt @@ -1,6 +1,6 @@ include(CTest) -if (FORTRAN) +if (FORTRAN AND NOT BUILD_EXTRA_UNIT_ONLY) set(CMAKE_Fortran_MODULE_DIRECTORY ${HIGHS_BINARY_DIR}/modules) add_executable(fortrantest TestFortranAPI.f90) if (NOT FAST_BUILD) @@ -13,7 +13,7 @@ if (FORTRAN) ${HIGHS_SOURCE_DIR}/check) endif() -if (NOT FAST_BUILD OR ALL_TESTS) +if ((NOT FAST_BUILD OR ALL_TESTS) AND NOT (BUILD_EXTRA_UNIT_ONLY)) # prepare Catch library set(CATCH_INCLUDE_DIR ${HIGHS_SOURCE_DIR}/extern) add_library(Catch INTERFACE) @@ -413,3 +413,46 @@ if (BUILD_EXTRA_PROBLEM_SET) message(STATUS "${HIGHS_SOURCE_DIR}/check/highs-problem-set") include(highs-problem-set) endif() + +if (BUILD_EXTRA_UNIT_TESTS AND BUILD_EXTRA_UNIT_ONLY) + # prepare Catch library + set(CATCH_INCLUDE_DIR ${HIGHS_SOURCE_DIR}/extern) + add_library(Catch INTERFACE) + target_include_directories(Catch INTERFACE ${CATCH_INCLUDE_DIR}) + + + list(APPEND CMAKE_MODULE_PATH "${HIGHS_SOURCE_DIR}/check/highs-unit-tests") + message(STATUS "${HIGHS_SOURCE_DIR}/check/highs-unit-tests") + include(highs-unit-tests) + + set(TEST_SOURCES TestMain.cpp ${HIGHS_EXTRA_UNIT_TESTS}) + message(STATUS ${TEST_SOURCES}) + + add_executable(unit_tests_extra ${TEST_SOURCES}) + target_link_libraries(unit_tests_extra Catch) + + if (BUILD_CXX) + configure_file(${HIGHS_SOURCE_DIR}/check/HCheckConfig.h.in ${HIGHS_BINARY_DIR}/HCheckConfig.h) + target_link_libraries(unit_tests_extra highs) + endif() + + add_test(NAME unit-test-extra-build + COMMAND ${CMAKE_COMMAND} + --build ${HIGHS_BINARY_DIR} + --target unit_tests_extra + # --config ${CMAKE_BUILD_TYPE} + ) + + # Avoid that several build jobs try to concurretly build. + set_tests_properties(unit-test-extra-build + PROPERTIES + RESOURCE_LOCK unittestbin) + + # create a binary running all the tests in the executable + add_test(NAME unit_tests_extra COMMAND unit_tests_extra --success) + set_tests_properties(unit_tests_extra + PROPERTIES + DEPENDS unit-test-extra-build) + set_tests_properties(unit_tests_extra PROPERTIES TIMEOUT 10000) + +endif() \ No newline at end of file From 9a20d8650a90e4ffdd4cc2d92589cf72a224ec6b Mon Sep 17 00:00:00 2001 From: Ivet Galabova Date: Mon, 11 Nov 2024 13:53:38 +0200 Subject: [PATCH 28/75] workflow fix and update (push) extra tests repo --- .github/workflows/build-unit-tests-external.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/build-unit-tests-external.yml b/.github/workflows/build-unit-tests-external.yml index a458289de3..6009eca366 100644 --- a/.github/workflows/build-unit-tests-external.yml +++ b/.github/workflows/build-unit-tests-external.yml @@ -21,7 +21,6 @@ jobs: - name: Create symlink shell: bash working-directory: ${{runner.workspace}} - working-directory: run: ln -s ${{runner.workspace}}/highs-unit-tests $GITHUB_WORKSPACE/check - name: Create Build Environment From e927e02ab20a0e1ac5d9cf99ac628780f87d640f Mon Sep 17 00:00:00 2001 From: Ivet Galabova Date: Tue, 12 Nov 2024 15:20:34 +0200 Subject: [PATCH 29/75] extra tests dummy and all tests --- .../workflows/build-unit-tests-external.yml | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/.github/workflows/build-unit-tests-external.yml b/.github/workflows/build-unit-tests-external.yml index 6009eca366..c8f6e2932f 100644 --- a/.github/workflows/build-unit-tests-external.yml +++ b/.github/workflows/build-unit-tests-external.yml @@ -42,33 +42,33 @@ jobs: shell: bash run: ctest --parallel --timeout 300 --output-on-failure - # release_all_tests: - # runs-on: ${{ matrix.os }} - # strategy: - # matrix: - # os: [ubuntu-latest] + release_all_tests: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest] - # steps: - # - uses: actions/checkout@v4 + steps: + - uses: actions/checkout@v4 - # - name: Create Build Environment - # run: cmake -E make_directory ${{runner.workspace}}/build + - name: Create Build Environment + run: cmake -E make_directory ${{runner.workspace}}/build - # - name: Configure CMake All - # shell: bash - # working-directory: ${{runner.workspace}}/build - # run: cmake $GITHUB_WORKSPACE -DALL_TESTS=ON -DBUILD_EXTRA_UNIT_TESTS=ON + - name: Configure CMake All + shell: bash + working-directory: ${{runner.workspace}}/build + run: cmake $GITHUB_WORKSPACE -DALL_TESTS=ON -DBUILD_EXTRA_UNIT_TESTS=ON - # - name: Build All - # working-directory: ${{runner.workspace}}/build - # shell: bash - # run: | - # cmake --build . --parallel + - name: Build All + working-directory: ${{runner.workspace}}/build + shell: bash + run: | + cmake --build . --parallel - # - name: Test All - # working-directory: ${{runner.workspace}}/build - # shell: bash - # run: ctest --parallel --timeout 300 --output-on-failure + - name: Test All + working-directory: ${{runner.workspace}}/build + shell: bash + run: ctest --parallel --timeout 300 --output-on-failure # release__windows_extra_unit_tests: # runs-on: windows-2019 From 61b3ca0fcb1ee374e6a64ef0185af7eb2608df50 Mon Sep 17 00:00:00 2001 From: Ivet Galabova Date: Tue, 12 Nov 2024 16:25:00 +0200 Subject: [PATCH 30/75] clone extra tests repo for all tests --- .github/workflows/build-unit-tests-external.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/workflows/build-unit-tests-external.yml b/.github/workflows/build-unit-tests-external.yml index c8f6e2932f..8ee3f4cc02 100644 --- a/.github/workflows/build-unit-tests-external.yml +++ b/.github/workflows/build-unit-tests-external.yml @@ -51,6 +51,17 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Clone extra unit tests repo + shell: bash + working-directory: ${{runner.workspace}} + run: | + git clone https://github.com/galabovaa/highs-unit-tests.git + + - name: Create symlink + shell: bash + working-directory: ${{runner.workspace}} + run: ln -s ${{runner.workspace}}/highs-unit-tests $GITHUB_WORKSPACE/check + - name: Create Build Environment run: cmake -E make_directory ${{runner.workspace}}/build From c2bba11728c884415ca90dde50ba97b1e2a10b9c Mon Sep 17 00:00:00 2001 From: Ivet Galabova Date: Wed, 13 Nov 2024 10:44:14 +0200 Subject: [PATCH 31/75] HiGHS Release 1.8.1 --- .github/workflows/build-nuget-package.yml | 2 +- .github/workflows/test-nuget-macos.yml | 4 ++-- .github/workflows/test-nuget-package.yml | 12 ++++++------ .github/workflows/test-nuget-ubuntu.yml | 4 ++-- .github/workflows/test-nuget-win.yml | 4 ++-- FEATURES.md | 5 +++++ MODULE.bazel | 2 +- README.md | 2 +- Version.txt | 2 +- docs/src/interfaces/csharp.md | 2 +- meson.build | 2 +- nuget/README.md | 2 +- pyproject.toml | 2 +- tests/test_highspy.py | 4 ++-- 14 files changed, 27 insertions(+), 22 deletions(-) diff --git a/.github/workflows/build-nuget-package.yml b/.github/workflows/build-nuget-package.yml index ab9bb2d3af..8e87ea2b2f 100644 --- a/.github/workflows/build-nuget-package.yml +++ b/.github/workflows/build-nuget-package.yml @@ -185,7 +185,7 @@ jobs: - name: Dotnet pack working-directory: ${{runner.workspace}}/build/dotnet/Highs.Native - run: dotnet pack -c Release /p:Version=1.8.0 + run: dotnet pack -c Release /p:Version=1.8.1 - uses: actions/upload-artifact@v4 with: diff --git a/.github/workflows/test-nuget-macos.yml b/.github/workflows/test-nuget-macos.yml index 942494ebaf..20b102d015 100644 --- a/.github/workflows/test-nuget-macos.yml +++ b/.github/workflows/test-nuget-macos.yml @@ -35,7 +35,7 @@ jobs: - name: Dotnet pack working-directory: ${{runner.workspace}}/build/dotnet/Highs.Native - run: dotnet pack -c Release /p:Version=1.8.0 + run: dotnet pack -c Release /p:Version=1.8.1 - name: Add local feed run: dotnet nuget add source ${{runner.workspace}}/nugets @@ -80,7 +80,7 @@ jobs: - name: Dotnet pack working-directory: ${{runner.workspace}}/build/dotnet/Highs.Native - run: dotnet pack -c Release /p:Version=1.8.0 + run: dotnet pack -c Release /p:Version=1.8.1 - name: Add local feed run: dotnet nuget add source ${{runner.workspace}}/nugets diff --git a/.github/workflows/test-nuget-package.yml b/.github/workflows/test-nuget-package.yml index 76393bb97a..69559912ce 100644 --- a/.github/workflows/test-nuget-package.yml +++ b/.github/workflows/test-nuget-package.yml @@ -35,7 +35,7 @@ jobs: - name: Dotnet pack working-directory: ${{runner.workspace}}/build/dotnet/Highs.Native - run: dotnet pack -c Release /p:Version=1.8.0 + run: dotnet pack -c Release /p:Version=1.8.1 - name: Add local feed run: dotnet nuget add source ${{runner.workspace}}/nugets @@ -81,7 +81,7 @@ jobs: - name: Dotnet pack working-directory: ${{runner.workspace}}/build/dotnet/Highs.Native - run: dotnet pack -c Release /p:Version=1.8.0 + run: dotnet pack -c Release /p:Version=1.8.1 - name: Add local feed run: dotnet nuget add source ${{runner.workspace}}/nugets @@ -124,7 +124,7 @@ jobs: - name: Dotnet pack working-directory: ${{runner.workspace}}/build/dotnet/Highs.Native - run: dotnet pack -c Release /p:Version=1.8.0 + run: dotnet pack -c Release /p:Version=1.8.1 - name: Add local feed run: dotnet nuget add source ${{runner.workspace}}/nugets @@ -167,7 +167,7 @@ jobs: - name: Dotnet pack working-directory: ${{runner.workspace}}/build/dotnet/Highs.Native - run: dotnet pack -c Release /p:Version=1.8.0 + run: dotnet pack -c Release /p:Version=1.8.1 - name: Add local feed run: dotnet nuget add source ${{runner.workspace}}/nugets @@ -212,7 +212,7 @@ jobs: - name: Dotnet pack working-directory: ${{runner.workspace}}/build/dotnet/Highs.Native - run: dotnet pack -c Release /p:Version=1.8.0 + run: dotnet pack -c Release /p:Version=1.8.1 - name: Add local feed run: dotnet nuget add source -n name ${{runner.workspace}}\nugets @@ -228,5 +228,5 @@ jobs: dotnet new console rm Program.cs cp ${{runner.workspace}}\HiGHS\examples\call_highs_from_csharp.cs . - dotnet add package Highs.Native -v 1.8.0 -s ${{runner.workspace}}\nugets + dotnet add package Highs.Native -v 1.8.1 -s ${{runner.workspace}}\nugets dotnet run diff --git a/.github/workflows/test-nuget-ubuntu.yml b/.github/workflows/test-nuget-ubuntu.yml index 7c233abd36..67b60c11c9 100644 --- a/.github/workflows/test-nuget-ubuntu.yml +++ b/.github/workflows/test-nuget-ubuntu.yml @@ -31,7 +31,7 @@ jobs: - name: Dotnet pack working-directory: ${{runner.workspace}}/build/dotnet/Highs.Native - run: dotnet pack -c Release /p:Version=1.8.0 + run: dotnet pack -c Release /p:Version=1.8.1 - name: Add local feed run: dotnet nuget add source ${{runner.workspace}}/nugets @@ -74,7 +74,7 @@ jobs: - name: Dotnet pack working-directory: ${{runner.workspace}}/build/dotnet/Highs.Native - run: dotnet pack -c Release /p:Version=1.8.0 + run: dotnet pack -c Release /p:Version=1.8.1 - name: Add local feed run: dotnet nuget add source ${{runner.workspace}}/nugets diff --git a/.github/workflows/test-nuget-win.yml b/.github/workflows/test-nuget-win.yml index 47bdf0ac60..8d6e74ef38 100644 --- a/.github/workflows/test-nuget-win.yml +++ b/.github/workflows/test-nuget-win.yml @@ -33,7 +33,7 @@ jobs: - name: Dotnet pack working-directory: ${{runner.workspace}}/build/dotnet/Highs.Native - run: dotnet pack -c Release /p:Version=1.8.0 + run: dotnet pack -c Release /p:Version=1.8.1 - name: Add local feed run: dotnet nuget add source -n name ${{runner.workspace}}\nugets @@ -49,5 +49,5 @@ jobs: dotnet new console rm Program.cs cp ${{runner.workspace}}\HiGHS\examples\call_highs_from_csharp.cs . - dotnet add package Highs.Native -v 1.8.0 -s ${{runner.workspace}}\nugets + dotnet add package Highs.Native -v 1.8.1 -s ${{runner.workspace}}\nugets dotnet run diff --git a/FEATURES.md b/FEATURES.md index 708e928092..034eca4232 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -20,6 +20,11 @@ Now computing the primal-dual integral, reporting it, and making it available as Trivial primal heuristics "all zero", "all lower bound", "all upper bound", and "lock point" added to the MIP solver +# Build changes + +Added wheels for Python 3.13 + +Updated command line options and moved them out of the library and into the executable diff --git a/MODULE.bazel b/MODULE.bazel index 44a64ec712..adb22ca979 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -1,6 +1,6 @@ module( name = "highs", - version = "1.8.0", + version = "1.8.1", ) bazel_dep( diff --git a/README.md b/README.md index 2758c02fdd..62cfef67b4 100644 --- a/README.md +++ b/README.md @@ -208,7 +208,7 @@ The nuget package Highs.Native is on https://www.nuget.org, at https://www.nuget It can be added to your C# project with `dotnet` ```shell -dotnet add package Highs.Native --version 1.8.0 +dotnet add package Highs.Native --version 1.8.1 ``` The nuget package contains runtime libraries for diff --git a/Version.txt b/Version.txt index baa9e163b1..d8e7ce799d 100644 --- a/Version.txt +++ b/Version.txt @@ -1,4 +1,4 @@ HIGHS_MAJOR=1 HIGHS_MINOR=8 -HIGHS_PATCH=0 +HIGHS_PATCH=1 #PRE_RELEASE=YES diff --git a/docs/src/interfaces/csharp.md b/docs/src/interfaces/csharp.md index 5d492d9baf..e9b24f1946 100644 --- a/docs/src/interfaces/csharp.md +++ b/docs/src/interfaces/csharp.md @@ -17,7 +17,7 @@ The nuget package Highs.Native is on https://www.nuget.org, at https://www.nuget It can be added to your C# project with `dotnet` ```shell -dotnet add package Highs.Native --version 1.8.0 +dotnet add package Highs.Native --version 1.8.1 ``` The nuget package contains runtime libraries for diff --git a/meson.build b/meson.build index 5795a668cf..de7119ea66 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('highs', 'cpp', 'c', - version : '1.8.0', + version : '1.8.1', meson_version: '>= 1.1.0', default_options : ['warning_level=1', 'cpp_std=c++17', diff --git a/nuget/README.md b/nuget/README.md index 3153cb0f8b..68d2034802 100644 --- a/nuget/README.md +++ b/nuget/README.md @@ -7,7 +7,7 @@ The nuget package Highs.Native is on https://www.nuget.org, at https://www.nuget It can be added to your C# project with `dotnet` ```shell -dotnet add package Highs.Native --version 1.8.0 +dotnet add package Highs.Native --version 1.8.1 ``` The nuget package contains runtime libraries for diff --git a/pyproject.toml b/pyproject.toml index 5ba6f6e4ae..0a1dee0f11 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ build-backend = "scikit_build_core.build" [project] name = "highspy" -version = "1.8.0" +version = "1.8.1" description = "A thin set of pybind11 wrappers to HiGHS" authors = [{ name = "HiGHS developers", email = "highsopt@gmail.com" }] readme = "README.md" diff --git a/tests/test_highspy.py b/tests/test_highspy.py index 083e9ff680..aaa731d983 100644 --- a/tests/test_highspy.py +++ b/tests/test_highspy.py @@ -112,10 +112,10 @@ def get_infeasible_model(self): def test_version(self): h = self.get_basic_model() - self.assertEqual(h.version(), "1.8.0") + self.assertEqual(h.version(), "1.8.1") self.assertEqual(h.versionMajor(), 1) self.assertEqual(h.versionMinor(), 8) - self.assertEqual(h.versionPatch(), 0) + self.assertEqual(h.versionPatch(), 1) def test_basics(self): h = self.get_basic_model() From 3ea858a49532ce22d11d1755c3b4eb39fe8a9a2a Mon Sep 17 00:00:00 2001 From: fwesselm Date: Wed, 13 Nov 2024 16:53:26 +0100 Subject: [PATCH 32/75] Add util for free column substitution --- src/presolve/HPresolve.cpp | 51 ++++++++++++++++++-------------------- src/presolve/HPresolve.h | 3 +++ 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/presolve/HPresolve.cpp b/src/presolve/HPresolve.cpp index a6f0776410..67e88613d9 100644 --- a/src/presolve/HPresolve.cpp +++ b/src/presolve/HPresolve.cpp @@ -2986,19 +2986,12 @@ HPresolve::Result HPresolve::singletonCol(HighsPostsolveStack& postsolve_stack, if (logging_on) analysis_.startPresolveRuleLog(kPresolveRuleFreeColSubstitution); + // todo, store which side of an implied free dual variable needs to be used // for substitution storeRow(row); - HighsPostsolveStack::RowType rowType; - double rhs; - dualImpliedFreeGetRhsAndRowType(row, rhs, rowType); - - postsolve_stack.freeColSubstitution(row, col, rhs, model->col_cost_[col], - rowType, getStoredRow(), - getColumnVector(col)); - // todo, check integrality of coefficients and allow this - substitute(row, col, rhs); + substituteFreeCol(postsolve_stack, row, col); analysis_.logging_on_ = logging_on; if (logging_on) @@ -3010,6 +3003,26 @@ HPresolve::Result HPresolve::singletonCol(HighsPostsolveStack& postsolve_stack, return Result::kOk; } +void HPresolve::substituteFreeCol(HighsPostsolveStack& postsolve_stack, + HighsInt row, HighsInt col, + bool relaxRowDualBounds) { + assert(!rowDeleted[row]); + assert(!colDeleted[col]); + assert(isDualImpliedFree(row)); + assert(isImpliedFree(col)); + + HighsPostsolveStack::RowType rowType; + double rhs; + dualImpliedFreeGetRhsAndRowType(row, rhs, rowType, relaxRowDualBounds); + + postsolve_stack.freeColSubstitution(row, col, rhs, model->col_cost_[col], + rowType, getStoredRow(), + getColumnVector(col)); + + // todo, check integrality of coefficients and allow this + substitute(row, col, rhs); +} + HPresolve::Result HPresolve::rowPresolve(HighsPostsolveStack& postsolve_stack, HighsInt row) { assert(!rowDeleted[row]); @@ -4846,18 +4859,9 @@ HPresolve::Result HPresolve::aggregator(HighsPostsolveStack& postsolve_stack) { // in the case where the row has length two or the column has length two // we always do the substitution since the fillin can never be problematic if (rowsize[row] == 2 || colsize[col] == 2) { - double rhs; - HighsPostsolveStack::RowType rowType; - dualImpliedFreeGetRhsAndRowType(row, rhs, rowType, true); - storeRow(row); - - postsolve_stack.freeColSubstitution(row, col, rhs, model->col_cost_[col], - rowType, getStoredRow(), - getColumnVector(col)); + substituteFreeCol(postsolve_stack, row, col, true); substitutionOpportunities[i].first = -1; - - substitute(row, col, rhs); HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); HPRESOLVE_CHECKED_CALL(checkLimits(postsolve_stack)); continue; @@ -4894,15 +4898,8 @@ HPresolve::Result HPresolve::aggregator(HighsPostsolveStack& postsolve_stack) { } nfail = 0; - double rhs; - HighsPostsolveStack::RowType rowType; - dualImpliedFreeGetRhsAndRowType(row, rhs, rowType, true); - - postsolve_stack.freeColSubstitution(row, col, rhs, model->col_cost_[col], - rowType, getStoredRow(), - getColumnVector(col)); + substituteFreeCol(postsolve_stack, row, col, true); substitutionOpportunities[i].first = -1; - substitute(row, col, rhs); HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); HPRESOLVE_CHECKED_CALL(checkLimits(postsolve_stack)); } diff --git a/src/presolve/HPresolve.h b/src/presolve/HPresolve.h index 75625e22a2..ce0bfb2bcb 100644 --- a/src/presolve/HPresolve.h +++ b/src/presolve/HPresolve.h @@ -315,6 +315,9 @@ class HPresolve { Result singletonCol(HighsPostsolveStack& postsolve_stack, HighsInt col); + void substituteFreeCol(HighsPostsolveStack& postsolve_stack, HighsInt row, + HighsInt col, bool relaxRowDualBounds = false); + Result rowPresolve(HighsPostsolveStack& postsolve_stack, HighsInt row); Result colPresolve(HighsPostsolveStack& postsolve_stack, HighsInt col); From a2a25d0c08a5f1700baa567dafbb1881271b624d Mon Sep 17 00:00:00 2001 From: fwesselm Date: Wed, 13 Nov 2024 20:57:36 +0100 Subject: [PATCH 33/75] Use HPRESOLVE_CHECKED_CALL --- src/presolve/HPresolve.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/presolve/HPresolve.cpp b/src/presolve/HPresolve.cpp index 67e88613d9..874033b3a2 100644 --- a/src/presolve/HPresolve.cpp +++ b/src/presolve/HPresolve.cpp @@ -3784,15 +3784,17 @@ HPresolve::Result HPresolve::rowPresolve(HighsPostsolveStack& postsolve_stack, // in reverse, it will first restore the column primal and dual values // as the dual values are required to find the proper dual multiplier for // the row and the column that we put in the basis. - Result res = checkForcingRow(row, HighsInt{1}, model->row_lower_[row], - HighsPostsolveStack::RowType::kGeq); - if (rowDeleted[row]) return res; + HPRESOLVE_CHECKED_CALL( + checkForcingRow(row, HighsInt{1}, model->row_lower_[row], + HighsPostsolveStack::RowType::kGeq)); + if (rowDeleted[row]) return Result::kOk; } else if (impliedRowLower >= model->row_upper_[row] - primal_feastol) { // forcing row in the other direction - Result res = checkForcingRow(row, HighsInt{-1}, model->row_upper_[row], - HighsPostsolveStack::RowType::kLeq); - if (rowDeleted[row]) return res; + HPRESOLVE_CHECKED_CALL( + checkForcingRow(row, HighsInt{-1}, model->row_upper_[row], + HighsPostsolveStack::RowType::kLeq)); + if (rowDeleted[row]) return Result::kOk; } } From 416f3d78298428f263d5fbb891ac828c7e0e58ba Mon Sep 17 00:00:00 2001 From: fwesselm Date: Thu, 14 Nov 2024 09:17:46 +0100 Subject: [PATCH 34/75] Add utility for detecting dominated columns --- src/presolve/HPresolve.cpp | 107 +++++++++++++++++++++++++++++++++++++ src/presolve/HPresolve.h | 3 ++ 2 files changed, 110 insertions(+) diff --git a/src/presolve/HPresolve.cpp b/src/presolve/HPresolve.cpp index a6f0776410..67d44175b2 100644 --- a/src/presolve/HPresolve.cpp +++ b/src/presolve/HPresolve.cpp @@ -4039,6 +4039,113 @@ HPresolve::Result HPresolve::colPresolve(HighsPostsolveStack& postsolve_stack, return Result::kOk; } +HPresolve::Result HPresolve::detectDominatedCol( + HighsPostsolveStack& postsolve_stack, HighsInt col, + bool handleSingletonRows) { + assert(!colDeleted[col]); + + double colDualUpper = + -impliedDualRowBounds.getSumLower(col, -model->col_cost_[col]); + double colDualLower = + -impliedDualRowBounds.getSumUpper(col, -model->col_cost_[col]); + + const bool logging_on = analysis_.logging_on_; + + auto dominatedCol = [&](HighsInt col, double dualBound, double bound, + HighsInt direction) { + if (direction * dualBound <= options->dual_feasibility_tolerance) + return Result::kOk; + if (direction * bound == -kHighsInf) return Result::kDualInfeasible; + if (logging_on) analysis_.startPresolveRuleLog(kPresolveRuleDominatedCol); + bool unbounded = false; + if (direction > 0) + unbounded = fixColToLowerOrUnbounded(postsolve_stack, col); + else + unbounded = fixColToUpperOrUnbounded(postsolve_stack, col); + if (unbounded) { + // Handle unboundedness + presolve_status_ = HighsPresolveStatus::kUnboundedOrInfeasible; + return Result::kDualInfeasible; + } + analysis_.logging_on_ = logging_on; + if (logging_on) analysis_.stopPresolveRuleLog(kPresolveRuleDominatedCol); + if (handleSingletonRows) + HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); + return checkLimits(postsolve_stack); + }; + + auto weaklyDominatedCol = [&](HighsInt col, double dualBound, double bound, + double otherBound, HighsInt direction) { + if (direction * dualBound < -options->dual_feasibility_tolerance) + return Result::kOk; + if (direction * bound != -kHighsInf) { + if (logging_on) analysis_.startPresolveRuleLog(kPresolveRuleDominatedCol); + bool unbounded = false; + if (direction > 0) + unbounded = fixColToLowerOrUnbounded(postsolve_stack, col); + else + unbounded = fixColToUpperOrUnbounded(postsolve_stack, col); + if (unbounded) { + // Handle unboundedness + presolve_status_ = HighsPresolveStatus::kUnboundedOrInfeasible; + return Result::kDualInfeasible; + } + analysis_.logging_on_ = logging_on; + if (logging_on) analysis_.stopPresolveRuleLog(kPresolveRuleDominatedCol); + return checkLimits(postsolve_stack); + } else if (analysis_.allow_rule_[kPresolveRuleForcingCol]) { + HighsCDouble sum = 0; + if (direction > 0) + sum = impliedDualRowBounds.getSumUpperOrig(col); + else + sum = impliedDualRowBounds.getSumLowerOrig(col); + if (sum == 0.0) { + if (logging_on) analysis_.startPresolveRuleLog(kPresolveRuleForcingCol); + postsolve_stack.forcingColumn( + col, getColumnVector(col), model->col_cost_[col], otherBound, + direction < 0, model->integrality_[col] == HighsVarType::kInteger); + markColDeleted(col); + HighsInt coliter = colhead[col]; + while (coliter != -1) { + HighsInt row = Arow[coliter]; + double rhs = direction * Avalue[coliter] > 0.0 + ? model->row_upper_[row] + : model->row_lower_[row]; + coliter = Anext[coliter]; + + postsolve_stack.forcingColumnRemovedRow(col, row, rhs, + getRowVector(row)); + removeRow(row); + } + analysis_.logging_on_ = logging_on; + if (logging_on) analysis_.stopPresolveRuleLog(kPresolveRuleForcingCol); + return checkLimits(postsolve_stack); + } + } + return Result::kOk; + }; + + // check for dominated column + HPRESOLVE_CHECKED_CALL( + dominatedCol(col, colDualLower, model->col_lower_[col], HighsInt{1})); + if (colDeleted[col]) return Result::kOk; + + HPRESOLVE_CHECKED_CALL( + dominatedCol(col, colDualUpper, model->col_upper_[col], HighsInt{-1})); + if (colDeleted[col]) return Result::kOk; + + // check for weakly dominated column + HPRESOLVE_CHECKED_CALL( + weaklyDominatedCol(col, colDualLower, model->col_lower_[col], + model->col_upper_[col], HighsInt{1})); + if (colDeleted[col]) return Result::kOk; + + HPRESOLVE_CHECKED_CALL( + weaklyDominatedCol(col, colDualUpper, model->col_upper_[col], + model->col_lower_[col], HighsInt{-1})); + return Result::kOk; +} + HPresolve::Result HPresolve::initialRowAndColPresolve( HighsPostsolveStack& postsolve_stack) { // do a full scan over the rows as the singleton arrays and the changed row diff --git a/src/presolve/HPresolve.h b/src/presolve/HPresolve.h index 75625e22a2..b49555597c 100644 --- a/src/presolve/HPresolve.h +++ b/src/presolve/HPresolve.h @@ -319,6 +319,9 @@ class HPresolve { Result colPresolve(HighsPostsolveStack& postsolve_stack, HighsInt col); + Result detectDominatedCol(HighsPostsolveStack& postsolve_stack, HighsInt col, + bool handleSingletonRows = true); + Result initialRowAndColPresolve(HighsPostsolveStack& postsolve_stack); HighsModelStatus run(HighsPostsolveStack& postsolve_stack); From c1a2a279ff68f38dc36552ffa84f0f3a102da455 Mon Sep 17 00:00:00 2001 From: fwesselm Date: Thu, 14 Nov 2024 10:36:35 +0100 Subject: [PATCH 35/75] Use new utility and add some comments --- src/presolve/HPresolve.cpp | 231 ++++--------------------------------- 1 file changed, 24 insertions(+), 207 deletions(-) diff --git a/src/presolve/HPresolve.cpp b/src/presolve/HPresolve.cpp index 67d44175b2..f60c1fe843 100644 --- a/src/presolve/HPresolve.cpp +++ b/src/presolve/HPresolve.cpp @@ -2853,121 +2853,9 @@ HPresolve::Result HPresolve::singletonCol(HighsPostsolveStack& postsolve_stack, return Result::kOk; } - double colDualUpper = - -impliedDualRowBounds.getSumLower(col, -model->col_cost_[col]); - double colDualLower = - -impliedDualRowBounds.getSumUpper(col, -model->col_cost_[col]); - - const bool logging_on = analysis_.logging_on_; - // check for dominated column - if (colDualLower > options->dual_feasibility_tolerance) { - if (model->col_lower_[col] == -kHighsInf) return Result::kDualInfeasible; - if (logging_on) analysis_.startPresolveRuleLog(kPresolveRuleDominatedCol); - if (fixColToLowerOrUnbounded(postsolve_stack, col)) { - // Handle unboundedness - presolve_status_ = HighsPresolveStatus::kUnboundedOrInfeasible; - return Result::kDualInfeasible; - } - analysis_.logging_on_ = logging_on; - if (logging_on) analysis_.stopPresolveRuleLog(kPresolveRuleDominatedCol); - return checkLimits(postsolve_stack); - } - - if (colDualUpper < -options->dual_feasibility_tolerance) { - if (model->col_upper_[col] == kHighsInf) return Result::kDualInfeasible; - if (logging_on) analysis_.startPresolveRuleLog(kPresolveRuleDominatedCol); - if (fixColToUpperOrUnbounded(postsolve_stack, col)) { - // Handle unboundedness - presolve_status_ = HighsPresolveStatus::kUnboundedOrInfeasible; - return Result::kDualInfeasible; - } - analysis_.logging_on_ = logging_on; - if (logging_on) analysis_.stopPresolveRuleLog(kPresolveRuleDominatedCol); - return checkLimits(postsolve_stack); - } - - // check for weakly dominated column - if (colDualUpper <= options->dual_feasibility_tolerance) { - if (model->col_upper_[col] != kHighsInf) { - if (logging_on) analysis_.startPresolveRuleLog(kPresolveRuleDominatedCol); - if (fixColToUpperOrUnbounded(postsolve_stack, col)) { - // Handle unboundedness - presolve_status_ = HighsPresolveStatus::kUnboundedOrInfeasible; - return Result::kDualInfeasible; - } - analysis_.logging_on_ = logging_on; - if (logging_on) analysis_.stopPresolveRuleLog(kPresolveRuleDominatedCol); - } else if (impliedDualRowBounds.getSumLowerOrig(col) == 0.0 && - analysis_.allow_rule_[kPresolveRuleForcingCol]) { - // todo: forcing column, since this implies colDual >= 0 and we - // already checked that colDual <= 0 and since the cost are 0.0 - // all the rows are at a dual multiplier of zero and we can - // determine one nonbasic row in postsolve, and make the other - // rows and the column basic. The columns primal value is - // computed from the nonbasic row which is chosen such that the - // values of all rows are primal feasible printf("removing - // forcing column of size %" HIGHSINT_FORMAT "\n", - // colsize[col]); - if (logging_on) analysis_.startPresolveRuleLog(kPresolveRuleForcingCol); - postsolve_stack.forcingColumn( - col, getColumnVector(col), model->col_cost_[col], - model->col_lower_[col], true, - model->integrality_[col] == HighsVarType::kInteger); - markColDeleted(col); - HighsInt coliter = colhead[col]; - while (coliter != -1) { - HighsInt row = Arow[coliter]; - double rhs = Avalue[coliter] > 0.0 ? model->row_lower_[row] - : model->row_upper_[row]; - coliter = Anext[coliter]; - - postsolve_stack.forcingColumnRemovedRow(col, row, rhs, - getRowVector(row)); - removeRow(row); - } - analysis_.logging_on_ = logging_on; - if (logging_on) analysis_.stopPresolveRuleLog(kPresolveRuleForcingCol); - } - return checkLimits(postsolve_stack); - } - if (colDualLower >= -options->dual_feasibility_tolerance) { - if (model->col_lower_[col] != -kHighsInf) { - if (logging_on) analysis_.startPresolveRuleLog(kPresolveRuleDominatedCol); - if (fixColToLowerOrUnbounded(postsolve_stack, col)) { - // Handle unboundedness - presolve_status_ = HighsPresolveStatus::kUnboundedOrInfeasible; - return Result::kDualInfeasible; - } - analysis_.logging_on_ = logging_on; - if (logging_on) analysis_.stopPresolveRuleLog(kPresolveRuleDominatedCol); - } else if (impliedDualRowBounds.getSumUpperOrig(col) == 0.0 && - analysis_.allow_rule_[kPresolveRuleForcingCol]) { - // forcing column, since this implies colDual <= 0 and we already checked - // that colDual >= 0 - // printf("removing forcing column of size %" HIGHSINT_FORMAT "\n", - // colsize[col]); - if (logging_on) analysis_.startPresolveRuleLog(kPresolveRuleForcingCol); - postsolve_stack.forcingColumn( - col, getColumnVector(col), model->col_cost_[col], - model->col_upper_[col], false, - model->integrality_[col] == HighsVarType::kInteger); - markColDeleted(col); - HighsInt coliter = colhead[col]; - while (coliter != -1) { - HighsInt row = Arow[coliter]; - double rhs = Avalue[coliter] > 0.0 ? model->row_upper_[row] - : model->row_lower_[row]; - coliter = Anext[coliter]; - - postsolve_stack.forcingColumnRemovedRow(col, row, rhs, - getRowVector(row)); - removeRow(row); - } - analysis_.logging_on_ = logging_on; - if (logging_on) analysis_.stopPresolveRuleLog(kPresolveRuleForcingCol); - } - return checkLimits(postsolve_stack); - } + // detect strong / weak domination + HPRESOLVE_CHECKED_CALL(detectDominatedCol(postsolve_stack, col, false)); + if (colDeleted[col]) return Result::kOk; if (mipsolver != nullptr) convertImpliedInteger(col, row); @@ -2984,6 +2872,8 @@ HPresolve::Result HPresolve::singletonCol(HighsPostsolveStack& postsolve_stack, !isImpliedIntegral(col)) return Result::kOk; + const bool logging_on = analysis_.logging_on_; + if (logging_on) analysis_.startPresolveRuleLog(kPresolveRuleFreeColSubstitution); // todo, store which side of an implied free dual variable needs to be used @@ -3864,98 +3754,9 @@ HPresolve::Result HPresolve::colPresolve(HighsPostsolveStack& postsolve_stack, break; } - double colDualUpper = - -impliedDualRowBounds.getSumLower(col, -model->col_cost_[col]); - double colDualLower = - -impliedDualRowBounds.getSumUpper(col, -model->col_cost_[col]); - - // check for dominated column - if (colDualLower > options->dual_feasibility_tolerance) { - if (model->col_lower_[col] == -kHighsInf) - return Result::kDualInfeasible; - else { - if (fixColToLowerOrUnbounded(postsolve_stack, col)) { - // Handle unboundedness - presolve_status_ = HighsPresolveStatus::kUnboundedOrInfeasible; - return Result::kDualInfeasible; - } - HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); - } - return checkLimits(postsolve_stack); - } - - if (colDualUpper < -options->dual_feasibility_tolerance) { - if (model->col_upper_[col] == kHighsInf) - return Result::kDualInfeasible; - else { - if (fixColToUpperOrUnbounded(postsolve_stack, col)) { - // Handle unboundedness - presolve_status_ = HighsPresolveStatus::kUnboundedOrInfeasible; - return Result::kDualInfeasible; - } - HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); - } - return checkLimits(postsolve_stack); - } - - // check for weakly dominated column - if (colDualUpper <= options->dual_feasibility_tolerance) { - if (model->col_upper_[col] != kHighsInf) { - if (fixColToUpperOrUnbounded(postsolve_stack, col)) { - // Handle unboundedness - presolve_status_ = HighsPresolveStatus::kUnboundedOrInfeasible; - return Result::kDualInfeasible; - } - HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); - return checkLimits(postsolve_stack); - } else if (impliedDualRowBounds.getSumLowerOrig(col) == 0.0) { - if (logging_on) analysis_.startPresolveRuleLog(kPresolveRuleForcingCol); - postsolve_stack.forcingColumn( - col, getColumnVector(col), model->col_cost_[col], - model->col_lower_[col], true, - model->integrality_[col] == HighsVarType::kInteger); - markColDeleted(col); - HighsInt coliter = colhead[col]; - while (coliter != -1) { - HighsInt row = Arow[coliter]; - double rhs = Avalue[coliter] > 0.0 ? model->row_lower_[row] - : model->row_upper_[row]; - coliter = Anext[coliter]; - postsolve_stack.forcingColumnRemovedRow(col, row, rhs, - getRowVector(row)); - removeRow(row); - } - analysis_.logging_on_ = logging_on; - if (logging_on) analysis_.stopPresolveRuleLog(kPresolveRuleForcingCol); - } - } else if (colDualLower >= -options->dual_feasibility_tolerance) { - // symmetric case for fixing to the lower bound - if (model->col_lower_[col] != -kHighsInf) { - if (fixColToLowerOrUnbounded(postsolve_stack, col)) { - // Handle unboundedness - presolve_status_ = HighsPresolveStatus::kUnboundedOrInfeasible; - return Result::kDualInfeasible; - } - HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); - return checkLimits(postsolve_stack); - } else if (impliedDualRowBounds.getSumUpperOrig(col) == 0.0) { - postsolve_stack.forcingColumn( - col, getColumnVector(col), model->col_cost_[col], - model->col_upper_[col], false, - model->integrality_[col] == HighsVarType::kInteger); - markColDeleted(col); - HighsInt coliter = colhead[col]; - while (coliter != -1) { - HighsInt row = Arow[coliter]; - double rhs = Avalue[coliter] > 0.0 ? model->row_upper_[row] - : model->row_lower_[row]; - coliter = Anext[coliter]; - postsolve_stack.forcingColumnRemovedRow(col, row, rhs, - getRowVector(row)); - removeRow(row); - } - } - } + // detect strong / weak domination + HPRESOLVE_CHECKED_CALL(detectDominatedCol(postsolve_stack, col)); + if (colDeleted[col]) return Result::kOk; // column is not (weakly) dominated @@ -4044,6 +3845,7 @@ HPresolve::Result HPresolve::detectDominatedCol( bool handleSingletonRows) { assert(!colDeleted[col]); + // get bounds on column dual double colDualUpper = -impliedDualRowBounds.getSumLower(col, -model->col_cost_[col]); double colDualLower = @@ -4053,10 +3855,15 @@ HPresolve::Result HPresolve::detectDominatedCol( auto dominatedCol = [&](HighsInt col, double dualBound, double bound, HighsInt direction) { + // column is (strongly) dominated if the bounds on the column dual satisfy: + // 1. lower bound > dual feasibility tolerance (direction = 1) or + // 2. upper bound < -dual feasibility tolerance (direction = -1). if (direction * dualBound <= options->dual_feasibility_tolerance) return Result::kOk; + // cannot fix to +-infinity -> infeasible if (direction * bound == -kHighsInf) return Result::kDualInfeasible; if (logging_on) analysis_.startPresolveRuleLog(kPresolveRuleDominatedCol); + // fix variable bool unbounded = false; if (direction > 0) unbounded = fixColToLowerOrUnbounded(postsolve_stack, col); @@ -4069,6 +3876,7 @@ HPresolve::Result HPresolve::detectDominatedCol( } analysis_.logging_on_ = logging_on; if (logging_on) analysis_.stopPresolveRuleLog(kPresolveRuleDominatedCol); + // handle row singletons (if requested) if (handleSingletonRows) HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); return checkLimits(postsolve_stack); @@ -4076,10 +3884,14 @@ HPresolve::Result HPresolve::detectDominatedCol( auto weaklyDominatedCol = [&](HighsInt col, double dualBound, double bound, double otherBound, HighsInt direction) { + // column is weakly dominated if the bounds on the column dual satisfy: + // 1. lower bound >= -dual feasibility tolerance (direction = 1) or + // 2. upper bound <= dual feasibility tolerance (direction = -1). if (direction * dualBound < -options->dual_feasibility_tolerance) return Result::kOk; if (direction * bound != -kHighsInf) { if (logging_on) analysis_.startPresolveRuleLog(kPresolveRuleDominatedCol); + // fix variable bool unbounded = false; if (direction > 0) unbounded = fixColToLowerOrUnbounded(postsolve_stack, col); @@ -4092,14 +3904,19 @@ HPresolve::Result HPresolve::detectDominatedCol( } analysis_.logging_on_ = logging_on; if (logging_on) analysis_.stopPresolveRuleLog(kPresolveRuleDominatedCol); + // handle row singletons (if requested) + if (handleSingletonRows) + HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); return checkLimits(postsolve_stack); } else if (analysis_.allow_rule_[kPresolveRuleForcingCol]) { + // get bound on dual (column) activity HighsCDouble sum = 0; if (direction > 0) sum = impliedDualRowBounds.getSumUpperOrig(col); else sum = impliedDualRowBounds.getSumLowerOrig(col); if (sum == 0.0) { + // remove column and rows if (logging_on) analysis_.startPresolveRuleLog(kPresolveRuleForcingCol); postsolve_stack.forcingColumn( col, getColumnVector(col), model->col_cost_[col], otherBound, From b19b01d5eee359577f95a640026643718dae43f6 Mon Sep 17 00:00:00 2001 From: fwesselm Date: Fri, 15 Nov 2024 15:59:40 +0100 Subject: [PATCH 36/75] Fix incorrect checks against kHighsInf --- src/presolve/HPresolve.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/presolve/HPresolve.cpp b/src/presolve/HPresolve.cpp index a6f0776410..941960212f 100644 --- a/src/presolve/HPresolve.cpp +++ b/src/presolve/HPresolve.cpp @@ -354,7 +354,7 @@ bool HPresolve::isImpliedInteger(HighsInt col) { if ((model->col_lower_[col] != -kHighsInf && fractionality(model->col_lower_[col]) > options->small_matrix_value) || - (model->col_upper_[col] != -kHighsInf && + (model->col_upper_[col] != kHighsInf && fractionality(model->col_upper_[col]) > options->small_matrix_value)) return false; @@ -2356,7 +2356,7 @@ void HPresolve::scaleStoredRow(HighsInt row, double scale, bool integral) { if (integral) { if (model->row_upper_[row] != kHighsInf) model->row_upper_[row] = std::round(model->row_upper_[row]); - if (model->row_lower_[row] != kHighsInf) + if (model->row_lower_[row] != -kHighsInf) model->row_lower_[row] = std::round(model->row_lower_[row]); } @@ -5685,7 +5685,7 @@ HPresolve::Result HPresolve::detectParallelRowsAndCols( // incorporate the check for the variable types that allow domination. if (objDiff < -options->dual_feasibility_tolerance) { // scaled col is better than duplicate col - if (colUpperInf() && model->col_lower_[duplicateCol] != kHighsInf) + if (colUpperInf() && model->col_lower_[duplicateCol] != -kHighsInf) reductionCase = kDominanceDuplicateColToLower; else if (duplicateColLowerInf() && (colScale < 0 || model->col_upper_[col] != kHighsInf) && From cccb716869d9cbe1fd7a758d505a740312ad050d Mon Sep 17 00:00:00 2001 From: jajhall Date: Fri, 15 Nov 2024 19:06:06 +0000 Subject: [PATCH 37/75] Added unit test for ceil failure --- check/TestHighsIntegers.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/check/TestHighsIntegers.cpp b/check/TestHighsIntegers.cpp index 7c8de3c9b4..5d1aece96b 100644 --- a/check/TestHighsIntegers.cpp +++ b/check/TestHighsIntegers.cpp @@ -1,5 +1,6 @@ #include "HCheckConfig.h" #include "catch.hpp" +#include "util/HighsCDouble.h" #include "util/HighsIntegers.h" const bool dev_run = false; @@ -38,3 +39,18 @@ TEST_CASE("HighsIntegers", "[util]") { if (dev_run) printf("integral scalar is %g\n", integralscalar); } + +TEST_CASE("HighsCdouble-ceil", "[util]") { + // For fix-2041 + HighsCDouble a = 1e-23; + double ceil_a; + double double_a; + ceil_a = double(ceil(a)); + double_a = double(a); + if (dev_run) { + printf("ceil_a = %23.18g\n", ceil_a); + printf("double_a = %23.18g\n", double_a); + } + REQUIRE(ceil_a >= double_a); + REQUIRE(ceil(a) >= a); +} From 258c74cd75c298ab9b02fbcfc0da6fef1402fe10 Mon Sep 17 00:00:00 2001 From: jajhall Date: Fri, 15 Nov 2024 23:10:28 +0000 Subject: [PATCH 38/75] Investigating --- check/TestCheckSolution.cpp | 2 +- check/TestHighsIntegers.cpp | 1 + check/TestMipSolver.cpp | 8 ++++---- src/util/HighsCDouble.h | 9 ++++++++- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/check/TestCheckSolution.cpp b/check/TestCheckSolution.cpp index e24499f3b6..a4bc79ae87 100644 --- a/check/TestCheckSolution.cpp +++ b/check/TestCheckSolution.cpp @@ -1,4 +1,4 @@ -//#include +// #include #include #include "HCheckConfig.h" diff --git a/check/TestHighsIntegers.cpp b/check/TestHighsIntegers.cpp index 5d1aece96b..aff8449ed8 100644 --- a/check/TestHighsIntegers.cpp +++ b/check/TestHighsIntegers.cpp @@ -43,6 +43,7 @@ TEST_CASE("HighsIntegers", "[util]") { TEST_CASE("HighsCdouble-ceil", "[util]") { // For fix-2041 HighsCDouble a = 1e-23; + a = 1e-12; double ceil_a; double double_a; ceil_a = double(ceil(a)); diff --git a/check/TestMipSolver.cpp b/check/TestMipSolver.cpp index cfbe1ffbf2..ecb3502316 100644 --- a/check/TestMipSolver.cpp +++ b/check/TestMipSolver.cpp @@ -679,16 +679,16 @@ TEST_CASE("IP-infeasible-unbounded", "[highs_test_mip_solver]") { highs.run(); HighsModelStatus required_model_status; if (k == 0) { - // Presolve off + // Presolve off if (l == 0) { - // MIP solver proves infeasiblilty + // MIP solver proves infeasiblilty required_model_status = HighsModelStatus::kInfeasible; } else { - // Relaxation is unbounded, but origin is feasible + // Relaxation is unbounded, but origin is feasible required_model_status = HighsModelStatus::kUnbounded; } } else { - // Presolve on, and identifies primal infeasible or unbounded + // Presolve on, and identifies primal infeasible or unbounded required_model_status = HighsModelStatus::kUnboundedOrInfeasible; } REQUIRE(highs.getModelStatus() == required_model_status); diff --git a/src/util/HighsCDouble.h b/src/util/HighsCDouble.h index b02622d162..741809b079 100644 --- a/src/util/HighsCDouble.h +++ b/src/util/HighsCDouble.h @@ -300,7 +300,14 @@ class HighsCDouble { double ceil_x = std::ceil(double(x)); HighsCDouble res; - two_sum(res.hi, res.lo, ceil_x, std::ceil(double(x - ceil_x))); + HighsCDouble x_m_ceil_x = x - ceil_x; + double double_x_m_ceil_x = double(x_m_ceil_x); + double ceil_double_x_m_ceil_x = std::ceil(double_x_m_ceil_x); + HighsCDouble local_res; + + two_sum(local_res.hi, local_res.lo, ceil_x, ceil_double_x_m_ceil_x); + res = local_res; + // two_sum(res.hi, res.lo, ceil_x, std::ceil(double(x - ceil_x))); return res; } From 59d0a865686b6b3db3881038e97a714be8026c14 Mon Sep 17 00:00:00 2001 From: fwesselm Date: Sun, 17 Nov 2024 16:40:43 +0100 Subject: [PATCH 39/75] Simplify a little more --- src/presolve/HPresolve.cpp | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/src/presolve/HPresolve.cpp b/src/presolve/HPresolve.cpp index 941960212f..5d01ed0fb8 100644 --- a/src/presolve/HPresolve.cpp +++ b/src/presolve/HPresolve.cpp @@ -5568,17 +5568,21 @@ HPresolve::Result HPresolve::detectParallelRowsAndCols( // compensating column is integral bool checkColImplBounds = true; bool checkDuplicateColImplBounds = true; + auto isLowerStrictlyImplied = [&](HighsInt col) { + return (model->col_lower_[col] == -kHighsInf || + implColLower[col] > model->col_lower_[col] + primal_feastol); + }; + auto isUpperStrictlyImplied = [&](HighsInt col) { + return (model->col_upper_[col] == kHighsInf || + implColUpper[col] < model->col_upper_[col] - primal_feastol); + }; auto colUpperInf = [&]() { if (!checkColImplBounds) return false; if (mipsolver == nullptr) { // for LP we check strict redundancy of the bounds as otherwise dual // postsolve might fail when the bound is used in the optimal solution - return colScale > 0 ? model->col_upper_[col] == kHighsInf || - implColUpper[col] < - model->col_upper_[col] - primal_feastol - : model->col_lower_[col] == -kHighsInf || - implColLower[col] > - model->col_lower_[col] + primal_feastol; + return colScale > 0 ? isUpperStrictlyImplied(col) + : isLowerStrictlyImplied(col); } else { // for MIP we do not need dual postsolve so the reduction is valid if // the bound is weakly redundant @@ -5589,12 +5593,8 @@ HPresolve::Result HPresolve::detectParallelRowsAndCols( auto colLowerInf = [&]() { if (!checkColImplBounds) return false; if (mipsolver == nullptr) { - return colScale > 0 ? model->col_lower_[col] == -kHighsInf || - implColLower[col] > - model->col_lower_[col] + primal_feastol - : model->col_upper_[col] == kHighsInf || - implColUpper[col] < - model->col_upper_[col] - primal_feastol; + return colScale > 0 ? isLowerStrictlyImplied(col) + : isUpperStrictlyImplied(col); } else { return colScale > 0 ? isLowerImplied(col) : isUpperImplied(col); } @@ -5603,9 +5603,7 @@ HPresolve::Result HPresolve::detectParallelRowsAndCols( auto duplicateColUpperInf = [&]() { if (!checkDuplicateColImplBounds) return false; if (mipsolver == nullptr) { - return model->col_upper_[duplicateCol] == kHighsInf || - implColUpper[duplicateCol] < - model->col_upper_[duplicateCol] - primal_feastol; + return isUpperStrictlyImplied(duplicateCol); } else { return isUpperImplied(duplicateCol); } @@ -5614,9 +5612,7 @@ HPresolve::Result HPresolve::detectParallelRowsAndCols( auto duplicateColLowerInf = [&]() { if (!checkDuplicateColImplBounds) return false; if (mipsolver == nullptr) { - return model->col_lower_[duplicateCol] == -kHighsInf || - implColLower[duplicateCol] > - model->col_lower_[duplicateCol] + primal_feastol; + return isLowerStrictlyImplied(duplicateCol); } else { return isLowerImplied(duplicateCol); } From 98b05a0f896f00014a12abe783d163f382160e88 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Mon, 18 Nov 2024 14:04:11 +0000 Subject: [PATCH 40/75] Treating |x|<1 as a special case in floor(HighsCDouble x) and ceil(HighsCDouble x) --- check/TestHighsIntegers.cpp | 115 ++++++++++++++++++++++++++++++++---- src/util/HighsCDouble.h | 21 ++++--- 2 files changed, 115 insertions(+), 21 deletions(-) diff --git a/check/TestHighsIntegers.cpp b/check/TestHighsIntegers.cpp index aff8449ed8..b56ec69908 100644 --- a/check/TestHighsIntegers.cpp +++ b/check/TestHighsIntegers.cpp @@ -2,6 +2,7 @@ #include "catch.hpp" #include "util/HighsCDouble.h" #include "util/HighsIntegers.h" +#include "util/HighsRandom.h" const bool dev_run = false; @@ -40,18 +41,106 @@ TEST_CASE("HighsIntegers", "[util]") { if (dev_run) printf("integral scalar is %g\n", integralscalar); } -TEST_CASE("HighsCdouble-ceil", "[util]") { - // For fix-2041 - HighsCDouble a = 1e-23; - a = 1e-12; - double ceil_a; - double double_a; - ceil_a = double(ceil(a)); - double_a = double(a); - if (dev_run) { - printf("ceil_a = %23.18g\n", ceil_a); - printf("double_a = %23.18g\n", double_a); +void testCeil(HighsCDouble x) { + double ceil_x; + double double_x; + ceil_x = double(ceil(x)); + double_x = double(x); + REQUIRE(ceil_x >= double_x); + REQUIRE(ceil(x) >= x); +} + +void testFloor(HighsCDouble x) { + double floor_x; + double double_x; + floor_x = double(floor(x)); + double_x = double(x); + REQUIRE(floor_x <= double_x); + REQUIRE(floor(x) <= x); +} +TEST_CASE("HighsCDouble-ceil", "[util]") { + HighsCDouble x; + x = -1e-34; + testCeil(x); + x = -1e-32; + testCeil(x); + x = -1e-30; + testCeil(x); + x = -1e-23; + testCeil(x); + x = -1e-12; + testCeil(x); + x = -1e-1; + testCeil(x); + x = -0.99; + testCeil(x); + + x = 0.99; + testCeil(x); + x = 1e-1; + testCeil(x); + x = 1e-12; + testCeil(x); + // This and rest failed in #2041 + x = 1e-23; + testCeil(x); + x = 1e-30; + testCeil(x); + x = 1e-32; + testCeil(x); + x = 1e-34; + testCeil(x); + + HighsRandom rand; + for (HighsInt k = 0; k < 1000; k++) { + double man = rand.fraction(); + HighsInt power = 2 - rand.integer(5); + double exp = std::pow(10, power); + x = man * exp; + testCeil(x); + } +} + +TEST_CASE("HighsCDouble-floor", "[util]") { + HighsCDouble x; + + x = 1e-34; + testFloor(x); + x = 1e-32; + testFloor(x); + x = 1e-30; + testFloor(x); + x = 1e-23; + testFloor(x); + x = 1e-12; + testFloor(x); + x = 1e-1; + testFloor(x); + x = 0.99; + testFloor(x); + + x = -0.99; + testFloor(x); + x = -1e-1; + testFloor(x); + x = -1e-12; + testFloor(x); + // This and rest failed in #2041 + x = -1e-23; + testFloor(x); + x = -1e-30; + testFloor(x); + x = -1e-32; + testFloor(x); + x = -1e-34; + testFloor(x); + + HighsRandom rand; + for (HighsInt k = 0; k < 1000; k++) { + double man = rand.fraction(); + HighsInt power = 2 - rand.integer(5); + double exp = std::pow(10, power); + x = man * exp; + testFloor(x); } - REQUIRE(ceil_a >= double_a); - REQUIRE(ceil(a) >= a); } diff --git a/src/util/HighsCDouble.h b/src/util/HighsCDouble.h index 741809b079..773c710495 100644 --- a/src/util/HighsCDouble.h +++ b/src/util/HighsCDouble.h @@ -289,6 +289,12 @@ class HighsCDouble { } friend HighsCDouble floor(const HighsCDouble& x) { + // Treat |x| < 1 as special case, as per (for example) + // https://github.com/shibatch/tlfloat: see #2041 + if (abs(x) < 1) { + if (x == 0 || x > 0) return HighsCDouble(0.0); + return HighsCDouble(-1.0); + } double floor_x = std::floor(double(x)); HighsCDouble res; @@ -297,17 +303,16 @@ class HighsCDouble { } friend HighsCDouble ceil(const HighsCDouble& x) { + // Treat |x| < 1 as special case, as per (for example) + // https://github.com/shibatch/tlfloat: see #2041 + if (abs(x) < 1) { + if (x == 0 || x < 0) return HighsCDouble(0.0); + return HighsCDouble(1.0); + } double ceil_x = std::ceil(double(x)); HighsCDouble res; - HighsCDouble x_m_ceil_x = x - ceil_x; - double double_x_m_ceil_x = double(x_m_ceil_x); - double ceil_double_x_m_ceil_x = std::ceil(double_x_m_ceil_x); - HighsCDouble local_res; - - two_sum(local_res.hi, local_res.lo, ceil_x, ceil_double_x_m_ceil_x); - res = local_res; - // two_sum(res.hi, res.lo, ceil_x, std::ceil(double(x - ceil_x))); + two_sum(res.hi, res.lo, ceil_x, std::ceil(double(x - ceil_x))); return res; } From 5ccc2dc40b1c41eb26213b0a277ac196fa118e43 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Mon, 18 Nov 2024 14:24:48 +0000 Subject: [PATCH 41/75] PDLP gets 5.5018458957e+03 not 5.50184588e+03 on windows_debug64 --- check/CMakeLists.txt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/check/CMakeLists.txt b/check/CMakeLists.txt index 051b09623a..f8bb62d133 100644 --- a/check/CMakeLists.txt +++ b/check/CMakeLists.txt @@ -257,9 +257,10 @@ if ((NOT FAST_BUILD OR ALL_TESTS) AND NOT (BUILD_EXTRA_UNIT_ONLY)) "standgub\; 1.25769949\;" ) elseif(WIN32) - # on windows e226 model status is unknown, rel gap e00 + # on windows e226 model status is unknown + # on windows 25fv47 model status can be unknown, with objective 5.5018458957e+03 set(pdlpInstances - "25fv47\; 5.50184588\;" + "25fv47\; 5.5018458\;" "adlittle\; 2.25494963\;" "afiro\;-4.64753142\;" "avgas\;-7.749999999\;" @@ -455,4 +456,4 @@ if (BUILD_EXTRA_UNIT_TESTS AND BUILD_EXTRA_UNIT_ONLY) DEPENDS unit-test-extra-build) set_tests_properties(unit_tests_extra PROPERTIES TIMEOUT 10000) -endif() \ No newline at end of file +endif() From d4a4a305c96cc1d366fa233e5e109adea471202a Mon Sep 17 00:00:00 2001 From: Julian Hall Date: Tue, 19 Nov 2024 16:07:06 +0000 Subject: [PATCH 42/75] Created writeLocalModel unit test, assessHessianDimensions, and fixed bug in lpDimensionsOk --- check/TestFilereader.cpp | 43 +++++++++++++++++++++++++++++++++ src/lp_data/Highs.cpp | 11 ++++++++- src/lp_data/HighsLpUtils.cpp | 2 +- src/model/HighsHessianUtils.cpp | 21 ++++++++++------ 4 files changed, 67 insertions(+), 10 deletions(-) diff --git a/check/TestFilereader.cpp b/check/TestFilereader.cpp index 915b73a25c..551718ab79 100644 --- a/check/TestFilereader.cpp +++ b/check/TestFilereader.cpp @@ -11,6 +11,7 @@ #include "lp_data/HighsLpUtils.h" const bool dev_run = false; +const double inf = kHighsInf; TEST_CASE("filereader-edge-cases", "[highs_filereader]") { std::string model = ""; @@ -357,3 +358,45 @@ TEST_CASE("filereader-dD2e", "[highs_filereader]") { // double objective_value = highs.getInfo().objective_function_value; // REQUIRE(objective_value == optimal_objective_value); // } + +TEST_CASE("writeLocalModel", "[highs_filereader]") { + Highs h; + // h.setOptionValue("output_flag", dev_run); + std::string write_model_file = "foo.mps"; + HighsModel model; + HighsLp& lp = model.lp_;; + lp.num_col_ = 2; + lp.num_row_ = 3; + lp.col_cost_ = {8, 10}; + lp.row_lower_ = {7, 12, 6}; + lp.row_upper_ = {inf, inf, inf}; + lp.a_matrix_.start_ = {0, 3, 6}; + lp.a_matrix_.index_ = {0, 1, 2, 0, 1, 2}; + lp.a_matrix_.value_ = {2, 3, 2, 2, 4, 1}; + + // if (dev_run) + printf("\nModel with no column lower or upper bounds\n"); + REQUIRE(h.writeLocalModel(model, write_model_file) == HighsStatus::kError); + lp.col_lower_ = {0, 0}; + + // if (dev_run) + printf("\nModel with no column upper bounds\n"); + REQUIRE(h.writeLocalModel(model, write_model_file) == HighsStatus::kError); + lp.col_upper_ = {inf, inf}; + + // Model has no dimensions for a_matrix_, but these are set in + // writeLocalModel. + printf("\nModel with no column or row names\n"); + REQUIRE(h.writeLocalModel(model, write_model_file) == HighsStatus::kWarning); + lp.col_names_ = {"C0", "C1"}; + lp.row_names_ = {"R0", "R1", "R2"}; + + printf("\nModel with column and row names\n"); + REQUIRE(h.writeLocalModel(model, write_model_file) == HighsStatus::kOk); + + // Introduce illegal index + lp.a_matrix_.index_ = {0, -1, 2, 0, 1, 2}; + REQUIRE(h.writeLocalModel(model, write_model_file) == HighsStatus::kError); + + std::remove(write_model_file.c_str()); +} diff --git a/src/lp_data/Highs.cpp b/src/lp_data/Highs.cpp index c1c32546a6..dd5049b876 100644 --- a/src/lp_data/Highs.cpp +++ b/src/lp_data/Highs.cpp @@ -706,8 +706,17 @@ HighsStatus Highs::writeLocalModel(HighsModel& model, const std::string& filename) { HighsStatus return_status = HighsStatus::kOk; + HighsLp& lp = model.lp_; + // Dimensions in a_matrix_ may not be set, so take them from lp + lp.setMatrixDimensions(); // Ensure that the LP is column-wise - model.lp_.ensureColwise(); + lp.ensureColwise(); + // Ensure that the dimensions are OK + if (!lpDimensionsOk("writeLocalModel", lp, options_.log_options)) return HighsStatus::kError; + if (model.hessian_.dim_ > 0) { + HighsStatus call_status = assessHessianDimensions(options_, model.hessian_); + if (call_status == HighsStatus::kError) return call_status; + } // Check for repeated column or row names that would corrupt the file if (model.lp_.col_hash_.hasDuplicate(model.lp_.col_names_)) { highsLogUser(options_.log_options, HighsLogType::kError, diff --git a/src/lp_data/HighsLpUtils.cpp b/src/lp_data/HighsLpUtils.cpp index 89b31e04aa..af8c39c822 100644 --- a/src/lp_data/HighsLpUtils.cpp +++ b/src/lp_data/HighsLpUtils.cpp @@ -130,7 +130,7 @@ bool lpDimensionsOk(std::string message, const HighsLp& lp, HighsInt col_upper_size = lp.col_upper_.size(); bool legal_col_cost_size = col_cost_size >= num_col; bool legal_col_lower_size = col_lower_size >= num_col; - bool legal_col_upper_size = col_lower_size >= num_col; + bool legal_col_upper_size = col_upper_size >= num_col; if (!legal_col_cost_size) highsLogUser(log_options, HighsLogType::kError, "LP dimension validation (%s) fails on col_cost.size() = %d < " diff --git a/src/model/HighsHessianUtils.cpp b/src/model/HighsHessianUtils.cpp index 6e809cc759..aad2a8c900 100644 --- a/src/model/HighsHessianUtils.cpp +++ b/src/model/HighsHessianUtils.cpp @@ -28,14 +28,8 @@ HighsStatus assessHessian(HighsHessian& hessian, const HighsOptions& options) { HighsStatus return_status = HighsStatus::kOk; HighsStatus call_status; - // Assess the Hessian dimensions and vector sizes, returning on error - vector hessian_p_end; - const bool partitioned = false; - call_status = assessMatrixDimensions( - options.log_options, hessian.dim_, partitioned, hessian.start_, - hessian_p_end, hessian.index_, hessian.value_); - return_status = interpretCallStatus(options.log_options, call_status, - return_status, "assessMatrixDimensions"); + return_status = interpretCallStatus(options.log_options, assessHessianDimensions(options, hessian), + return_status, "assessHessianDimensions"); if (return_status == HighsStatus::kError) return return_status; // If the Hessian has no columns there is nothing left to test @@ -110,6 +104,17 @@ HighsStatus assessHessian(HighsHessian& hessian, const HighsOptions& options) { return return_status; } +HighsStatus assessHessianDimensions(const HighsOptions& options, + HighsHessian& hessian) { + if (hessian.dim_ == 0) return HighsStatus::kOk; + + // Assess the Hessian dimensions and vector sizes + vector hessian_p_end; + const bool partitioned = false; + return assessMatrixDimensions(options.log_options, hessian.dim_, partitioned, hessian.start_, + hessian_p_end, hessian.index_, hessian.value_); +} + void completeHessianDiagonal(const HighsOptions& options, HighsHessian& hessian) { // Count the number of missing diagonal entries From f94a2c7dfe21bf01878051ff7f854b975101587e Mon Sep 17 00:00:00 2001 From: Julian Hall Date: Tue, 19 Nov 2024 16:43:41 +0000 Subject: [PATCH 43/75] Added tests for starts and indices to HighsSparseMatrix, and using them in writeLocalModel --- check/TestFilereader.cpp | 36 ++++++++++++++------ src/lp_data/Highs.cpp | 18 ++++++++-- src/model/HighsHessianUtils.cpp | 8 +++-- src/util/HighsMatrixUtils.cpp | 2 +- src/util/HighsSparseMatrix.cpp | 59 +++++++++++++++++++++++++++++++++ src/util/HighsSparseMatrix.h | 3 ++ 6 files changed, 110 insertions(+), 16 deletions(-) diff --git a/check/TestFilereader.cpp b/check/TestFilereader.cpp index 551718ab79..63fe50e8a2 100644 --- a/check/TestFilereader.cpp +++ b/check/TestFilereader.cpp @@ -361,10 +361,11 @@ TEST_CASE("filereader-dD2e", "[highs_filereader]") { TEST_CASE("writeLocalModel", "[highs_filereader]") { Highs h; - // h.setOptionValue("output_flag", dev_run); + h.setOptionValue("output_flag", dev_run); std::string write_model_file = "foo.mps"; HighsModel model; - HighsLp& lp = model.lp_;; + HighsLp& lp = model.lp_; + ; lp.num_col_ = 2; lp.num_row_ = 3; lp.col_cost_ = {8, 10}; @@ -374,29 +375,44 @@ TEST_CASE("writeLocalModel", "[highs_filereader]") { lp.a_matrix_.index_ = {0, 1, 2, 0, 1, 2}; lp.a_matrix_.value_ = {2, 3, 2, 2, 4, 1}; - // if (dev_run) - printf("\nModel with no column lower or upper bounds\n"); + if (dev_run) printf("\nModel with no column lower or upper bounds\n"); REQUIRE(h.writeLocalModel(model, write_model_file) == HighsStatus::kError); lp.col_lower_ = {0, 0}; - // if (dev_run) - printf("\nModel with no column upper bounds\n"); + if (dev_run) printf("\nModel with no column upper bounds\n"); REQUIRE(h.writeLocalModel(model, write_model_file) == HighsStatus::kError); lp.col_upper_ = {inf, inf}; // Model has no dimensions for a_matrix_, but these are set in - // writeLocalModel. - printf("\nModel with no column or row names\n"); + // writeLocalModel. + if (dev_run) printf("\nModel with no column or row names\n"); REQUIRE(h.writeLocalModel(model, write_model_file) == HighsStatus::kWarning); lp.col_names_ = {"C0", "C1"}; lp.row_names_ = {"R0", "R1", "R2"}; - printf("\nModel with column and row names\n"); + if (dev_run) printf("\nModel with column and row names\n"); REQUIRE(h.writeLocalModel(model, write_model_file) == HighsStatus::kOk); + // Introduce illegal start + if (dev_run) printf("\nModel with start entry > num_nz\n"); + lp.a_matrix_.start_ = {0, 7, 6}; + REQUIRE(h.writeLocalModel(model, write_model_file) == HighsStatus::kError); + + // Introduce illegal start + if (dev_run) printf("\nModel with start entry -1\n"); + lp.a_matrix_.start_ = {0, -1, 6}; + REQUIRE(h.writeLocalModel(model, write_model_file) == HighsStatus::kError); + lp.a_matrix_.start_ = {0, 3, 6}; + // Introduce illegal index + if (dev_run) printf("\nModel with index entry -1\n"); lp.a_matrix_.index_ = {0, -1, 2, 0, 1, 2}; REQUIRE(h.writeLocalModel(model, write_model_file) == HighsStatus::kError); - + + // Introduce illegal index + if (dev_run) printf("\nModel with index entry 3 >= num_row\n"); + lp.a_matrix_.index_ = {0, 1, 3, 0, 1, 2}; + REQUIRE(h.writeLocalModel(model, write_model_file) == HighsStatus::kError); + std::remove(write_model_file.c_str()); } diff --git a/src/lp_data/Highs.cpp b/src/lp_data/Highs.cpp index dd5049b876..67c6d44e33 100644 --- a/src/lp_data/Highs.cpp +++ b/src/lp_data/Highs.cpp @@ -705,18 +705,32 @@ HighsStatus Highs::writePresolvedModel(const std::string& filename) { HighsStatus Highs::writeLocalModel(HighsModel& model, const std::string& filename) { HighsStatus return_status = HighsStatus::kOk; + HighsStatus call_status; HighsLp& lp = model.lp_; // Dimensions in a_matrix_ may not be set, so take them from lp lp.setMatrixDimensions(); + // Ensure that the LP is column-wise lp.ensureColwise(); + // Ensure that the dimensions are OK - if (!lpDimensionsOk("writeLocalModel", lp, options_.log_options)) return HighsStatus::kError; + if (!lpDimensionsOk("writeLocalModel", lp, options_.log_options)) + return HighsStatus::kError; + if (model.hessian_.dim_ > 0) { - HighsStatus call_status = assessHessianDimensions(options_, model.hessian_); + call_status = assessHessianDimensions(options_, model.hessian_); if (call_status == HighsStatus::kError) return call_status; } + + // Check that the matrix starts are OK + call_status = lp.a_matrix_.assessStart(options_.log_options); + if (call_status == HighsStatus::kError) return call_status; + + // Check that the matrix indices are within bounds + call_status = lp.a_matrix_.assessIndexBounds(options_.log_options); + if (call_status == HighsStatus::kError) return call_status; + // Check for repeated column or row names that would corrupt the file if (model.lp_.col_hash_.hasDuplicate(model.lp_.col_names_)) { highsLogUser(options_.log_options, HighsLogType::kError, diff --git a/src/model/HighsHessianUtils.cpp b/src/model/HighsHessianUtils.cpp index aad2a8c900..56a68f1543 100644 --- a/src/model/HighsHessianUtils.cpp +++ b/src/model/HighsHessianUtils.cpp @@ -28,7 +28,8 @@ HighsStatus assessHessian(HighsHessian& hessian, const HighsOptions& options) { HighsStatus return_status = HighsStatus::kOk; HighsStatus call_status; - return_status = interpretCallStatus(options.log_options, assessHessianDimensions(options, hessian), + return_status = interpretCallStatus(options.log_options, + assessHessianDimensions(options, hessian), return_status, "assessHessianDimensions"); if (return_status == HighsStatus::kError) return return_status; @@ -111,8 +112,9 @@ HighsStatus assessHessianDimensions(const HighsOptions& options, // Assess the Hessian dimensions and vector sizes vector hessian_p_end; const bool partitioned = false; - return assessMatrixDimensions(options.log_options, hessian.dim_, partitioned, hessian.start_, - hessian_p_end, hessian.index_, hessian.value_); + return assessMatrixDimensions(options.log_options, hessian.dim_, partitioned, + hessian.start_, hessian_p_end, hessian.index_, + hessian.value_); } void completeHessianDiagonal(const HighsOptions& options, diff --git a/src/util/HighsMatrixUtils.cpp b/src/util/HighsMatrixUtils.cpp index b55f4df79a..7b70695065 100644 --- a/src/util/HighsMatrixUtils.cpp +++ b/src/util/HighsMatrixUtils.cpp @@ -68,7 +68,7 @@ HighsStatus assessMatrix( // // Check whether the first start is zero if (matrix_start[0]) { - highsLogUser(log_options, HighsLogType::kWarning, + highsLogUser(log_options, HighsLogType::kError, "%s matrix start vector begins with %" HIGHSINT_FORMAT " rather than 0\n", matrix_name.c_str(), matrix_start[0]); diff --git a/src/util/HighsSparseMatrix.cpp b/src/util/HighsSparseMatrix.cpp index a493d2e7ad..d063f0468e 100644 --- a/src/util/HighsSparseMatrix.cpp +++ b/src/util/HighsSparseMatrix.cpp @@ -722,6 +722,65 @@ void HighsSparseMatrix::deleteRows( this->num_row_ = new_num_row; } +HighsStatus HighsSparseMatrix::assessStart(const HighsLogOptions& log_options) { + // Identify main dimensions + HighsInt vec_dim; + HighsInt num_vec; + if (this->isColwise()) { + vec_dim = this->num_row_; + num_vec = this->num_col_; + } else { + vec_dim = this->num_col_; + num_vec = this->num_row_; + } + if (this->start_[0]) { + highsLogUser(log_options, HighsLogType::kError, + "Matrix start[0] = %d, not 0\n", int(this->start_[0])); + return HighsStatus::kError; + } + HighsInt num_nz = this->numNz(); + for (HighsInt iVec = 1; iVec < num_vec; iVec++) { + if (this->start_[iVec] < this->start_[iVec - 1]) { + highsLogUser(log_options, HighsLogType::kError, + "Matrix start[%d] = %d > %d = start[%d]\n", int(iVec), + int(this->start_[iVec]), int(this->start_[iVec - 1]), + int(iVec - 1)); + return HighsStatus::kError; + } + if (this->start_[iVec] > num_nz) { + highsLogUser(log_options, HighsLogType::kError, + "Matrix start[%d] = %d > %d = number of nonzeros\n", + int(iVec), int(this->start_[iVec]), int(num_nz)); + return HighsStatus::kError; + } + } + return HighsStatus::kOk; +} + +HighsStatus HighsSparseMatrix::assessIndexBounds( + const HighsLogOptions& log_options) { + // Identify main dimensions + HighsInt vec_dim; + HighsInt num_vec; + if (this->isColwise()) { + vec_dim = this->num_row_; + // num_vec = this->num_col_; + } else { + vec_dim = this->num_col_; + // num_vec = this->num_row_; + } + HighsInt num_nz = this->numNz(); + for (HighsInt iEl = 1; iEl < num_nz; iEl++) { + if (this->index_[iEl] < 0 || this->index_[iEl] >= vec_dim) { + highsLogUser(log_options, HighsLogType::kError, + "Matrix index[%d] = %d is not in legal range of [0, %d)\n", + int(iEl), int(this->index_[iEl]), vec_dim); + return HighsStatus::kError; + } + } + return HighsStatus::kOk; +} + HighsStatus HighsSparseMatrix::assess(const HighsLogOptions& log_options, const std::string matrix_name, const double small_matrix_value, diff --git a/src/util/HighsSparseMatrix.h b/src/util/HighsSparseMatrix.h index 7333a37e64..9694183191 100644 --- a/src/util/HighsSparseMatrix.h +++ b/src/util/HighsSparseMatrix.h @@ -66,6 +66,9 @@ class HighsSparseMatrix { void deleteRows(const HighsIndexCollection& index_collection); HighsStatus assessDimensions(const HighsLogOptions& log_options, const std::string matrix_name); + HighsStatus assessStart(const HighsLogOptions& log_options); + HighsStatus assessIndexBounds(const HighsLogOptions& log_options); + HighsStatus assess(const HighsLogOptions& log_options, const std::string matrix_name, const double small_matrix_value, From b3d1b61362ac014533ddc1c91745e53adeb2e30c Mon Sep 17 00:00:00 2001 From: JAJHall Date: Wed, 20 Nov 2024 11:36:24 +0000 Subject: [PATCH 44/75] Added HighsLinearObjective struct and vector of such as member of Highs class; added blend_multi_objectives option --- src/Highs.h | 3 ++- src/lp_data/HStruct.h | 10 ++++++++++ src/lp_data/Highs.cpp | 1 + src/lp_data/HighsInterface.cpp | 9 +++++++++ src/lp_data/HighsOptions.h | 9 +++++++++ 5 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/Highs.h b/src/Highs.h index 34d1044f5f..c7711a896b 100644 --- a/src/Highs.h +++ b/src/Highs.h @@ -1389,6 +1389,8 @@ class Highs { ICrashInfo icrash_info_; HighsModel model_; + std::vector multi_linear_objective_; + HighsModel presolved_model_; HighsTimer timer_; @@ -1397,7 +1399,6 @@ class Highs { HighsInfo info_; HighsRanging ranging_; HighsIis iis_; - std::vector saved_objective_and_solution_; HighsPresolveStatus model_presolve_status_ = diff --git a/src/lp_data/HStruct.h b/src/lp_data/HStruct.h index e54657d406..3963d9df1c 100644 --- a/src/lp_data/HStruct.h +++ b/src/lp_data/HStruct.h @@ -144,4 +144,14 @@ struct HighsIllConditioning { void clear(); }; +struct HighsLinearObjective { + double weight; + double offset; + std::vector coefficients; + double abs_tolerance; + double rel_tolerance; + HighsInt priority; + void clear(); +}; + #endif /* LP_DATA_HSTRUCT_H_ */ diff --git a/src/lp_data/Highs.cpp b/src/lp_data/Highs.cpp index 67c6d44e33..3e340907d9 100644 --- a/src/lp_data/Highs.cpp +++ b/src/lp_data/Highs.cpp @@ -59,6 +59,7 @@ HighsStatus Highs::clear() { HighsStatus Highs::clearModel() { model_.clear(); + multi_linear_objective_.clear(); return clearSolver(); } diff --git a/src/lp_data/HighsInterface.cpp b/src/lp_data/HighsInterface.cpp index a40813d1fe..0934129e62 100644 --- a/src/lp_data/HighsInterface.cpp +++ b/src/lp_data/HighsInterface.cpp @@ -3584,3 +3584,12 @@ bool Highs::infeasibleBoundsOk() { int(num_true_infeasible_bound)); return num_true_infeasible_bound == 0; } + +void HighsLinearObjective::clear() { + this->weight = 0.0; + this->offset = 0.0; + this->coefficients.clear(); + this->abs_tolerance = 0.0; + this->rel_tolerance = 0.0; + this->priority = 0; +} diff --git a/src/lp_data/HighsOptions.h b/src/lp_data/HighsOptions.h index cfb395a3fc..38df4e7617 100644 --- a/src/lp_data/HighsOptions.h +++ b/src/lp_data/HighsOptions.h @@ -348,6 +348,9 @@ struct HighsOptionsStruct { // Options for IIS calculation HighsInt iis_strategy; + // Option for multi-objective optimization + bool blend_multi_objectives; + // Advanced options HighsInt log_dev_level; bool log_githash; @@ -1120,6 +1123,12 @@ class HighsOptions : public HighsOptionsStruct { kIisStrategyMax); records.push_back(record_int); + record_bool = new OptionRecordBool( + "blend_multi_objectives", + "Blend multiple objectives or apply lexicographically: Default = true", advanced, + &blend_multi_objectives, true); + records.push_back(record_bool); + // Fix the number of user settable options num_user_settable_options_ = static_cast(records.size()); From 4fa6d2f5eb5a6a44ee6519863ddf8cca9db9a3bc Mon Sep 17 00:00:00 2001 From: JAJHall Date: Wed, 20 Nov 2024 11:40:42 +0000 Subject: [PATCH 45/75] Renamed Highs::run() to Highs::solve(), and created Highs::run() that currently just returns Highs::solve() --- src/Highs.h | 7 ++++++- src/lp_data/Highs.cpp | 6 +++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Highs.h b/src/Highs.h index c7711a896b..4a21110273 100644 --- a/src/Highs.h +++ b/src/Highs.h @@ -183,10 +183,15 @@ class Highs { HighsStatus presolve(); /** - * @brief Solve the incumbent model according to the specified options + * @brief Run the solver, accounting for any multiple objective */ HighsStatus run(); + /** + * @brief Solve the incumbent model according to the specified options + */ + HighsStatus solve(); + /** * @brief Postsolve the incumbent model using a solution */ diff --git a/src/lp_data/Highs.cpp b/src/lp_data/Highs.cpp index 3e340907d9..e147eb85c4 100644 --- a/src/lp_data/Highs.cpp +++ b/src/lp_data/Highs.cpp @@ -877,9 +877,13 @@ HighsStatus Highs::presolve() { return returnFromHighs(return_status); } +HighsStatus Highs::run() { + return this->solve(); +} + // Checks the options calls presolve and postsolve if needed. Solvers are called // with callSolveLp(..) -HighsStatus Highs::run() { +HighsStatus Highs::solve() { HighsInt min_highs_debug_level = kHighsDebugLevelMin; // kHighsDebugLevelCostly; // kHighsDebugLevelMax; From 615663c85acf37f7bad21eaa1e9e5278598e6abe Mon Sep 17 00:00:00 2001 From: JAJHall Date: Wed, 20 Nov 2024 11:41:41 +0000 Subject: [PATCH 46/75] Formatted --- src/lp_data/Highs.cpp | 4 +--- src/lp_data/HighsOptions.h | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/lp_data/Highs.cpp b/src/lp_data/Highs.cpp index e147eb85c4..b7c3881a0e 100644 --- a/src/lp_data/Highs.cpp +++ b/src/lp_data/Highs.cpp @@ -877,9 +877,7 @@ HighsStatus Highs::presolve() { return returnFromHighs(return_status); } -HighsStatus Highs::run() { - return this->solve(); -} +HighsStatus Highs::run() { return this->solve(); } // Checks the options calls presolve and postsolve if needed. Solvers are called // with callSolveLp(..) diff --git a/src/lp_data/HighsOptions.h b/src/lp_data/HighsOptions.h index 38df4e7617..abbf7624ae 100644 --- a/src/lp_data/HighsOptions.h +++ b/src/lp_data/HighsOptions.h @@ -1125,8 +1125,8 @@ class HighsOptions : public HighsOptionsStruct { record_bool = new OptionRecordBool( "blend_multi_objectives", - "Blend multiple objectives or apply lexicographically: Default = true", advanced, - &blend_multi_objectives, true); + "Blend multiple objectives or apply lexicographically: Default = true", + advanced, &blend_multi_objectives, true); records.push_back(record_bool); // Fix the number of user settable options From e063490148e31e9ed906dc3b8af7f5a0bb01b197 Mon Sep 17 00:00:00 2001 From: Julian Hall Date: Wed, 20 Nov 2024 17:34:00 +0000 Subject: [PATCH 47/75] Created Highs::passLinearObjectives Highs::addLinearObjective Highs::clearLinearObjectives, and unit test in TestMultipleObjective.cpp --- check/CMakeLists.txt | 2 + check/TestHighsIntegers.cpp | 108 +----------------------------------- src/Highs.h | 24 ++++++-- src/lp_data/Highs.cpp | 59 +++++++++++++++++++- 4 files changed, 81 insertions(+), 112 deletions(-) diff --git a/check/CMakeLists.txt b/check/CMakeLists.txt index f8bb62d133..700b5eee36 100644 --- a/check/CMakeLists.txt +++ b/check/CMakeLists.txt @@ -48,6 +48,7 @@ if ((NOT FAST_BUILD OR ALL_TESTS) AND NOT (BUILD_EXTRA_UNIT_ONLY)) TestBasis.cpp TestBasisSolves.cpp TestCrossover.cpp + TestHighsCDouble.cpp TestHighsHash.cpp TestHighsIntegers.cpp TestHighsParallel.cpp @@ -66,6 +67,7 @@ if ((NOT FAST_BUILD OR ALL_TESTS) AND NOT (BUILD_EXTRA_UNIT_ONLY)) TestLpModification.cpp TestLpOrientation.cpp TestModelProperties.cpp + TestMultiObjective.cpp TestPdlp.cpp TestPresolve.cpp TestQpSolver.cpp diff --git a/check/TestHighsIntegers.cpp b/check/TestHighsIntegers.cpp index b56ec69908..c08b288610 100644 --- a/check/TestHighsIntegers.cpp +++ b/check/TestHighsIntegers.cpp @@ -1,8 +1,8 @@ #include "HCheckConfig.h" #include "catch.hpp" -#include "util/HighsCDouble.h" +// #include "util/HighsCDouble.h" #include "util/HighsIntegers.h" -#include "util/HighsRandom.h" +// #include "util/HighsRandom.h" const bool dev_run = false; @@ -40,107 +40,3 @@ TEST_CASE("HighsIntegers", "[util]") { if (dev_run) printf("integral scalar is %g\n", integralscalar); } - -void testCeil(HighsCDouble x) { - double ceil_x; - double double_x; - ceil_x = double(ceil(x)); - double_x = double(x); - REQUIRE(ceil_x >= double_x); - REQUIRE(ceil(x) >= x); -} - -void testFloor(HighsCDouble x) { - double floor_x; - double double_x; - floor_x = double(floor(x)); - double_x = double(x); - REQUIRE(floor_x <= double_x); - REQUIRE(floor(x) <= x); -} -TEST_CASE("HighsCDouble-ceil", "[util]") { - HighsCDouble x; - x = -1e-34; - testCeil(x); - x = -1e-32; - testCeil(x); - x = -1e-30; - testCeil(x); - x = -1e-23; - testCeil(x); - x = -1e-12; - testCeil(x); - x = -1e-1; - testCeil(x); - x = -0.99; - testCeil(x); - - x = 0.99; - testCeil(x); - x = 1e-1; - testCeil(x); - x = 1e-12; - testCeil(x); - // This and rest failed in #2041 - x = 1e-23; - testCeil(x); - x = 1e-30; - testCeil(x); - x = 1e-32; - testCeil(x); - x = 1e-34; - testCeil(x); - - HighsRandom rand; - for (HighsInt k = 0; k < 1000; k++) { - double man = rand.fraction(); - HighsInt power = 2 - rand.integer(5); - double exp = std::pow(10, power); - x = man * exp; - testCeil(x); - } -} - -TEST_CASE("HighsCDouble-floor", "[util]") { - HighsCDouble x; - - x = 1e-34; - testFloor(x); - x = 1e-32; - testFloor(x); - x = 1e-30; - testFloor(x); - x = 1e-23; - testFloor(x); - x = 1e-12; - testFloor(x); - x = 1e-1; - testFloor(x); - x = 0.99; - testFloor(x); - - x = -0.99; - testFloor(x); - x = -1e-1; - testFloor(x); - x = -1e-12; - testFloor(x); - // This and rest failed in #2041 - x = -1e-23; - testFloor(x); - x = -1e-30; - testFloor(x); - x = -1e-32; - testFloor(x); - x = -1e-34; - testFloor(x); - - HighsRandom rand; - for (HighsInt k = 0; k < 1000; k++) { - double man = rand.fraction(); - HighsInt power = 2 - rand.integer(5); - double exp = std::pow(10, power); - x = man * exp; - testFloor(x); - } -} diff --git a/src/Highs.h b/src/Highs.h index 4a21110273..12fb05b3f0 100644 --- a/src/Highs.h +++ b/src/Highs.h @@ -150,6 +150,23 @@ class Highs { HighsStatus passHessian(const HighsInt dim, const HighsInt num_nz, const HighsInt format, const HighsInt* start, const HighsInt* index, const double* value); + /** + * @brief Pass multiple linear objectives for the incumbent model + */ + HighsStatus passLinearObjectives( + const HighsInt num_linear_objective, + const HighsLinearObjective* linear_objective); + + /** + * @brief Add a linear objective for the incumbent model + */ + HighsStatus addLinearObjective(const HighsLinearObjective& linear_objective); + + /** + * @brief Clear the multiple linear objective data + */ + HighsStatus clearLinearObjectives(); + /** * @brief Pass a column name to the incumbent model */ @@ -187,11 +204,6 @@ class Highs { */ HighsStatus run(); - /** - * @brief Solve the incumbent model according to the specified options - */ - HighsStatus solve(); - /** * @brief Postsolve the incumbent model using a solution */ @@ -1430,6 +1442,8 @@ class Highs { bool written_log_header = false; + HighsStatus solve(); + void exactResizeModel() { this->model_.lp_.exactResize(); this->model_.hessian_.exactResize(); diff --git a/src/lp_data/Highs.cpp b/src/lp_data/Highs.cpp index b7c3881a0e..cd4f4b0e59 100644 --- a/src/lp_data/Highs.cpp +++ b/src/lp_data/Highs.cpp @@ -578,6 +578,39 @@ HighsStatus Highs::passHessian(const HighsInt dim, const HighsInt num_nz, return passHessian(hessian); } +HighsStatus Highs::passLinearObjectives( + const HighsInt num_linear_objective, + const HighsLinearObjective* linear_objective) { + if (num_linear_objective < 0) return HighsStatus::kOk; + this->multi_linear_objective_.clear(); + for (HighsInt iObj = 0; iObj < num_linear_objective; iObj++) + if (this->addLinearObjective(linear_objective[iObj]) != HighsStatus::kOk) + return HighsStatus::kError; + ; + return HighsStatus::kOk; +} + +HighsStatus Highs::addLinearObjective( + const HighsLinearObjective& linear_objective) { + HighsInt linear_objective_coefficients_size = + linear_objective.coefficients.size(); + if (linear_objective_coefficients_size != this->model_.lp_.num_col_) { + highsLogUser(options_.log_options, HighsLogType::kError, + "Coefficient vector for linear objective has size %d != %d = " + "lp.num_col_\n", + int(linear_objective_coefficients_size), + int(this->model_.lp_.num_col_)); + return HighsStatus::kError; + } + this->multi_linear_objective_.push_back(linear_objective); + return HighsStatus::kOk; +} + +HighsStatus Highs::clearLinearObjectives() { + this->multi_linear_objective_.clear(); + return HighsStatus::kOk; +} + HighsStatus Highs::passColName(const HighsInt col, const std::string& name) { const HighsInt num_col = this->model_.lp_.num_col_; if (col < 0 || col >= num_col) { @@ -877,7 +910,31 @@ HighsStatus Highs::presolve() { return returnFromHighs(return_status); } -HighsStatus Highs::run() { return this->solve(); } +HighsStatus Highs::run() { + HighsInt num_multi_linear_objective = this->multi_linear_objective_.size(); + printf("Has %d multiple linear objectives\n", + int(num_multi_linear_objective)); + if (!this->multi_linear_objective_.size()) return this->solve(); + HighsLp& lp = this->model_.lp_; + HighsLinearObjective& multi_linear_objective = + this->multi_linear_objective_[0]; + if (multi_linear_objective.coefficients.size() != + static_cast(lp.num_col_)) { + highsLogUser(options_.log_options, HighsLogType::kError, + "Multiple linear objective coefficient vector %d has size " + "incompatible with model\n", + int(0)); + return HighsStatus::kError; + } + this->clearSolver(); + // Objective is multiplied by the weight and minimized + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) + lp.col_cost_[iCol] = multi_linear_objective.weight * + multi_linear_objective.coefficients[iCol]; + lp.offset_ = multi_linear_objective.weight * multi_linear_objective.offset; + lp.sense_ = ObjSense::kMinimize; + return this->solve(); +} // Checks the options calls presolve and postsolve if needed. Solvers are called // with callSolveLp(..) From ac831eca476c9229b35962107f0fef9c61a4ecd2 Mon Sep 17 00:00:00 2001 From: Julian Hall Date: Wed, 20 Nov 2024 17:34:40 +0000 Subject: [PATCH 48/75] Added check/TestMultiObjective.cpp and check/TestHighsCDouble.cpp --- check/TestHighsCDouble.cpp | 109 +++++++++++++++++++++++++++++++++++ check/TestMultiObjective.cpp | 56 ++++++++++++++++++ 2 files changed, 165 insertions(+) create mode 100644 check/TestHighsCDouble.cpp create mode 100644 check/TestMultiObjective.cpp diff --git a/check/TestHighsCDouble.cpp b/check/TestHighsCDouble.cpp new file mode 100644 index 0000000000..2f17a40598 --- /dev/null +++ b/check/TestHighsCDouble.cpp @@ -0,0 +1,109 @@ +#include "HCheckConfig.h" +#include "catch.hpp" +#include "util/HighsCDouble.h" +#include "util/HighsRandom.h" + +void testCeil(HighsCDouble x) { + double ceil_x; + double double_x; + ceil_x = double(ceil(x)); + double_x = double(x); + REQUIRE(ceil_x >= double_x); + REQUIRE(ceil(x) >= x); +} + +void testFloor(HighsCDouble x) { + double floor_x; + double double_x; + floor_x = double(floor(x)); + double_x = double(x); + REQUIRE(floor_x <= double_x); + REQUIRE(floor(x) <= x); +} + +TEST_CASE("HighsCDouble-ceil", "[util]") { + HighsCDouble x; + x = -1e-34; + testCeil(x); + x = -1e-32; + testCeil(x); + x = -1e-30; + testCeil(x); + x = -1e-23; + testCeil(x); + x = -1e-12; + testCeil(x); + x = -1e-1; + testCeil(x); + x = -0.99; + testCeil(x); + + x = 0.99; + testCeil(x); + x = 1e-1; + testCeil(x); + x = 1e-12; + testCeil(x); + // This and rest failed in #2041 + x = 1e-23; + testCeil(x); + x = 1e-30; + testCeil(x); + x = 1e-32; + testCeil(x); + x = 1e-34; + testCeil(x); + + HighsRandom rand; + for (HighsInt k = 0; k < 1000; k++) { + double man = rand.fraction(); + HighsInt power = 2 - rand.integer(5); + double exp = std::pow(10, power); + x = man * exp; + testCeil(x); + } +} + +TEST_CASE("HighsCDouble-floor", "[util]") { + HighsCDouble x; + + x = 1e-34; + testFloor(x); + x = 1e-32; + testFloor(x); + x = 1e-30; + testFloor(x); + x = 1e-23; + testFloor(x); + x = 1e-12; + testFloor(x); + x = 1e-1; + testFloor(x); + x = 0.99; + testFloor(x); + + x = -0.99; + testFloor(x); + x = -1e-1; + testFloor(x); + x = -1e-12; + testFloor(x); + // This and rest failed in #2041 + x = -1e-23; + testFloor(x); + x = -1e-30; + testFloor(x); + x = -1e-32; + testFloor(x); + x = -1e-34; + testFloor(x); + + HighsRandom rand; + for (HighsInt k = 0; k < 1000; k++) { + double man = rand.fraction(); + HighsInt power = 2 - rand.integer(5); + double exp = std::pow(10, power); + x = man * exp; + testFloor(x); + } +} diff --git a/check/TestMultiObjective.cpp b/check/TestMultiObjective.cpp new file mode 100644 index 0000000000..013b8d3a8d --- /dev/null +++ b/check/TestMultiObjective.cpp @@ -0,0 +1,56 @@ +#include "Highs.h" +#include "catch.hpp" + +const bool dev_run = true; + +TEST_CASE("multi-objective", "[util]") { + HighsLp lp; + lp.num_col_ = 2; + lp.num_row_ = 3; + lp.col_cost_ = {0, 0}; + lp.col_lower_ = {0, 0}; + lp.col_upper_ = {kHighsInf, kHighsInf}; + lp.row_lower_ = {-kHighsInf, -kHighsInf, -kHighsInf}; + lp.row_upper_ = {18, 8, 14}; + lp.a_matrix_.start_ = {0, 3, 6}; + lp.a_matrix_.index_ = {0, 1, 2, 0, 1, 2}; + lp.a_matrix_.value_ = {3, 1, 1, 1, 1, 2}; + Highs h; + h.setOptionValue("output_flag", dev_run); + h.passModel(lp); + + HighsLinearObjective linear_objective; + std::vector linear_objectives; + + // Begin with an illegal linear objective + linear_objective.weight = -1; + linear_objective.offset = -1; + linear_objective.coefficients = {2, 1, 0}; + linear_objective.abs_tolerance = 0.0; + linear_objective.rel_tolerance = 1.0; + linear_objective.priority = 0; + REQUIRE(h.addLinearObjective(linear_objective) == HighsStatus::kError); + + // Now legalise the linear objective so LP has nonunique optimal + // solutions on the line joining (2, 6) and (5, 3) + linear_objective.coefficients = {1, 1}; + REQUIRE(h.addLinearObjective(linear_objective) == HighsStatus::kOk); + + h.run(); + h.writeSolution("", kSolutionStylePretty); + REQUIRE(h.getInfo().objective_function_value == -7); + // Save the linear objective for the next + linear_objectives.push_back(linear_objective); + + // Add a second linear objective with a very small minimization + // weight that should push the optimal solution to (2, 6) + linear_objective.weight = 1e-4; + linear_objective.offset = 0; + linear_objective.coefficients = {-1, 0}; + REQUIRE(h.addLinearObjective(linear_objective) == HighsStatus::kOk); + + h.run(); + h.writeSolution("", kSolutionStylePretty); + // REQUIRE(h.getSolution().col_value[0] == 2); + // REQUIRE(h.getSolution().col_value[1] == 6); +} From 5db015b407c66b81d6f64295a205beda4197539b Mon Sep 17 00:00:00 2001 From: JAJHall Date: Thu, 21 Nov 2024 14:17:49 +0000 Subject: [PATCH 49/75] Need to define solution, info and model status after lexicographic optimization, and prevent equal priorities --- check/TestMultiObjective.cpp | 40 +++++++-- src/Highs.h | 2 + src/lp_data/Highs.cpp | 169 +++++++++++++++++++++++++++++++---- 3 files changed, 185 insertions(+), 26 deletions(-) diff --git a/check/TestMultiObjective.cpp b/check/TestMultiObjective.cpp index 013b8d3a8d..2ade329a7d 100644 --- a/check/TestMultiObjective.cpp +++ b/check/TestMultiObjective.cpp @@ -3,6 +3,10 @@ const bool dev_run = true; +bool smallDoubleDifference(double v0, double v1) { + return std::fabs(v0 - v1) < 1e-12; +} + TEST_CASE("multi-objective", "[util]") { HighsLp lp; lp.num_col_ = 2; @@ -23,34 +27,56 @@ TEST_CASE("multi-objective", "[util]") { std::vector linear_objectives; // Begin with an illegal linear objective + printf("\nPass illegal linear objective\n"); linear_objective.weight = -1; linear_objective.offset = -1; linear_objective.coefficients = {2, 1, 0}; linear_objective.abs_tolerance = 0.0; linear_objective.rel_tolerance = 1.0; - linear_objective.priority = 0; + linear_objective.priority = 10; REQUIRE(h.addLinearObjective(linear_objective) == HighsStatus::kError); // Now legalise the linear objective so LP has nonunique optimal // solutions on the line joining (2, 6) and (5, 3) + printf("\nPass legal linear objective\n"); linear_objective.coefficients = {1, 1}; REQUIRE(h.addLinearObjective(linear_objective) == HighsStatus::kOk); - h.run(); + REQUIRE(h.run() == HighsStatus::kOk); h.writeSolution("", kSolutionStylePretty); - REQUIRE(h.getInfo().objective_function_value == -7); + REQUIRE(smallDoubleDifference(h.getInfo().objective_function_value, -7)); // Save the linear objective for the next linear_objectives.push_back(linear_objective); // Add a second linear objective with a very small minimization // weight that should push the optimal solution to (2, 6) + printf("\nPass second linear objective\n"); linear_objective.weight = 1e-4; linear_objective.offset = 0; - linear_objective.coefficients = {-1, 0}; + linear_objective.coefficients = {1, 0}; + linear_objective.priority = 0; REQUIRE(h.addLinearObjective(linear_objective) == HighsStatus::kOk); - h.run(); + REQUIRE(h.run() == HighsStatus::kOk); + h.writeSolution("", kSolutionStylePretty); + REQUIRE(smallDoubleDifference(h.getSolution().col_value[0], 2)); + REQUIRE(smallDoubleDifference(h.getSolution().col_value[1], 6)); + linear_objectives.push_back(linear_objective); + + printf("\nClear and pass two linear objectives\n"); + REQUIRE(h.clearLinearObjectives() == HighsStatus::kOk); + REQUIRE(h.passLinearObjectives(2, linear_objectives.data()) == + HighsStatus::kOk); + REQUIRE(h.run() == HighsStatus::kOk); + h.writeSolution("", kSolutionStylePretty); + REQUIRE(smallDoubleDifference(h.getSolution().col_value[0], 2)); + REQUIRE(smallDoubleDifference(h.getSolution().col_value[1], 6)); + + // Now test lexicographic optimization + h.setOptionValue("blend_multi_objectives", false); + printf("\nLexicographic using existing multi objective data\n"); + REQUIRE(h.run() == HighsStatus::kOk); h.writeSolution("", kSolutionStylePretty); - // REQUIRE(h.getSolution().col_value[0] == 2); - // REQUIRE(h.getSolution().col_value[1] == 6); + REQUIRE(smallDoubleDifference(h.getSolution().col_value[0], 2)); + REQUIRE(smallDoubleDifference(h.getSolution().col_value[1], 6)); } diff --git a/src/Highs.h b/src/Highs.h index 12fb05b3f0..0bc6bb0239 100644 --- a/src/Highs.h +++ b/src/Highs.h @@ -1523,6 +1523,8 @@ class Highs { HighsStatus returnFromRun(const HighsStatus return_status, const bool undo_mods); HighsStatus returnFromHighs(const HighsStatus return_status); + HighsStatus returnFromLexicographicOptimization( + const HighsStatus return_status, HighsInt original_lp_num_row); void reportSolvedLpQpStats(); // Interface methods diff --git a/src/lp_data/Highs.cpp b/src/lp_data/Highs.cpp index cd4f4b0e59..ecb06af171 100644 --- a/src/lp_data/Highs.cpp +++ b/src/lp_data/Highs.cpp @@ -602,6 +602,18 @@ HighsStatus Highs::addLinearObjective( int(this->model_.lp_.num_col_)); return HighsStatus::kError; } + if (linear_objective.abs_tolerance < 0) { + highsLogUser(options_.log_options, HighsLogType::kError, + "Illegal absolute linear objective tolerance of %g < 0\n", + linear_objective.abs_tolerance); + return HighsStatus::kError; + } + if (linear_objective.rel_tolerance < 1) { + highsLogUser(options_.log_options, HighsLogType::kError, + "Illegal relative linear objective tolerance of %g < 1\n", + linear_objective.rel_tolerance); + return HighsStatus::kError; + } this->multi_linear_objective_.push_back(linear_objective); return HighsStatus::kOk; } @@ -910,30 +922,143 @@ HighsStatus Highs::presolve() { return returnFromHighs(return_status); } +bool comparison(std::pair x1, + std::pair x2) { + return x1.first >= x2.first; +} + HighsStatus Highs::run() { - HighsInt num_multi_linear_objective = this->multi_linear_objective_.size(); - printf("Has %d multiple linear objectives\n", - int(num_multi_linear_objective)); + HighsInt num_linear_objective = this->multi_linear_objective_.size(); + printf("Has %d linear objectives\n", int(num_linear_objective)); if (!this->multi_linear_objective_.size()) return this->solve(); HighsLp& lp = this->model_.lp_; - HighsLinearObjective& multi_linear_objective = - this->multi_linear_objective_[0]; - if (multi_linear_objective.coefficients.size() != - static_cast(lp.num_col_)) { - highsLogUser(options_.log_options, HighsLogType::kError, - "Multiple linear objective coefficient vector %d has size " - "incompatible with model\n", - int(0)); - return HighsStatus::kError; + for (HighsInt iObj = 0; iObj < num_linear_objective; iObj++) { + HighsLinearObjective& multi_linear_objective = + this->multi_linear_objective_[iObj]; + if (multi_linear_objective.coefficients.size() != + static_cast(lp.num_col_)) { + highsLogUser(options_.log_options, HighsLogType::kError, + "Multiple linear objective coefficient vector %d has size " + "incompatible with model\n", + int(iObj)); + return HighsStatus::kError; + } } + this->clearSolver(); - // Objective is multiplied by the weight and minimized - for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) - lp.col_cost_[iCol] = multi_linear_objective.weight * - multi_linear_objective.coefficients[iCol]; - lp.offset_ = multi_linear_objective.weight * multi_linear_objective.offset; - lp.sense_ = ObjSense::kMinimize; - return this->solve(); + if (this->options_.blend_multi_objectives) { + // Objectives are blended by weight and minimized + lp.offset_ = 0; + lp.col_cost_.assign(lp.num_col_, 0); + for (HighsInt iObj = 0; iObj < num_linear_objective; iObj++) { + HighsLinearObjective& multi_linear_objective = + this->multi_linear_objective_[iObj]; + lp.offset_ += + multi_linear_objective.weight * multi_linear_objective.offset; + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) + lp.col_cost_[iCol] += multi_linear_objective.weight * + multi_linear_objective.coefficients[iCol]; + } + lp.sense_ = ObjSense::kMinimize; + return this->solve(); + } + + // Objectives are applied lexicographically + std::vector> priority_objective; + + for (HighsInt iObj = 0; iObj < num_linear_objective; iObj++) + priority_objective.push_back( + std::make_pair(this->multi_linear_objective_[iObj].priority, iObj)); + std::sort(priority_objective.begin(), priority_objective.end(), comparison); + // Clear LP objective + lp.offset_ = 0; + lp.col_cost_.assign(lp.num_col_, 0); + const HighsInt original_lp_num_row = lp.num_row_; + std::vector index(lp.num_col_); + std::vector value(lp.num_col_); + for (HighsInt iIx = 0; iIx < num_linear_objective; iIx++) { + HighsInt iObj = priority_objective[iIx].second; + printf("\nEntry %d is objective %d with priority %d\n", int(iIx), int(iObj), + int(priority_objective[iIx].first)); + // Use this objective + HighsLinearObjective& linear_objective = + this->multi_linear_objective_[iObj]; + lp.offset_ = linear_objective.offset; + lp.col_cost_ = linear_objective.coefficients; + lp.sense_ = + linear_objective.weight > 0 ? ObjSense::kMinimize : ObjSense::kMaximize; + printf("LP objective function is %s %g ", + lp.sense_ == ObjSense::kMinimize ? "min" : "max", lp.offset_); + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) + printf(" + %g x[%d]", lp.col_cost_[iCol], int(iCol)); + printf("\n"); + HighsStatus solve_status = this->solve(); + if (solve_status == HighsStatus::kError) + return returnFromLexicographicOptimization(HighsStatus::kError, + original_lp_num_row); + if (model_status_ != HighsModelStatus::kOptimal) { + highsLogUser(options_.log_options, HighsLogType::kWarning, + "After priority %d solve, model status is %s\n", int(), + modelStatusToString(model_status_).c_str()); + return returnFromLexicographicOptimization(HighsStatus::kWarning, + original_lp_num_row); + } + this->writeSolution("", kSolutionStylePretty); + if (iIx == num_linear_objective - 1) break; + // Add the constraint + HighsInt nnz = 0; + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { + if (lp.col_cost_[iCol]) { + index[nnz] = iCol; + value[nnz] = lp.col_cost_[iCol]; + nnz++; + } + } + double objective = info_.objective_function_value; + HighsStatus add_row_status; + if (lp.sense_ == ObjSense::kMinimize) { + // Minimizing, so set a greater upper bound than the objective + double upper_bound = objective + linear_objective.abs_tolerance; + if (objective >= 0) { + // Guarantees objective of at least t.f^* + // + // so (t.f^*-f^*)/f^* = t-1 + upper_bound = + std::min(objective * linear_objective.rel_tolerance, upper_bound); + } else if (objective < 0) { + // Guarantees objective of at least (2-t).f^* + // + // so ((2-t).f^*-f^*)/f^* = 1-t + upper_bound = std::min( + objective * (2.0 - linear_objective.rel_tolerance), upper_bound); + } + upper_bound -= lp.offset_; + add_row_status = this->addRow(-kHighsInf, upper_bound, nnz, index.data(), + value.data()); + } else { + // Maximizing, so set a lesser lower bound than the objective + double lower_bound = objective - linear_objective.abs_tolerance; + if (objective >= 0) { + // Guarantees objective of at most (2-t).f^* + // + // so ((2-t).f^*-f^*)/f^* = 1-t + lower_bound = std::max( + objective * (2.0 - linear_objective.rel_tolerance), lower_bound); + } else if (objective < 0) { + // Guarantees objective of at least t.f^* + // + // so (t.f^*-f^*)/f^* = t-1 + lower_bound = + std::max(objective * linear_objective.rel_tolerance, lower_bound); + } + lower_bound -= lp.offset_; + add_row_status = + this->addRow(lower_bound, kHighsInf, nnz, index.data(), value.data()); + } + assert(add_row_status == HighsStatus::kOk); + } + return returnFromLexicographicOptimization(HighsStatus::kOk, + original_lp_num_row); } // Checks the options calls presolve and postsolve if needed. Solvers are called @@ -4536,6 +4661,12 @@ HighsStatus Highs::returnFromRun(const HighsStatus run_return_status, return returnFromHighs(return_status); } +HighsStatus Highs::returnFromLexicographicOptimization( + HighsStatus return_status, HighsInt original_lp_num_row) { + this->deleteRows(original_lp_num_row, this->model_.lp_.num_row_ - 1); + return return_status; +} + HighsStatus Highs::returnFromHighs(HighsStatus highs_return_status) { // Applies checks before returning from HiGHS HighsStatus return_status = highs_return_status; From b5aac76597d9dae3a466348a7b190780618ad6bc Mon Sep 17 00:00:00 2001 From: JAJHall Date: Thu, 21 Nov 2024 17:37:35 +0000 Subject: [PATCH 50/75] Repeated priorities illegal; needs more testing --- check/TestMultiObjective.cpp | 42 +++++++++++++--- src/Highs.h | 7 ++- src/lp_data/Highs.cpp | 89 ++++++++++++++++++++++++---------- src/lp_data/HighsInfo.cpp | 6 +-- src/lp_data/HighsInterface.cpp | 62 +++++++++++++++++++++++ 5 files changed, 169 insertions(+), 37 deletions(-) diff --git a/check/TestMultiObjective.cpp b/check/TestMultiObjective.cpp index 2ade329a7d..6039026bc1 100644 --- a/check/TestMultiObjective.cpp +++ b/check/TestMultiObjective.cpp @@ -1,7 +1,7 @@ #include "Highs.h" #include "catch.hpp" -const bool dev_run = true; +const bool dev_run = false; bool smallDoubleDifference(double v0, double v1) { return std::fabs(v0 - v1) < 1e-12; @@ -27,18 +27,17 @@ TEST_CASE("multi-objective", "[util]") { std::vector linear_objectives; // Begin with an illegal linear objective - printf("\nPass illegal linear objective\n"); + if (dev_run) printf("\nPass illegal linear objective\n"); linear_objective.weight = -1; linear_objective.offset = -1; linear_objective.coefficients = {2, 1, 0}; linear_objective.abs_tolerance = 0.0; linear_objective.rel_tolerance = 1.0; - linear_objective.priority = 10; REQUIRE(h.addLinearObjective(linear_objective) == HighsStatus::kError); // Now legalise the linear objective so LP has nonunique optimal // solutions on the line joining (2, 6) and (5, 3) - printf("\nPass legal linear objective\n"); + if (dev_run) printf("\nPass legal linear objective\n"); linear_objective.coefficients = {1, 1}; REQUIRE(h.addLinearObjective(linear_objective) == HighsStatus::kOk); @@ -50,11 +49,10 @@ TEST_CASE("multi-objective", "[util]") { // Add a second linear objective with a very small minimization // weight that should push the optimal solution to (2, 6) - printf("\nPass second linear objective\n"); + if (dev_run) printf("\nPass second linear objective\n"); linear_objective.weight = 1e-4; linear_objective.offset = 0; linear_objective.coefficients = {1, 0}; - linear_objective.priority = 0; REQUIRE(h.addLinearObjective(linear_objective) == HighsStatus::kOk); REQUIRE(h.run() == HighsStatus::kOk); @@ -63,7 +61,7 @@ TEST_CASE("multi-objective", "[util]") { REQUIRE(smallDoubleDifference(h.getSolution().col_value[1], 6)); linear_objectives.push_back(linear_objective); - printf("\nClear and pass two linear objectives\n"); + if (dev_run) printf("\nClear and pass two linear objectives\n"); REQUIRE(h.clearLinearObjectives() == HighsStatus::kOk); REQUIRE(h.passLinearObjectives(2, linear_objectives.data()) == HighsStatus::kOk); @@ -72,9 +70,37 @@ TEST_CASE("multi-objective", "[util]") { REQUIRE(smallDoubleDifference(h.getSolution().col_value[0], 2)); REQUIRE(smallDoubleDifference(h.getSolution().col_value[1], 6)); + // Set illegal priorities - that can be passed OK since + // blend_multi_objectives = true + if (dev_run) + printf( + "\nSetting priorities that will be illegal when using lexicographic " + "optimization\n"); + linear_objectives[0].priority = 0; + linear_objectives[1].priority = 0; + REQUIRE(h.passLinearObjectives(2, linear_objectives.data()) == + HighsStatus::kOk); + // Now test lexicographic optimization h.setOptionValue("blend_multi_objectives", false); - printf("\nLexicographic using existing multi objective data\n"); + + if (dev_run) printf("\nLexicographic using illegal priorities\n"); + REQUIRE(h.run() == HighsStatus::kError); + + if (dev_run) + printf( + "\nSetting priorities that are illegal now blend_multi_objectives = " + "false\n"); + REQUIRE(h.passLinearObjectives(2, linear_objectives.data()) == + HighsStatus::kError); + + if (dev_run) + printf("\nSetting legal priorities for blend_multi_objectives = false\n"); + linear_objectives[0].priority = 10; + REQUIRE(h.passLinearObjectives(2, linear_objectives.data()) == + HighsStatus::kOk); + + if (dev_run) printf("\nLexicographic using existing multi objective data\n"); REQUIRE(h.run() == HighsStatus::kOk); h.writeSolution("", kSolutionStylePretty); REQUIRE(smallDoubleDifference(h.getSolution().col_value[0], 2)); diff --git a/src/Highs.h b/src/Highs.h index 0bc6bb0239..5f40efb732 100644 --- a/src/Highs.h +++ b/src/Highs.h @@ -160,7 +160,8 @@ class Highs { /** * @brief Add a linear objective for the incumbent model */ - HighsStatus addLinearObjective(const HighsLinearObjective& linear_objective); + HighsStatus addLinearObjective(const HighsLinearObjective& linear_objective, + const HighsInt iObj = -1); /** * @brief Clear the multiple linear objective data @@ -1645,6 +1646,10 @@ class Highs { const bool constraint, const double ill_conditioning_bound); bool infeasibleBoundsOk(); + bool validLinearObjective(const HighsLinearObjective& linear_objective, + const HighsInt iObj) const; + bool hasRepeatedLinearObjectivePriorities( + const HighsLinearObjective* linear_objective = nullptr) const; }; // Start of deprecated methods not in the Highs class diff --git a/src/lp_data/Highs.cpp b/src/lp_data/Highs.cpp index ecb06af171..4ef0143b3b 100644 --- a/src/lp_data/Highs.cpp +++ b/src/lp_data/Highs.cpp @@ -584,36 +584,21 @@ HighsStatus Highs::passLinearObjectives( if (num_linear_objective < 0) return HighsStatus::kOk; this->multi_linear_objective_.clear(); for (HighsInt iObj = 0; iObj < num_linear_objective; iObj++) - if (this->addLinearObjective(linear_objective[iObj]) != HighsStatus::kOk) + if (this->addLinearObjective(linear_objective[iObj], iObj) != + HighsStatus::kOk) return HighsStatus::kError; - ; return HighsStatus::kOk; } HighsStatus Highs::addLinearObjective( - const HighsLinearObjective& linear_objective) { - HighsInt linear_objective_coefficients_size = - linear_objective.coefficients.size(); - if (linear_objective_coefficients_size != this->model_.lp_.num_col_) { + const HighsLinearObjective& linear_objective, const HighsInt iObj) { + if (model_.isQp()) { highsLogUser(options_.log_options, HighsLogType::kError, - "Coefficient vector for linear objective has size %d != %d = " - "lp.num_col_\n", - int(linear_objective_coefficients_size), - int(this->model_.lp_.num_col_)); + "Cannot define additional linear objective for QP\n"); return HighsStatus::kError; } - if (linear_objective.abs_tolerance < 0) { - highsLogUser(options_.log_options, HighsLogType::kError, - "Illegal absolute linear objective tolerance of %g < 0\n", - linear_objective.abs_tolerance); - return HighsStatus::kError; - } - if (linear_objective.rel_tolerance < 1) { - highsLogUser(options_.log_options, HighsLogType::kError, - "Illegal relative linear objective tolerance of %g < 1\n", - linear_objective.rel_tolerance); + if (!this->validLinearObjective(linear_objective, iObj)) return HighsStatus::kError; - } this->multi_linear_objective_.push_back(linear_objective); return HighsStatus::kOk; } @@ -929,8 +914,8 @@ bool comparison(std::pair x1, HighsStatus Highs::run() { HighsInt num_linear_objective = this->multi_linear_objective_.size(); - printf("Has %d linear objectives\n", int(num_linear_objective)); if (!this->multi_linear_objective_.size()) return this->solve(); + // Handle multiple linear objectives HighsLp& lp = this->model_.lp_; for (HighsInt iObj = 0; iObj < num_linear_objective; iObj++) { HighsLinearObjective& multi_linear_objective = @@ -964,6 +949,21 @@ HighsStatus Highs::run() { } // Objectives are applied lexicographically + if (model_.isQp() && num_linear_objective > 1) { + // Lexicographic optimization with a single linear objective is + // trivially standard optimization, so is OK + highsLogUser( + options_.log_options, HighsLogType::kError, + "Cannot perform non-trivial lexicographic optimization for QP\n"); + return HighsStatus::kError; + } + // Check whether there are repeated linear objective priorities + if (hasRepeatedLinearObjectivePriorities()) { + highsLogUser( + options_.log_options, HighsLogType::kError, + "Repeated priorities for lexicographic optimization is illegal\n"); + return HighsStatus::kError; + } std::vector> priority_objective; for (HighsInt iObj = 0; iObj < num_linear_objective; iObj++) @@ -978,8 +978,8 @@ HighsStatus Highs::run() { std::vector value(lp.num_col_); for (HighsInt iIx = 0; iIx < num_linear_objective; iIx++) { HighsInt iObj = priority_objective[iIx].second; - printf("\nEntry %d is objective %d with priority %d\n", int(iIx), int(iObj), - int(priority_objective[iIx].first)); + printf("\nHighs::run() Entry %d is objective %d with priority %d\n", + int(iIx), int(iObj), int(priority_objective[iIx].first)); // Use this objective HighsLinearObjective& linear_objective = this->multi_linear_objective_[iObj]; @@ -987,7 +987,7 @@ HighsStatus Highs::run() { lp.col_cost_ = linear_objective.coefficients; lp.sense_ = linear_objective.weight > 0 ? ObjSense::kMinimize : ObjSense::kMaximize; - printf("LP objective function is %s %g ", + printf("Highs::run() LP objective function is %s %g ", lp.sense_ == ObjSense::kMinimize ? "min" : "max", lp.offset_); for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) printf(" + %g x[%d]", lp.col_cost_[iCol], int(iCol)); @@ -4663,7 +4663,46 @@ HighsStatus Highs::returnFromRun(const HighsStatus run_return_status, HighsStatus Highs::returnFromLexicographicOptimization( HighsStatus return_status, HighsInt original_lp_num_row) { + const bool lexicographic_optimization_logging = false; + if (lexicographic_optimization_logging) + printf("\nOn return, model status is %s\n", + this->modelStatusToString(this->model_status_).c_str()); + + // Save model_status_ and info_ since they are cleared by calling + // deleteRows + HighsModelStatus model_status = this->model_status_; + HighsInfo info = this->info_; + if (lexicographic_optimization_logging) + writeInfoToFile(stdout, true, info_.records, HighsFileType::kMinimal); + this->deleteRows(original_lp_num_row, this->model_.lp_.num_row_ - 1); + if (lexicographic_optimization_logging) + printf("\nAfter deleteRows, model status %s\n", + this->modelStatusToString(model_status_).c_str()); + + // Recover model_status_ and info_, and then account for lack of basis or dual + // solution + this->model_status_ = model_status; + this->info_ = info; + info_.objective_function_value = 0; + info_.basis_validity = kBasisValidityInvalid; + info_.dual_solution_status = kSolutionStatusNone; + info_.num_dual_infeasibilities = kHighsIllegalInfeasibilityCount; + info_.max_dual_infeasibility = kHighsIllegalInfeasibilityMeasure; + info_.sum_dual_infeasibilities = kHighsIllegalInfeasibilityMeasure; + info_.max_complementarity_violation = kHighsIllegalComplementarityViolation; + info_.sum_complementarity_violations = kHighsIllegalComplementarityViolation; + this->solution_.value_valid = true; + + if (lexicographic_optimization_logging) { + printf("On return solution is\n"); + for (HighsInt iCol = 0; iCol < this->model_.lp_.num_col_; iCol++) + printf("Col %2d Primal = %11.6g; Dual = %11.6g\n", int(iCol), + solution_.col_value[iCol], solution_.col_value[iCol]); + for (HighsInt iRow = 0; iRow < this->model_.lp_.num_row_; iRow++) + printf("Row %2d Primal = %11.6g; Dual = %11.6g\n", int(iRow), + solution_.row_value[iRow], solution_.row_value[iRow]); + } return return_status; } diff --git a/src/lp_data/HighsInfo.cpp b/src/lp_data/HighsInfo.cpp index ce01a585b8..2792f64550 100644 --- a/src/lp_data/HighsInfo.cpp +++ b/src/lp_data/HighsInfo.cpp @@ -292,7 +292,7 @@ void reportInfo(FILE* file, const InfoRecordInt64& info, fprintf(file, "\n# %s\n# [type: int64_t]\n%s = %" PRId64 "\n", info.description.c_str(), info.name.c_str(), *info.value); } else { - fprintf(file, "%s = %" PRId64 "\n", info.name.c_str(), *info.value); + fprintf(file, "%-30s = %" PRId64 "\n", info.name.c_str(), *info.value); } } @@ -306,7 +306,7 @@ void reportInfo(FILE* file, const InfoRecordInt& info, fprintf(file, "\n# %s\n# [type: HighsInt]\n%s = %" HIGHSINT_FORMAT "\n", info.description.c_str(), info.name.c_str(), *info.value); } else { - fprintf(file, "%s = %" HIGHSINT_FORMAT "\n", info.name.c_str(), + fprintf(file, "%-30s = %" HIGHSINT_FORMAT "\n", info.name.c_str(), *info.value); } } @@ -321,6 +321,6 @@ void reportInfo(FILE* file, const InfoRecordDouble& info, fprintf(file, "\n# %s\n# [type: double]\n%s = %g\n", info.description.c_str(), info.name.c_str(), *info.value); } else { - fprintf(file, "%s = %g\n", info.name.c_str(), *info.value); + fprintf(file, "%-30s = %g\n", info.name.c_str(), *info.value); } } diff --git a/src/lp_data/HighsInterface.cpp b/src/lp_data/HighsInterface.cpp index 0934129e62..ad37bdb923 100644 --- a/src/lp_data/HighsInterface.cpp +++ b/src/lp_data/HighsInterface.cpp @@ -3585,6 +3585,68 @@ bool Highs::infeasibleBoundsOk() { return num_true_infeasible_bound == 0; } +bool Highs::validLinearObjective(const HighsLinearObjective& linear_objective, + const HighsInt iObj) const { + HighsInt linear_objective_coefficients_size = + linear_objective.coefficients.size(); + if (linear_objective_coefficients_size != this->model_.lp_.num_col_) { + highsLogUser( + options_.log_options, HighsLogType::kError, + "Coefficient vector for linear objective %s has size %d != %d = " + "lp.num_col_\n", + iObj >= 0 ? std::to_string(iObj).c_str() : "", + int(linear_objective_coefficients_size), + int(this->model_.lp_.num_col_)); + return false; + } + if (linear_objective.abs_tolerance < 0) { + highsLogUser(options_.log_options, HighsLogType::kError, + "Linear objective %s has illegal absolute linear objective " + "tolerance of %g < 0\n", + iObj >= 0 ? std::to_string(iObj).c_str() : "", + linear_objective.abs_tolerance); + return false; + } + if (linear_objective.rel_tolerance < 1) { + highsLogUser(options_.log_options, HighsLogType::kError, + "Linear objective %s has illegal relative linear objective " + "tolerance of %g < 1\n", + iObj >= 0 ? std::to_string(iObj).c_str() : "", + linear_objective.rel_tolerance); + return false; + } + if (!options_.blend_multi_objectives && + hasRepeatedLinearObjectivePriorities(&linear_objective)) { + highsLogUser( + options_.log_options, HighsLogType::kError, + "Repeated priorities for lexicographic optimization is illegal\n"); + return false; + } + return true; +} + +bool Highs::hasRepeatedLinearObjectivePriorities( + const HighsLinearObjective* linear_objective) const { + // Look for repeated values in the linear objective priorities, also + // comparing linear_objective if it's not a null pointer. Cost is + // O(n^2), but who will have more than O(1) linear objectives! + HighsInt num_linear_objective = this->multi_linear_objective_.size(); + if (num_linear_objective <= 0 || + num_linear_objective <= 1 && !linear_objective) + return false; + for (HighsInt iObj0 = 0; iObj0 < num_linear_objective; iObj0++) { + HighsInt priority0 = this->multi_linear_objective_[iObj0].priority; + for (HighsInt iObj1 = iObj0 + 1; iObj1 < num_linear_objective; iObj1++) { + HighsInt priority1 = this->multi_linear_objective_[iObj1].priority; + if (priority1 == priority0) return true; + } + if (linear_objective) { + if (linear_objective->priority == priority0) return true; + } + } + return false; +} + void HighsLinearObjective::clear() { this->weight = 0.0; this->offset = 0.0; From 9f95d387ea6181ec03de6b3d76b9760c29c70292 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Thu, 21 Nov 2024 23:22:33 +0000 Subject: [PATCH 51/75] More unit tests of multi-objective optimization --- check/TestMultiObjective.cpp | 62 +++++++++- src/Highs.h | 2 + src/lp_data/Highs.cpp | 210 ++++++--------------------------- src/lp_data/HighsInterface.cpp | 165 ++++++++++++++++++++++++++ 4 files changed, 261 insertions(+), 178 deletions(-) diff --git a/check/TestMultiObjective.cpp b/check/TestMultiObjective.cpp index 6039026bc1..4cd0b729aa 100644 --- a/check/TestMultiObjective.cpp +++ b/check/TestMultiObjective.cpp @@ -4,7 +4,9 @@ const bool dev_run = false; bool smallDoubleDifference(double v0, double v1) { - return std::fabs(v0 - v1) < 1e-12; + double difference = std::fabs(v0 - v1); + // printf("smallDoubleDifference = %g\n", difference); + return difference < 1e-4; } TEST_CASE("multi-objective", "[util]") { @@ -105,4 +107,62 @@ TEST_CASE("multi-objective", "[util]") { h.writeSolution("", kSolutionStylePretty); REQUIRE(smallDoubleDifference(h.getSolution().col_value[0], 2)); REQUIRE(smallDoubleDifference(h.getSolution().col_value[1], 6)); + + // Back to blending + h.setOptionValue("blend_multi_objectives", true); + // h.setOptionValue("output_flag", true); + REQUIRE(h.clearLinearObjectives() == HighsStatus::kOk); + linear_objectives[0].coefficients = {1.0001, 1}; + linear_objectives[0].abs_tolerance = -1e-5; + linear_objectives[0].rel_tolerance = 0.95; + + if (dev_run) printf("\nBlending: Illegal abs_tolerance \n"); + REQUIRE(h.passLinearObjectives(1, linear_objectives.data()) == + HighsStatus::kError); + linear_objectives[0].abs_tolerance = 1e-5; + + if (dev_run) printf("\nBlending: Illegal rel_tolerance \n"); + REQUIRE(h.passLinearObjectives(1, linear_objectives.data()) == + HighsStatus::kError); + linear_objectives[0].rel_tolerance = 1.05; + + linear_objectives[1].weight = 1e-3; + if (dev_run) + printf( + "\nBlending: first solve objective just giving unique optimal " + "solution\n"); + REQUIRE(h.passLinearObjectives(1, linear_objectives.data()) == + HighsStatus::kOk); + + REQUIRE(h.run() == HighsStatus::kOk); + h.writeSolution("", kSolutionStylePretty); + + REQUIRE(h.passLinearObjectives(2, linear_objectives.data()) == + HighsStatus::kOk); + + REQUIRE(h.run() == HighsStatus::kOk); + h.writeSolution("", kSolutionStylePretty); + + // Back to lexicographic optimization + h.setOptionValue("blend_multi_objectives", false); + + if (dev_run) printf("\nLexicographic using non-trivial tolerances\n"); + REQUIRE(h.run() == HighsStatus::kOk); + h.writeSolution("", kSolutionStylePretty); + + REQUIRE(smallDoubleDifference(h.getSolution().col_value[0], 4.9)); + REQUIRE(smallDoubleDifference(h.getSolution().col_value[1], 3.1)); + + linear_objectives[0].abs_tolerance = kHighsInf; + + REQUIRE(h.passLinearObjectives(2, linear_objectives.data()) == + HighsStatus::kOk); + + REQUIRE(h.run() == HighsStatus::kOk); + h.writeSolution("", kSolutionStylePretty); + + // printf("Solution = [%23.18g, %23.18g]\n", h.getSolution().col_value[0], + // h.getSolution().col_value[1]); + REQUIRE(smallDoubleDifference(h.getSolution().col_value[0], 1.30069)); + REQUIRE(smallDoubleDifference(h.getSolution().col_value[1], 6.34966)); } diff --git a/src/Highs.h b/src/Highs.h index 5f40efb732..bd7090f826 100644 --- a/src/Highs.h +++ b/src/Highs.h @@ -1623,6 +1623,8 @@ class Highs { HighsInt* iis_col_index, HighsInt* iis_row_index, HighsInt* iis_col_bound, HighsInt* iis_row_bound); + HighsStatus multiobjectiveSolve(); + bool aFormatOk(const HighsInt num_nz, const HighsInt format); bool qFormatOk(const HighsInt num_nz, const HighsInt format); void clearZeroHessian(); diff --git a/src/lp_data/Highs.cpp b/src/lp_data/Highs.cpp index 4ef0143b3b..9a3bd54c06 100644 --- a/src/lp_data/Highs.cpp +++ b/src/lp_data/Highs.cpp @@ -907,158 +907,10 @@ HighsStatus Highs::presolve() { return returnFromHighs(return_status); } -bool comparison(std::pair x1, - std::pair x2) { - return x1.first >= x2.first; -} - HighsStatus Highs::run() { HighsInt num_linear_objective = this->multi_linear_objective_.size(); - if (!this->multi_linear_objective_.size()) return this->solve(); - // Handle multiple linear objectives - HighsLp& lp = this->model_.lp_; - for (HighsInt iObj = 0; iObj < num_linear_objective; iObj++) { - HighsLinearObjective& multi_linear_objective = - this->multi_linear_objective_[iObj]; - if (multi_linear_objective.coefficients.size() != - static_cast(lp.num_col_)) { - highsLogUser(options_.log_options, HighsLogType::kError, - "Multiple linear objective coefficient vector %d has size " - "incompatible with model\n", - int(iObj)); - return HighsStatus::kError; - } - } - - this->clearSolver(); - if (this->options_.blend_multi_objectives) { - // Objectives are blended by weight and minimized - lp.offset_ = 0; - lp.col_cost_.assign(lp.num_col_, 0); - for (HighsInt iObj = 0; iObj < num_linear_objective; iObj++) { - HighsLinearObjective& multi_linear_objective = - this->multi_linear_objective_[iObj]; - lp.offset_ += - multi_linear_objective.weight * multi_linear_objective.offset; - for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) - lp.col_cost_[iCol] += multi_linear_objective.weight * - multi_linear_objective.coefficients[iCol]; - } - lp.sense_ = ObjSense::kMinimize; - return this->solve(); - } - - // Objectives are applied lexicographically - if (model_.isQp() && num_linear_objective > 1) { - // Lexicographic optimization with a single linear objective is - // trivially standard optimization, so is OK - highsLogUser( - options_.log_options, HighsLogType::kError, - "Cannot perform non-trivial lexicographic optimization for QP\n"); - return HighsStatus::kError; - } - // Check whether there are repeated linear objective priorities - if (hasRepeatedLinearObjectivePriorities()) { - highsLogUser( - options_.log_options, HighsLogType::kError, - "Repeated priorities for lexicographic optimization is illegal\n"); - return HighsStatus::kError; - } - std::vector> priority_objective; - - for (HighsInt iObj = 0; iObj < num_linear_objective; iObj++) - priority_objective.push_back( - std::make_pair(this->multi_linear_objective_[iObj].priority, iObj)); - std::sort(priority_objective.begin(), priority_objective.end(), comparison); - // Clear LP objective - lp.offset_ = 0; - lp.col_cost_.assign(lp.num_col_, 0); - const HighsInt original_lp_num_row = lp.num_row_; - std::vector index(lp.num_col_); - std::vector value(lp.num_col_); - for (HighsInt iIx = 0; iIx < num_linear_objective; iIx++) { - HighsInt iObj = priority_objective[iIx].second; - printf("\nHighs::run() Entry %d is objective %d with priority %d\n", - int(iIx), int(iObj), int(priority_objective[iIx].first)); - // Use this objective - HighsLinearObjective& linear_objective = - this->multi_linear_objective_[iObj]; - lp.offset_ = linear_objective.offset; - lp.col_cost_ = linear_objective.coefficients; - lp.sense_ = - linear_objective.weight > 0 ? ObjSense::kMinimize : ObjSense::kMaximize; - printf("Highs::run() LP objective function is %s %g ", - lp.sense_ == ObjSense::kMinimize ? "min" : "max", lp.offset_); - for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) - printf(" + %g x[%d]", lp.col_cost_[iCol], int(iCol)); - printf("\n"); - HighsStatus solve_status = this->solve(); - if (solve_status == HighsStatus::kError) - return returnFromLexicographicOptimization(HighsStatus::kError, - original_lp_num_row); - if (model_status_ != HighsModelStatus::kOptimal) { - highsLogUser(options_.log_options, HighsLogType::kWarning, - "After priority %d solve, model status is %s\n", int(), - modelStatusToString(model_status_).c_str()); - return returnFromLexicographicOptimization(HighsStatus::kWarning, - original_lp_num_row); - } - this->writeSolution("", kSolutionStylePretty); - if (iIx == num_linear_objective - 1) break; - // Add the constraint - HighsInt nnz = 0; - for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { - if (lp.col_cost_[iCol]) { - index[nnz] = iCol; - value[nnz] = lp.col_cost_[iCol]; - nnz++; - } - } - double objective = info_.objective_function_value; - HighsStatus add_row_status; - if (lp.sense_ == ObjSense::kMinimize) { - // Minimizing, so set a greater upper bound than the objective - double upper_bound = objective + linear_objective.abs_tolerance; - if (objective >= 0) { - // Guarantees objective of at least t.f^* - // - // so (t.f^*-f^*)/f^* = t-1 - upper_bound = - std::min(objective * linear_objective.rel_tolerance, upper_bound); - } else if (objective < 0) { - // Guarantees objective of at least (2-t).f^* - // - // so ((2-t).f^*-f^*)/f^* = 1-t - upper_bound = std::min( - objective * (2.0 - linear_objective.rel_tolerance), upper_bound); - } - upper_bound -= lp.offset_; - add_row_status = this->addRow(-kHighsInf, upper_bound, nnz, index.data(), - value.data()); - } else { - // Maximizing, so set a lesser lower bound than the objective - double lower_bound = objective - linear_objective.abs_tolerance; - if (objective >= 0) { - // Guarantees objective of at most (2-t).f^* - // - // so ((2-t).f^*-f^*)/f^* = 1-t - lower_bound = std::max( - objective * (2.0 - linear_objective.rel_tolerance), lower_bound); - } else if (objective < 0) { - // Guarantees objective of at least t.f^* - // - // so (t.f^*-f^*)/f^* = t-1 - lower_bound = - std::max(objective * linear_objective.rel_tolerance, lower_bound); - } - lower_bound -= lp.offset_; - add_row_status = - this->addRow(lower_bound, kHighsInf, nnz, index.data(), value.data()); - } - assert(add_row_status == HighsStatus::kOk); - } - return returnFromLexicographicOptimization(HighsStatus::kOk, - original_lp_num_row); + if (num_linear_objective == 0) return this->solve(); + return this->multiobjectiveSolve(); } // Checks the options calls presolve and postsolve if needed. Solvers are called @@ -4675,33 +4527,37 @@ HighsStatus Highs::returnFromLexicographicOptimization( if (lexicographic_optimization_logging) writeInfoToFile(stdout, true, info_.records, HighsFileType::kMinimal); - this->deleteRows(original_lp_num_row, this->model_.lp_.num_row_ - 1); - if (lexicographic_optimization_logging) - printf("\nAfter deleteRows, model status %s\n", - this->modelStatusToString(model_status_).c_str()); - - // Recover model_status_ and info_, and then account for lack of basis or dual - // solution - this->model_status_ = model_status; - this->info_ = info; - info_.objective_function_value = 0; - info_.basis_validity = kBasisValidityInvalid; - info_.dual_solution_status = kSolutionStatusNone; - info_.num_dual_infeasibilities = kHighsIllegalInfeasibilityCount; - info_.max_dual_infeasibility = kHighsIllegalInfeasibilityMeasure; - info_.sum_dual_infeasibilities = kHighsIllegalInfeasibilityMeasure; - info_.max_complementarity_violation = kHighsIllegalComplementarityViolation; - info_.sum_complementarity_violations = kHighsIllegalComplementarityViolation; - this->solution_.value_valid = true; - - if (lexicographic_optimization_logging) { - printf("On return solution is\n"); - for (HighsInt iCol = 0; iCol < this->model_.lp_.num_col_; iCol++) - printf("Col %2d Primal = %11.6g; Dual = %11.6g\n", int(iCol), - solution_.col_value[iCol], solution_.col_value[iCol]); - for (HighsInt iRow = 0; iRow < this->model_.lp_.num_row_; iRow++) - printf("Row %2d Primal = %11.6g; Dual = %11.6g\n", int(iRow), - solution_.row_value[iRow], solution_.row_value[iRow]); + HighsInt num_linear_objective = this->multi_linear_objective_.size(); + if (num_linear_objective > 1) { + this->deleteRows(original_lp_num_row, this->model_.lp_.num_row_ - 1); + if (lexicographic_optimization_logging) + printf("\nAfter deleteRows, model status %s\n", + this->modelStatusToString(model_status_).c_str()); + + // Recover model_status_ and info_, and then account for lack of basis or + // dual solution + this->model_status_ = model_status; + this->info_ = info; + info_.objective_function_value = 0; + info_.basis_validity = kBasisValidityInvalid; + info_.dual_solution_status = kSolutionStatusNone; + info_.num_dual_infeasibilities = kHighsIllegalInfeasibilityCount; + info_.max_dual_infeasibility = kHighsIllegalInfeasibilityMeasure; + info_.sum_dual_infeasibilities = kHighsIllegalInfeasibilityMeasure; + info_.max_complementarity_violation = kHighsIllegalComplementarityViolation; + info_.sum_complementarity_violations = + kHighsIllegalComplementarityViolation; + this->solution_.value_valid = true; + + if (lexicographic_optimization_logging) { + printf("On return solution is\n"); + for (HighsInt iCol = 0; iCol < this->model_.lp_.num_col_; iCol++) + printf("Col %2d Primal = %11.6g; Dual = %11.6g\n", int(iCol), + solution_.col_value[iCol], solution_.col_value[iCol]); + for (HighsInt iRow = 0; iRow < this->model_.lp_.num_row_; iRow++) + printf("Row %2d Primal = %11.6g; Dual = %11.6g\n", int(iRow), + solution_.row_value[iRow], solution_.row_value[iRow]); + } } return return_status; } diff --git a/src/lp_data/HighsInterface.cpp b/src/lp_data/HighsInterface.cpp index ad37bdb923..361431bf04 100644 --- a/src/lp_data/HighsInterface.cpp +++ b/src/lp_data/HighsInterface.cpp @@ -3647,6 +3647,171 @@ bool Highs::hasRepeatedLinearObjectivePriorities( return false; } +bool comparison(std::pair x1, + std::pair x2) { + return x1.first >= x2.first; +} + +HighsStatus Highs::multiobjectiveSolve() { + HighsInt num_linear_objective = this->multi_linear_objective_.size(); + assert(num_linear_objective > 0); + HighsLp& lp = this->model_.lp_; + for (HighsInt iObj = 0; iObj < num_linear_objective; iObj++) { + HighsLinearObjective& multi_linear_objective = + this->multi_linear_objective_[iObj]; + if (multi_linear_objective.coefficients.size() != + static_cast(lp.num_col_)) { + highsLogUser(options_.log_options, HighsLogType::kError, + "Multiple linear objective coefficient vector %d has size " + "incompatible with model\n", + int(iObj)); + return HighsStatus::kError; + } + } + + this->clearSolver(); + if (this->options_.blend_multi_objectives) { + // Objectives are blended by weight and minimized + lp.offset_ = 0; + lp.col_cost_.assign(lp.num_col_, 0); + for (HighsInt iObj = 0; iObj < num_linear_objective; iObj++) { + HighsLinearObjective& multi_linear_objective = + this->multi_linear_objective_[iObj]; + lp.offset_ += + multi_linear_objective.weight * multi_linear_objective.offset; + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) + lp.col_cost_[iCol] += multi_linear_objective.weight * + multi_linear_objective.coefficients[iCol]; + } + lp.sense_ = ObjSense::kMinimize; + printf("Highs::run() LP objective function is %s %g ", + lp.sense_ == ObjSense::kMinimize ? "min" : "max", lp.offset_); + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) + printf(" + (%g) x[%d]", lp.col_cost_[iCol], int(iCol)); + printf("\n"); + return this->solve(); + } + + // Objectives are applied lexicographically + if (model_.isQp() && num_linear_objective > 1) { + // Lexicographic optimization with a single linear objective is + // trivially standard optimization, so is OK + highsLogUser( + options_.log_options, HighsLogType::kError, + "Cannot perform non-trivial lexicographic optimization for QP\n"); + return HighsStatus::kError; + } + // Check whether there are repeated linear objective priorities + if (hasRepeatedLinearObjectivePriorities()) { + highsLogUser( + options_.log_options, HighsLogType::kError, + "Repeated priorities for lexicographic optimization is illegal\n"); + return HighsStatus::kError; + } + std::vector> priority_objective; + + for (HighsInt iObj = 0; iObj < num_linear_objective; iObj++) + priority_objective.push_back( + std::make_pair(this->multi_linear_objective_[iObj].priority, iObj)); + std::sort(priority_objective.begin(), priority_objective.end(), comparison); + // Clear LP objective + lp.offset_ = 0; + lp.col_cost_.assign(lp.num_col_, 0); + const HighsInt original_lp_num_row = lp.num_row_; + std::vector index(lp.num_col_); + std::vector value(lp.num_col_); + for (HighsInt iIx = 0; iIx < num_linear_objective; iIx++) { + HighsInt iObj = priority_objective[iIx].second; + printf("\nHighs::run() Entry %d is objective %d with priority %d\n", + int(iIx), int(iObj), int(priority_objective[iIx].first)); + // Use this objective + HighsLinearObjective& linear_objective = + this->multi_linear_objective_[iObj]; + lp.offset_ = linear_objective.offset; + lp.col_cost_ = linear_objective.coefficients; + lp.sense_ = + linear_objective.weight > 0 ? ObjSense::kMinimize : ObjSense::kMaximize; + printf("Highs::run() LP objective function is %s %g ", + lp.sense_ == ObjSense::kMinimize ? "min" : "max", lp.offset_); + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) + printf(" + %g x[%d]", lp.col_cost_[iCol], int(iCol)); + printf("\n"); + HighsStatus solve_status = this->solve(); + if (solve_status == HighsStatus::kError) + return returnFromLexicographicOptimization(HighsStatus::kError, + original_lp_num_row); + if (model_status_ != HighsModelStatus::kOptimal) { + highsLogUser(options_.log_options, HighsLogType::kWarning, + "After priority %d solve, model status is %s\n", int(), + modelStatusToString(model_status_).c_str()); + return returnFromLexicographicOptimization(HighsStatus::kWarning, + original_lp_num_row); + } + this->writeSolution("", kSolutionStylePretty); + if (iIx == num_linear_objective - 1) break; + // Add the constraint + HighsInt nnz = 0; + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { + if (lp.col_cost_[iCol]) { + index[nnz] = iCol; + value[nnz] = lp.col_cost_[iCol]; + nnz++; + } + } + double objective = info_.objective_function_value; + HighsStatus add_row_status; + double lower_bound = -kHighsInf; + double upper_bound = kHighsInf; + if (lp.sense_ == ObjSense::kMinimize) { + // Minimizing, so set a greater upper bound than the objective + upper_bound = objective + linear_objective.abs_tolerance; + if (objective >= 0) { + // Guarantees objective of at least t.f^* + // + // so (t.f^*-f^*)/f^* = t-1 + upper_bound = + std::min(objective * linear_objective.rel_tolerance, upper_bound); + } else if (objective < 0) { + // Guarantees objective of at least (2-t).f^* + // + // so ((2-t).f^*-f^*)/f^* = 1-t + upper_bound = std::min( + objective * (2.0 - linear_objective.rel_tolerance), upper_bound); + } + upper_bound -= lp.offset_; + } else { + // Maximizing, so set a lesser lower bound than the objective + lower_bound = objective - linear_objective.abs_tolerance; + if (objective >= 0) { + // Guarantees objective of at most (2-t).f^* + // + // so ((2-t).f^*-f^*)/f^* = 1-t + double lower_bound_from_rel_tolerance = + objective * (2.0 - linear_objective.rel_tolerance); + lower_bound = std::max( + objective * (2.0 - linear_objective.rel_tolerance), lower_bound); + } else if (objective < 0) { + // Guarantees objective of at least t.f^* + // + // so (t.f^*-f^*)/f^* = t-1 + lower_bound = + std::max(objective * linear_objective.rel_tolerance, lower_bound); + } + lower_bound -= lp.offset_; + } + printf("Highs::run() Add objective constraint %g <= ", lower_bound); + for (HighsInt iEl = 0; iEl < nnz; iEl++) + printf(" + (%g) x[%d]", value[iEl], int(index[iEl])); + printf(" <= %g\n", upper_bound); + + add_row_status = + this->addRow(lower_bound, upper_bound, nnz, index.data(), value.data()); + assert(add_row_status == HighsStatus::kOk); + } + return returnFromLexicographicOptimization(HighsStatus::kOk, + original_lp_num_row); +} + void HighsLinearObjective::clear() { this->weight = 0.0; this->offset = 0.0; From aa09efb69afd570eeb0a5d883a9c6d442ed41ae7 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Fri, 22 Nov 2024 10:05:28 +0000 Subject: [PATCH 52/75] Before removing dev printing --- check/TestMultiObjective.cpp | 302 ++++++++++++++++++--------------- src/lp_data/Highs.cpp | 49 ------ src/lp_data/HighsInterface.cpp | 134 ++++++++++----- 3 files changed, 250 insertions(+), 235 deletions(-) diff --git a/check/TestMultiObjective.cpp b/check/TestMultiObjective.cpp index 4cd0b729aa..580dcd50e2 100644 --- a/check/TestMultiObjective.cpp +++ b/check/TestMultiObjective.cpp @@ -23,146 +23,166 @@ TEST_CASE("multi-objective", "[util]") { lp.a_matrix_.value_ = {3, 1, 1, 1, 1, 2}; Highs h; h.setOptionValue("output_flag", dev_run); - h.passModel(lp); - HighsLinearObjective linear_objective; - std::vector linear_objectives; - - // Begin with an illegal linear objective - if (dev_run) printf("\nPass illegal linear objective\n"); - linear_objective.weight = -1; - linear_objective.offset = -1; - linear_objective.coefficients = {2, 1, 0}; - linear_objective.abs_tolerance = 0.0; - linear_objective.rel_tolerance = 1.0; - REQUIRE(h.addLinearObjective(linear_objective) == HighsStatus::kError); - - // Now legalise the linear objective so LP has nonunique optimal - // solutions on the line joining (2, 6) and (5, 3) - if (dev_run) printf("\nPass legal linear objective\n"); - linear_objective.coefficients = {1, 1}; - REQUIRE(h.addLinearObjective(linear_objective) == HighsStatus::kOk); - - REQUIRE(h.run() == HighsStatus::kOk); - h.writeSolution("", kSolutionStylePretty); - REQUIRE(smallDoubleDifference(h.getInfo().objective_function_value, -7)); - // Save the linear objective for the next - linear_objectives.push_back(linear_objective); - - // Add a second linear objective with a very small minimization - // weight that should push the optimal solution to (2, 6) - if (dev_run) printf("\nPass second linear objective\n"); - linear_objective.weight = 1e-4; - linear_objective.offset = 0; - linear_objective.coefficients = {1, 0}; - REQUIRE(h.addLinearObjective(linear_objective) == HighsStatus::kOk); - - REQUIRE(h.run() == HighsStatus::kOk); - h.writeSolution("", kSolutionStylePretty); - REQUIRE(smallDoubleDifference(h.getSolution().col_value[0], 2)); - REQUIRE(smallDoubleDifference(h.getSolution().col_value[1], 6)); - linear_objectives.push_back(linear_objective); - - if (dev_run) printf("\nClear and pass two linear objectives\n"); - REQUIRE(h.clearLinearObjectives() == HighsStatus::kOk); - REQUIRE(h.passLinearObjectives(2, linear_objectives.data()) == - HighsStatus::kOk); - REQUIRE(h.run() == HighsStatus::kOk); - h.writeSolution("", kSolutionStylePretty); - REQUIRE(smallDoubleDifference(h.getSolution().col_value[0], 2)); - REQUIRE(smallDoubleDifference(h.getSolution().col_value[1], 6)); - - // Set illegal priorities - that can be passed OK since - // blend_multi_objectives = true - if (dev_run) - printf( - "\nSetting priorities that will be illegal when using lexicographic " - "optimization\n"); - linear_objectives[0].priority = 0; - linear_objectives[1].priority = 0; - REQUIRE(h.passLinearObjectives(2, linear_objectives.data()) == - HighsStatus::kOk); - - // Now test lexicographic optimization - h.setOptionValue("blend_multi_objectives", false); - - if (dev_run) printf("\nLexicographic using illegal priorities\n"); - REQUIRE(h.run() == HighsStatus::kError); - - if (dev_run) - printf( - "\nSetting priorities that are illegal now blend_multi_objectives = " - "false\n"); - REQUIRE(h.passLinearObjectives(2, linear_objectives.data()) == - HighsStatus::kError); - - if (dev_run) - printf("\nSetting legal priorities for blend_multi_objectives = false\n"); - linear_objectives[0].priority = 10; - REQUIRE(h.passLinearObjectives(2, linear_objectives.data()) == - HighsStatus::kOk); - - if (dev_run) printf("\nLexicographic using existing multi objective data\n"); - REQUIRE(h.run() == HighsStatus::kOk); - h.writeSolution("", kSolutionStylePretty); - REQUIRE(smallDoubleDifference(h.getSolution().col_value[0], 2)); - REQUIRE(smallDoubleDifference(h.getSolution().col_value[1], 6)); - - // Back to blending - h.setOptionValue("blend_multi_objectives", true); - // h.setOptionValue("output_flag", true); - REQUIRE(h.clearLinearObjectives() == HighsStatus::kOk); - linear_objectives[0].coefficients = {1.0001, 1}; - linear_objectives[0].abs_tolerance = -1e-5; - linear_objectives[0].rel_tolerance = 0.95; - - if (dev_run) printf("\nBlending: Illegal abs_tolerance \n"); - REQUIRE(h.passLinearObjectives(1, linear_objectives.data()) == - HighsStatus::kError); - linear_objectives[0].abs_tolerance = 1e-5; - - if (dev_run) printf("\nBlending: Illegal rel_tolerance \n"); - REQUIRE(h.passLinearObjectives(1, linear_objectives.data()) == - HighsStatus::kError); - linear_objectives[0].rel_tolerance = 1.05; - - linear_objectives[1].weight = 1e-3; - if (dev_run) - printf( - "\nBlending: first solve objective just giving unique optimal " - "solution\n"); - REQUIRE(h.passLinearObjectives(1, linear_objectives.data()) == - HighsStatus::kOk); - - REQUIRE(h.run() == HighsStatus::kOk); - h.writeSolution("", kSolutionStylePretty); - - REQUIRE(h.passLinearObjectives(2, linear_objectives.data()) == - HighsStatus::kOk); - - REQUIRE(h.run() == HighsStatus::kOk); - h.writeSolution("", kSolutionStylePretty); - - // Back to lexicographic optimization - h.setOptionValue("blend_multi_objectives", false); - - if (dev_run) printf("\nLexicographic using non-trivial tolerances\n"); - REQUIRE(h.run() == HighsStatus::kOk); - h.writeSolution("", kSolutionStylePretty); - - REQUIRE(smallDoubleDifference(h.getSolution().col_value[0], 4.9)); - REQUIRE(smallDoubleDifference(h.getSolution().col_value[1], 3.1)); - - linear_objectives[0].abs_tolerance = kHighsInf; - - REQUIRE(h.passLinearObjectives(2, linear_objectives.data()) == - HighsStatus::kOk); - - REQUIRE(h.run() == HighsStatus::kOk); - h.writeSolution("", kSolutionStylePretty); - - // printf("Solution = [%23.18g, %23.18g]\n", h.getSolution().col_value[0], - // h.getSolution().col_value[1]); - REQUIRE(smallDoubleDifference(h.getSolution().col_value[0], 1.30069)); - REQUIRE(smallDoubleDifference(h.getSolution().col_value[1], 6.34966)); + for (HighsInt k = 0; k < 2; k++) { + // Pass 0 is continuous; pass 1 integer + if (dev_run) + printf("\n******************\nPass %d: var type is %s\n******************\n", int(k), k==0 ? "continuous" : "integer"); + for (HighsInt l = 0; l < 2; l++) { + // Pass 0 is with unsigned weights and coefficients + double obj_mu = l == 0 ? 1 : -1; + if (dev_run) + printf("\n******************\nPass %d: objective multiplier is %g\n******************\n", int(l), obj_mu); + + if (k == 0) { + lp.integrality_.clear(); + } else if (k == 1) { + lp.integrality_ = {HighsVarType::kInteger, HighsVarType::kInteger}; + } + h.passModel(lp); + + h.setOptionValue("blend_multi_objectives", true); + + HighsLinearObjective linear_objective; + std::vector linear_objectives; + REQUIRE(h.clearLinearObjectives() == HighsStatus::kOk); + + // Begin with an illegal linear objective + if (dev_run) printf("\nPass illegal linear objective\n"); + linear_objective.weight = -obj_mu; + linear_objective.offset = -obj_mu; + linear_objective.coefficients = {obj_mu*2, obj_mu*1, obj_mu*0}; + linear_objective.abs_tolerance = 0.0; + linear_objective.rel_tolerance = 0.0; + REQUIRE(h.addLinearObjective(linear_objective) == HighsStatus::kError); + // Now legalise the linear objective so LP has nonunique optimal + // solutions on the line joining (2, 6) and (5, 3) + if (dev_run) printf("\nPass legal linear objective\n"); + linear_objective.coefficients = {obj_mu*1, obj_mu*1}; + REQUIRE(h.addLinearObjective(linear_objective) == HighsStatus::kOk); + + REQUIRE(h.run() == HighsStatus::kOk); + h.writeSolution("", kSolutionStylePretty); + REQUIRE(smallDoubleDifference(h.getInfo().objective_function_value, -7)); + // Save the linear objective for the next + linear_objectives.push_back(linear_objective); + + // Add a second linear objective with a very small minimization + // weight that should push the optimal solution to (2, 6) + if (dev_run) printf("\nPass second linear objective\n"); + linear_objective.weight = obj_mu*1e-4; + linear_objective.offset = 0; + linear_objective.coefficients = {obj_mu*1, obj_mu*0}; + REQUIRE(h.addLinearObjective(linear_objective) == HighsStatus::kOk); + + REQUIRE(h.run() == HighsStatus::kOk); + h.writeSolution("", kSolutionStylePretty); + REQUIRE(smallDoubleDifference(h.getSolution().col_value[0], 2)); + REQUIRE(smallDoubleDifference(h.getSolution().col_value[1], 6)); + linear_objectives.push_back(linear_objective); + + if (dev_run) printf("\nClear and pass two linear objectives\n"); + REQUIRE(h.clearLinearObjectives() == HighsStatus::kOk); + REQUIRE(h.passLinearObjectives(2, linear_objectives.data()) == + HighsStatus::kOk); + REQUIRE(h.run() == HighsStatus::kOk); + h.writeSolution("", kSolutionStylePretty); + REQUIRE(smallDoubleDifference(h.getSolution().col_value[0], 2)); + REQUIRE(smallDoubleDifference(h.getSolution().col_value[1], 6)); + + // Set illegal priorities - that can be passed OK since + // blend_multi_objectives = true + if (dev_run) + printf( + "\nSetting priorities that will be illegal when using lexicographic " + "optimization\n"); + linear_objectives[0].priority = 0; + linear_objectives[1].priority = 0; + REQUIRE(h.passLinearObjectives(2, linear_objectives.data()) == + HighsStatus::kOk); + + // Now test lexicographic optimization + h.setOptionValue("blend_multi_objectives", false); + + if (dev_run) printf("\nLexicographic using illegal priorities\n"); + REQUIRE(h.run() == HighsStatus::kError); + + if (dev_run) + printf( + "\nSetting priorities that are illegal now blend_multi_objectives = " + "false\n"); + REQUIRE(h.passLinearObjectives(2, linear_objectives.data()) == + HighsStatus::kError); + + if (dev_run) + printf("\nSetting legal priorities for blend_multi_objectives = false\n"); + linear_objectives[0].priority = 10; + REQUIRE(h.passLinearObjectives(2, linear_objectives.data()) == + HighsStatus::kOk); + + if (dev_run) printf("\nLexicographic using existing multi objective data\n"); + REQUIRE(h.run() == HighsStatus::kOk); + h.writeSolution("", kSolutionStylePretty); + REQUIRE(smallDoubleDifference(h.getSolution().col_value[0], 2)); + REQUIRE(smallDoubleDifference(h.getSolution().col_value[1], 6)); + + // Back to blending + h.setOptionValue("blend_multi_objectives", true); + // h.setOptionValue("output_flag", true); + REQUIRE(h.clearLinearObjectives() == HighsStatus::kOk); + linear_objectives[0].coefficients = {obj_mu*1.0001, obj_mu*1}; + linear_objectives[0].abs_tolerance = 1e-5; + linear_objectives[0].rel_tolerance = 0.05; + linear_objectives[1].weight = obj_mu*1e-3; + if (dev_run) + printf( + "\nBlending: first solve objective just giving unique optimal " + "solution\n"); + REQUIRE(h.passLinearObjectives(1, linear_objectives.data()) == + HighsStatus::kOk); + + REQUIRE(h.run() == HighsStatus::kOk); + h.writeSolution("", kSolutionStylePretty); + + REQUIRE(h.passLinearObjectives(2, linear_objectives.data()) == + HighsStatus::kOk); + + REQUIRE(h.run() == HighsStatus::kOk); + h.writeSolution("", kSolutionStylePretty); + + // Back to lexicographic optimization + h.setOptionValue("blend_multi_objectives", false); + + if (dev_run) printf("\nLexicographic using non-trivial tolerances\n"); + REQUIRE(h.run() == HighsStatus::kOk); + h.writeSolution("", kSolutionStylePretty); + + if (k == 0) { + REQUIRE(smallDoubleDifference(h.getSolution().col_value[0], 4.9)); + REQUIRE(smallDoubleDifference(h.getSolution().col_value[1], 3.1)); + } else { + REQUIRE(smallDoubleDifference(h.getSolution().col_value[0], 5)); + REQUIRE(smallDoubleDifference(h.getSolution().col_value[1], 3)); + } + + linear_objectives[0].abs_tolerance = kHighsInf; + + REQUIRE(h.passLinearObjectives(2, linear_objectives.data()) == + HighsStatus::kOk); + + REQUIRE(h.run() == HighsStatus::kOk); + h.writeSolution("", kSolutionStylePretty); + + // printf("Solution = [%23.18g, %23.18g]\n", h.getSolution().col_value[0], + // h.getSolution().col_value[1]); + if (k == 0) { + REQUIRE(smallDoubleDifference(h.getSolution().col_value[0], 1.30069)); + REQUIRE(smallDoubleDifference(h.getSolution().col_value[1], 6.34966)); + } else { + REQUIRE(smallDoubleDifference(h.getSolution().col_value[0], 2)); + REQUIRE(smallDoubleDifference(h.getSolution().col_value[1], 6)); + } + } + } } + diff --git a/src/lp_data/Highs.cpp b/src/lp_data/Highs.cpp index 9a3bd54c06..6352a68a09 100644 --- a/src/lp_data/Highs.cpp +++ b/src/lp_data/Highs.cpp @@ -4513,55 +4513,6 @@ HighsStatus Highs::returnFromRun(const HighsStatus run_return_status, return returnFromHighs(return_status); } -HighsStatus Highs::returnFromLexicographicOptimization( - HighsStatus return_status, HighsInt original_lp_num_row) { - const bool lexicographic_optimization_logging = false; - if (lexicographic_optimization_logging) - printf("\nOn return, model status is %s\n", - this->modelStatusToString(this->model_status_).c_str()); - - // Save model_status_ and info_ since they are cleared by calling - // deleteRows - HighsModelStatus model_status = this->model_status_; - HighsInfo info = this->info_; - if (lexicographic_optimization_logging) - writeInfoToFile(stdout, true, info_.records, HighsFileType::kMinimal); - - HighsInt num_linear_objective = this->multi_linear_objective_.size(); - if (num_linear_objective > 1) { - this->deleteRows(original_lp_num_row, this->model_.lp_.num_row_ - 1); - if (lexicographic_optimization_logging) - printf("\nAfter deleteRows, model status %s\n", - this->modelStatusToString(model_status_).c_str()); - - // Recover model_status_ and info_, and then account for lack of basis or - // dual solution - this->model_status_ = model_status; - this->info_ = info; - info_.objective_function_value = 0; - info_.basis_validity = kBasisValidityInvalid; - info_.dual_solution_status = kSolutionStatusNone; - info_.num_dual_infeasibilities = kHighsIllegalInfeasibilityCount; - info_.max_dual_infeasibility = kHighsIllegalInfeasibilityMeasure; - info_.sum_dual_infeasibilities = kHighsIllegalInfeasibilityMeasure; - info_.max_complementarity_violation = kHighsIllegalComplementarityViolation; - info_.sum_complementarity_violations = - kHighsIllegalComplementarityViolation; - this->solution_.value_valid = true; - - if (lexicographic_optimization_logging) { - printf("On return solution is\n"); - for (HighsInt iCol = 0; iCol < this->model_.lp_.num_col_; iCol++) - printf("Col %2d Primal = %11.6g; Dual = %11.6g\n", int(iCol), - solution_.col_value[iCol], solution_.col_value[iCol]); - for (HighsInt iRow = 0; iRow < this->model_.lp_.num_row_; iRow++) - printf("Row %2d Primal = %11.6g; Dual = %11.6g\n", int(iRow), - solution_.row_value[iRow], solution_.row_value[iRow]); - } - } - return return_status; -} - HighsStatus Highs::returnFromHighs(HighsStatus highs_return_status) { // Applies checks before returning from HiGHS HighsStatus return_status = highs_return_status; diff --git a/src/lp_data/HighsInterface.cpp b/src/lp_data/HighsInterface.cpp index 361431bf04..562b66918a 100644 --- a/src/lp_data/HighsInterface.cpp +++ b/src/lp_data/HighsInterface.cpp @@ -3599,22 +3599,6 @@ bool Highs::validLinearObjective(const HighsLinearObjective& linear_objective, int(this->model_.lp_.num_col_)); return false; } - if (linear_objective.abs_tolerance < 0) { - highsLogUser(options_.log_options, HighsLogType::kError, - "Linear objective %s has illegal absolute linear objective " - "tolerance of %g < 0\n", - iObj >= 0 ? std::to_string(iObj).c_str() : "", - linear_objective.abs_tolerance); - return false; - } - if (linear_objective.rel_tolerance < 1) { - highsLogUser(options_.log_options, HighsLogType::kError, - "Linear objective %s has illegal relative linear objective " - "tolerance of %g < 1\n", - iObj >= 0 ? std::to_string(iObj).c_str() : "", - linear_objective.rel_tolerance); - return false; - } if (!options_.blend_multi_objectives && hasRepeatedLinearObjectivePriorities(&linear_objective)) { highsLogUser( @@ -3652,6 +3636,56 @@ bool comparison(std::pair x1, return x1.first >= x2.first; } +HighsStatus Highs::returnFromLexicographicOptimization( + HighsStatus return_status, HighsInt original_lp_num_row) { + const bool lexicographic_optimization_logging = false; + if (lexicographic_optimization_logging) + printf("\nOn return, model status is %s\n", + this->modelStatusToString(this->model_status_).c_str()); + + // Save model_status_ and info_ since they are cleared by calling + // deleteRows + HighsModelStatus model_status = this->model_status_; + HighsInfo info = this->info_; + if (lexicographic_optimization_logging) + writeInfoToFile(stdout, true, info_.records, HighsFileType::kMinimal); + + HighsInt num_linear_objective = this->multi_linear_objective_.size(); + if (num_linear_objective > 1) { + this->deleteRows(original_lp_num_row, this->model_.lp_.num_row_ - 1); + if (lexicographic_optimization_logging) + printf("\nAfter deleteRows, model status %s\n", + this->modelStatusToString(model_status_).c_str()); + + // Recover model_status_ and info_, and then account for lack of basis or + // dual solution + this->model_status_ = model_status; + this->info_ = info; + info_.objective_function_value = 0; + info_.basis_validity = kBasisValidityInvalid; + info_.dual_solution_status = kSolutionStatusNone; + info_.num_dual_infeasibilities = kHighsIllegalInfeasibilityCount; + info_.max_dual_infeasibility = kHighsIllegalInfeasibilityMeasure; + info_.sum_dual_infeasibilities = kHighsIllegalInfeasibilityMeasure; + info_.max_complementarity_violation = kHighsIllegalComplementarityViolation; + info_.sum_complementarity_violations = + kHighsIllegalComplementarityViolation; + this->solution_.value_valid = true; + + if (lexicographic_optimization_logging) { + printf("On return solution is\n"); + for (HighsInt iCol = 0; iCol < this->model_.lp_.num_col_; iCol++) + printf("Col %2d Primal = %11.6g; Dual = %11.6g\n", int(iCol), + solution_.col_value[iCol], solution_.col_value[iCol]); + for (HighsInt iRow = 0; iRow < this->model_.lp_.num_row_; iRow++) + printf("Row %2d Primal = %11.6g; Dual = %11.6g\n", int(iRow), + solution_.row_value[iRow], solution_.row_value[iRow]); + } + this->model_.lp_.col_cost_.assign(this->model_.lp_.num_col_, 0); + } + return return_status; +} + HighsStatus Highs::multiobjectiveSolve() { HighsInt num_linear_objective = this->multi_linear_objective_.size(); assert(num_linear_objective > 0); @@ -3721,6 +3755,7 @@ HighsStatus Highs::multiobjectiveSolve() { std::vector index(lp.num_col_); std::vector value(lp.num_col_); for (HighsInt iIx = 0; iIx < num_linear_objective; iIx++) { + HighsInt priority = priority_objective[iIx].first; HighsInt iObj = priority_objective[iIx].second; printf("\nHighs::run() Entry %d is objective %d with priority %d\n", int(iIx), int(iObj), int(priority_objective[iIx].first)); @@ -3742,7 +3777,7 @@ HighsStatus Highs::multiobjectiveSolve() { original_lp_num_row); if (model_status_ != HighsModelStatus::kOptimal) { highsLogUser(options_.log_options, HighsLogType::kWarning, - "After priority %d solve, model status is %s\n", int(), + "After priority %d solve, model status is %s\n", int(priority), modelStatusToString(model_status_).c_str()); return returnFromLexicographicOptimization(HighsStatus::kWarning, original_lp_num_row); @@ -3764,41 +3799,50 @@ HighsStatus Highs::multiobjectiveSolve() { double upper_bound = kHighsInf; if (lp.sense_ == ObjSense::kMinimize) { // Minimizing, so set a greater upper bound than the objective - upper_bound = objective + linear_objective.abs_tolerance; - if (objective >= 0) { - // Guarantees objective of at least t.f^* - // - // so (t.f^*-f^*)/f^* = t-1 - upper_bound = - std::min(objective * linear_objective.rel_tolerance, upper_bound); - } else if (objective < 0) { - // Guarantees objective of at least (2-t).f^* - // - // so ((2-t).f^*-f^*)/f^* = 1-t - upper_bound = std::min( - objective * (2.0 - linear_objective.rel_tolerance), upper_bound); + if (linear_objective.abs_tolerance >= 0) + upper_bound = objective + linear_objective.abs_tolerance; + if (linear_objective.rel_tolerance >= 0) { + if (objective >= 0) { + // Guarantees objective of at least (1+t).f^* + // + // so ((1+t).f^*-f^*)/f^* = t + upper_bound = std::min( + objective * (1.0 + linear_objective.rel_tolerance), upper_bound); + } else if (objective < 0) { + // Guarantees objective of at least (1-t).f^* + // + // so ((1-t).f^*-f^*)/f^* = -t + upper_bound = std::min( + objective * (1.0 - linear_objective.rel_tolerance), upper_bound); + } } upper_bound -= lp.offset_; } else { // Maximizing, so set a lesser lower bound than the objective - lower_bound = objective - linear_objective.abs_tolerance; - if (objective >= 0) { - // Guarantees objective of at most (2-t).f^* - // - // so ((2-t).f^*-f^*)/f^* = 1-t - double lower_bound_from_rel_tolerance = - objective * (2.0 - linear_objective.rel_tolerance); - lower_bound = std::max( - objective * (2.0 - linear_objective.rel_tolerance), lower_bound); - } else if (objective < 0) { - // Guarantees objective of at least t.f^* - // - // so (t.f^*-f^*)/f^* = t-1 - lower_bound = - std::max(objective * linear_objective.rel_tolerance, lower_bound); + if (linear_objective.abs_tolerance >= 0) + lower_bound = objective - linear_objective.abs_tolerance; + if (linear_objective.rel_tolerance >= 0) { + if (objective >= 0) { + // Guarantees objective of at most (1-t).f^* + // + // so ((1-t).f^*-f^*)/f^* = -t + lower_bound = std::max( + objective * (1.0 - linear_objective.rel_tolerance), lower_bound); + } else if (objective < 0) { + // Guarantees objective of at least (1+t).f^* + // + // so ((1+t).f^*-f^*)/f^* = t + lower_bound = std::max( + objective * (1.0 + linear_objective.rel_tolerance), lower_bound); + } } lower_bound -= lp.offset_; } + if (lower_bound == -kHighsInf && upper_bound == kHighsInf) + highsLogUser(options_.log_options, HighsLogType::kWarning, + "After priority %d solve, no objective constraint due to absolute tolerance being %g < 0," + " and relative tolerance being %g < 0\n", + int(priority), linear_objective.abs_tolerance, linear_objective.rel_tolerance); printf("Highs::run() Add objective constraint %g <= ", lower_bound); for (HighsInt iEl = 0; iEl < nnz; iEl++) printf(" + (%g) x[%d]", value[iEl], int(index[iEl])); From 714ce34828ebb48de57b31ca544a532b2b1ac969 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Fri, 22 Nov 2024 10:08:27 +0000 Subject: [PATCH 53/75] Removed dev logging; formatted --- check/TestMultiObjective.cpp | 118 ++++++++++++++++++--------------- src/lp_data/HighsInterface.cpp | 111 +++++++++++-------------------- 2 files changed, 100 insertions(+), 129 deletions(-) diff --git a/check/TestMultiObjective.cpp b/check/TestMultiObjective.cpp index 580dcd50e2..9761c3045b 100644 --- a/check/TestMultiObjective.cpp +++ b/check/TestMultiObjective.cpp @@ -26,18 +26,23 @@ TEST_CASE("multi-objective", "[util]") { for (HighsInt k = 0; k < 2; k++) { // Pass 0 is continuous; pass 1 integer - if (dev_run) - printf("\n******************\nPass %d: var type is %s\n******************\n", int(k), k==0 ? "continuous" : "integer"); + if (dev_run) + printf( + "\n******************\nPass %d: var type is %s\n******************\n", + int(k), k == 0 ? "continuous" : "integer"); for (HighsInt l = 0; l < 2; l++) { // Pass 0 is with unsigned weights and coefficients double obj_mu = l == 0 ? 1 : -1; - if (dev_run) - printf("\n******************\nPass %d: objective multiplier is %g\n******************\n", int(l), obj_mu); + if (dev_run) + printf( + "\n******************\nPass %d: objective multiplier is " + "%g\n******************\n", + int(l), obj_mu); if (k == 0) { - lp.integrality_.clear(); + lp.integrality_.clear(); } else if (k == 1) { - lp.integrality_ = {HighsVarType::kInteger, HighsVarType::kInteger}; + lp.integrality_ = {HighsVarType::kInteger, HighsVarType::kInteger}; } h.passModel(lp); @@ -51,14 +56,14 @@ TEST_CASE("multi-objective", "[util]") { if (dev_run) printf("\nPass illegal linear objective\n"); linear_objective.weight = -obj_mu; linear_objective.offset = -obj_mu; - linear_objective.coefficients = {obj_mu*2, obj_mu*1, obj_mu*0}; + linear_objective.coefficients = {obj_mu * 2, obj_mu * 1, obj_mu * 0}; linear_objective.abs_tolerance = 0.0; linear_objective.rel_tolerance = 0.0; REQUIRE(h.addLinearObjective(linear_objective) == HighsStatus::kError); // Now legalise the linear objective so LP has nonunique optimal // solutions on the line joining (2, 6) and (5, 3) if (dev_run) printf("\nPass legal linear objective\n"); - linear_objective.coefficients = {obj_mu*1, obj_mu*1}; + linear_objective.coefficients = {obj_mu * 1, obj_mu * 1}; REQUIRE(h.addLinearObjective(linear_objective) == HighsStatus::kOk); REQUIRE(h.run() == HighsStatus::kOk); @@ -70,9 +75,9 @@ TEST_CASE("multi-objective", "[util]") { // Add a second linear objective with a very small minimization // weight that should push the optimal solution to (2, 6) if (dev_run) printf("\nPass second linear objective\n"); - linear_objective.weight = obj_mu*1e-4; + linear_objective.weight = obj_mu * 1e-4; linear_objective.offset = 0; - linear_objective.coefficients = {obj_mu*1, obj_mu*0}; + linear_objective.coefficients = {obj_mu * 1, obj_mu * 0}; REQUIRE(h.addLinearObjective(linear_objective) == HighsStatus::kOk); REQUIRE(h.run() == HighsStatus::kOk); @@ -84,7 +89,7 @@ TEST_CASE("multi-objective", "[util]") { if (dev_run) printf("\nClear and pass two linear objectives\n"); REQUIRE(h.clearLinearObjectives() == HighsStatus::kOk); REQUIRE(h.passLinearObjectives(2, linear_objectives.data()) == - HighsStatus::kOk); + HighsStatus::kOk); REQUIRE(h.run() == HighsStatus::kOk); h.writeSolution("", kSolutionStylePretty); REQUIRE(smallDoubleDifference(h.getSolution().col_value[0], 2)); @@ -93,96 +98,99 @@ TEST_CASE("multi-objective", "[util]") { // Set illegal priorities - that can be passed OK since // blend_multi_objectives = true if (dev_run) - printf( - "\nSetting priorities that will be illegal when using lexicographic " - "optimization\n"); + printf( + "\nSetting priorities that will be illegal when using " + "lexicographic " + "optimization\n"); linear_objectives[0].priority = 0; linear_objectives[1].priority = 0; REQUIRE(h.passLinearObjectives(2, linear_objectives.data()) == - HighsStatus::kOk); + HighsStatus::kOk); // Now test lexicographic optimization h.setOptionValue("blend_multi_objectives", false); if (dev_run) printf("\nLexicographic using illegal priorities\n"); REQUIRE(h.run() == HighsStatus::kError); - + if (dev_run) - printf( - "\nSetting priorities that are illegal now blend_multi_objectives = " - "false\n"); + printf( + "\nSetting priorities that are illegal now blend_multi_objectives " + "= " + "false\n"); REQUIRE(h.passLinearObjectives(2, linear_objectives.data()) == - HighsStatus::kError); - + HighsStatus::kError); + if (dev_run) - printf("\nSetting legal priorities for blend_multi_objectives = false\n"); + printf( + "\nSetting legal priorities for blend_multi_objectives = false\n"); linear_objectives[0].priority = 10; REQUIRE(h.passLinearObjectives(2, linear_objectives.data()) == - HighsStatus::kOk); - - if (dev_run) printf("\nLexicographic using existing multi objective data\n"); + HighsStatus::kOk); + + if (dev_run) + printf("\nLexicographic using existing multi objective data\n"); REQUIRE(h.run() == HighsStatus::kOk); h.writeSolution("", kSolutionStylePretty); REQUIRE(smallDoubleDifference(h.getSolution().col_value[0], 2)); REQUIRE(smallDoubleDifference(h.getSolution().col_value[1], 6)); - + // Back to blending h.setOptionValue("blend_multi_objectives", true); // h.setOptionValue("output_flag", true); REQUIRE(h.clearLinearObjectives() == HighsStatus::kOk); - linear_objectives[0].coefficients = {obj_mu*1.0001, obj_mu*1}; + linear_objectives[0].coefficients = {obj_mu * 1.0001, obj_mu * 1}; linear_objectives[0].abs_tolerance = 1e-5; linear_objectives[0].rel_tolerance = 0.05; - linear_objectives[1].weight = obj_mu*1e-3; + linear_objectives[1].weight = obj_mu * 1e-3; if (dev_run) - printf( - "\nBlending: first solve objective just giving unique optimal " - "solution\n"); + printf( + "\nBlending: first solve objective just giving unique optimal " + "solution\n"); REQUIRE(h.passLinearObjectives(1, linear_objectives.data()) == - HighsStatus::kOk); - + HighsStatus::kOk); + REQUIRE(h.run() == HighsStatus::kOk); h.writeSolution("", kSolutionStylePretty); - + REQUIRE(h.passLinearObjectives(2, linear_objectives.data()) == - HighsStatus::kOk); - + HighsStatus::kOk); + REQUIRE(h.run() == HighsStatus::kOk); h.writeSolution("", kSolutionStylePretty); - + // Back to lexicographic optimization h.setOptionValue("blend_multi_objectives", false); - + if (dev_run) printf("\nLexicographic using non-trivial tolerances\n"); REQUIRE(h.run() == HighsStatus::kOk); h.writeSolution("", kSolutionStylePretty); - + if (k == 0) { - REQUIRE(smallDoubleDifference(h.getSolution().col_value[0], 4.9)); - REQUIRE(smallDoubleDifference(h.getSolution().col_value[1], 3.1)); + REQUIRE(smallDoubleDifference(h.getSolution().col_value[0], 4.9)); + REQUIRE(smallDoubleDifference(h.getSolution().col_value[1], 3.1)); } else { - REQUIRE(smallDoubleDifference(h.getSolution().col_value[0], 5)); - REQUIRE(smallDoubleDifference(h.getSolution().col_value[1], 3)); + REQUIRE(smallDoubleDifference(h.getSolution().col_value[0], 5)); + REQUIRE(smallDoubleDifference(h.getSolution().col_value[1], 3)); } - + linear_objectives[0].abs_tolerance = kHighsInf; - + REQUIRE(h.passLinearObjectives(2, linear_objectives.data()) == - HighsStatus::kOk); - + HighsStatus::kOk); + REQUIRE(h.run() == HighsStatus::kOk); h.writeSolution("", kSolutionStylePretty); - - // printf("Solution = [%23.18g, %23.18g]\n", h.getSolution().col_value[0], - // h.getSolution().col_value[1]); + + // printf("Solution = [%23.18g, %23.18g]\n", + // h.getSolution().col_value[0], h.getSolution().col_value[1]); if (k == 0) { - REQUIRE(smallDoubleDifference(h.getSolution().col_value[0], 1.30069)); - REQUIRE(smallDoubleDifference(h.getSolution().col_value[1], 6.34966)); + REQUIRE(smallDoubleDifference(h.getSolution().col_value[0], 1.30069)); + REQUIRE(smallDoubleDifference(h.getSolution().col_value[1], 6.34966)); } else { - REQUIRE(smallDoubleDifference(h.getSolution().col_value[0], 2)); - REQUIRE(smallDoubleDifference(h.getSolution().col_value[1], 6)); + REQUIRE(smallDoubleDifference(h.getSolution().col_value[0], 2)); + REQUIRE(smallDoubleDifference(h.getSolution().col_value[1], 6)); } } } } - diff --git a/src/lp_data/HighsInterface.cpp b/src/lp_data/HighsInterface.cpp index 562b66918a..7f472a55fe 100644 --- a/src/lp_data/HighsInterface.cpp +++ b/src/lp_data/HighsInterface.cpp @@ -3638,25 +3638,13 @@ bool comparison(std::pair x1, HighsStatus Highs::returnFromLexicographicOptimization( HighsStatus return_status, HighsInt original_lp_num_row) { - const bool lexicographic_optimization_logging = false; - if (lexicographic_optimization_logging) - printf("\nOn return, model status is %s\n", - this->modelStatusToString(this->model_status_).c_str()); - // Save model_status_ and info_ since they are cleared by calling // deleteRows HighsModelStatus model_status = this->model_status_; HighsInfo info = this->info_; - if (lexicographic_optimization_logging) - writeInfoToFile(stdout, true, info_.records, HighsFileType::kMinimal); - HighsInt num_linear_objective = this->multi_linear_objective_.size(); if (num_linear_objective > 1) { this->deleteRows(original_lp_num_row, this->model_.lp_.num_row_ - 1); - if (lexicographic_optimization_logging) - printf("\nAfter deleteRows, model status %s\n", - this->modelStatusToString(model_status_).c_str()); - // Recover model_status_ and info_, and then account for lack of basis or // dual solution this->model_status_ = model_status; @@ -3671,16 +3659,6 @@ HighsStatus Highs::returnFromLexicographicOptimization( info_.sum_complementarity_violations = kHighsIllegalComplementarityViolation; this->solution_.value_valid = true; - - if (lexicographic_optimization_logging) { - printf("On return solution is\n"); - for (HighsInt iCol = 0; iCol < this->model_.lp_.num_col_; iCol++) - printf("Col %2d Primal = %11.6g; Dual = %11.6g\n", int(iCol), - solution_.col_value[iCol], solution_.col_value[iCol]); - for (HighsInt iRow = 0; iRow < this->model_.lp_.num_row_; iRow++) - printf("Row %2d Primal = %11.6g; Dual = %11.6g\n", int(iRow), - solution_.row_value[iRow], solution_.row_value[iRow]); - } this->model_.lp_.col_cost_.assign(this->model_.lp_.num_col_, 0); } return return_status; @@ -3718,11 +3696,6 @@ HighsStatus Highs::multiobjectiveSolve() { multi_linear_objective.coefficients[iCol]; } lp.sense_ = ObjSense::kMinimize; - printf("Highs::run() LP objective function is %s %g ", - lp.sense_ == ObjSense::kMinimize ? "min" : "max", lp.offset_); - for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) - printf(" + (%g) x[%d]", lp.col_cost_[iCol], int(iCol)); - printf("\n"); return this->solve(); } @@ -3757,8 +3730,6 @@ HighsStatus Highs::multiobjectiveSolve() { for (HighsInt iIx = 0; iIx < num_linear_objective; iIx++) { HighsInt priority = priority_objective[iIx].first; HighsInt iObj = priority_objective[iIx].second; - printf("\nHighs::run() Entry %d is objective %d with priority %d\n", - int(iIx), int(iObj), int(priority_objective[iIx].first)); // Use this objective HighsLinearObjective& linear_objective = this->multi_linear_objective_[iObj]; @@ -3766,19 +3737,14 @@ HighsStatus Highs::multiobjectiveSolve() { lp.col_cost_ = linear_objective.coefficients; lp.sense_ = linear_objective.weight > 0 ? ObjSense::kMinimize : ObjSense::kMaximize; - printf("Highs::run() LP objective function is %s %g ", - lp.sense_ == ObjSense::kMinimize ? "min" : "max", lp.offset_); - for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) - printf(" + %g x[%d]", lp.col_cost_[iCol], int(iCol)); - printf("\n"); HighsStatus solve_status = this->solve(); if (solve_status == HighsStatus::kError) return returnFromLexicographicOptimization(HighsStatus::kError, original_lp_num_row); if (model_status_ != HighsModelStatus::kOptimal) { highsLogUser(options_.log_options, HighsLogType::kWarning, - "After priority %d solve, model status is %s\n", int(priority), - modelStatusToString(model_status_).c_str()); + "After priority %d solve, model status is %s\n", + int(priority), modelStatusToString(model_status_).c_str()); return returnFromLexicographicOptimization(HighsStatus::kWarning, original_lp_num_row); } @@ -3800,54 +3766,51 @@ HighsStatus Highs::multiobjectiveSolve() { if (lp.sense_ == ObjSense::kMinimize) { // Minimizing, so set a greater upper bound than the objective if (linear_objective.abs_tolerance >= 0) - upper_bound = objective + linear_objective.abs_tolerance; + upper_bound = objective + linear_objective.abs_tolerance; if (linear_objective.rel_tolerance >= 0) { - if (objective >= 0) { - // Guarantees objective of at least (1+t).f^* - // - // so ((1+t).f^*-f^*)/f^* = t - upper_bound = std::min( - objective * (1.0 + linear_objective.rel_tolerance), upper_bound); - } else if (objective < 0) { - // Guarantees objective of at least (1-t).f^* - // - // so ((1-t).f^*-f^*)/f^* = -t - upper_bound = std::min( - objective * (1.0 - linear_objective.rel_tolerance), upper_bound); - } + if (objective >= 0) { + // Guarantees objective of at least (1+t).f^* + // + // so ((1+t).f^*-f^*)/f^* = t + upper_bound = std::min( + objective * (1.0 + linear_objective.rel_tolerance), upper_bound); + } else if (objective < 0) { + // Guarantees objective of at least (1-t).f^* + // + // so ((1-t).f^*-f^*)/f^* = -t + upper_bound = std::min( + objective * (1.0 - linear_objective.rel_tolerance), upper_bound); + } } upper_bound -= lp.offset_; } else { // Maximizing, so set a lesser lower bound than the objective if (linear_objective.abs_tolerance >= 0) - lower_bound = objective - linear_objective.abs_tolerance; + lower_bound = objective - linear_objective.abs_tolerance; if (linear_objective.rel_tolerance >= 0) { - if (objective >= 0) { - // Guarantees objective of at most (1-t).f^* - // - // so ((1-t).f^*-f^*)/f^* = -t - lower_bound = std::max( - objective * (1.0 - linear_objective.rel_tolerance), lower_bound); - } else if (objective < 0) { - // Guarantees objective of at least (1+t).f^* - // - // so ((1+t).f^*-f^*)/f^* = t - lower_bound = std::max( - objective * (1.0 + linear_objective.rel_tolerance), lower_bound); - } + if (objective >= 0) { + // Guarantees objective of at most (1-t).f^* + // + // so ((1-t).f^*-f^*)/f^* = -t + lower_bound = std::max( + objective * (1.0 - linear_objective.rel_tolerance), lower_bound); + } else if (objective < 0) { + // Guarantees objective of at least (1+t).f^* + // + // so ((1+t).f^*-f^*)/f^* = t + lower_bound = std::max( + objective * (1.0 + linear_objective.rel_tolerance), lower_bound); + } } lower_bound -= lp.offset_; } - if (lower_bound == -kHighsInf && upper_bound == kHighsInf) - highsLogUser(options_.log_options, HighsLogType::kWarning, - "After priority %d solve, no objective constraint due to absolute tolerance being %g < 0," - " and relative tolerance being %g < 0\n", - int(priority), linear_objective.abs_tolerance, linear_objective.rel_tolerance); - printf("Highs::run() Add objective constraint %g <= ", lower_bound); - for (HighsInt iEl = 0; iEl < nnz; iEl++) - printf(" + (%g) x[%d]", value[iEl], int(index[iEl])); - printf(" <= %g\n", upper_bound); - + if (lower_bound == -kHighsInf && upper_bound == kHighsInf) + highsLogUser(options_.log_options, HighsLogType::kWarning, + "After priority %d solve, no objective constraint due to " + "absolute tolerance being %g < 0," + " and relative tolerance being %g < 0\n", + int(priority), linear_objective.abs_tolerance, + linear_objective.rel_tolerance); add_row_status = this->addRow(lower_bound, upper_bound, nnz, index.data(), value.data()); assert(add_row_status == HighsStatus::kOk); From e38350721bb2687201a709c69c0b42ce75d5d6f3 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Fri, 22 Nov 2024 10:58:27 +0000 Subject: [PATCH 54/75] Started modifying documentation --- FEATURES.md | 34 +-------- docs/make.jl | 9 ++- docs/src/guide/further.md | 17 +++++ docs/src/interfaces/python/example-py.md | 11 ++- docs/src/options/definitions.md | 5 ++ docs/src/structures/classes/HighsBasis.md | 8 -- docs/src/structures/classes/HighsInfo.md | 80 -------------------- docs/src/structures/classes/HighsSolution.md | 10 --- src/lp_data/HighsOptions.h | 2 + 9 files changed, 43 insertions(+), 133 deletions(-) delete mode 100644 docs/src/structures/classes/HighsBasis.md delete mode 100644 docs/src/structures/classes/HighsInfo.md delete mode 100644 docs/src/structures/classes/HighsSolution.md diff --git a/FEATURES.md b/FEATURES.md index 034eca4232..76f4c4997d 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -2,39 +2,13 @@ ## Code changes -When primal infeasiblity is detected in presolve, no dual ray is available so, previously, the `has_dual_ray` parameter of `Highs::getDualRay` returned false and that was it. Now, if a null pointer is not passed for `dual_ray_value`, `Highs::getDualRay` will compute a dual ray - at the cost of solving the feasiblility LP without presolve. The same is now true for `Highs::getPrimalRay`. `Highs::getDualUnboundednessDirection` has been introduced to determine the product between the constraint matrix and the dual ray, forcing the calculation of the latter if necessary. Once a dual ray is known for the incumbent model in HiGHS, subsequent calls to `Highs::getDualRay` and `Highs::getDualUnboundednessDirection` will be vastly cheaper - -The method `Highs::getDualObjectiveValue` now exitsts to compute the dual objective value, returning `HighsStatus::kError` if it is not possible. - -The method `Highs::getStandardFormLp` now exists to return the incumbent LP in standard form - overlooking any integrality or Hessian. To determine the sizes of the vectors of data, the method is called without specifying pointers to the data arrays. - -Added documentation on the use of presolve when solving an incumbent model, and clarifying the use of the method `Highs::presolve`. - -HiGHS will now read a `MIPLIB` solution file - -Added time limit check to `HPresolve::strengthenInequalities` - -Added `getColIntegrality` to `highspy` - -Now computing the primal-dual integral, reporting it, and making it available as `HighsInfo::primal_dual_integral` - -Trivial primal heuristics "all zero", "all lower bound", "all upper bound", and "lock point" added to the MIP solver - -# Build changes - -Added wheels for Python 3.13 - -Updated command line options and moved them out of the library and into the executable - - - - - - - +HiGHS now handles multiple linear objectives by either blending using weights, or performing lexicographic optimization: see https://ergo-code.github.io/HiGHS/stable/guide/further/#guide-multi-objective-optimization +Fixed minor bug in bound checking in presolve +Fixed bug in `floor(HighsCDouble x)` and `ceil(HighsCDouble x)` when argument is small +Added some sanity checks to Highs::writeLocalModel to prevent segfaults if called directly by a user diff --git a/docs/make.jl b/docs/make.jl index c1f6bc70b9..ab42d07be3 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -64,9 +64,12 @@ Documenter.makedocs( "structures/classes/HighsLp.md", "structures/classes/HighsHessian.md", "structures/classes/HighsModel.md", - "structures/classes/HighsSolution.md", - "structures/classes/HighsBasis.md", - "structures/classes/HighsInfo.md", + ], + "Structures" => Any[, + "structures/structs/HighsSolution.md", + "structures/structs/HighsBasis.md", + "structures/structs/HighsInfo.md", + "structures/structs/HighsLinearObjective.md", ], ], "Callbacks" => "callbacks.md", diff --git a/docs/src/guide/further.md b/docs/src/guide/further.md index 3597928f5b..cd7f6cf730 100644 --- a/docs/src/guide/further.md +++ b/docs/src/guide/further.md @@ -104,3 +104,20 @@ HiGHS. Note that this does not affect how the incumbent model is solved. There are two corresponding [postsolve](@ref Presolve/postsolve) methods, according to whether there are just solution values, or also a basis. + +## [Multi-objective optimization](@id guide-multi-objective-optimization) + +Users can specify multiple linear objectives with respect to which +HiGHS will optimize by either blending them, or by performing +lexicographic optimization according to the truth of the +[blend_multi_objectives](@ref blend_multi_objectives) option. Each +linear objective is represented by the following data, held in the +[HighsLinearObjective](@ref HighsLinearObjective) structure + +- weight: Scalar of type double - The weight of this objective when blending +- offset: Scalar of type double - The offset of this objective +- coefficients: Vector of type double - The coefficients of this objective +- abs\_tolerance: Scalar of type double - The absolute tolerance on this objective when performing lexicographic optimization +- rel\_tolerance: Scalar of type double - The relative tolerance on this objective when performing lexicographic optimization +- priority: Scalar of type HighsInt - The priority of this objective when performing lexicographic optimization + diff --git a/docs/src/interfaces/python/example-py.md b/docs/src/interfaces/python/example-py.md index 50d40c59e7..6fce25e870 100644 --- a/docs/src/interfaces/python/example-py.md +++ b/docs/src/interfaces/python/example-py.md @@ -237,5 +237,12 @@ print('Basis validity = ', h.basisValidityToString(info.basis_validity)) * `presolveRuleTypeToString` * `postsolve` - - +## Multi-objective optimization + +* `passLinearObjectives` +* `addLinearObjective` +* `clearLinearObjectives` + + + + diff --git a/docs/src/options/definitions.md b/docs/src/options/definitions.md index 8055067601..f4e932523c 100644 --- a/docs/src/options/definitions.md +++ b/docs/src/options/definitions.md @@ -353,3 +353,8 @@ - Range: {0, 2147483647} - Default: 4000 +## blend\_multi\_objectives +- Blend multiple objectives or apply lexicographically +- Type: boolean +- Default: "true" + diff --git a/docs/src/structures/classes/HighsBasis.md b/docs/src/structures/classes/HighsBasis.md deleted file mode 100644 index bfc71c7efd..0000000000 --- a/docs/src/structures/classes/HighsBasis.md +++ /dev/null @@ -1,8 +0,0 @@ -# HighsBasis - -The basis of a model is communicated via an instance of the HighsBasis class - -- valid: Scalar of type bool - Indicates whether the basis is valid -- col\_status: Vector of type [HighsBasisStatus](@ref) - Comparison with [HighsBasisStatus](@ref) gives the basis status of a column -- row\_status: Vector of type [HighsBasisStatus](@ref) - Comparison with [HighsBasisStatus](@ref) gives the basis status of a row - diff --git a/docs/src/structures/classes/HighsInfo.md b/docs/src/structures/classes/HighsInfo.md deleted file mode 100644 index f0ddebee4f..0000000000 --- a/docs/src/structures/classes/HighsInfo.md +++ /dev/null @@ -1,80 +0,0 @@ -# HighsInfo - -Scalar information about a solved model is communicated via an instance of the HighsInfo class - -## valid -- Indicates whether the values in a HighsInfo instance are valid -- Type: bool - -## simplex\_iteration\_count -- The number of simplex iterations performed -- Type: integer - -## ipm\_iteration\_count -- The number of interior point iterations performed -- Type: integer - -## crossover\_iteration\_count -- The number of crossover iterations performed -- Type: integer - -## qp\_iteration\_count -- The number of QP iterations performed -- Type: integer - -## primal\_solution\_status -- Comparison with [SolutionStatus](@ref) gives the status of the [primal](@ref Primal-values) solution -- Type: integer - -## dual\_solution\_status -- Comparison with [SolutionStatus](@ref) gives the status of the [dual](@ref Dual-values) solution -- Type: integer - -## basis\_validity -- Comparison with [BasisValidity](@ref) gives the status of any basis information -- Type: integer - -## objective\_function\_value -- The optimal value of the objective function -- Type: double - -## mip\_node\_count -- The number of nodes generated by the MIP solver -- Type: long integer - -## mip\_dual\_bound -- The [dual bound](@ref terminology-mip) for the MIP solver -- Type: double - -## mip\_gap -- The absolute value of the gap between the primal and bounds, relative to the primal bound. -- Type: double - -## max\_integrality\_violation -- The maximum deviation from an integer value over all the discrete variables -- Type: double - -## num\_primal\_infeasibilities -- The number of variables violating a bound by more than the [primal feasibility tolerance](@ref primal_feasibility_tolerance). -- Type: integer - -## max\_primal\_infeasibility -- The maximum violation of a bound on a variable -- Type: double - -## sum\_primal\_infeasibilities -- The sum of violations of bounds by variables -- Type: double - -## num\_dual\_infeasibilities -- The number of variables violating dual feasibility by more than the [dual feasibility tolerance](@ref dual_feasibility_tolerance). -- Type: integer - -## max\_dual\_infeasibility -- The maximum dual feasibility violation -- Type: double - -## sum\_dual\_infeasibilities -- The sum of dual feasibility violations -- Type: double - diff --git a/docs/src/structures/classes/HighsSolution.md b/docs/src/structures/classes/HighsSolution.md deleted file mode 100644 index 78fc48a959..0000000000 --- a/docs/src/structures/classes/HighsSolution.md +++ /dev/null @@ -1,10 +0,0 @@ -# HighsSolution - -The solution of a model is communicated via an instance of the HighsSolution class - -- value\_valid: Scalar of type bool - Indicates whether the column and row values are valid -- dual\_valid: Scalar of type bool - Indicates whether the column and row [duals](@ref Dual-values) are valid -- col\_value: Vector of type double - Values of the columns (variables) -- col\_dual: Vector of type double - Duals of the columns (variables) -- row\_value: Vector of type double - Values of the rows (constraints) -- row\_dual: Vector of type double - Duals of the rows (constraints) diff --git a/src/lp_data/HighsOptions.h b/src/lp_data/HighsOptions.h index abbf7624ae..f7e7142e57 100644 --- a/src/lp_data/HighsOptions.h +++ b/src/lp_data/HighsOptions.h @@ -488,6 +488,8 @@ struct HighsOptionsStruct { pdlp_d_gap_tol(0.0), qp_iteration_limit(0), qp_nullspace_limit(0), + iis_strategy(0), + blend_multi_objectives(false), log_dev_level(0), log_githash(false), solve_relaxation(false), From 423032284cf431b280136cb027184e1b1568b63b Mon Sep 17 00:00:00 2001 From: JAJHall Date: Fri, 22 Nov 2024 12:18:14 +0000 Subject: [PATCH 55/75] Corrected make.jl and format in HighsOptions.h --- docs/make.jl | 2 +- src/lp_data/HighsOptions.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/make.jl b/docs/make.jl index ab42d07be3..c2d48c170b 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -65,7 +65,7 @@ Documenter.makedocs( "structures/classes/HighsHessian.md", "structures/classes/HighsModel.md", ], - "Structures" => Any[, + "Structures" => Any[ "structures/structs/HighsSolution.md", "structures/structs/HighsBasis.md", "structures/structs/HighsInfo.md", diff --git a/src/lp_data/HighsOptions.h b/src/lp_data/HighsOptions.h index f7e7142e57..82424a0adc 100644 --- a/src/lp_data/HighsOptions.h +++ b/src/lp_data/HighsOptions.h @@ -488,8 +488,8 @@ struct HighsOptionsStruct { pdlp_d_gap_tol(0.0), qp_iteration_limit(0), qp_nullspace_limit(0), - iis_strategy(0), - blend_multi_objectives(false), + iis_strategy(0), + blend_multi_objectives(false), log_dev_level(0), log_githash(false), solve_relaxation(false), From 857ee9b34d57576a1dee75c642a7b3a6194bcd5e Mon Sep 17 00:00:00 2001 From: JAJHall Date: Fri, 22 Nov 2024 12:41:05 +0000 Subject: [PATCH 56/75] Added docs/src/structures/structs --- docs/make.jl | 12 +-- docs/src/guide/further.md | 29 +++++++ docs/src/structures/structs/HighsBasis.md | 8 ++ docs/src/structures/structs/HighsInfo.md | 80 +++++++++++++++++++ .../structs/HighsLinearObjective.md | 11 +++ docs/src/structures/structs/HighsSolution.md | 10 +++ 6 files changed, 144 insertions(+), 6 deletions(-) create mode 100644 docs/src/structures/structs/HighsBasis.md create mode 100644 docs/src/structures/structs/HighsInfo.md create mode 100644 docs/src/structures/structs/HighsLinearObjective.md create mode 100644 docs/src/structures/structs/HighsSolution.md diff --git a/docs/make.jl b/docs/make.jl index c2d48c170b..f598979b78 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -65,12 +65,12 @@ Documenter.makedocs( "structures/classes/HighsHessian.md", "structures/classes/HighsModel.md", ], - "Structures" => Any[ - "structures/structs/HighsSolution.md", - "structures/structs/HighsBasis.md", - "structures/structs/HighsInfo.md", - "structures/structs/HighsLinearObjective.md", - ], +# "Structures" => Any[ +# "structures/structs/HighsSolution.md", +# "structures/structs/HighsBasis.md", +# "structures/structs/HighsInfo.md", +# "structures/structs/HighsLinearObjective.md", +# ], ], "Callbacks" => "callbacks.md", "Interfaces" => Any[ diff --git a/docs/src/guide/further.md b/docs/src/guide/further.md index cd7f6cf730..6e51fbb9f1 100644 --- a/docs/src/guide/further.md +++ b/docs/src/guide/further.md @@ -121,3 +121,32 @@ linear objective is represented by the following data, held in the - rel\_tolerance: Scalar of type double - The relative tolerance on this objective when performing lexicographic optimization - priority: Scalar of type HighsInt - The priority of this objective when performing lexicographic optimization +### Methods + +Multi-objective optimization in HiGHS is defined by the following methods + +- passLinearObjectives - Pass multiple linear objectives as their number `num_linear_objective` and pointer to a vector of `HighsLinearObjective` instances, overwriting any previous linear objectives +- addLinearObjective - Add a single `HighsLinearObjective` instance to any already stored in HiGHS +- clearLinearObjectives - Clears any linear objectives stored in HiGHS + +When there is at least one `HighsLinearObjective` instance in HiGHS, +the `col_cost_` data in the incumbent model is ignored. + +### Blending multiple linear objectives + +When [blend_multi_objectives](@ref blend_multi_objectives) is `true`, +as it is by default, any `HighsLinearObjective` instances will be +combined according to the `weight` values, and the resulting objective +will be minimized. Hence, any objectives that should be maximized +within the combination must have a negative `weight` value. + +### Lexicographic optimization of multiple linear objectives + +When [blend_multi_objectives](@ref blend_multi_objectives) is `false`, +HiGHS will optimize lexicographically with respect to any +`HighsLinearObjective` instances. This is carried out according to the +`priority` values in `HighsLinearObjective` instances. Note that all +priority values must be distinct. + + + diff --git a/docs/src/structures/structs/HighsBasis.md b/docs/src/structures/structs/HighsBasis.md new file mode 100644 index 0000000000..6bac592bd5 --- /dev/null +++ b/docs/src/structures/structs/HighsBasis.md @@ -0,0 +1,8 @@ +# HighsBasis + +The basis of a model is communicated via an instance of the HighsBasis structure + +- valid: Scalar of type bool - Indicates whether the basis is valid +- col\_status: Vector of type [HighsBasisStatus](@ref) - Comparison with [HighsBasisStatus](@ref) gives the basis status of a column +- row\_status: Vector of type [HighsBasisStatus](@ref) - Comparison with [HighsBasisStatus](@ref) gives the basis status of a row + diff --git a/docs/src/structures/structs/HighsInfo.md b/docs/src/structures/structs/HighsInfo.md new file mode 100644 index 0000000000..9a3217adc3 --- /dev/null +++ b/docs/src/structures/structs/HighsInfo.md @@ -0,0 +1,80 @@ +# HighsInfo + +Scalar information about a solved model is communicated via an instance of the HighsInfo structure + +## valid +- Indicates whether the values in a HighsInfo instance are valid +- Type: bool + +## simplex\_iteration\_count +- The number of simplex iterations performed +- Type: integer + +## ipm\_iteration\_count +- The number of interior point iterations performed +- Type: integer + +## crossover\_iteration\_count +- The number of crossover iterations performed +- Type: integer + +## qp\_iteration\_count +- The number of QP iterations performed +- Type: integer + +## primal\_solution\_status +- Comparison with [SolutionStatus](@ref) gives the status of the [primal](@ref Primal-values) solution +- Type: integer + +## dual\_solution\_status +- Comparison with [SolutionStatus](@ref) gives the status of the [dual](@ref Dual-values) solution +- Type: integer + +## basis\_validity +- Comparison with [BasisValidity](@ref) gives the status of any basis information +- Type: integer + +## objective\_function\_value +- The optimal value of the objective function +- Type: double + +## mip\_node\_count +- The number of nodes generated by the MIP solver +- Type: long integer + +## mip\_dual\_bound +- The [dual bound](@ref terminology-mip) for the MIP solver +- Type: double + +## mip\_gap +- The absolute value of the gap between the primal and bounds, relative to the primal bound. +- Type: double + +## max\_integrality\_violation +- The maximum deviation from an integer value over all the discrete variables +- Type: double + +## num\_primal\_infeasibilities +- The number of variables violating a bound by more than the [primal feasibility tolerance](@ref primal_feasibility_tolerance). +- Type: integer + +## max\_primal\_infeasibility +- The maximum violation of a bound on a variable +- Type: double + +## sum\_primal\_infeasibilities +- The sum of violations of bounds by variables +- Type: double + +## num\_dual\_infeasibilities +- The number of variables violating dual feasibility by more than the [dual feasibility tolerance](@ref dual_feasibility_tolerance). +- Type: integer + +## max\_dual\_infeasibility +- The maximum dual feasibility violation +- Type: double + +## sum\_dual\_infeasibilities +- The sum of dual feasibility violations +- Type: double + diff --git a/docs/src/structures/structs/HighsLinearObjective.md b/docs/src/structures/structs/HighsLinearObjective.md new file mode 100644 index 0000000000..9cc9f09d12 --- /dev/null +++ b/docs/src/structures/structs/HighsLinearObjective.md @@ -0,0 +1,11 @@ +# HighsLinearObjective + +A linear objective for a model is communicated via an instance of the HighsLinearObjective structure + +- weight: Scalar of type double - The weight of this objective when blending +- offset: Scalar of type double - The offset of this objective +- coefficients: Vector of type double - The coefficients of this objective +- abs\_tolerance: Scalar of type double - The absolute tolerance on this objective when performing lexicographic optimization +- rel\_tolerance: Scalar of type double - The relative tolerance on this objective when performing lexicographic optimization +- priority: Scalar of type HighsInt - The priority of this objective when performing lexicographic optimization + diff --git a/docs/src/structures/structs/HighsSolution.md b/docs/src/structures/structs/HighsSolution.md new file mode 100644 index 0000000000..5b9dbf6a73 --- /dev/null +++ b/docs/src/structures/structs/HighsSolution.md @@ -0,0 +1,10 @@ +# HighsSolution + +The solution of a model is communicated via an instance of the HighsSolution structure + +- value\_valid: Scalar of type bool - Indicates whether the column and row values are valid +- dual\_valid: Scalar of type bool - Indicates whether the column and row [duals](@ref Dual-values) are valid +- col\_value: Vector of type double - Values of the columns (variables) +- col\_dual: Vector of type double - Duals of the columns (variables) +- row\_value: Vector of type double - Values of the rows (constraints) +- row\_dual: Vector of type double - Duals of the rows (constraints) From 93762ae1869b3f07f106b5008e0f7827d7a7d853 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Fri, 22 Nov 2024 12:53:36 +0000 Subject: [PATCH 57/75] Added Structures to make.jl --- docs/make.jl | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/make.jl b/docs/make.jl index f598979b78..c2d48c170b 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -65,12 +65,12 @@ Documenter.makedocs( "structures/classes/HighsHessian.md", "structures/classes/HighsModel.md", ], -# "Structures" => Any[ -# "structures/structs/HighsSolution.md", -# "structures/structs/HighsBasis.md", -# "structures/structs/HighsInfo.md", -# "structures/structs/HighsLinearObjective.md", -# ], + "Structures" => Any[ + "structures/structs/HighsSolution.md", + "structures/structs/HighsBasis.md", + "structures/structs/HighsInfo.md", + "structures/structs/HighsLinearObjective.md", + ], ], "Callbacks" => "callbacks.md", "Interfaces" => Any[ From 4c1fa212cfe3bb721e6094b1803049516f7f515e Mon Sep 17 00:00:00 2001 From: JAJHall Date: Fri, 22 Nov 2024 13:08:36 +0000 Subject: [PATCH 58/75] Added some maths --- docs/src/guide/further.md | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/docs/src/guide/further.md b/docs/src/guide/further.md index 6e51fbb9f1..9c929eb115 100644 --- a/docs/src/guide/further.md +++ b/docs/src/guide/further.md @@ -110,7 +110,7 @@ a basis. Users can specify multiple linear objectives with respect to which HiGHS will optimize by either blending them, or by performing lexicographic optimization according to the truth of the -[blend_multi_objectives](@ref blend_multi_objectives) option. Each +[blend\_multi\_objectives](@ref blend_multi_objectives) option. Each linear objective is represented by the following data, held in the [HighsLinearObjective](@ref HighsLinearObjective) structure @@ -134,7 +134,7 @@ the `col_cost_` data in the incumbent model is ignored. ### Blending multiple linear objectives -When [blend_multi_objectives](@ref blend_multi_objectives) is `true`, +When [blend\_multi\_objectives](@ref blend_multi_objectives) is `true`, as it is by default, any `HighsLinearObjective` instances will be combined according to the `weight` values, and the resulting objective will be minimized. Hence, any objectives that should be maximized @@ -142,11 +142,18 @@ within the combination must have a negative `weight` value. ### Lexicographic optimization of multiple linear objectives -When [blend_multi_objectives](@ref blend_multi_objectives) is `false`, +When [blend\_multi\_objectives](@ref blend_multi_objectives) is `false`, HiGHS will optimize lexicographically with respect to any -`HighsLinearObjective` instances. This is carried out according to the -`priority` values in `HighsLinearObjective` instances. Note that all -priority values must be distinct. +`HighsLinearObjective` instances. This is carried out as follows, according to the +`priority` values in `HighsLinearObjective` instances. Note that _all +priority values must be distinct_. + +- Minimize/maximize with respect to the linear objective of highest priority value, according to whether its `weight` is positive/negative + +- Add a constraint to the model so that the value of the linear objective of highest priority satsifies a bound given by the values of `abs\_tolerance` and/or `rel\_tolerance`. + +-- If the objective was minimized to a value ``f^*``, then the constraint ensures that the this objective value is no creater than ``f^*+```abs\_tolerance` + From 9e7ac0a4a10307b951c15db18ac6f6b2ff4e212e Mon Sep 17 00:00:00 2001 From: JAJHall Date: Fri, 22 Nov 2024 13:53:56 +0000 Subject: [PATCH 59/75] Removed some unnecessary commas? --- docs/make.jl | 5 +++-- docs/src/structures/classes/index.md | 3 --- docs/src/structures/structs/index.md | 10 ++++++++++ 3 files changed, 13 insertions(+), 5 deletions(-) create mode 100644 docs/src/structures/structs/index.md diff --git a/docs/make.jl b/docs/make.jl index c2d48c170b..93947c773c 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -63,13 +63,14 @@ Documenter.makedocs( "structures/classes/HighsSparseMatrix.md", "structures/classes/HighsLp.md", "structures/classes/HighsHessian.md", - "structures/classes/HighsModel.md", + "structures/classes/HighsModel.md" ], "Structures" => Any[ + "structures/structs/index.md", "structures/structs/HighsSolution.md", "structures/structs/HighsBasis.md", "structures/structs/HighsInfo.md", - "structures/structs/HighsLinearObjective.md", + "structures/structs/HighsLinearObjective.md" ], ], "Callbacks" => "callbacks.md", diff --git a/docs/src/structures/classes/index.md b/docs/src/structures/classes/index.md index f2f5ed4326..9277bc5ef8 100644 --- a/docs/src/structures/classes/index.md +++ b/docs/src/structures/classes/index.md @@ -6,8 +6,5 @@ The data members of fundamental classes in HiGHS are defined in this section. * [HighsLp](@ref) * [HighsHessian](@ref) * [HighsModel](@ref) - * [HighsSolution](@ref) - * [HighsBasis](@ref) - * [HighsInfo](@ref) Class data members for internal use only are not documented. diff --git a/docs/src/structures/structs/index.md b/docs/src/structures/structs/index.md new file mode 100644 index 0000000000..d8c763c2b3 --- /dev/null +++ b/docs/src/structures/structs/index.md @@ -0,0 +1,10 @@ +# [Overview](@id structs-overview) + +The data members of fundamental structs in HiGHS are defined in this section. + + * [HighsSolution](@ref) + * [HighsBasis](@ref) + * [HighsInfo](@ref) + * [HighsLinearObjective](@ref) + +Structure data members for internal use only are not documented. From 7dc5a14b9821aaeed87a7eead6d6bd01e39c370a Mon Sep 17 00:00:00 2001 From: JAJHall Date: Fri, 22 Nov 2024 14:52:02 +0000 Subject: [PATCH 60/75] Use the solution of one MIP to provide an integer feasible solution of the next in lexicographic optimization --- docs/src/guide/further.md | 15 +++++++++++++-- src/lp_data/HighsInterface.cpp | 33 +++++++++++++++++++++++++++++++-- 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/docs/src/guide/further.md b/docs/src/guide/further.md index 9c929eb115..a01e29e37b 100644 --- a/docs/src/guide/further.md +++ b/docs/src/guide/further.md @@ -150,10 +150,21 @@ priority values must be distinct_. - Minimize/maximize with respect to the linear objective of highest priority value, according to whether its `weight` is positive/negative -- Add a constraint to the model so that the value of the linear objective of highest priority satsifies a bound given by the values of `abs\_tolerance` and/or `rel\_tolerance`. +- Add a constraint to the model so that the value of the linear objective of highest priority satsifies a bound given by the values of `abs_tolerance` and/or `rel_tolerance`. --- If the objective was minimized to a value ``f^*``, then the constraint ensures that the this objective value is no creater than ``f^*+```abs\_tolerance` +-- If the objective was minimized to a value ``f^*>=0``, then the constraint ensures that the this objective value is no greater than ``\min(f^*+```abs_tolerance```,~f^*(1+```rel_tolerance```))`. +-- If the objective was minimized to a value ``f^*<0``, then the constraint ensures that the this objective value is no greater than ``\min(f^*+```abs_tolerance```,~f^*(1-```rel_tolerance```))`. +-- If the objective was maximized to a value ``f^*>=0``, then the constraint ensures that the this objective value is no less than ``\max(f^*-```abs_tolerance```,~f^*(1-```rel_tolerance```))`. + +-- If the objective was maximized to a value ``f^*<0``, then the constraint ensures that the this objective value is no less than ``\max(f^*-```abs_tolerance```,~f^*(1+```rel_tolerance```))`. + +- Minimize/maximize with respect to the linear objective of next highest priority, and then add a corresponding objective constraint to the model, repeating until optimization with respect to the linear objective of lowest priority has taken place. + +Note + +- Negative values of `abs_tolerance` and `rel_tolerance` will be ignored. This is a convenient way of "switching off" a bounding technique that is not of interest. +- When the model is continuous, no dual information will be returned if there is more than one linear objective. diff --git a/src/lp_data/HighsInterface.cpp b/src/lp_data/HighsInterface.cpp index 7f472a55fe..3e2676b74a 100644 --- a/src/lp_data/HighsInterface.cpp +++ b/src/lp_data/HighsInterface.cpp @@ -1755,7 +1755,6 @@ HighsStatus Highs::getDualRayInterface(bool& has_dual_ray, this->setOptionValue("presolve", kHighsOffString); this->setOptionValue("solve_relaxation", true); HighsStatus call_status = this->run(); - this->writeSolution("", kSolutionStylePretty); if (call_status != HighsStatus::kOk) return_status = call_status; has_dual_ray = ekk_instance_.status_.has_dual_ray; has_invert = ekk_instance_.status_.has_invert; @@ -3727,6 +3726,9 @@ HighsStatus Highs::multiobjectiveSolve() { const HighsInt original_lp_num_row = lp.num_row_; std::vector index(lp.num_col_); std::vector value(lp.num_col_); + // Use the solution of one MIP to provide an integer feasible + // solution of the next + HighsSolution solution; for (HighsInt iIx = 0; iIx < num_linear_objective; iIx++) { HighsInt priority = priority_objective[iIx].first; HighsInt iObj = priority_objective[iIx].second; @@ -3737,6 +3739,28 @@ HighsStatus Highs::multiobjectiveSolve() { lp.col_cost_ = linear_objective.coefficients; lp.sense_ = linear_objective.weight > 0 ? ObjSense::kMinimize : ObjSense::kMaximize; + if (lp.isMip() && solution.value_valid) { + HighsStatus set_solution_status = this->setSolution(solution); + if (set_solution_status == HighsStatus::kError) { + highsLogUser(options_.log_options, HighsLogType::kError, + "Failure to use one MIP to provide an integer feasible " + "solution of the next\n"); + return returnFromLexicographicOptimization(HighsStatus::kError, + original_lp_num_row); + } + bool valid, integral, feasible; + HighsStatus assess_primal_solution = + assessPrimalSolution(valid, integral, feasible); + if (!valid || !integral || !feasible) { + highsLogUser(options_.log_options, HighsLogType::kWarning, + "Failure to use one MIP to provide an integer feasible " + "solution of the next: " + "status is valid = %s, integral = %s, feasible = %s\n", + highsBoolToString(valid).c_str(), + highsBoolToString(integral).c_str(), + highsBoolToString(feasible).c_str()); + } + } HighsStatus solve_status = this->solve(); if (solve_status == HighsStatus::kError) return returnFromLexicographicOptimization(HighsStatus::kError, @@ -3748,8 +3772,13 @@ HighsStatus Highs::multiobjectiveSolve() { return returnFromLexicographicOptimization(HighsStatus::kWarning, original_lp_num_row); } - this->writeSolution("", kSolutionStylePretty); if (iIx == num_linear_objective - 1) break; + if (lp.isMip()) { + // Save the solution to provide an integer feasible solution of + // the next MIP + solution.col_value = this->solution_.col_value; + solution.value_valid = true; + } // Add the constraint HighsInt nnz = 0; for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { From 23102c67bc8f88cf982bed3e9f44af09b3d9a98d Mon Sep 17 00:00:00 2001 From: JAJHall Date: Fri, 22 Nov 2024 17:36:01 +0000 Subject: [PATCH 61/75] Finished first draft of documentation; add C and Python API --- docs/src/guide/further.md | 37 +++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/docs/src/guide/further.md b/docs/src/guide/further.md index a01e29e37b..91a4dc75ca 100644 --- a/docs/src/guide/further.md +++ b/docs/src/guide/further.md @@ -125,9 +125,9 @@ linear objective is represented by the following data, held in the Multi-objective optimization in HiGHS is defined by the following methods -- passLinearObjectives - Pass multiple linear objectives as their number `num_linear_objective` and pointer to a vector of `HighsLinearObjective` instances, overwriting any previous linear objectives -- addLinearObjective - Add a single `HighsLinearObjective` instance to any already stored in HiGHS -- clearLinearObjectives - Clears any linear objectives stored in HiGHS +- [passLinearObjectives](@ref Multi-objective-optimization] - Pass multiple linear objectives as their number `num_linear_objective` and pointer to a vector of `HighsLinearObjective` instances, overwriting any previous linear objectives +- [addLinearObjective](@ref Multi-objective-optimization] - Add a single `HighsLinearObjective` instance to any already stored in HiGHS +- [clearLinearObjectives](@ref Multi-objective-optimization] - Clears any linear objectives stored in HiGHS When there is at least one `HighsLinearObjective` instance in HiGHS, the `col_cost_` data in the incumbent model is ignored. @@ -148,23 +148,36 @@ HiGHS will optimize lexicographically with respect to any `priority` values in `HighsLinearObjective` instances. Note that _all priority values must be distinct_. -- Minimize/maximize with respect to the linear objective of highest priority value, according to whether its `weight` is positive/negative +* Minimize/maximize with respect to the linear objective of highest priority value, according to whether its `weight` is positive/negative -- Add a constraint to the model so that the value of the linear objective of highest priority satsifies a bound given by the values of `abs_tolerance` and/or `rel_tolerance`. +* Add a constraint to the model so that the value of the linear objective of highest priority satsifies a bound given by the values of `abs_tolerance` and/or `rel_tolerance`. --- If the objective was minimized to a value ``f^*>=0``, then the constraint ensures that the this objective value is no greater than ``\min(f^*+```abs_tolerance```,~f^*(1+```rel_tolerance```))`. + + If the objective was minimized to a value ``f^*>=0``, then the constraint ensures that the this objective value is no greater than +```math --- If the objective was minimized to a value ``f^*<0``, then the constraint ensures that the this objective value is no greater than ``\min(f^*+```abs_tolerance```,~f^*(1-```rel_tolerance```))`. +\min(f^*+`abs_tolerance`,~f^*[1+`rel_tolerance`]). +``` --- If the objective was maximized to a value ``f^*>=0``, then the constraint ensures that the this objective value is no less than ``\max(f^*-```abs_tolerance```,~f^*(1-```rel_tolerance```))`. + + If the objective was minimized to a value ``f^*<0``, then the constraint ensures that the this objective value is no greater than +```math +\min(f^*+`abs_tolerance`,~f^*[1-`rel_tolerance`]). +``` --- If the objective was maximized to a value ``f^*<0``, then the constraint ensures that the this objective value is no less than ``\max(f^*-```abs_tolerance```,~f^*(1+```rel_tolerance```))`. + + If the objective was maximized to a value ``f^*>=0``, then the constraint ensures that the this objective value is no less than +```math +\max(f^*-`abs_tolerance`,~f^*[1-`rel_tolerance`]). +``` -- Minimize/maximize with respect to the linear objective of next highest priority, and then add a corresponding objective constraint to the model, repeating until optimization with respect to the linear objective of lowest priority has taken place. + + If the objective was maximized to a value ``f^*<0``, then the constraint ensures that the this objective value is no less than +```math +\max(f^*-`abs_tolerance`,~f^*[1+`rel_tolerance`]). +``` + +* Minimize/maximize with respect to the linear objective of next highest priority, and then add a corresponding objective constraint to the model, repeating until optimization with respect to the linear objective of lowest priority has taken place. Note -- Negative values of `abs_tolerance` and `rel_tolerance` will be ignored. This is a convenient way of "switching off" a bounding technique that is not of interest. -- When the model is continuous, no dual information will be returned if there is more than one linear objective. +* Negative values of `abs_tolerance` and `rel_tolerance` will be ignored. This is a convenient way of "switching off" a bounding technique that is not of interest. +* When the model is continuous, no dual information will be returned if there is more than one linear objective. From cda67caa36783147a0f8b9d656910b7435fac7a9 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Fri, 22 Nov 2024 18:46:40 +0000 Subject: [PATCH 62/75] Debugging the C API for multi-objective optimization --- check/TestCAPI.c | 114 +++++++++++++++++++++++++++------ src/interfaces/highs_c_api.cpp | 40 ++++++++++++ src/interfaces/highs_c_api.h | 16 ++++- 3 files changed, 151 insertions(+), 19 deletions(-) diff --git a/check/TestCAPI.c b/check/TestCAPI.c index e5848f67a1..9b94ccc126 100644 --- a/check/TestCAPI.c +++ b/check/TestCAPI.c @@ -1855,6 +1855,83 @@ void test_getModel() { Highs_destroy(highs); } +void test_multiObjective() { + void* highs; + highs = Highs_create(); + const double inf = Highs_getInfinity(highs); + + HighsInt num_col = 2; + HighsInt num_row = 3; + HighsInt num_nz = num_col * num_row; + HighsInt a_format = kHighsMatrixFormatColwise; + HighsInt sense = kHighsObjSenseMaximize; + double offset = -1; + double col_cost[2] = {1, 1}; + double col_lower[2] = {0, 0}; + double col_upper[2] = {inf, inf}; + double row_lower[3] = {-inf, -inf, -inf}; + double row_upper[3] = {18, 8, 14}; + HighsInt a_start[3] = {0, 3, 6}; + HighsInt a_index[6] = {0, 1, 2, 0, 1, 2}; + double a_value[6] = {3, 1, 1, 1, 1, 2}; + HighsInt integrality[2] = {kHighsVarTypeInteger, kHighsVarTypeInteger}; + + // Highs_setBoolOptionValue(highs, "output_flag", dev_run); + HighsInt return_status = Highs_passLp(highs, num_col, num_row, num_nz, a_format, sense, + offset, col_cost, col_lower, col_upper, + row_lower, row_upper, a_start, a_index, a_value); + assert(return_status == kHighsStatusOk); + return_status = Highs_run(highs); + assert(return_status == kHighsStatusOk); + HighsInt model_status = Highs_getModelStatus(highs); + assert(model_status == kHighsModelStatusOptimal); + Highs_writeSolutionPretty(highs, ""); + + return_status = Highs_clearLinearObjectives(highs); + assert(return_status == kHighsStatusOk); + + double weight = -1; + double linear_objective_offset = -1; + double coefficients[2] = {2, 1}; + double abs_tolerance = 0; + double rel_tolerance = 0; + HighsInt priority = 10; + return_status = Highs_addLinearObjective(highs, weight, linear_objective_offset, coefficients, abs_tolerance, rel_tolerance, priority); + assert(return_status == kHighsStatusOk); + + weight = 1e-4; + linear_objective_offset = 0; + coefficients[0] = 1; + coefficients[1] = 0; + priority = 0; + return_status = Highs_addLinearObjective(highs, weight, linear_objective_offset, coefficients, abs_tolerance, rel_tolerance, priority); + assert(return_status == kHighsStatusOk); + + return_status = Highs_run(highs); + assert(return_status == kHighsStatusOk); + model_status = Highs_getModelStatus(highs); + assert(model_status == kHighsModelStatusOptimal); + + Highs_writeSolutionPretty(highs, ""); + double* col_value = (double*)malloc(sizeof(double) * num_col); + return_status = + Highs_getSolution(highs, col_value, NULL, NULL, NULL); + assertDoubleValuesEqual("col_value[0]", col_value[0], 2); + assertDoubleValuesEqual("col_value[1]", col_value[1], 6); + + Highs_setBoolOptionValue(highs, "blend_multi_objectives", 0); + + // double weight = {}; + // double offset = {}; + // double coefficients = {}; + // double abs_tolerance = {}; + // double rel_tolerance = {}; + // HighsInt priority = {}; + + Highs_destroy(highs); + free(col_value); +} + /* The horrible C in this causes problems in some of the CI tests, so suppress thius test until the C has been improved @@ -1901,24 +1978,25 @@ iteration_count1); assertLogical("Dual", logic); } */ int main() { - minimal_api_illegal_lp(); - test_callback(); - version_api(); - full_api(); - minimal_api_lp(); - minimal_api_mip(); - minimal_api_qp(); - full_api_options(); - full_api_lp(); - full_api_mip(); - full_api_qp(); - pass_presolve_get_lp(); - options(); - test_getColsByRange(); - test_passHessian(); - test_ranging(); - test_feasibilityRelaxation(); - test_getModel(); + // minimal_api_illegal_lp(); + // test_callback(); + // version_api(); + // full_api(); + // minimal_api_lp(); + // minimal_api_mip(); + // minimal_api_qp(); + // full_api_options(); + // full_api_lp(); + // full_api_mip(); + // full_api_qp(); + // pass_presolve_get_lp(); + // options(); + // test_getColsByRange(); + // test_passHessian(); + // test_ranging(); + // test_feasibilityRelaxation(); + // test_getModel(); + test_multiObjective(); return 0; } // test_setSolution(); diff --git a/src/interfaces/highs_c_api.cpp b/src/interfaces/highs_c_api.cpp index 8dad0e5216..54ca0ff74b 100644 --- a/src/interfaces/highs_c_api.cpp +++ b/src/interfaces/highs_c_api.cpp @@ -291,6 +291,46 @@ HighsInt Highs_passHessian(void* highs, const HighsInt dim, ->passHessian(dim, num_nz, format, start, index, value); } +HighsInt Highs_passLinearObjectives(const void* highs, const HighsInt num_linear_objective, + const double* weight, const double* offset, const double* coefficients, + const double* abs_tolerance, const double* rel_tolerance, const HighsInt* priority) { + HighsInt status = Highs_clearLinearObjectives(highs); + if (status != kHighsStatusOk) return status; + HighsInt num_col = Highs_getNumCol(highs); + for (HighsInt iObj = 0; iObj < num_linear_objective; iObj++) { + status = Highs_addLinearObjectiveWithIndex(highs, weight[iObj], offset[iObj], &coefficients[iObj*num_col], abs_tolerance[iObj], rel_tolerance[iObj], priority[iObj], iObj); + if (status != kHighsStatusOk) return status; + } + return kHighsStatusOk; +} + +HighsInt Highs_addLinearObjective(const void* highs, + const double weight, const double offset, const double* coefficients, + const double abs_tolerance, const double rel_tolerance, const HighsInt priority) { + return Highs_addLinearObjectiveWithIndex(highs, weight, offset, coefficients, abs_tolerance, rel_tolerance, priority, -1); +} + +HighsInt Highs_addLinearObjectiveWithIndex(const void* highs, + const double weight, const double offset, const double* coefficients, + const double abs_tolerance, const double rel_tolerance, const HighsInt priority, + const HighsInt iObj) { + HighsLinearObjective linear_objective; + HighsInt num_col = Highs_getNumCol(highs); + linear_objective.weight = weight; + linear_objective.offset = offset; + for (HighsInt iCol = 0; iCol < num_col; iCol++) + linear_objective.coefficients.push_back(coefficients[iCol]); + linear_objective.abs_tolerance = abs_tolerance; + linear_objective.rel_tolerance = rel_tolerance; + linear_objective.priority = priority; + linear_objective.weight = weight; + return HighsInt(((Highs*)highs)->addLinearObjective(linear_objective)); +} + +HighsInt Highs_clearLinearObjectives(const void* highs) { + return HighsInt(((Highs*)highs)->clearLinearObjectives()); +} + HighsInt Highs_passRowName(const void* highs, const HighsInt row, const char* name) { return (HighsInt)((Highs*)highs)->passRowName(row, std::string(name)); diff --git a/src/interfaces/highs_c_api.h b/src/interfaces/highs_c_api.h index a59ec64a17..ba5255d98f 100644 --- a/src/interfaces/highs_c_api.h +++ b/src/interfaces/highs_c_api.h @@ -557,6 +557,20 @@ HighsInt Highs_passHessian(void* highs, const HighsInt dim, const HighsInt* start, const HighsInt* index, const double* value); +HighsInt Highs_passLinearObjectives(const void* highs, const HighsInt num_linear_objective, + const double* weight, const double* offset, const double* coefficients, + const double* abs_tolerance, const double* rel_tolerance, const HighsInt* priority); + +HighsInt Highs_addLinearObjective(const void* highs, + const double weight, const double offset, const double* coefficients, + const double abs_tolerance, const double rel_tolerance, const HighsInt priority); + +HighsInt Highs_addLinearObjectiveWithIndex(const void* highs, + const double weight, const double offset, const double* coefficients, + const double abs_tolerance, const double rel_tolerance, const HighsInt priority, + const HighsInt iObj); + +HighsInt Highs_clearLinearObjectives(const void* highs); /** * Pass the name of a row. * @@ -945,7 +959,7 @@ HighsInt Highs_getDualRay(const void* highs, HighsInt* has_dual_ray, * filled with the unboundedness * direction. */ -HighsInt getDualUnboundednessDirection( +HighsInt Highs_getDualUnboundednessDirection( const void* highs, HighsInt* has_dual_unboundedness_direction, double* dual_unboundedness_direction_value); From 3c71c597666134e46d74435d61baf29cd6b4fbe5 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Sat, 23 Nov 2024 14:11:39 +0000 Subject: [PATCH 63/75] Added C API and logging during multi-objective optimization --- check/TestCAPI.c | 65 ++++++++++++++++++++------- check/TestMultiObjective.cpp | 2 +- src/Highs.h | 4 +- src/interfaces/highs_c_api.cpp | 37 ++++++++++------ src/interfaces/highs_c_api.h | 31 ++++++++----- src/lp_data/HighsInterface.cpp | 81 ++++++++++++++++++++++++++++++++++ 6 files changed, 177 insertions(+), 43 deletions(-) diff --git a/check/TestCAPI.c b/check/TestCAPI.c index 9b94ccc126..e16fd5b0aa 100644 --- a/check/TestCAPI.c +++ b/check/TestCAPI.c @@ -1876,23 +1876,18 @@ void test_multiObjective() { double a_value[6] = {3, 1, 1, 1, 1, 2}; HighsInt integrality[2] = {kHighsVarTypeInteger, kHighsVarTypeInteger}; - // Highs_setBoolOptionValue(highs, "output_flag", dev_run); + Highs_setBoolOptionValue(highs, "output_flag", dev_run); HighsInt return_status = Highs_passLp(highs, num_col, num_row, num_nz, a_format, sense, offset, col_cost, col_lower, col_upper, row_lower, row_upper, a_start, a_index, a_value); assert(return_status == kHighsStatusOk); - return_status = Highs_run(highs); - assert(return_status == kHighsStatusOk); - HighsInt model_status = Highs_getModelStatus(highs); - assert(model_status == kHighsModelStatusOptimal); - Highs_writeSolutionPretty(highs, ""); return_status = Highs_clearLinearObjectives(highs); assert(return_status == kHighsStatusOk); double weight = -1; double linear_objective_offset = -1; - double coefficients[2] = {2, 1}; + double coefficients[2] = {1, 1}; double abs_tolerance = 0; double rel_tolerance = 0; HighsInt priority = 10; @@ -1909,7 +1904,7 @@ void test_multiObjective() { return_status = Highs_run(highs); assert(return_status == kHighsStatusOk); - model_status = Highs_getModelStatus(highs); + HighsInt model_status = Highs_getModelStatus(highs); assert(model_status == kHighsModelStatusOptimal); Highs_writeSolutionPretty(highs, ""); @@ -1921,13 +1916,53 @@ void test_multiObjective() { Highs_setBoolOptionValue(highs, "blend_multi_objectives", 0); - // double weight = {}; - // double offset = {}; - // double coefficients = {}; - // double abs_tolerance = {}; - // double rel_tolerance = {}; - // HighsInt priority = {}; - + if (dev_run) printf("\n***************\nLexicographic 1\n***************\n"); + double weight2[2] = {-1, 1e-4}; + double linear_objective_offset2[2] = {-1, 0}; + double coefficients2[4] = {1, 1, 1, 0}; + double abs_tolerance2[2] = {0, -1}; + double rel_tolerance2[2] = {0, -1}; + HighsInt priority2[2] = {10, 0}; + return_status = Highs_passLinearObjectives(highs, 2, weight2, linear_objective_offset2, coefficients2, abs_tolerance2, rel_tolerance2, priority2); + return_status = Highs_run(highs); + assert(return_status == kHighsStatusOk); + model_status = Highs_getModelStatus(highs); + assert(model_status == kHighsModelStatusOptimal); + Highs_writeSolutionPretty(highs, ""); + return_status = + Highs_getSolution(highs, col_value, NULL, NULL, NULL); + assertDoubleValuesEqual("col_value[0]", col_value[0], 2); + assertDoubleValuesEqual("col_value[1]", col_value[1], 6); + + // weight2[1] = 1e-5; + coefficients2[0] = 1.0001; + abs_tolerance2[0] = 1e-5; + rel_tolerance2[0] = 0.05; + return_status = Highs_passLinearObjectives(highs, 2, weight2, linear_objective_offset2, coefficients2, abs_tolerance2, rel_tolerance2, priority2); + return_status = Highs_run(highs); + assert(return_status == kHighsStatusOk); + model_status = Highs_getModelStatus(highs); + assert(model_status == kHighsModelStatusOptimal); + Highs_writeSolutionPretty(highs, ""); + return_status = + Highs_getSolution(highs, col_value, NULL, NULL, NULL); + assertDoubleValuesEqual("col_value[0]", col_value[0], 4.9); + assertDoubleValuesEqual("col_value[1]", col_value[1], 3.1); + + if (dev_run) printf("\n***************\nLexicographic 2\n***************\n"); + abs_tolerance2[0] = -1; + + return_status = Highs_passLinearObjectives(highs, 2, weight2, linear_objective_offset2, coefficients2, abs_tolerance2, rel_tolerance2, priority2); + return_status = Highs_run(highs); + assert(return_status == kHighsStatusOk); + model_status = Highs_getModelStatus(highs); + assert(model_status == kHighsModelStatusOptimal); + Highs_writeSolutionPretty(highs, ""); + return_status = + Highs_getSolution(highs, col_value, NULL, NULL, NULL); + assertDoubleValuesEqual("col_value[0]", col_value[0], 1.30069); + assertDoubleValuesEqual("col_value[1]", col_value[1], 6.34966); + Highs_destroy(highs); free(col_value); } diff --git a/check/TestMultiObjective.cpp b/check/TestMultiObjective.cpp index 9761c3045b..1a15e8d9d2 100644 --- a/check/TestMultiObjective.cpp +++ b/check/TestMultiObjective.cpp @@ -56,7 +56,7 @@ TEST_CASE("multi-objective", "[util]") { if (dev_run) printf("\nPass illegal linear objective\n"); linear_objective.weight = -obj_mu; linear_objective.offset = -obj_mu; - linear_objective.coefficients = {obj_mu * 2, obj_mu * 1, obj_mu * 0}; + linear_objective.coefficients = {obj_mu * 1, obj_mu * 1, obj_mu * 0}; linear_objective.abs_tolerance = 0.0; linear_objective.rel_tolerance = 0.0; REQUIRE(h.addLinearObjective(linear_objective) == HighsStatus::kError); diff --git a/src/Highs.h b/src/Highs.h index bd7090f826..04a4a3ce25 100644 --- a/src/Highs.h +++ b/src/Highs.h @@ -1524,8 +1524,6 @@ class Highs { HighsStatus returnFromRun(const HighsStatus return_status, const bool undo_mods); HighsStatus returnFromHighs(const HighsStatus return_status); - HighsStatus returnFromLexicographicOptimization( - const HighsStatus return_status, HighsInt original_lp_num_row); void reportSolvedLpQpStats(); // Interface methods @@ -1623,6 +1621,8 @@ class Highs { HighsInt* iis_col_index, HighsInt* iis_row_index, HighsInt* iis_col_bound, HighsInt* iis_row_bound); + HighsStatus returnFromLexicographicOptimization( + const HighsStatus return_status, HighsInt original_lp_num_row); HighsStatus multiobjectiveSolve(); bool aFormatOk(const HighsInt num_nz, const HighsInt format); diff --git a/src/interfaces/highs_c_api.cpp b/src/interfaces/highs_c_api.cpp index 54ca0ff74b..655c0fce30 100644 --- a/src/interfaces/highs_c_api.cpp +++ b/src/interfaces/highs_c_api.cpp @@ -291,34 +291,45 @@ HighsInt Highs_passHessian(void* highs, const HighsInt dim, ->passHessian(dim, num_nz, format, start, index, value); } -HighsInt Highs_passLinearObjectives(const void* highs, const HighsInt num_linear_objective, - const double* weight, const double* offset, const double* coefficients, - const double* abs_tolerance, const double* rel_tolerance, const HighsInt* priority) { +HighsInt Highs_passLinearObjectives(const void* highs, + const HighsInt num_linear_objective, + const double* weight, const double* offset, + const double* coefficients, + const double* abs_tolerance, + const double* rel_tolerance, + const HighsInt* priority) { HighsInt status = Highs_clearLinearObjectives(highs); if (status != kHighsStatusOk) return status; HighsInt num_col = Highs_getNumCol(highs); for (HighsInt iObj = 0; iObj < num_linear_objective; iObj++) { - status = Highs_addLinearObjectiveWithIndex(highs, weight[iObj], offset[iObj], &coefficients[iObj*num_col], abs_tolerance[iObj], rel_tolerance[iObj], priority[iObj], iObj); + status = Highs_addLinearObjectiveWithIndex( + highs, weight[iObj], offset[iObj], &coefficients[iObj * num_col], + abs_tolerance[iObj], rel_tolerance[iObj], priority[iObj], iObj); if (status != kHighsStatusOk) return status; } return kHighsStatusOk; } -HighsInt Highs_addLinearObjective(const void* highs, - const double weight, const double offset, const double* coefficients, - const double abs_tolerance, const double rel_tolerance, const HighsInt priority) { - return Highs_addLinearObjectiveWithIndex(highs, weight, offset, coefficients, abs_tolerance, rel_tolerance, priority, -1); +HighsInt Highs_addLinearObjective(const void* highs, const double weight, + const double offset, + const double* coefficients, + const double abs_tolerance, + const double rel_tolerance, + const HighsInt priority) { + return Highs_addLinearObjectiveWithIndex(highs, weight, offset, coefficients, + abs_tolerance, rel_tolerance, + priority, -1); } -HighsInt Highs_addLinearObjectiveWithIndex(const void* highs, - const double weight, const double offset, const double* coefficients, - const double abs_tolerance, const double rel_tolerance, const HighsInt priority, - const HighsInt iObj) { +HighsInt Highs_addLinearObjectiveWithIndex( + const void* highs, const double weight, const double offset, + const double* coefficients, const double abs_tolerance, + const double rel_tolerance, const HighsInt priority, const HighsInt iObj) { HighsLinearObjective linear_objective; HighsInt num_col = Highs_getNumCol(highs); linear_objective.weight = weight; linear_objective.offset = offset; - for (HighsInt iCol = 0; iCol < num_col; iCol++) + for (HighsInt iCol = 0; iCol < num_col; iCol++) linear_objective.coefficients.push_back(coefficients[iCol]); linear_objective.abs_tolerance = abs_tolerance; linear_objective.rel_tolerance = rel_tolerance; diff --git a/src/interfaces/highs_c_api.h b/src/interfaces/highs_c_api.h index ba5255d98f..db8764fae6 100644 --- a/src/interfaces/highs_c_api.h +++ b/src/interfaces/highs_c_api.h @@ -557,18 +557,25 @@ HighsInt Highs_passHessian(void* highs, const HighsInt dim, const HighsInt* start, const HighsInt* index, const double* value); -HighsInt Highs_passLinearObjectives(const void* highs, const HighsInt num_linear_objective, - const double* weight, const double* offset, const double* coefficients, - const double* abs_tolerance, const double* rel_tolerance, const HighsInt* priority); - -HighsInt Highs_addLinearObjective(const void* highs, - const double weight, const double offset, const double* coefficients, - const double abs_tolerance, const double rel_tolerance, const HighsInt priority); - -HighsInt Highs_addLinearObjectiveWithIndex(const void* highs, - const double weight, const double offset, const double* coefficients, - const double abs_tolerance, const double rel_tolerance, const HighsInt priority, - const HighsInt iObj); +HighsInt Highs_passLinearObjectives(const void* highs, + const HighsInt num_linear_objective, + const double* weight, const double* offset, + const double* coefficients, + const double* abs_tolerance, + const double* rel_tolerance, + const HighsInt* priority); + +HighsInt Highs_addLinearObjective(const void* highs, const double weight, + const double offset, + const double* coefficients, + const double abs_tolerance, + const double rel_tolerance, + const HighsInt priority); + +HighsInt Highs_addLinearObjectiveWithIndex( + const void* highs, const double weight, const double offset, + const double* coefficients, const double abs_tolerance, + const double rel_tolerance, const HighsInt priority, const HighsInt iObj); HighsInt Highs_clearLinearObjectives(const void* highs); /** diff --git a/src/lp_data/HighsInterface.cpp b/src/lp_data/HighsInterface.cpp index 3e2676b74a..3669f69f78 100644 --- a/src/lp_data/HighsInterface.cpp +++ b/src/lp_data/HighsInterface.cpp @@ -3664,7 +3664,9 @@ HighsStatus Highs::returnFromLexicographicOptimization( } HighsStatus Highs::multiobjectiveSolve() { + const HighsInt coeff_logging_size_limit = 10; HighsInt num_linear_objective = this->multi_linear_objective_.size(); + assert(num_linear_objective > 0); HighsLp& lp = this->model_.lp_; for (HighsInt iObj = 0; iObj < num_linear_objective; iObj++) { @@ -3680,6 +3682,37 @@ HighsStatus Highs::multiobjectiveSolve() { } } + std::unique_ptr multi_objective_log; + highsLogUser(options_.log_options, HighsLogType::kInfo, + "Solving with %d multiple linear objectives, %s\n", + int(num_linear_objective), + this->options_.blend_multi_objectives + ? "blending objectives by weight" + : "using lexicographic optimization by priority"); + highsLogUser( + options_.log_options, HighsLogType::kInfo, + "Ix weight offset abs_tol rel_tol priority%s\n", + lp.num_col_ < coeff_logging_size_limit ? " coefficients" : ""); + for (HighsInt iObj = 0; iObj < num_linear_objective; iObj++) { + HighsLinearObjective& linear_objective = + this->multi_linear_objective_[iObj]; + multi_objective_log = + std::unique_ptr(new std::stringstream()); + *multi_objective_log << highsFormatToString( + "%2d %11.6g %11.6g %11.6g %11.6g %11d ", int(iObj), + linear_objective.weight, linear_objective.offset, + linear_objective.abs_tolerance, linear_objective.rel_tolerance, + linear_objective.priority); + if (lp.num_col_ < coeff_logging_size_limit) { + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) + *multi_objective_log << highsFormatToString( + "%s c_{%1d} = %g", iCol == 0 ? "" : ",", int(iCol), + linear_objective.coefficients[iCol]); + } + *multi_objective_log << "\n"; + highsLogUser(options_.log_options, HighsLogType::kInfo, "%s", + multi_objective_log->str().c_str()); + } this->clearSolver(); if (this->options_.blend_multi_objectives) { // Objectives are blended by weight and minimized @@ -3695,6 +3728,22 @@ HighsStatus Highs::multiobjectiveSolve() { multi_linear_objective.coefficients[iCol]; } lp.sense_ = ObjSense::kMinimize; + + multi_objective_log = + std::unique_ptr(new std::stringstream()); + *multi_objective_log << highsFormatToString( + "Solving with blended objective"); + if (lp.num_col_ < coeff_logging_size_limit) { + *multi_objective_log << highsFormatToString( + ": %s %g", lp.sense_ == ObjSense::kMinimize ? "min" : "max", + lp.offset_); + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) + *multi_objective_log << highsFormatToString( + " + (%g) x[%d]", lp.col_cost_[iCol], int(iCol)); + } + *multi_objective_log << "\n"; + highsLogUser(options_.log_options, HighsLogType::kInfo, "%s", + multi_objective_log->str().c_str()); return this->solve(); } @@ -3761,6 +3810,21 @@ HighsStatus Highs::multiobjectiveSolve() { highsBoolToString(feasible).c_str()); } } + multi_objective_log = + std::unique_ptr(new std::stringstream()); + *multi_objective_log << highsFormatToString("Solving with objective %d", + int(iObj)); + if (lp.num_col_ < coeff_logging_size_limit) { + *multi_objective_log << highsFormatToString( + ": %s %g", lp.sense_ == ObjSense::kMinimize ? "min" : "max", + lp.offset_); + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) + *multi_objective_log << highsFormatToString( + " + (%g) x[%d]", lp.col_cost_[iCol], int(iCol)); + } + *multi_objective_log << "\n"; + highsLogUser(options_.log_options, HighsLogType::kInfo, "%s", + multi_objective_log->str().c_str()); HighsStatus solve_status = this->solve(); if (solve_status == HighsStatus::kError) return returnFromLexicographicOptimization(HighsStatus::kError, @@ -3772,6 +3836,7 @@ HighsStatus Highs::multiobjectiveSolve() { return returnFromLexicographicOptimization(HighsStatus::kWarning, original_lp_num_row); } + this->writeSolution("", kSolutionStylePretty); if (iIx == num_linear_objective - 1) break; if (lp.isMip()) { // Save the solution to provide an integer feasible solution of @@ -3840,6 +3905,22 @@ HighsStatus Highs::multiobjectiveSolve() { " and relative tolerance being %g < 0\n", int(priority), linear_objective.abs_tolerance, linear_objective.rel_tolerance); + multi_objective_log = + std::unique_ptr(new std::stringstream()); + *multi_objective_log << highsFormatToString( + "Add constraint for objective %d: ", int(iObj)); + if (nnz < coeff_logging_size_limit) { + *multi_objective_log << highsFormatToString("%g <= ", lower_bound); + for (HighsInt iEl = 0; iEl < nnz; iEl++) + *multi_objective_log << highsFormatToString( + "%s(%g) x[%d]", iEl > 0 ? " + " : "", value[iEl], int(index[iEl])); + *multi_objective_log << highsFormatToString(" <= %g\n", upper_bound); + } else { + *multi_objective_log << highsFormatToString("Bounds [%g, %g]\n", + lower_bound, upper_bound); + } + highsLogUser(options_.log_options, HighsLogType::kInfo, "%s", + multi_objective_log->str().c_str()); add_row_status = this->addRow(lower_bound, upper_bound, nnz, index.data(), value.data()); assert(add_row_status == HighsStatus::kOk); From 0493f8c274be1b9204a156f2920fbef0522973c1 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Sat, 23 Nov 2024 17:27:10 +0000 Subject: [PATCH 64/75] highspy API for multi-objective optimization is done and checked; formatted --- check/TestCAPI.c | 36 ++++++++++++------------ docs/src/interfaces/python/example-py.md | 1 - examples/call_highs_from_python.py | 6 +++- src/highs_bindings.cpp | 14 +++++++++ src/highspy/__init__.py | 1 + src/lp_data/HighsInterface.cpp | 1 - 6 files changed, 38 insertions(+), 21 deletions(-) diff --git a/check/TestCAPI.c b/check/TestCAPI.c index e16fd5b0aa..d75cc71305 100644 --- a/check/TestCAPI.c +++ b/check/TestCAPI.c @@ -2013,24 +2013,24 @@ iteration_count1); assertLogical("Dual", logic); } */ int main() { - // minimal_api_illegal_lp(); - // test_callback(); - // version_api(); - // full_api(); - // minimal_api_lp(); - // minimal_api_mip(); - // minimal_api_qp(); - // full_api_options(); - // full_api_lp(); - // full_api_mip(); - // full_api_qp(); - // pass_presolve_get_lp(); - // options(); - // test_getColsByRange(); - // test_passHessian(); - // test_ranging(); - // test_feasibilityRelaxation(); - // test_getModel(); + minimal_api_illegal_lp(); + test_callback(); + version_api(); + full_api(); + minimal_api_lp(); + minimal_api_mip(); + minimal_api_qp(); + full_api_options(); + full_api_lp(); + full_api_mip(); + full_api_qp(); + pass_presolve_get_lp(); + options(); + test_getColsByRange(); + test_passHessian(); + test_ranging(); + test_feasibilityRelaxation(); + test_getModel(); test_multiObjective(); return 0; } diff --git a/docs/src/interfaces/python/example-py.md b/docs/src/interfaces/python/example-py.md index 6fce25e870..fd4eef038b 100644 --- a/docs/src/interfaces/python/example-py.md +++ b/docs/src/interfaces/python/example-py.md @@ -239,7 +239,6 @@ print('Basis validity = ', h.basisValidityToString(info.basis_validity)) ## Multi-objective optimization -* `passLinearObjectives` * `addLinearObjective` * `clearLinearObjectives` diff --git a/examples/call_highs_from_python.py b/examples/call_highs_from_python.py index 2ff88f281b..61e6356f3e 100644 --- a/examples/call_highs_from_python.py +++ b/examples/call_highs_from_python.py @@ -493,4 +493,8 @@ def user_interrupt_callback( print("row_bound:", iis.row_bound) print("col_index:", iis.col_index) -print("col_bound:", iis.col_bound) \ No newline at end of file +print("col_bound:", iis.col_bound) + +# ~~~ +# Clear so that incumbent model is empty +h.clear() diff --git a/src/highs_bindings.cpp b/src/highs_bindings.cpp index b8f7e249cf..64db9172d3 100644 --- a/src/highs_bindings.cpp +++ b/src/highs_bindings.cpp @@ -159,6 +159,10 @@ HighsStatus highs_passHessianPointers(Highs* h, const HighsInt dim, q_value_ptr); } +HighsStatus highs_addLinearObjective(Highs* h, const HighsLinearObjective& linear_objective) { + return h->addLinearObjective(linear_objective, -1); +} + HighsStatus highs_postsolve(Highs* h, const HighsSolution& solution, const HighsBasis& basis) { return h->postsolve(solution, basis); @@ -939,6 +943,8 @@ PYBIND11_MODULE(_core, m) { .def("passModel", &highs_passLpPointers) .def("passHessian", &highs_passHessian) .def("passHessian", &highs_passHessianPointers) + .def("addLinearObjective", &highs_addLinearObjective) + .def("clearLinearObjectives", &Highs::clearLinearObjectives) .def("passColName", &Highs::passColName) .def("passRowName", &Highs::passRowName) .def("readModel", &Highs::readModel) @@ -1148,6 +1154,14 @@ PYBIND11_MODULE(_core, m) { .def(py::init<>()) .def_readwrite("simplex_time", &HighsIisInfo::simplex_time) .def_readwrite("simplex_iterations", &HighsIisInfo::simplex_iterations); + py::class_(m, "HighsLinearObjective") + .def(py::init<>()) + .def_readwrite("weight", &HighsLinearObjective::weight) + .def_readwrite("offset", &HighsLinearObjective::offset) + .def_readwrite("coefficients", &HighsLinearObjective::coefficients) + .def_readwrite("abs_tolerance", &HighsLinearObjective::abs_tolerance) + .def_readwrite("rel_tolerance", &HighsLinearObjective::rel_tolerance) + .def_readwrite("priority", &HighsLinearObjective::priority); // constants m.attr("kHighsInf") = kHighsInf; m.attr("kHighsIInf") = kHighsIInf; diff --git a/src/highspy/__init__.py b/src/highspy/__init__.py index 9e3c03a0e1..e3044e8ccb 100644 --- a/src/highspy/__init__.py +++ b/src/highspy/__init__.py @@ -29,6 +29,7 @@ HighsRanging, kHighsInf, kHighsIInf, + HighsLinearObjective, HIGHS_VERSION_MAJOR, HIGHS_VERSION_MINOR, HIGHS_VERSION_PATCH, diff --git a/src/lp_data/HighsInterface.cpp b/src/lp_data/HighsInterface.cpp index 3669f69f78..1c7ad16b9f 100644 --- a/src/lp_data/HighsInterface.cpp +++ b/src/lp_data/HighsInterface.cpp @@ -3836,7 +3836,6 @@ HighsStatus Highs::multiobjectiveSolve() { return returnFromLexicographicOptimization(HighsStatus::kWarning, original_lp_num_row); } - this->writeSolution("", kSolutionStylePretty); if (iIx == num_linear_objective - 1) break; if (lp.isMip()) { // Save the solution to provide an integer feasible solution of From f85262adf7f57dfef45f298430ad624d903511f1 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Mon, 25 Nov 2024 09:30:19 +0000 Subject: [PATCH 65/75] Removed Highs_addLinearObjectiveWithIndex from C API, and added docstrings for other multiple linear objective methods --- docs/src/guide/further.md | 1 - src/interfaces/highs_c_api.cpp | 27 ++++++++--------- src/interfaces/highs_c_api.h | 53 +++++++++++++++++++++++++++++++--- 3 files changed, 63 insertions(+), 18 deletions(-) diff --git a/docs/src/guide/further.md b/docs/src/guide/further.md index 91a4dc75ca..b5fb80ec2d 100644 --- a/docs/src/guide/further.md +++ b/docs/src/guide/further.md @@ -125,7 +125,6 @@ linear objective is represented by the following data, held in the Multi-objective optimization in HiGHS is defined by the following methods -- [passLinearObjectives](@ref Multi-objective-optimization] - Pass multiple linear objectives as their number `num_linear_objective` and pointer to a vector of `HighsLinearObjective` instances, overwriting any previous linear objectives - [addLinearObjective](@ref Multi-objective-optimization] - Add a single `HighsLinearObjective` instance to any already stored in HiGHS - [clearLinearObjectives](@ref Multi-objective-optimization] - Clears any linear objectives stored in HiGHS diff --git a/src/interfaces/highs_c_api.cpp b/src/interfaces/highs_c_api.cpp index 655c0fce30..3c464796e2 100644 --- a/src/interfaces/highs_c_api.cpp +++ b/src/interfaces/highs_c_api.cpp @@ -300,12 +300,22 @@ HighsInt Highs_passLinearObjectives(const void* highs, const HighsInt* priority) { HighsInt status = Highs_clearLinearObjectives(highs); if (status != kHighsStatusOk) return status; - HighsInt num_col = Highs_getNumCol(highs); + HighsLinearObjective linear_objective; for (HighsInt iObj = 0; iObj < num_linear_objective; iObj++) { - status = Highs_addLinearObjectiveWithIndex( - highs, weight[iObj], offset[iObj], &coefficients[iObj * num_col], - abs_tolerance[iObj], rel_tolerance[iObj], priority[iObj], iObj); + HighsInt num_col = Highs_getNumCol(highs); + linear_objective.weight = weight[iObj]; + linear_objective.offset = offset[iObj]; + for (HighsInt iCol = 0; iCol < num_col; iCol++) + linear_objective.coefficients.push_back( + coefficients[iObj * num_col + iCol]); + linear_objective.abs_tolerance = abs_tolerance[iObj]; + linear_objective.rel_tolerance = rel_tolerance[iObj]; + linear_objective.priority = priority[iObj]; + linear_objective.weight = weight[iObj]; + status = + HighsInt(((Highs*)highs)->addLinearObjective(linear_objective, iObj)); if (status != kHighsStatusOk) return status; + linear_objective.coefficients.clear(); } return kHighsStatusOk; } @@ -316,15 +326,6 @@ HighsInt Highs_addLinearObjective(const void* highs, const double weight, const double abs_tolerance, const double rel_tolerance, const HighsInt priority) { - return Highs_addLinearObjectiveWithIndex(highs, weight, offset, coefficients, - abs_tolerance, rel_tolerance, - priority, -1); -} - -HighsInt Highs_addLinearObjectiveWithIndex( - const void* highs, const double weight, const double offset, - const double* coefficients, const double abs_tolerance, - const double rel_tolerance, const HighsInt priority, const HighsInt iObj) { HighsLinearObjective linear_objective; HighsInt num_col = Highs_getNumCol(highs); linear_objective.weight = weight; diff --git a/src/interfaces/highs_c_api.h b/src/interfaces/highs_c_api.h index db8764fae6..0feb39bf35 100644 --- a/src/interfaces/highs_c_api.h +++ b/src/interfaces/highs_c_api.h @@ -557,6 +557,28 @@ HighsInt Highs_passHessian(void* highs, const HighsInt dim, const HighsInt* start, const HighsInt* index, const double* value); +/** + * Passes multiple linear objective data to HiGHS, clearing any such + * data already in HiGHS + * + * @param highs A pointer to the Highs instance. + * @param weight A pointer to the weights of the linear objective, with + * its positive/negative sign determining whether it is + * minimized or maximized during lexicographic optimization + * @param offset A pointer to the objective offsets + * @param coefficients A pointer to the objective coefficients + * @param abs_tolerance A pointer to the absolute tolerances used when + * constructing objective constraints during lexicographic + * optimization + * @param rel_tolerance A pointer to the relative tolerances used when + * constructing objective constraints during lexicographic + * optimization + * @param priority A pointer to the priorities of the objectives during + * lexicographic optimization + * + * @returns A `kHighsStatus` constant indicating whether the call succeeded. + */ + HighsInt Highs_passLinearObjectives(const void* highs, const HighsInt num_linear_objective, const double* weight, const double* offset, @@ -565,6 +587,26 @@ HighsInt Highs_passLinearObjectives(const void* highs, const double* rel_tolerance, const HighsInt* priority); +/** + * Adds linear objective data to HiGHS + * + * @param highs A pointer to the Highs instance. + * @param weight The weight of the linear objective, with its + * positive/negative sign determining whether it is + * minimized or maximized during lexicographic + * optimization + * @param offset The objective offset + * @param coefficients A pointer to the objective coefficients + * @param abs_tolerance The absolute tolerance used when constructing an + * objective constraint during lexicographic optimization + * @param rel_tolerance The relative tolerance used when constructing an + * objective constraint during lexicographic optimization + * @param priority The priority of this objective during lexicographic + * optimization + * + * @returns A `kHighsStatus` constant indicating whether the call succeeded. + */ + HighsInt Highs_addLinearObjective(const void* highs, const double weight, const double offset, const double* coefficients, @@ -572,10 +614,13 @@ HighsInt Highs_addLinearObjective(const void* highs, const double weight, const double rel_tolerance, const HighsInt priority); -HighsInt Highs_addLinearObjectiveWithIndex( - const void* highs, const double weight, const double offset, - const double* coefficients, const double abs_tolerance, - const double rel_tolerance, const HighsInt priority, const HighsInt iObj); +/** + * Clears any multiple linear objective data in HiGHS + * + * @param highs A pointer to the Highs instance. + * + * @returns A `kHighsStatus` constant indicating whether the call succeeded. + */ HighsInt Highs_clearLinearObjectives(const void* highs); /** From 901e5d5af58d49b69c404c7dcba8d54212944ec8 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Mon, 25 Nov 2024 10:08:30 +0000 Subject: [PATCH 66/75] Hopefully corrected further.md --- docs/src/guide/further.md | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/docs/src/guide/further.md b/docs/src/guide/further.md index b5fb80ec2d..0bfa519867 100644 --- a/docs/src/guide/further.md +++ b/docs/src/guide/further.md @@ -150,28 +150,22 @@ priority values must be distinct_. * Minimize/maximize with respect to the linear objective of highest priority value, according to whether its `weight` is positive/negative * Add a constraint to the model so that the value of the linear objective of highest priority satsifies a bound given by the values of `abs_tolerance` and/or `rel_tolerance`. - - + If the objective was minimized to a value ``f^*>=0``, then the constraint ensures that the this objective value is no greater than + + If the objective was minimized to a value ``f^*\ge0``, then the constraint ensures that the this objective value is no greater than ```math - -\min(f^*+`abs_tolerance`,~f^*[1+`rel_tolerance`]). +\min(f^*+`abs\_tolerance`,~f^*\times[1+`rel\_tolerance`]). ``` - - + If the objective was minimized to a value ``f^*<0``, then the constraint ensures that the this objective value is no greater than + + If the objective was minimized to a value ``f^*\lt0``, then the constraint ensures that the this objective value is no greater than ```math -\min(f^*+`abs_tolerance`,~f^*[1-`rel_tolerance`]). +\min(f^*+`abs\_tolerance`,~f^*\times[1-`rel\_tolerance`]). ``` - - + If the objective was maximized to a value ``f^*>=0``, then the constraint ensures that the this objective value is no less than + + If the objective was maximized to a value ``f^*\ge0``, then the constraint ensures that the this objective value is no less than ```math -\max(f^*-`abs_tolerance`,~f^*[1-`rel_tolerance`]). +\max(f^*-`abs\_tolerance`,~f^*\times[1-`rel\_tolerance`]). ``` - - + If the objective was maximized to a value ``f^*<0``, then the constraint ensures that the this objective value is no less than + + If the objective was maximized to a value ``f^*\lt0``, then the constraint ensures that the this objective value is no less than ```math -\max(f^*-`abs_tolerance`,~f^*[1+`rel_tolerance`]). +\max(f^*-`abs\_tolerance`,~f^*\times[1+`rel\_tolerance`]). ``` - * Minimize/maximize with respect to the linear objective of next highest priority, and then add a corresponding objective constraint to the model, repeating until optimization with respect to the linear objective of lowest priority has taken place. Note From 0778fae054f47b7509864ce84879681e1cb29f33 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Mon, 25 Nov 2024 10:34:57 +0000 Subject: [PATCH 67/75] New hope to correct further.md --- docs/src/guide/further.md | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/docs/src/guide/further.md b/docs/src/guide/further.md index 0bfa519867..65d25ff658 100644 --- a/docs/src/guide/further.md +++ b/docs/src/guide/further.md @@ -151,21 +151,13 @@ priority values must be distinct_. * Add a constraint to the model so that the value of the linear objective of highest priority satsifies a bound given by the values of `abs_tolerance` and/or `rel_tolerance`. + If the objective was minimized to a value ``f^*\ge0``, then the constraint ensures that the this objective value is no greater than -```math -\min(f^*+`abs\_tolerance`,~f^*\times[1+`rel\_tolerance`]). -``` - + If the objective was minimized to a value ``f^*\lt0``, then the constraint ensures that the this objective value is no greater than -```math -\min(f^*+`abs\_tolerance`,~f^*\times[1-`rel\_tolerance`]). -``` +``\min(f^*+abs\_tolerance,~f^*\times[1+rel\_tolerance]).`` + + If the objective was minimized to a value ``f^*<0``, then the constraint ensures that the this objective value is no greater than +``\min(f^*+abs\_tolerance,~f^*\times[1-rel\_tolerance]).`` + If the objective was maximized to a value ``f^*\ge0``, then the constraint ensures that the this objective value is no less than -```math -\max(f^*-`abs\_tolerance`,~f^*\times[1-`rel\_tolerance`]). -``` - + If the objective was maximized to a value ``f^*\lt0``, then the constraint ensures that the this objective value is no less than -```math -\max(f^*-`abs\_tolerance`,~f^*\times[1+`rel\_tolerance`]). -``` +``\max(f^*-abs\_tolerance,~f^*\times[1-rel\_tolerance]).`` + + If the objective was maximized to a value ``f^*<0``, then the constraint ensures that the this objective value is no less than +``\max(f^*-abs\_tolerance,~f^*\times[1+rel\_tolerance]).`` * Minimize/maximize with respect to the linear objective of next highest priority, and then add a corresponding objective constraint to the model, repeating until optimization with respect to the linear objective of lowest priority has taken place. Note From 1d25e744436c42dd933b784d61661b513e36ae3c Mon Sep 17 00:00:00 2001 From: JAJHall Date: Mon, 25 Nov 2024 11:20:10 +0000 Subject: [PATCH 68/75] Again... new hope to correct further.md --- docs/src/guide/further.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/src/guide/further.md b/docs/src/guide/further.md index 65d25ff658..4d828f7055 100644 --- a/docs/src/guide/further.md +++ b/docs/src/guide/further.md @@ -152,12 +152,16 @@ priority values must be distinct_. * Add a constraint to the model so that the value of the linear objective of highest priority satsifies a bound given by the values of `abs_tolerance` and/or `rel_tolerance`. + If the objective was minimized to a value ``f^*\ge0``, then the constraint ensures that the this objective value is no greater than ``\min(f^*+abs\_tolerance,~f^*\times[1+rel\_tolerance]).`` + + If the objective was minimized to a value ``f^*<0``, then the constraint ensures that the this objective value is no greater than ``\min(f^*+abs\_tolerance,~f^*\times[1-rel\_tolerance]).`` + + If the objective was maximized to a value ``f^*\ge0``, then the constraint ensures that the this objective value is no less than ``\max(f^*-abs\_tolerance,~f^*\times[1-rel\_tolerance]).`` + + If the objective was maximized to a value ``f^*<0``, then the constraint ensures that the this objective value is no less than ``\max(f^*-abs\_tolerance,~f^*\times[1+rel\_tolerance]).`` + * Minimize/maximize with respect to the linear objective of next highest priority, and then add a corresponding objective constraint to the model, repeating until optimization with respect to the linear objective of lowest priority has taken place. Note From bf5c7db411750955ecbf953331d896ac0fc7d56a Mon Sep 17 00:00:00 2001 From: JAJHall Date: Mon, 25 Nov 2024 11:32:21 +0000 Subject: [PATCH 69/75] More... again... new hope to correct further.md --- docs/src/guide/further.md | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/docs/src/guide/further.md b/docs/src/guide/further.md index 4d828f7055..ed111f6682 100644 --- a/docs/src/guide/further.md +++ b/docs/src/guide/further.md @@ -150,17 +150,13 @@ priority values must be distinct_. * Minimize/maximize with respect to the linear objective of highest priority value, according to whether its `weight` is positive/negative * Add a constraint to the model so that the value of the linear objective of highest priority satsifies a bound given by the values of `abs_tolerance` and/or `rel_tolerance`. - + If the objective was minimized to a value ``f^*\ge0``, then the constraint ensures that the this objective value is no greater than -``\min(f^*+abs\_tolerance,~f^*\times[1+rel\_tolerance]).`` + + If the objective was minimized to a value ``f^*\ge0``, then the constraint ensures that the this objective value is no greater than ``\min(f^*+abs\_tolerance,~f^*\times[1+rel\_tolerance]).`` - + If the objective was minimized to a value ``f^*<0``, then the constraint ensures that the this objective value is no greater than -``\min(f^*+abs\_tolerance,~f^*\times[1-rel\_tolerance]).`` + + If the objective was minimized to a value ``f^*<0``, then the constraint ensures that the this objective value is no greater than ``\min(f^*+abs\_tolerance,~f^*\times[1-rel\_tolerance]).`` - + If the objective was maximized to a value ``f^*\ge0``, then the constraint ensures that the this objective value is no less than -``\max(f^*-abs\_tolerance,~f^*\times[1-rel\_tolerance]).`` + + If the objective was maximized to a value ``f^*\ge0``, then the constraint ensures that the this objective value is no less than ``\max(f^*-abs\_tolerance,~f^*\times[1-rel\_tolerance]).`` - + If the objective was maximized to a value ``f^*<0``, then the constraint ensures that the this objective value is no less than -``\max(f^*-abs\_tolerance,~f^*\times[1+rel\_tolerance]).`` + + If the objective was maximized to a value ``f^*<0``, then the constraint ensures that the this objective value is no less than ``\max(f^*-abs\_tolerance,~f^*\times[1+rel\_tolerance]).`` * Minimize/maximize with respect to the linear objective of next highest priority, and then add a corresponding objective constraint to the model, repeating until optimization with respect to the linear objective of lowest priority has taken place. From 52d1b58dde02eb17bd08160becfcfa278b20e84a Mon Sep 17 00:00:00 2001 From: JAJHall Date: Mon, 25 Nov 2024 12:03:45 +0000 Subject: [PATCH 70/75] Added examples/knapsack.py to illustrate setSolution in highspy --- examples/knapsack.py | 55 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 examples/knapsack.py diff --git a/examples/knapsack.py b/examples/knapsack.py new file mode 100644 index 0000000000..992cac793d --- /dev/null +++ b/examples/knapsack.py @@ -0,0 +1,55 @@ +import numpy as np +import highspy + +h = highspy.Highs() +h.setOptionValue("output_flag", False); +h.setOptionValue("presolve", "off"); +inf = highspy.kHighsInf +lp = highspy.HighsLp() +lp.sense_ = highspy.ObjSense.kMaximize +lp.num_col_ = 5 +lp.num_row_ = 1 +lp.col_cost_ = np.array([8, 5, 3, 11, 7], dtype=np.double) +lp.col_lower_ = np.array([0, 0, 0, 0, 0], dtype=np.double) +lp.col_upper_ = np.array([1, 1, 1, 1, 1], dtype=np.double) +lp.row_lower_ = np.array([-inf], dtype=np.double) +lp.row_upper_ = np.array([11], dtype=np.double) +lp.a_matrix_.format_ = highspy.MatrixFormat.kRowwise +lp.a_matrix_.start_ = np.array([0, 5]) +lp.a_matrix_.index_ = np.array([0, 1, 2, 3, 4]) +lp.a_matrix_.value_ = np.array([4, 3, 1, 5, 4], dtype=np.double) +lp.integrality_ = np.array([highspy.HighsVarType.kInteger, highspy.HighsVarType.kInteger, highspy.HighsVarType.kInteger, highspy.HighsVarType.kInteger, highspy.HighsVarType.kInteger]) +h.passModel(lp) + +h.run() +solution = h.getSolution() +print(f"Solution: ({solution.col_value[0]}, {solution.col_value[1]}, {solution.col_value[2]}, {solution.col_value[3]}, {solution.col_value[4]})") + +# Solution is [1, 0, 1, 1, 0] + +# Illustrate setSolution + +# First by passing back the optimal solution + +h.clearSolver() +h.setSolution(solution) +h.run() +solution = h.getSolution() +print(f"Solution: ({solution.col_value[0]}, {solution.col_value[1]}, {solution.col_value[2]}, {solution.col_value[3]}, {solution.col_value[4]})") + +# Now passing back the optimal values of two variables as a sparse solution +h.clearSolver() +index = np.array([0, 3]) +value = np.array([1, 1], dtype=np.double) +h.setSolution(2, index, value) +h.run() +solution = h.getSolution() +print(f"Solution: ({solution.col_value[0]}, {solution.col_value[1]}, {solution.col_value[2]}, {solution.col_value[3]}, {solution.col_value[4]})") + +# Test passing back the optimal value of one variable, and a non-optimal value of another, as a sparse solution in untyped array +h.clearSolver() +h.setSolution(2, np.array([0, 4]), np.array([1, 1])) +h.run() +solution = h.getSolution() +print(f"Solution: ({solution.col_value[0]}, {solution.col_value[1]}, {solution.col_value[2]}, {solution.col_value[3]}, {solution.col_value[4]})") + From 92852f64317cb79a7353c5e4905ef958b5f36b05 Mon Sep 17 00:00:00 2001 From: Stefan Vigerske Date: Mon, 25 Nov 2024 17:42:45 +0100 Subject: [PATCH 71/75] fix constructor initialization order --- src/mip/HighsPrimalHeuristics.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mip/HighsPrimalHeuristics.cpp b/src/mip/HighsPrimalHeuristics.cpp index 5908f4373c..1c4aff5daf 100644 --- a/src/mip/HighsPrimalHeuristics.cpp +++ b/src/mip/HighsPrimalHeuristics.cpp @@ -37,10 +37,10 @@ HighsPrimalHeuristics::HighsPrimalHeuristics(HighsMipSolver& mipsolver) : mipsolver(mipsolver), - lp_iterations(0), total_repair_lp(0), total_repair_lp_feasible(0), total_repair_lp_iterations(0), + lp_iterations(0), randgen(mipsolver.options_mip_->random_seed) { successObservations = 0; numSuccessObservations = 0; From 9fe14972efda787a01fb862abb7c86f81fbaac19 Mon Sep 17 00:00:00 2001 From: Stefan Vigerske Date: Mon, 25 Nov 2024 17:44:06 +0100 Subject: [PATCH 72/75] fix missing prior declartion of public function - make function local --- src/mip/HighsMipSolverData.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/mip/HighsMipSolverData.cpp b/src/mip/HighsMipSolverData.cpp index e84f9b5396..522804d18b 100644 --- a/src/mip/HighsMipSolverData.cpp +++ b/src/mip/HighsMipSolverData.cpp @@ -2501,7 +2501,8 @@ bool HighsMipSolverData::interruptFromCallbackWithData( return mipsolver.callback_->callbackAction(callback_type, message); } -double possInfRelDiff(const double v0, const double v1, const double den) { +static double possInfRelDiff(const double v0, const double v1, + const double den) { double rel_diff; if (std::fabs(v0) == kHighsInf) { if (std::fabs(v1) == kHighsInf) { From 2c9e304a2f48d8f3fddbfc9ebfc4d9916c0eeeb7 Mon Sep 17 00:00:00 2001 From: Ralf Gommers Date: Wed, 27 Nov 2024 14:20:52 +0100 Subject: [PATCH 73/75] MAINT: declare that `highspy._core` supports free-threaded CPython This allows `highspy` to be imported in a free-threaded CPython (see PEP 703) interpreter without raising a warning and re-enabling the GIL. We've tested thread-safety of this extension module reasonably well in SciPy now, and everything seems fine on all tested platforms (Linux, macOS, Windows and x86-64/arm64). --- src/highs_bindings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/highs_bindings.cpp b/src/highs_bindings.cpp index 64db9172d3..5b4069a96c 100644 --- a/src/highs_bindings.cpp +++ b/src/highs_bindings.cpp @@ -642,7 +642,7 @@ HighsStatus highs_setCallback( data.ptr()); } -PYBIND11_MODULE(_core, m) { +PYBIND11_MODULE(_core, m, py::mod_gil_not_used()) { // To keep a smaller diff, for reviewers, the declarations are not moved, but // keep in mind: // C++ enum classes :: don't need .export_values() From 9fdc6115adc47c340f5f9c5fb3f73f5398661a26 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Thu, 28 Nov 2024 16:09:37 +0000 Subject: [PATCH 74/75] First draft of HighsSimplexStats --- check/TestLpSolvers.cpp | 18 +++++++++++++ src/Highs.h | 4 +++ src/lp_data/HStruct.h | 15 +++++++++++ src/lp_data/HighsSolve.cpp | 2 +- src/simplex/HApp.h | 4 +++ src/simplex/HEkk.cpp | 41 +++++++++++++++++++++++++++++- src/simplex/HEkk.h | 7 +++++ src/simplex/HighsSimplexAnalysis.h | 1 + 8 files changed, 90 insertions(+), 2 deletions(-) diff --git a/check/TestLpSolvers.cpp b/check/TestLpSolvers.cpp index 72b94594d0..c1470fbb58 100644 --- a/check/TestLpSolvers.cpp +++ b/check/TestLpSolvers.cpp @@ -29,6 +29,7 @@ void testDualObjective(const std::string model) { std::max(1.0, std::fabs(primal_objective)); REQUIRE(relative_primal_dual_gap < 1e-12); } + void testSolver(Highs& highs, const std::string solver, IterationCount& default_iteration_count, const HighsInt int_simplex_strategy = 0) { @@ -641,3 +642,20 @@ TEST_CASE("standard-form-lp", "[highs_lp_solver]") { "maximizing\n"); testStandardForm(highs.getLp()); } + +TEST_CASE("simplex-stats", "[highs_lp_solver]") { + HighsStatus return_status; + + Highs h; + // h.setOptionValue("output_flag", dev_run); + std::string model_file = + std::string(HIGHS_DIR) + "/check/instances/adlittle.mps"; + REQUIRE(h.readModel(model_file) == HighsStatus::kOk); + const HighsSimplexStats& simplex_stats = h.getSimplexStats(); + simplex_stats.report(stdout); + h.reportSimplexStats(stdout); + REQUIRE(h.run() == HighsStatus::kOk); + simplex_stats.report(stdout); + h.reportSimplexStats(stdout); + +} diff --git a/src/Highs.h b/src/Highs.h index 04a4a3ce25..e84558039d 100644 --- a/src/Highs.h +++ b/src/Highs.h @@ -1215,6 +1215,10 @@ class Highs { static void resetGlobalScheduler(bool blocking = false); // Start of advanced methods for HiGHS MIP solver + + const HighsSimplexStats& getSimplexStats() const { return ekk_instance_.getSimplexStats(); } + void reportSimplexStats(FILE* file) const { ekk_instance_.reportSimplexStats(file); } + /** * @brief Get the hot start basis data from the most recent simplex * solve. Advanced method: for HiGHS MIP solver diff --git a/src/lp_data/HStruct.h b/src/lp_data/HStruct.h index 3963d9df1c..86a2bde39c 100644 --- a/src/lp_data/HStruct.h +++ b/src/lp_data/HStruct.h @@ -154,4 +154,19 @@ struct HighsLinearObjective { void clear(); }; +struct HighsSimplexStats { + bool valid; + HighsInt iteration_count; + HighsInt num_invert; + HighsInt last_invert_num_el; + HighsInt last_factored_basis_num_el; + double col_aq_density; + double row_ep_density; + double row_ap_density; + double row_DSE_density; + void report(FILE* file) const; + void clear(); +}; + + #endif /* LP_DATA_HSTRUCT_H_ */ diff --git a/src/lp_data/HighsSolve.cpp b/src/lp_data/HighsSolve.cpp index f1df5239a3..118b92c54a 100644 --- a/src/lp_data/HighsSolve.cpp +++ b/src/lp_data/HighsSolve.cpp @@ -17,7 +17,7 @@ #include "pdlp/CupdlpWrapper.h" #include "simplex/HApp.h" -// The method below runs simplex or ipx solver on the lp. +// The method below runs simplex, ipx or pdlp solver on the lp. HighsStatus solveLp(HighsLpSolverObject& solver_object, const string message) { HighsStatus return_status = HighsStatus::kOk; HighsStatus call_status; diff --git a/src/simplex/HApp.h b/src/simplex/HApp.h index 5e48ae9cd3..905a290c3a 100644 --- a/src/simplex/HApp.h +++ b/src/simplex/HApp.h @@ -112,6 +112,10 @@ inline HighsStatus solveLpSimplex(HighsLpSolverObject& solver_object) { // return resetModelStatusAndHighsInfo(solver_object); + // Clear the simplex stats + ekk_instance.clearSimplexStats(); + ekk_instance.simplex_stats_.iteration_count = -ekk_instance.iteration_count_; + // Assumes that the LP has a positive number of rows, since // unconstrained LPs should be solved in solveLp bool positive_num_row = solver_object.lp_.num_row_ > 0; diff --git a/src/simplex/HEkk.cpp b/src/simplex/HEkk.cpp index dc30e2cc7e..126b56b364 100644 --- a/src/simplex/HEkk.cpp +++ b/src/simplex/HEkk.cpp @@ -296,6 +296,7 @@ void HEkk::invalidate() { assert(!this->status_.is_permuted); this->status_.initialised_for_solve = false; this->invalidateBasisMatrix(); + this->simplex_stats_.clear(); } void HEkk::invalidateBasisMatrix() { @@ -2082,6 +2083,7 @@ HighsInt HEkk::computeFactor() { // number of updates shouldn't be positive info_.update_count = 0; + simplex_stats_.num_invert++; return rank_deficiency; } @@ -3500,7 +3502,18 @@ HighsStatus HEkk::returnFromEkkSolve(const HighsStatus return_status) { // Note that in timeReporting(1), analysis_.analyse_simplex_time // reverts to its value given by options_ if (analysis_.analyse_simplex_time) analysis_.reportSimplexTimer(); - + simplex_stats_.valid = true; + // Since HEkk::iteration_count_ includes iteration on presolved LP, + // simplex_stats_.iteration_count is initialised to - + // HEkk::iteration_count_ + simplex_stats_.iteration_count += iteration_count_; + // simplex_stats_.num_invert is incremented internally + simplex_stats_.last_invert_num_el = simplex_nla_.factor_.invert_num_el; + simplex_stats_.last_factored_basis_num_el = simplex_nla_.factor_.basis_matrix_num_el; + simplex_stats_.col_aq_density = analysis_.col_aq_density; + simplex_stats_.row_ep_density = analysis_.row_ep_density; + simplex_stats_.row_ap_density = analysis_.row_ap_density; + simplex_stats_.row_DSE_density = analysis_.row_DSE_density; return return_status; } @@ -4406,3 +4419,29 @@ void HEkk::unitBtranResidual(const HighsInt row_out, const HVector& row_ep, residual_norm = max(fabs(residual.array[iRow]), residual_norm); } } + +void HighsSimplexStats::report(FILE* file) const { + fprintf(file, "\nSimplex stats\n"); + fprintf(file, " valid = %d\n", this->valid); + fprintf(file, " iteration_count = %d\n", this->iteration_count); + fprintf(file, " num_invert = %d\n", this->num_invert); + fprintf(file, " last_invert_num_el = %d\n", this->last_invert_num_el); + fprintf(file, " last_factored_basis_num_el = %d\n", this->last_factored_basis_num_el); + fprintf(file, " col_aq_density = %g\n", this->col_aq_density); + fprintf(file, " row_ep_density = %g\n", this->row_ep_density); + fprintf(file, " row_ap_density = %g\n", this->row_ap_density); + fprintf(file, " row_DSE_density = %g\n", this->row_DSE_density); + +} + +void HighsSimplexStats::clear() { + valid = false; + iteration_count = 0; + num_invert = 0; + last_invert_num_el = 0; + last_factored_basis_num_el = 0; + col_aq_density = 0; + row_ep_density = 0; + row_ap_density = 0; + row_DSE_density = 0; +} diff --git a/src/simplex/HEkk.h b/src/simplex/HEkk.h index 3b3da389da..9d54524fbc 100644 --- a/src/simplex/HEkk.h +++ b/src/simplex/HEkk.h @@ -156,6 +156,10 @@ class HEkk { const vector& rowLower, const vector& rowUpper); + const HighsSimplexStats& getSimplexStats() const { return simplex_stats_; } + void clearSimplexStats() { simplex_stats_.clear(); } + void reportSimplexStats(FILE* file) const { simplex_stats_.report(file); } + // Make this private later void chooseSimplexStrategyThreads(const HighsOptions& options, HighsSimplexInfo& info); @@ -171,6 +175,7 @@ class HEkk { const std::string message, const HighsInt alt_debug_level = -1) const; bool debugNlaScalingOk(const HighsLp& lp) const; + // Data members HighsCallback* callback_; HighsOptions* options_; @@ -255,6 +260,8 @@ class HEkk { std::vector bad_basis_change_; std::vector primal_phase1_dual_; + HighsSimplexStats simplex_stats_; + private: bool isUnconstrainedLp(); void initialiseForSolve(); diff --git a/src/simplex/HighsSimplexAnalysis.h b/src/simplex/HighsSimplexAnalysis.h index d7a93d2ca6..db43446f20 100644 --- a/src/simplex/HighsSimplexAnalysis.h +++ b/src/simplex/HighsSimplexAnalysis.h @@ -251,6 +251,7 @@ class HighsSimplexAnalysis { void reportFactorTimer(); void updateInvertFormData(const HFactor& factor); void reportInvertFormData(); + HighsInt numInvert() { return num_invert; } // Control methods to be moved to HEkkControl void dualSteepestEdgeWeightError(const double computed_edge_weight, From 258e8c063e6c517244ac3cad15f814d2494856ac Mon Sep 17 00:00:00 2001 From: JAJHall Date: Thu, 28 Nov 2024 17:00:06 +0000 Subject: [PATCH 75/75] HighsSimplexStats struct gives data about last simplex solve --- check/TestLpSolvers.cpp | 42 ++++++++++++++++++++++++++++++----------- src/Highs.h | 8 ++++++-- src/lp_data/HStruct.h | 5 ++--- src/simplex/HApp.h | 5 ++--- src/simplex/HEkk.cpp | 20 +++++++++++--------- src/simplex/HEkk.h | 7 ++++--- 6 files changed, 56 insertions(+), 31 deletions(-) diff --git a/check/TestLpSolvers.cpp b/check/TestLpSolvers.cpp index c1470fbb58..49da8322b0 100644 --- a/check/TestLpSolvers.cpp +++ b/check/TestLpSolvers.cpp @@ -644,18 +644,38 @@ TEST_CASE("standard-form-lp", "[highs_lp_solver]") { } TEST_CASE("simplex-stats", "[highs_lp_solver]") { - HighsStatus return_status; + HighsStatus return_status; Highs h; - // h.setOptionValue("output_flag", dev_run); - std::string model_file = - std::string(HIGHS_DIR) + "/check/instances/adlittle.mps"; - REQUIRE(h.readModel(model_file) == HighsStatus::kOk); const HighsSimplexStats& simplex_stats = h.getSimplexStats(); - simplex_stats.report(stdout); - h.reportSimplexStats(stdout); - REQUIRE(h.run() == HighsStatus::kOk); - simplex_stats.report(stdout); - h.reportSimplexStats(stdout); - + h.setOptionValue("output_flag", dev_run); + std::string model_file = + std::string(HIGHS_DIR) + "/check/instances/adlittle.mps"; + REQUIRE(h.readModel(model_file) == HighsStatus::kOk); + + REQUIRE(h.run() == HighsStatus::kOk); + REQUIRE(simplex_stats.valid); + REQUIRE(simplex_stats.iteration_count == 0); + REQUIRE(simplex_stats.num_invert == 1); + REQUIRE(simplex_stats.last_invert_num_el > 0); + REQUIRE(simplex_stats.last_factored_basis_num_el > 0); + REQUIRE(simplex_stats.col_aq_density == 0); + REQUIRE(simplex_stats.row_ep_density == 0); + REQUIRE(simplex_stats.row_ap_density == 0); + REQUIRE(simplex_stats.row_DSE_density == 0); + if (dev_run) h.reportSimplexStats(stdout); + + h.clearSolver(); + h.setOptionValue("presolve", kHighsOffString); + REQUIRE(h.run() == HighsStatus::kOk); + REQUIRE(simplex_stats.valid); + REQUIRE(simplex_stats.iteration_count > 0); + REQUIRE(simplex_stats.num_invert > 0); + REQUIRE(simplex_stats.last_invert_num_el > 0); + REQUIRE(simplex_stats.last_factored_basis_num_el > 0); + REQUIRE(simplex_stats.col_aq_density > 0); + REQUIRE(simplex_stats.row_ep_density > 0); + REQUIRE(simplex_stats.row_ap_density > 0); + REQUIRE(simplex_stats.row_DSE_density > 0); + if (dev_run) h.reportSimplexStats(stdout); } diff --git a/src/Highs.h b/src/Highs.h index e84558039d..70d800a5de 100644 --- a/src/Highs.h +++ b/src/Highs.h @@ -1216,8 +1216,12 @@ class Highs { // Start of advanced methods for HiGHS MIP solver - const HighsSimplexStats& getSimplexStats() const { return ekk_instance_.getSimplexStats(); } - void reportSimplexStats(FILE* file) const { ekk_instance_.reportSimplexStats(file); } + const HighsSimplexStats& getSimplexStats() const { + return ekk_instance_.getSimplexStats(); + } + void reportSimplexStats(FILE* file) const { + ekk_instance_.reportSimplexStats(file); + } /** * @brief Get the hot start basis data from the most recent simplex diff --git a/src/lp_data/HStruct.h b/src/lp_data/HStruct.h index 86a2bde39c..35a0b0ee42 100644 --- a/src/lp_data/HStruct.h +++ b/src/lp_data/HStruct.h @@ -164,9 +164,8 @@ struct HighsSimplexStats { double row_ep_density; double row_ap_density; double row_DSE_density; - void report(FILE* file) const; - void clear(); + void report(FILE* file, const std::string message = "") const; + void initialise(const HighsInt iteration_count_ = 0); }; - #endif /* LP_DATA_HSTRUCT_H_ */ diff --git a/src/simplex/HApp.h b/src/simplex/HApp.h index 905a290c3a..b8de2a3f59 100644 --- a/src/simplex/HApp.h +++ b/src/simplex/HApp.h @@ -112,9 +112,8 @@ inline HighsStatus solveLpSimplex(HighsLpSolverObject& solver_object) { // return resetModelStatusAndHighsInfo(solver_object); - // Clear the simplex stats - ekk_instance.clearSimplexStats(); - ekk_instance.simplex_stats_.iteration_count = -ekk_instance.iteration_count_; + // Initialise the simplex stats + ekk_instance.initialiseSimplexStats(); // Assumes that the LP has a positive number of rows, since // unconstrained LPs should be solved in solveLp diff --git a/src/simplex/HEkk.cpp b/src/simplex/HEkk.cpp index 126b56b364..caa4e641e0 100644 --- a/src/simplex/HEkk.cpp +++ b/src/simplex/HEkk.cpp @@ -296,7 +296,7 @@ void HEkk::invalidate() { assert(!this->status_.is_permuted); this->status_.initialised_for_solve = false; this->invalidateBasisMatrix(); - this->simplex_stats_.clear(); + this->simplex_stats_.initialise(); } void HEkk::invalidateBasisMatrix() { @@ -3509,7 +3509,8 @@ HighsStatus HEkk::returnFromEkkSolve(const HighsStatus return_status) { simplex_stats_.iteration_count += iteration_count_; // simplex_stats_.num_invert is incremented internally simplex_stats_.last_invert_num_el = simplex_nla_.factor_.invert_num_el; - simplex_stats_.last_factored_basis_num_el = simplex_nla_.factor_.basis_matrix_num_el; + simplex_stats_.last_factored_basis_num_el = + simplex_nla_.factor_.basis_matrix_num_el; simplex_stats_.col_aq_density = analysis_.col_aq_density; simplex_stats_.row_ep_density = analysis_.row_ep_density; simplex_stats_.row_ap_density = analysis_.row_ap_density; @@ -4420,23 +4421,24 @@ void HEkk::unitBtranResidual(const HighsInt row_out, const HVector& row_ep, } } -void HighsSimplexStats::report(FILE* file) const { - fprintf(file, "\nSimplex stats\n"); +void HighsSimplexStats::report(FILE* file, std::string message) const { + fprintf(file, "\nSimplex stats: %s\n", message.c_str()); fprintf(file, " valid = %d\n", this->valid); fprintf(file, " iteration_count = %d\n", this->iteration_count); fprintf(file, " num_invert = %d\n", this->num_invert); - fprintf(file, " last_invert_num_el = %d\n", this->last_invert_num_el); - fprintf(file, " last_factored_basis_num_el = %d\n", this->last_factored_basis_num_el); + fprintf(file, " last_invert_num_el = %d\n", + this->last_invert_num_el); + fprintf(file, " last_factored_basis_num_el = %d\n", + this->last_factored_basis_num_el); fprintf(file, " col_aq_density = %g\n", this->col_aq_density); fprintf(file, " row_ep_density = %g\n", this->row_ep_density); fprintf(file, " row_ap_density = %g\n", this->row_ap_density); fprintf(file, " row_DSE_density = %g\n", this->row_DSE_density); - } -void HighsSimplexStats::clear() { +void HighsSimplexStats::initialise(const HighsInt iteration_count_) { valid = false; - iteration_count = 0; + iteration_count = -iteration_count_; num_invert = 0; last_invert_num_el = 0; last_factored_basis_num_el = 0; diff --git a/src/simplex/HEkk.h b/src/simplex/HEkk.h index 9d54524fbc..0b253bebc0 100644 --- a/src/simplex/HEkk.h +++ b/src/simplex/HEkk.h @@ -157,8 +157,10 @@ class HEkk { const vector& rowUpper); const HighsSimplexStats& getSimplexStats() const { return simplex_stats_; } - void clearSimplexStats() { simplex_stats_.clear(); } - void reportSimplexStats(FILE* file) const { simplex_stats_.report(file); } + void initialiseSimplexStats() { simplex_stats_.initialise(iteration_count_); } + void reportSimplexStats(FILE* file, const std::string message = "") const { + simplex_stats_.report(file, message); + } // Make this private later void chooseSimplexStrategyThreads(const HighsOptions& options, @@ -175,7 +177,6 @@ class HEkk { const std::string message, const HighsInt alt_debug_level = -1) const; bool debugNlaScalingOk(const HighsLp& lp) const; - // Data members HighsCallback* callback_; HighsOptions* options_;