diff --git a/.github/workflows/build-python-package.yml b/.github/workflows/build-python-package.yml index 0b6c32f2fe..33bd47786f 100644 --- a/.github/workflows/build-python-package.yml +++ b/.github/workflows/build-python-package.yml @@ -78,17 +78,14 @@ jobs: # ubuntu 22 has a latest version of cmake, but setup-python # does not seem to provide all necessary modules to find python # from cmake. works on my machine, test the wheels manually - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 - # strategy: - # matrix: - # python: [3.12] - name: Install correct python version uses: actions/setup-python@v5 - # with: - # python-version: ${{ matrix.python }} + with: + python-version: '3.10' - name: Build wheel run: | diff --git a/.github/workflows/build-wheels-push.yml b/.github/workflows/build-wheels-push.yml index 899d865c9a..b38ebd2280 100644 --- a/.github/workflows/build-wheels-push.yml +++ b/.github/workflows/build-wheels-push.yml @@ -1,12 +1,12 @@ name: build-wheels-push -on: [] +# on: [] # on: push -# on: -# release: -# types: -# - published +on: + release: + types: + - published concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -82,51 +82,22 @@ jobs: name: cibw-wheels-${{ matrix.python }}-${{ matrix.buildplat[1] }} path: wheelhouse/*.whl - upload_testpypi: - name: >- - Publish highspy to TestPyPI - runs-on: ubuntu-latest - needs: [build_wheels, build_sdist] - # needs: [build_sdist] - - # upload to PyPI on every tag starting with 'v' - # if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') - - environment: - name: testpypi - url: https://test.pypi.org/p/highspy - - permissions: - id-token: write # IMPORTANT: mandatory for trusted publishing - steps: - - uses: actions/download-artifact@v4 - with: - pattern: cibw-* - path: dist - merge-multiple: true - - - name: Download all - uses: pypa/gh-action-pypi-publish@release/v1 - with: - repository-url: https://test.pypi.org/legacy/ - verbose: true - - # upload_pypi: + # upload_testpypi: # name: >- - # Publish highspy to PyPI + # Publish highspy to TestPyPI # runs-on: ubuntu-latest # needs: [build_wheels, build_sdist] + # # needs: [build_sdist] # # upload to PyPI on every tag starting with 'v' # # if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') # environment: - # name: pypi - # url: https://pypi.org/p/highspy + # name: testpypi + # url: https://test.pypi.org/p/highspy # permissions: # id-token: write # IMPORTANT: mandatory for trusted publishing - # steps: # - uses: actions/download-artifact@v4 # with: @@ -136,3 +107,32 @@ jobs: # - name: Download all # uses: pypa/gh-action-pypi-publish@release/v1 + # with: + # repository-url: https://test.pypi.org/legacy/ + # verbose: true + + upload_pypi: + name: >- + Publish highspy to PyPI + runs-on: ubuntu-latest + needs: [build_wheels, build_sdist] + + # upload to PyPI on every tag starting with 'v' + # if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') + + environment: + name: pypi + url: https://pypi.org/p/highspy + + permissions: + id-token: write # IMPORTANT: mandatory for trusted publishing + + steps: + - uses: actions/download-artifact@v4 + with: + pattern: cibw-* + path: dist + merge-multiple: true + + - name: Download all + uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.github/workflows/code-coverage.yml b/.github/workflows/code-coverage.yml new file mode 100644 index 0000000000..e4540f2173 --- /dev/null +++ b/.github/workflows/code-coverage.yml @@ -0,0 +1,67 @@ +name: code-coverage + +on: [push, pull_request] + +jobs: + debug: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest] + + steps: + - uses: actions/checkout@v4 + + - name: install + run: sudo apt-get update && sudo apt-get install lcov + + - name: Create Build Environment + run: cmake -E make_directory ${{runner.workspace}}/build + + - name: Configure CMake + shell: bash + working-directory: ${{runner.workspace}}/build + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=Debug -DHIGHS_COVERAGE=ON -DALL_TESTS=ON -DBUILD_SHARED_LIBS=OFF -D CMAKE_C_COMPILER=gcc -D CMAKE_CXX_COMPILER=g++ + + - name: Build + working-directory: ${{runner.workspace}}/build + shell: bash + run: | + cmake --build . --parallel --config Debug + + - name: Test + working-directory: ${{runner.workspace}}/build + shell: bash + run: ctest --parallel --timeout 300 --output-on-failure + + - name: Generate Report + working-directory: ${{runner.workspace}}/build + shell: bash + run: | + lcov -d . -c -o cov.info --ignore-errors empty + lcov --remove cov.info "/usr/include/*" -o cov.info + lcov --remove cov.info "/usr/lib/*" -o cov.info + lcov --remove cov.info "extern/pdqsort/*" -o cov.info + lcov --remove cov.info "extern/zstr/*" -o cov.info + lcov --remove cov.info "extern/catch*" -o cov.info + lcov --remove cov.info "app/cxxopts*" -o cov.info + lcov --remove cov.info "src/test*" -o cov.info + lcov --list cov.info + + - name: Genhtml Results Summary + working-directory: ${{runner.workspace}}/build + shell: bash + run: | + genhtml -o coverage cov.info + + # Made it past the first token issue. + # May need some more time to porpagate on the codecov side. + # - name: Upload coverage reports to Codecov + # uses: codecov/codecov-action@v5 + # with: + # token: ${{ secrets.CODECOV_TOKEN }} + # slug: ERGO-Code/HiGHS + # fail_ci_if_error: true # optional (default = false) + # files: ${{runner.workspace}}/build/cov.info # optional + # # name: codecov-umbrella # optional + # verbose: true # optional (default = false) diff --git a/.gitignore b/.gitignore index 096bc893e1..c36222e48d 100644 --- a/.gitignore +++ b/.gitignore @@ -230,6 +230,7 @@ pip-log.txt # Unit test / coverage reports .coverage +cov.info .tox #Translations diff --git a/CMakeLists.txt b/CMakeLists.txt index dc485c9471..6ef2d28cdd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -103,6 +103,8 @@ if (PYTHON_BUILD_SETUP) set(ZLIB OFF) endif() +option(HIGHS_COVERAGE "Activate the code coverage compilation" OFF) + # Address | Thread | Leak # Linux atm # Only Debug is theted atm @@ -328,16 +330,18 @@ if(NOT FAST_BUILD) endif() include(CheckCXXCompilerFlag) -if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(ppc64|powerpc64)" AND NOT APPLE) - check_cxx_compiler_flag("-mpopcntd" COMPILER_SUPPORTS_POPCNTD) - if(COMPILER_SUPPORTS_POPCNTD) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mpopcntd") - endif() -else() - check_cxx_compiler_flag("-mpopcnt" COMPILER_SUPPORTS_POPCNT) - if(COMPILER_SUPPORTS_POPCNT) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mpopcnt") - endif() +if (NOT HIGHS_COVERAGE) + if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(ppc64|powerpc64)" AND NOT APPLE) + check_cxx_compiler_flag("-mpopcntd" COMPILER_SUPPORTS_POPCNTD) + if(COMPILER_SUPPORTS_POPCNTD) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mpopcntd") + endif() + else() + check_cxx_compiler_flag("-mpopcnt" COMPILER_SUPPORTS_POPCNT) + if(COMPILER_SUPPORTS_POPCNT) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mpopcnt") + endif() + endif() endif() option(DEBUGSOL "check the debug solution" OFF) @@ -473,6 +477,58 @@ elseif (DEBUG_MEMORY STREQUAL "Leak") -fno-omit-frame-pointer ") endif() +# HiGHS coverage update in progress +if(FAST_BUILD AND HIGHS_COVERAGE) + if(WIN32) + message(FATAL_ERROR "Error: code coverage analysis is only available under Linux for now.") + endif() + + if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug") + message(FATAL_ERROR "Warning: to enable coverage, you must compile in Debug mode") + endif() + + # Disable IPO + set(CMAKE_INTERPROCEDURAL_OPTIMIZATION OFF) + message(STATUS "Building in coverage mode") + + # Enable coverage flags + add_compile_options(-O0) + add_compile_options(--coverage) + add_compile_options(-fprofile-update=atomic) + + add_link_options(-O0) + add_link_options(--coverage) # Ensure coverage data is linked correctly + + find_program(GCOV_PATH gcov) + find_program(LCOV_PATH lcov) + find_program(GENHTML_PATH genhtml) + + if(NOT GCOV_PATH) + message(FATAL_ERROR "gcov not found! Please install lcov and gcov. Aborting...") + endif() + + if(NOT LCOV_PATH) + message(FATAL_ERROR "lcov not found! Please install lcov and gcov. Aborting...") + endif() + + if(NOT GENHTML_PATH) + message(FATAL_ERROR "genhtml not found! Please install lcov and gcov. Aborting...") + endif() + + # add_custom_target(coverage + # COMMAND ${LCOV_PATH} -d bin -c -o cov.info --ignore-errors empty + # COMMAND ${LCOV_PATH} --remove "*/usr/include/*" -o ${CMAKE_BINARY_DIR}/cov.info.cleaned + # COMMAND ${LCOV_PATH} --remove "*/usr/lib/*" -o ${CMAKE_BINARY_DIR}/cov.info.cleaned + # COMMAND ${LCOV_PATH} --remove "extern/pdqsort/*" -o ${CMAKE_BINARY_DIR}/cov.info.cleaned + # COMMAND ${LCOV_PATH} --remove "extern/zstr/*" -o ${CMAKE_BINARY_DIR}/cov.info.cleaned + # COMMAND ${LCOV_PATH} --remove "app/cxxopts*" -o ${CMAKE_BINARY_DIR}/cov.info.cleaned + # COMMAND ${GENHTML_PATH} ${CMAKE_BINARY_DIR}/cov.info.cleaned -o ${CMAKE_BINARY_DIR}/cov_report + # VERBATIM + # WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + # COMMENT "Generating code coverage report v2025.") + +endif() + if(NOT FAST_BUILD) # For the moment keep above coverage part in case we are testing at CI. option(CI "CI extended tests" ON) @@ -495,7 +551,7 @@ if(NOT FAST_BUILD) endif() if(HIGHS_COVERAGE) - if(NOT CMAKE_BUILD_TYPE STREQUAL "DEBUG") + if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug" AND NOT CMAKE_BUILD_TYPE STREQUAL "DEBUG") message(FATAL_ERROR "Warning: to enable coverage, you must compile in DEBUG mode") endif() endif() diff --git a/FEATURES.md b/FEATURES.md index e3c62da2c9..f6ff74c98c 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -10,4 +10,12 @@ Added basis solve methods to highspy Added methods to get primal/dual ray and dual unboundedness direction to highspy +When a presolved LP has model status kUnknown, rather than returning this to the user, it performs postsolve and then uses the basis to solve the original LP +Fixed bug in presolve when pointers stored in HighsMatrixSlice get invalidated when the coefficient matrix is reallocated (e.g. when non-zeros are added in HPresolve::addToMatrix) + +Primal and dual residual tolerances - applied following IPM or PDLP solution - now documented as options + +Highs::getCols (Highs::getRows) now runs in linear time if the internal constraint matrix is stored column-wise (row-wise). Added ensureColwise/Rowwise to the Highs class, the C API and highspy so that users can set the internal constraint matrix storage orientation + +When columns and rows are deleted from the incumbent LP after a basic solution has been found, HiGHS no longer invalidates the basis. Now it maintains the basic and nonbasic status of the remaining variables and constraints. When the model is re-solved, this information is used to construct a starting basis. \ No newline at end of file diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 78a96e4d12..e26139c4bd 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -54,10 +54,6 @@ else() target_link_libraries(highs libhighs) - if(EMSCRIPTEN AND EMSCRIPTEN_HTML) - set(CMAKE_EXECUTABLE_SUFFIX ".html") - set_target_properties(highs PROPERTIES LINK_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/highs_webdemo_shell.html) - endif() target_include_directories(highs PRIVATE $ @@ -66,4 +62,10 @@ else() # install the binary install(TARGETS highs EXPORT highs-targets RUNTIME) +endif() + +# Add demo to FAST_BUILD as well. +if(EMSCRIPTEN AND EMSCRIPTEN_HTML) + set(CMAKE_EXECUTABLE_SUFFIX ".html") + set_target_properties(highs PROPERTIES LINK_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/highs_webdemo_shell.html) endif() \ No newline at end of file diff --git a/app/HighsRuntimeOptions.h b/app/HighsRuntimeOptions.h index 04d3133e4b..2892a2fa86 100644 --- a/app/HighsRuntimeOptions.h +++ b/app/HighsRuntimeOptions.h @@ -145,7 +145,7 @@ bool loadOptions(const HighsLogOptions& report_log_options, int argc, case HighsLoadOptionsStatus::kError: return false; case HighsLoadOptionsStatus::kEmpty: - writeOptionsToFile(stdout, options.records); + writeOptionsToFile(stdout, options.log_options, options.records); return false; default: break; diff --git a/app/RunHighs.cpp b/app/RunHighs.cpp index 8813755b4c..b5830bc370 100644 --- a/app/RunHighs.cpp +++ b/app/RunHighs.cpp @@ -47,6 +47,7 @@ int main(int argc, char** argv) { // settings defined in any options file. highs.passOptions(loaded_options); // highs.writeOptions("Options.md"); + highs.writeOptions("", true); // Load the model from model_file HighsStatus read_status = highs.readModel(model_file); diff --git a/app/cxxopts.hpp b/app/cxxopts.hpp index 30575e4659..5bef619c8f 100644 --- a/app/cxxopts.hpp +++ b/app/cxxopts.hpp @@ -2875,4 +2875,4 @@ Options::group_help(const std::string& group) const } // namespace cxxopts -#endif //CXXOPTS_HPP_INCLUDED \ No newline at end of file +#endif //CXXOPTS_HPP_INCLUDED diff --git a/check/Avgas.cpp b/check/Avgas.cpp index fb64f348fd..96db2cd0ba 100644 --- a/check/Avgas.cpp +++ b/check/Avgas.cpp @@ -15,324 +15,231 @@ #include #include // For printf +#include "lp_data/HConst.h" + const bool dev_run = false; -void Avgas::row(HighsInt row, HighsInt& num_row, HighsInt& num_row_nz, - std::vector& rowLower, std::vector& rowUpper, - std::vector& ARstart, std::vector& ARindex, - std::vector& ARvalue) { - rowLower.resize(num_row + 1); - rowUpper.resize(num_row + 1); - ARstart.resize(num_row + 1); - ARstart[num_row] = num_row_nz; +void Avgas::addRow(const HighsInt row, HighsInt& num_row, HighsInt& num_row_nz, + std::vector& rowLower, std::vector& rowUpper, + std::vector& ARstart, + std::vector& ARindex, + std::vector& ARvalue) { + double lower; + double upper; + std::vector index; + std::vector value; + getRow(row, lower, upper, index, value); + rowLower.push_back(lower); + rowUpper.push_back(upper); + HighsInt num_nz = index.size(); + ARstart.push_back(num_row_nz); + assert(HighsInt(ARstart.size()) == num_row + 1); + for (HighsInt iEl = 0; iEl < num_nz; iEl++) { + ARvalue.push_back(value[iEl]); + ARindex.push_back(index[iEl]); + } + num_row++; + num_row_nz += num_nz; +} + +void Avgas::getRow(const HighsInt row, double& lower, double& upper, + std::vector& index, std::vector& value) { + index.clear(); + value.clear(); + upper = kHighsInf; if (row == 0) { - rowLower[num_row] = -1; - rowUpper[num_row] = 1e31; - HighsInt num_new_nz = 2; - ARindex.resize(num_row_nz + num_new_nz); - ARvalue.resize(num_row_nz + num_new_nz); - ARindex[num_row_nz] = 0; - ARvalue[num_row_nz] = -1; - num_row_nz++; - ARindex[num_row_nz] = 1; - ARvalue[num_row_nz] = -1; - num_row_nz++; + lower = -1; + index.push_back(0); + value.push_back(-1); + index.push_back(1); + value.push_back(-1); } else if (row == 1) { - rowLower[num_row] = -1; - rowUpper[num_row] = 1e31; - HighsInt num_new_nz = 2; - ARindex.resize(num_row_nz + num_new_nz); - ARvalue.resize(num_row_nz + num_new_nz); - ARindex[num_row_nz] = 2; - ARvalue[num_row_nz] = -1; - num_row_nz++; - ARindex[num_row_nz] = 3; - ARvalue[num_row_nz] = -1; - num_row_nz++; + lower = -1; + index.push_back(2); + value.push_back(-1); + index.push_back(3); + value.push_back(-1); } else if (row == 2) { - rowLower[num_row] = -1; - rowUpper[num_row] = 1e31; - HighsInt num_new_nz = 2; - ARindex.resize(num_row_nz + num_new_nz); - ARvalue.resize(num_row_nz + num_new_nz); - ARindex[num_row_nz] = 4; - ARvalue[num_row_nz] = -1; - num_row_nz++; - ARindex[num_row_nz] = 5; - ARvalue[num_row_nz] = -1; - num_row_nz++; + lower = -1; + index.push_back(4); + value.push_back(-1); + index.push_back(5); + value.push_back(-1); } else if (row == 3) { - rowLower[num_row] = -1; - rowUpper[num_row] = 1e31; - HighsInt num_new_nz = 2; - ARindex.resize(num_row_nz + num_new_nz); - ARvalue.resize(num_row_nz + num_new_nz); - ARindex[num_row_nz] = 6; - ARvalue[num_row_nz] = -1; - num_row_nz++; - ARindex[num_row_nz] = 7; - ARvalue[num_row_nz] = -1; - num_row_nz++; + lower = -1; + index.push_back(6); + value.push_back(-1); + index.push_back(7); + value.push_back(-1); } else if (row == 4) { - rowLower[num_row] = -2; - rowUpper[num_row] = 1e31; - HighsInt num_new_nz = 4; - ARindex.resize(num_row_nz + num_new_nz); - ARvalue.resize(num_row_nz + num_new_nz); - ARindex[num_row_nz] = 0; - ARvalue[num_row_nz] = -1; - num_row_nz++; - ARindex[num_row_nz] = 2; - ARvalue[num_row_nz] = -1; - num_row_nz++; - ARindex[num_row_nz] = 4; - ARvalue[num_row_nz] = -1; - num_row_nz++; - ARindex[num_row_nz] = 6; - ARvalue[num_row_nz] = -1; - num_row_nz++; + lower = -2; + index.push_back(0); + value.push_back(-1); + index.push_back(2); + value.push_back(-1); + index.push_back(4); + value.push_back(-1); + index.push_back(6); + value.push_back(-1); } else if (row == 5) { - rowLower[num_row] = -2; - rowUpper[num_row] = 1e31; - HighsInt num_new_nz = 4; - ARindex.resize(num_row_nz + num_new_nz); - ARvalue.resize(num_row_nz + num_new_nz); - ARindex[num_row_nz] = 1; - ARvalue[num_row_nz] = -1; - num_row_nz++; - ARindex[num_row_nz] = 3; - ARvalue[num_row_nz] = -1; - num_row_nz++; - ARindex[num_row_nz] = 5; - ARvalue[num_row_nz] = -1; - num_row_nz++; - ARindex[num_row_nz] = 7; - ARvalue[num_row_nz] = -1; - num_row_nz++; + lower = -2; + index.push_back(1); + value.push_back(-1); + index.push_back(3); + value.push_back(-1); + index.push_back(5); + value.push_back(-1); + index.push_back(7); + value.push_back(-1); } else if (row == 6) { - rowLower[num_row] = 0; - rowUpper[num_row] = 1e31; - HighsInt num_new_nz = 3; - ARindex.resize(num_row_nz + num_new_nz); - ARvalue.resize(num_row_nz + num_new_nz); - ARindex[num_row_nz] = 0; - ARvalue[num_row_nz] = 2; - num_row_nz++; - ARindex[num_row_nz] = 2; - ARvalue[num_row_nz] = 1; - num_row_nz++; - ARindex[num_row_nz] = 6; - ARvalue[num_row_nz] = -1; - num_row_nz++; + lower = 0; + index.push_back(0); + value.push_back(2); + index.push_back(2); + value.push_back(1); + index.push_back(6); + value.push_back(-1); } else if (row == 7) { - rowLower[num_row] = 0; - rowUpper[num_row] = 1e31; - HighsInt num_new_nz = 4; - ARindex.resize(num_row_nz + num_new_nz); - ARvalue.resize(num_row_nz + num_new_nz); - ARindex[num_row_nz] = 0; - ARvalue[num_row_nz] = 5; - num_row_nz++; - ARindex[num_row_nz] = 2; - ARvalue[num_row_nz] = 3; - num_row_nz++; - ARindex[num_row_nz] = 4; - ARvalue[num_row_nz] = -3; - num_row_nz++; - ARindex[num_row_nz] = 6; - ARvalue[num_row_nz] = -1; - num_row_nz++; + lower = 0; + index.push_back(0); + value.push_back(5); + index.push_back(2); + value.push_back(3); + index.push_back(4); + value.push_back(-3); + index.push_back(6); + value.push_back(-1); } else if (row == 8) { - rowLower[num_row] = 0; - rowUpper[num_row] = 1e31; - HighsInt num_new_nz = 4; - ARindex.resize(num_row_nz + num_new_nz); - ARvalue.resize(num_row_nz + num_new_nz); - ARindex[num_row_nz] = 1; - ARvalue[num_row_nz] = 1; - num_row_nz++; - ARindex[num_row_nz] = 3; - ARvalue[num_row_nz] = -1; - num_row_nz++; - ARindex[num_row_nz] = 5; - ARvalue[num_row_nz] = -3; - num_row_nz++; - ARindex[num_row_nz] = 7; - ARvalue[num_row_nz] = -5; - num_row_nz++; + lower = 0; + index.push_back(1); + value.push_back(1); + index.push_back(3); + value.push_back(-1); + index.push_back(5); + value.push_back(-3); + index.push_back(7); + value.push_back(-5); } else if (row == 9) { - rowLower[num_row] = 0; - rowUpper[num_row] = 1e31; - HighsInt num_new_nz = 3; - ARindex.resize(num_row_nz + num_new_nz); - ARvalue.resize(num_row_nz + num_new_nz); - ARindex[num_row_nz] = 1; - ARvalue[num_row_nz] = 1; - num_row_nz++; - ARindex[num_row_nz] = 5; - ARvalue[num_row_nz] = -3; - num_row_nz++; - ARindex[num_row_nz] = 7; - ARvalue[num_row_nz] = -2; - num_row_nz++; + lower = 0; + index.push_back(1); + value.push_back(1); + index.push_back(5); + value.push_back(-3); + index.push_back(7); + value.push_back(-2); } else { - if (dev_run) printf("Avgas: row %" HIGHSINT_FORMAT " out of range\n", row); + if (dev_run) printf("Avgas: row %d out of range\n", HighsInt(row)); } - num_row++; } -void Avgas::col(HighsInt col, HighsInt& num_col, HighsInt& num_col_nz, - std::vector& colCost, std::vector& colLower, - std::vector& colUpper, std::vector& Astart, - std::vector& Aindex, std::vector& Avalue) { - colCost.resize(num_col + 1); - colLower.resize(num_col + 1); - colUpper.resize(num_col + 1); - Astart.resize(num_col + 1); - Astart[num_col] = num_col_nz; - HighsInt num_new_nz = 4; +void Avgas::addCol(HighsInt col, HighsInt& num_col, HighsInt& num_col_nz, + std::vector& colCost, std::vector& colLower, + std::vector& colUpper, std::vector& Astart, + std::vector& Aindex, std::vector& Avalue) { + double cost; + double lower; + double upper; + std::vector index; + std::vector value; + getCol(col, cost, lower, upper, index, value); + colCost.push_back(cost); + colLower.push_back(lower); + colUpper.push_back(upper); + HighsInt num_nz = index.size(); + Astart.push_back(num_col_nz); + assert(HighsInt(Astart.size()) == num_col + 1); + for (HighsInt iEl = 0; iEl < num_nz; iEl++) { + Avalue.push_back(value[iEl]); + Aindex.push_back(index[iEl]); + } + num_col++; + num_col_nz += num_nz; +} + +void Avgas::getCol(const HighsInt col, double& cost, double& lower, + double& upper, std::vector& index, + std::vector& value) { + index.clear(); + value.clear(); + lower = 0; + upper = 1; if (col == 0) { - colCost[num_col] = 0; - colLower[num_col] = 0; - colUpper[num_col] = 1; - Aindex.resize(num_col_nz + num_new_nz); - Avalue.resize(num_col_nz + num_new_nz); - Aindex[num_col_nz] = 0; - Avalue[num_col_nz] = -1; - num_col_nz++; - Aindex[num_col_nz] = 4; - Avalue[num_col_nz] = -1; - num_col_nz++; - Aindex[num_col_nz] = 6; - Avalue[num_col_nz] = 2; - num_col_nz++; - Aindex[num_col_nz] = 7; - Avalue[num_col_nz] = 5; - num_col_nz++; + cost = 0; + index.push_back(0); + value.push_back(-1); + index.push_back(4); + value.push_back(-1); + index.push_back(6); + value.push_back(2); + index.push_back(7); + value.push_back(5); } else if (col == 1) { - colCost[num_col] = -2; - colLower[num_col] = 0; - colUpper[num_col] = 1; - Aindex.resize(num_col_nz + num_new_nz); - Avalue.resize(num_col_nz + num_new_nz); - Aindex[num_col_nz] = 0; - Avalue[num_col_nz] = -1; - num_col_nz++; - Aindex[num_col_nz] = 5; - Avalue[num_col_nz] = -1; - num_col_nz++; - Aindex[num_col_nz] = 8; - Avalue[num_col_nz] = 1; - num_col_nz++; - Aindex[num_col_nz] = 9; - Avalue[num_col_nz] = 1; - num_col_nz++; + cost = -2; + index.push_back(0); + value.push_back(-1); + index.push_back(5); + value.push_back(-1); + index.push_back(8); + value.push_back(1); + index.push_back(9); + value.push_back(1); } else if (col == 2) { - colCost[num_col] = -1; - colLower[num_col] = 0; - colUpper[num_col] = 1; - Aindex.resize(num_col_nz + num_new_nz); - Avalue.resize(num_col_nz + num_new_nz); - Aindex[num_col_nz] = 1; - Avalue[num_col_nz] = -1; - num_col_nz++; - Aindex[num_col_nz] = 4; - Avalue[num_col_nz] = -1; - num_col_nz++; - Aindex[num_col_nz] = 6; - Avalue[num_col_nz] = 1; - num_col_nz++; - Aindex[num_col_nz] = 7; - Avalue[num_col_nz] = 3; - num_col_nz++; + cost = -1; + index.push_back(1); + value.push_back(-1); + index.push_back(4); + value.push_back(-1); + index.push_back(6); + value.push_back(1); + index.push_back(7); + value.push_back(3); } else if (col == 3) { - num_new_nz = 3; - colCost[num_col] = -3; - colLower[num_col] = 0; - colUpper[num_col] = 1; - Aindex.resize(num_col_nz + num_new_nz); - Avalue.resize(num_col_nz + num_new_nz); - Aindex[num_col_nz] = 1; - Avalue[num_col_nz] = -1; - num_col_nz++; - Aindex[num_col_nz] = 5; - Avalue[num_col_nz] = -1; - num_col_nz++; - Aindex[num_col_nz] = 8; - Avalue[num_col_nz] = -1; - num_col_nz++; + cost = -3; + index.push_back(1); + value.push_back(-1); + index.push_back(5); + value.push_back(-1); + index.push_back(8); + value.push_back(-1); } else if (col == 4) { - num_new_nz = 3; - colCost[num_col] = -2; - colLower[num_col] = 0; - colUpper[num_col] = 1; - Aindex.resize(num_col_nz + num_new_nz); - Avalue.resize(num_col_nz + num_new_nz); - Aindex[num_col_nz] = 2; - Avalue[num_col_nz] = -1; - num_col_nz++; - Aindex[num_col_nz] = 4; - Avalue[num_col_nz] = -1; - num_col_nz++; - Aindex[num_col_nz] = 7; - Avalue[num_col_nz] = -3; - num_col_nz++; + cost = -2; + index.push_back(2); + value.push_back(-1); + index.push_back(4); + value.push_back(-1); + index.push_back(7); + value.push_back(-3); } else if (col == 5) { - colCost[num_col] = -4; - colLower[num_col] = 0; - colUpper[num_col] = 1; - Aindex.resize(num_col_nz + num_new_nz); - Avalue.resize(num_col_nz + num_new_nz); - Aindex[num_col_nz] = 2; - Avalue[num_col_nz] = -1; - num_col_nz++; - Aindex[num_col_nz] = 5; - Avalue[num_col_nz] = -1; - num_col_nz++; - Aindex[num_col_nz] = 8; - Avalue[num_col_nz] = -3; - num_col_nz++; - Aindex[num_col_nz] = 9; - Avalue[num_col_nz] = -3; - num_col_nz++; + cost = -4; + index.push_back(2); + value.push_back(-1); + index.push_back(5); + value.push_back(-1); + index.push_back(8); + value.push_back(-3); + index.push_back(9); + value.push_back(-3); } else if (col == 6) { - colCost[num_col] = -3; - colLower[num_col] = 0; - colUpper[num_col] = 1; - Aindex.resize(num_col_nz + num_new_nz); - Avalue.resize(num_col_nz + num_new_nz); - Aindex[num_col_nz] = 3; - Avalue[num_col_nz] = -1; - num_col_nz++; - Aindex[num_col_nz] = 4; - Avalue[num_col_nz] = -1; - num_col_nz++; - Aindex[num_col_nz] = 6; - Avalue[num_col_nz] = -1; - num_col_nz++; - Aindex[num_col_nz] = 7; - Avalue[num_col_nz] = -1; - num_col_nz++; + cost = -3; + index.push_back(3); + value.push_back(-1); + index.push_back(4); + value.push_back(-1); + index.push_back(6); + value.push_back(-1); + index.push_back(7); + value.push_back(-1); } else if (col == 7) { - colCost[num_col] = -5; - colLower[num_col] = 0; - colUpper[num_col] = 1; - Aindex.resize(num_col_nz + num_new_nz); - Avalue.resize(num_col_nz + num_new_nz); - Aindex[num_col_nz] = 3; - Avalue[num_col_nz] = -1; - num_col_nz++; - Aindex[num_col_nz] = 5; - Avalue[num_col_nz] = -1; - num_col_nz++; - Aindex[num_col_nz] = 8; - Avalue[num_col_nz] = -5; - num_col_nz++; - Aindex[num_col_nz] = 9; - Avalue[num_col_nz] = -2; - num_col_nz++; + cost = -5; + index.push_back(3); + value.push_back(-1); + index.push_back(5); + value.push_back(-1); + index.push_back(8); + value.push_back(-5); + index.push_back(9); + value.push_back(-2); } else { - if (dev_run) printf("Avgas: col %" HIGHSINT_FORMAT " out of range\n", col); + if (dev_run) printf("Avgas: col %d out of range\n", HighsInt(col)); } - num_col++; } diff --git a/check/Avgas.h b/check/Avgas.h index e34bbb1b61..6e7154cfc2 100644 --- a/check/Avgas.h +++ b/check/Avgas.h @@ -17,19 +17,28 @@ #include "util/HighsInt.h" +const HighsInt avgas_num_col = 8; +const HighsInt avgas_num_row = 10; + /** * @brief Utilities for tests with AVGAS */ class Avgas { public: - void row(HighsInt row, HighsInt& num_row, HighsInt& num_row_nz, - std::vector& rowLower, std::vector& rowUpper, - std::vector& ARstart, std::vector& ARindex, - std::vector& ARvalue); + void addRow(const HighsInt row, HighsInt& num_row, HighsInt& num_row_nz, + std::vector& rowLower, std::vector& rowUpper, + std::vector& ARstart, std::vector& ARindex, + std::vector& ARvalue); + + void getRow(const HighsInt row, double& lower, double& upper, + std::vector& index, std::vector& value); + + void addCol(const HighsInt col, HighsInt& num_col, HighsInt& num_col_nz, + std::vector& colCost, std::vector& colLower, + std::vector& colUpper, std::vector& Astart, + std::vector& Aindex, std::vector& Avalue); - void col(HighsInt col, HighsInt& num_col, HighsInt& num_col_nz, - std::vector& colCost, std::vector& colLower, - std::vector& colUpper, std::vector& Astart, - std::vector& Aindex, std::vector& Avalue); + void getCol(const HighsInt col, double& cost, double& lower, double& upper, + std::vector& index, std::vector& value); }; #endif /* SIMPLEX_AVGAS_H_ */ diff --git a/check/CMakeLists.txt b/check/CMakeLists.txt index b1076e1825..4ba1f995eb 100644 --- a/check/CMakeLists.txt +++ b/check/CMakeLists.txt @@ -129,25 +129,29 @@ if ((NOT FAST_BUILD OR ALL_TESTS) AND NOT (BUILD_EXTRA_UNIT_ONLY)) add_test(NAME capi_unit_tests COMMAND capi_unit_tests) # Check whether test executable builds OK. - add_test(NAME unit-test-build - COMMAND ${CMAKE_COMMAND} - --build ${HIGHS_BINARY_DIR} - --target unit_tests - # --config ${CMAKE_BUILD_TYPE} - ) - + if (NOT HIGHS_COVERAGE) + add_test(NAME unit-test-build + COMMAND ${CMAKE_COMMAND} + --build ${HIGHS_BINARY_DIR} + --target unit_tests + # --config ${CMAKE_BUILD_TYPE} + ) - # Avoid that several build jobs try to concurretly build. - set_tests_properties(unit-test-build - PROPERTIES - RESOURCE_LOCK unittestbin) + # Avoid that several build jobs try to concurretly build. + set_tests_properties(unit-test-build + PROPERTIES + RESOURCE_LOCK unittestbin) - # create a binary running all the tests in the executable - add_test(NAME unit_tests_all COMMAND unit_tests --success) - set_tests_properties(unit_tests_all - PROPERTIES - DEPENDS unit-test-build) - set_tests_properties(unit_tests_all PROPERTIES TIMEOUT 10000) + # create a binary running all the tests in the executable + add_test(NAME unit_tests_all COMMAND unit_tests --success) + set_tests_properties(unit_tests_all + PROPERTIES + DEPENDS unit-test-build) + set_tests_properties(unit_tests_all PROPERTIES TIMEOUT 10000) + else() + add_test(NAME unit_tests_all COMMAND unit_tests --success) + set_tests_properties(unit_tests_all PROPERTIES TIMEOUT 10000) + endif() # An individual test can be added with the command below but the approach # above with a single add_test for all the unit tests automatically detects all diff --git a/check/TestLpModification.cpp b/check/TestLpModification.cpp index ea55d16264..9a443ea43f 100644 --- a/check/TestLpModification.cpp +++ b/check/TestLpModification.cpp @@ -5,6 +5,7 @@ #include "catch.hpp" #include "lp_data/HighsLpUtils.h" #include "util/HighsRandom.h" +#include "util/HighsTimer.h" #include "util/HighsUtils.h" const bool dev_run = false; @@ -38,6 +39,11 @@ bool areLpRowEqual(const HighsInt num_row0, const double* rowLower0, bool areLpEqual(const HighsLp lp0, const HighsLp lp1, const double infinite_bound); +bool equalSparseVectors(const HighsInt dim, const HighsInt num_nz0, + const HighsInt* index0, const double* value0, + const HighsInt num_nz1, const HighsInt* index1, + const double* value1); + void testDeleteKeep(const HighsIndexCollection& index_collection); bool testAllDeleteKeep(HighsInt num_row); @@ -425,8 +431,6 @@ TEST_CASE("LP-modification", "[highs_data]") { // options.log_dev_level = kHighsLogDevLevelVerbose; Avgas avgas; - const HighsInt avgas_num_col = 8; - const HighsInt avgas_num_row = 10; HighsInt num_row = 0; HighsInt num_row_nz = 0; std::vector rowLower; @@ -436,8 +440,8 @@ TEST_CASE("LP-modification", "[highs_data]") { std::vector ARvalue; for (HighsInt row = 0; row < avgas_num_row; row++) { - avgas.row(row, num_row, num_row_nz, rowLower, rowUpper, ARstart, ARindex, - ARvalue); + avgas.addRow(row, num_row, num_row_nz, rowLower, rowUpper, ARstart, ARindex, + ARvalue); } HighsInt num_col = 0; @@ -449,8 +453,8 @@ TEST_CASE("LP-modification", "[highs_data]") { std::vector Aindex; std::vector Avalue; for (HighsInt col = 0; col < avgas_num_col; col++) { - avgas.col(col, num_col, num_col_nz, colCost, colLower, colUpper, Astart, - Aindex, Avalue); + avgas.addCol(col, num_col, num_col_nz, colCost, colLower, colUpper, Astart, + Aindex, Avalue); } HighsStatus return_status; @@ -1876,6 +1880,39 @@ TEST_CASE("mod-duplicate-indices", "[highs_data]") { REQUIRE(objective0 == -7.75); } +bool equalSparseVectors(const HighsInt dim, const HighsInt num_nz0, + const HighsInt* index0, const double* value0, + const HighsInt num_nz1, const HighsInt* index1, + const double* value1) { + if (num_nz0 != num_nz1) { + if (dev_run) printf("num_nz0 != num_nz1\n"); + return false; + } + std::vector full_vector; + full_vector.assign(dim, 0); + for (HighsInt iEl = 0; iEl < num_nz0; iEl++) + full_vector[index0[iEl]] = value0[iEl]; + for (HighsInt iEl = 0; iEl < num_nz1; iEl++) { + HighsInt iRow = index1[iEl]; + if (full_vector[iRow] != value1[iEl]) { + if (dev_run) + printf("vector0[%d] = %g <> %g = vector1[%d]\n", int(iRow), + full_vector[iRow], value1[iEl], int(iRow)); + return false; + } + + full_vector[iRow] = 0; + } + for (HighsInt iRow = 0; iRow < dim; iRow++) + if (full_vector[iRow]) { + if (dev_run) + printf("Full vector[%d] = %g, not zero\n", int(iRow), + full_vector[iRow]); + return false; + } + return true; +} + TEST_CASE("resize-integrality", "[highs_data]") { Highs highs; highs.setOptionValue("output_flag", dev_run); @@ -1954,3 +1991,487 @@ TEST_CASE("zero-matrix-entries", "[highs_data]") { lp.a_matrix_.value_ = {1, 0, 0, 1}; REQUIRE(highs.passModel(lp) == HighsStatus::kOk); } + +void testAvgasGetRow(Highs& h) { + Avgas avgas; + double cost; + double lower; + double upper; + std::vector index; + std::vector value; + HighsInt get_num; + HighsInt lp_nnz; + std::vector lp_cost(1); + std::vector lp_lower(1); + std::vector lp_upper(1); + std::vector lp_start(1); + std::vector lp_index(avgas_num_col); + std::vector lp_value(avgas_num_col); + std::vector set(1); + std::vector mask(avgas_num_row); + for (HighsInt row = 0; row < avgas_num_row; row++) { + avgas.getRow(row, lower, upper, index, value); + HighsInt avgas_nnz = index.size(); + h.getRows(row, row, get_num, lp_lower.data(), lp_upper.data(), lp_nnz, + lp_start.data(), lp_index.data(), lp_value.data()); + REQUIRE(lp_lower[0] == lower); + REQUIRE(lp_upper[0] == upper); + REQUIRE(equalSparseVectors(avgas_num_col, avgas_nnz, index.data(), + value.data(), lp_nnz, lp_index.data(), + lp_value.data())); + } + HighsInt from_row = 2; + HighsInt to_row = 5; + HighsInt num_row = to_row - from_row + 1; + lp_lower.resize(num_row); + lp_upper.resize(num_row); + lp_start.resize(num_row); + lp_index.resize(num_row * avgas_num_col); + lp_value.resize(num_row * avgas_num_col); + h.getRows(from_row, to_row, get_num, lp_lower.data(), lp_upper.data(), lp_nnz, + lp_start.data(), lp_index.data(), lp_value.data()); + REQUIRE(get_num == num_row); + for (HighsInt row = 0; row < num_row; row++) { + HighsInt avgas_row = from_row + row; + avgas.getRow(avgas_row, lower, upper, index, value); + HighsInt avgas_nnz = index.size(); + REQUIRE(lp_lower[row] == lower); + REQUIRE(lp_upper[row] == upper); + HighsInt from_el = lp_start[row]; + HighsInt lp_col_nnz = + row < num_row - 1 ? lp_start[row + 1] - from_el : lp_nnz - from_el; + REQUIRE(equalSparseVectors(avgas_num_col, avgas_nnz, index.data(), + value.data(), lp_col_nnz, &lp_index[from_el], + &lp_value[from_el])); + } + set = {1, 2, 3, 6, 7}; + num_row = set.size(); + lp_lower.resize(num_row); + lp_upper.resize(num_row); + lp_start.resize(num_row); + lp_index.resize(num_row * avgas_num_col); + lp_value.resize(num_row * avgas_num_col); + h.getRows(num_row, set.data(), get_num, lp_lower.data(), lp_upper.data(), + lp_nnz, lp_start.data(), lp_index.data(), lp_value.data()); + REQUIRE(get_num == num_row); + for (HighsInt row = 0; row < num_row; row++) { + HighsInt avgas_row = set[row]; + avgas.getRow(avgas_row, lower, upper, index, value); + HighsInt avgas_nnz = index.size(); + REQUIRE(lp_lower[row] == lower); + REQUIRE(lp_upper[row] == upper); + HighsInt from_el = lp_start[row]; + HighsInt lp_col_nnz = + row < num_row - 1 ? lp_start[row + 1] - from_el : lp_nnz - from_el; + REQUIRE(equalSparseVectors(avgas_num_col, avgas_nnz, index.data(), + value.data(), lp_col_nnz, &lp_index[from_el], + &lp_value[from_el])); + } + mask[0] = 1; + mask[1] = 1; + mask[4] = 1; + mask[6] = 1; + mask[7] = 1; + num_row = 5; + lp_lower.resize(num_row); + lp_upper.resize(num_row); + lp_start.resize(num_row); + lp_index.resize(num_row * avgas_num_col); + lp_value.resize(num_row * avgas_num_col); + h.getRows(mask.data(), get_num, lp_lower.data(), lp_upper.data(), lp_nnz, + lp_start.data(), lp_index.data(), lp_value.data()); + REQUIRE(get_num == num_row); + HighsInt row = 0; + for (HighsInt iRow = 0; iRow < avgas_num_row; iRow++) { + if (!mask[iRow]) continue; + HighsInt avgas_row = iRow; + avgas.getRow(avgas_row, lower, upper, index, value); + HighsInt avgas_nnz = index.size(); + REQUIRE(lp_lower[row] == lower); + REQUIRE(lp_upper[row] == upper); + HighsInt from_el = lp_start[row]; + HighsInt lp_col_nnz = + row < num_row - 1 ? lp_start[row + 1] - from_el : lp_nnz - from_el; + REQUIRE(equalSparseVectors(avgas_num_col, avgas_nnz, index.data(), + value.data(), lp_col_nnz, &lp_index[from_el], + &lp_value[from_el])); + row++; + } +} + +void testAvgasGetCol(Highs& h) { + Avgas avgas; + double cost; + double lower; + double upper; + std::vector index; + std::vector value; + HighsInt get_num; + HighsInt lp_nnz; + std::vector lp_cost(1); + std::vector lp_lower(1); + std::vector lp_upper(1); + std::vector lp_start(1); + std::vector lp_index(avgas_num_row); + std::vector lp_value(avgas_num_row); + std::vector set(1); + std::vector mask(avgas_num_col); + mask.assign(avgas_num_col, 0); + for (HighsInt col = 0; col < avgas_num_col; col++) { + avgas.getCol(col, cost, lower, upper, index, value); + HighsInt avgas_nnz = index.size(); + h.getCols(col, col, get_num, lp_cost.data(), lp_lower.data(), + lp_upper.data(), lp_nnz, lp_start.data(), lp_index.data(), + lp_value.data()); + REQUIRE(lp_cost[0] == cost); + REQUIRE(lp_lower[0] == lower); + REQUIRE(lp_upper[0] == upper); + REQUIRE(equalSparseVectors(avgas_num_row, avgas_nnz, index.data(), + value.data(), lp_nnz, lp_index.data(), + lp_value.data())); + set[0] = col; + h.getCols(1, set.data(), get_num, lp_cost.data(), lp_lower.data(), + lp_upper.data(), lp_nnz, lp_start.data(), lp_index.data(), + lp_value.data()); + REQUIRE(lp_cost[0] == cost); + REQUIRE(lp_lower[0] == lower); + REQUIRE(lp_upper[0] == upper); + REQUIRE(equalSparseVectors(avgas_num_row, avgas_nnz, index.data(), + value.data(), lp_nnz, lp_index.data(), + lp_value.data())); + mask[col] = 1; + h.getCols(mask.data(), get_num, lp_cost.data(), lp_lower.data(), + lp_upper.data(), lp_nnz, lp_start.data(), lp_index.data(), + lp_value.data()); + REQUIRE(lp_cost[0] == cost); + REQUIRE(lp_lower[0] == lower); + REQUIRE(lp_upper[0] == upper); + REQUIRE(equalSparseVectors(avgas_num_row, avgas_nnz, index.data(), + value.data(), lp_nnz, lp_index.data(), + lp_value.data())); + mask[col] = 0; + } + HighsInt from_col = 2; + HighsInt to_col = 5; + HighsInt num_col = to_col - from_col + 1; + lp_cost.resize(num_col); + lp_lower.resize(num_col); + lp_upper.resize(num_col); + lp_start.resize(num_col); + lp_index.resize(num_col * avgas_num_row); + lp_value.resize(num_col * avgas_num_row); + h.getCols(from_col, to_col, get_num, lp_cost.data(), lp_lower.data(), + lp_upper.data(), lp_nnz, lp_start.data(), lp_index.data(), + lp_value.data()); + REQUIRE(get_num == num_col); + for (HighsInt col = 0; col < num_col; col++) { + HighsInt avgas_col = from_col + col; + avgas.getCol(avgas_col, cost, lower, upper, index, value); + HighsInt avgas_nnz = index.size(); + REQUIRE(lp_cost[col] == cost); + REQUIRE(lp_lower[col] == lower); + REQUIRE(lp_upper[col] == upper); + HighsInt from_el = lp_start[col]; + HighsInt lp_row_nnz = + col < num_col - 1 ? lp_start[col + 1] - from_el : lp_nnz - from_el; + REQUIRE(equalSparseVectors(avgas_num_row, avgas_nnz, index.data(), + value.data(), lp_row_nnz, &lp_index[from_el], + &lp_value[from_el])); + } + set = {1, 2, 3, 6, 7}; + num_col = set.size(); + lp_cost.resize(num_col); + lp_lower.resize(num_col); + lp_upper.resize(num_col); + lp_start.resize(num_col); + lp_index.resize(num_col * avgas_num_row); + lp_value.resize(num_col * avgas_num_row); + h.getCols(num_col, set.data(), get_num, lp_cost.data(), lp_lower.data(), + lp_upper.data(), lp_nnz, lp_start.data(), lp_index.data(), + lp_value.data()); + REQUIRE(get_num == num_col); + for (HighsInt col = 0; col < num_col; col++) { + HighsInt avgas_col = set[col]; + avgas.getCol(avgas_col, cost, lower, upper, index, value); + HighsInt avgas_nnz = index.size(); + REQUIRE(lp_cost[col] == cost); + REQUIRE(lp_lower[col] == lower); + REQUIRE(lp_upper[col] == upper); + HighsInt from_el = lp_start[col]; + HighsInt lp_row_nnz = + col < num_col - 1 ? lp_start[col + 1] - from_el : lp_nnz - from_el; + REQUIRE(equalSparseVectors(avgas_num_row, avgas_nnz, index.data(), + value.data(), lp_row_nnz, &lp_index[from_el], + &lp_value[from_el])); + } + mask[0] = 1; + mask[1] = 1; + mask[4] = 1; + mask[6] = 1; + mask[7] = 1; + num_col = 5; + lp_cost.resize(num_col); + lp_lower.resize(num_col); + lp_upper.resize(num_col); + lp_start.resize(num_col); + lp_index.resize(num_col * avgas_num_row); + lp_value.resize(num_col * avgas_num_row); + h.getCols(mask.data(), get_num, lp_cost.data(), lp_lower.data(), + lp_upper.data(), lp_nnz, lp_start.data(), lp_index.data(), + lp_value.data()); + REQUIRE(get_num == num_col); + HighsInt col = 0; + for (HighsInt iCol = 0; iCol < avgas_num_col; iCol++) { + if (!mask[iCol]) continue; + HighsInt avgas_col = iCol; + avgas.getCol(avgas_col, cost, lower, upper, index, value); + HighsInt avgas_nnz = index.size(); + REQUIRE(lp_cost[col] == cost); + REQUIRE(lp_lower[col] == lower); + REQUIRE(lp_upper[col] == upper); + HighsInt from_el = lp_start[col]; + HighsInt lp_row_nnz = + col < num_col - 1 ? lp_start[col + 1] - from_el : lp_nnz - from_el; + REQUIRE(equalSparseVectors(avgas_num_row, avgas_nnz, index.data(), + value.data(), lp_row_nnz, &lp_index[from_el], + &lp_value[from_el])); + col++; + } +} + +TEST_CASE("row-wise-get-row-avgas", "[highs_data]") { + Avgas avgas; + const HighsInt avgas_num_col = 8; + const HighsInt avgas_num_row = 10; + + Highs h; + h.setOptionValue("output_flag", dev_run); + double cost; + double lower; + double upper; + std::vector index; + std::vector value; + + for (HighsInt col = 0; col < avgas_num_col; col++) { + avgas.getCol(col, cost, lower, upper, index, value); + REQUIRE(h.addCol(cost, lower, upper, 0, nullptr, nullptr) == + HighsStatus::kOk); + } + for (HighsInt row = 0; row < avgas_num_row; row++) { + avgas.getRow(row, lower, upper, index, value); + HighsInt avgas_nnz = index.size(); + REQUIRE(h.addRow(lower, upper, avgas_nnz, index.data(), value.data()) == + HighsStatus::kOk); + } + + // Test extraction of rows and columns, with rowwise and colwise + // internal storage + h.ensureRowwise(); + testAvgasGetRow(h); + testAvgasGetCol(h); + + h.ensureColwise(); + testAvgasGetRow(h); + testAvgasGetCol(h); +} + +TEST_CASE("hot-start-after-delete", "[highs_data]") { + Highs h; + h.setOptionValue("output_flag", dev_run); + const HighsLp& lp = h.getLp(); + const HighsInfo& info = h.getInfo(); + const HighsBasis& basis = h.getBasis(); + const HighsSolution& solution = h.getSolution(); + std::string model = "avgas"; + std::string model_file = + std::string(HIGHS_DIR) + "/check/instances/" + model + ".mps"; + h.readModel(model_file); + h.run(); + HighsInt ieration_count0 = info.simplex_iteration_count; + if (dev_run) + printf("Initial solve takes %d iterations and yields objective = %g\n", + int(info.simplex_iteration_count), info.objective_function_value); + + HighsInt max_dim = std::max(lp.num_col_, lp.num_row_); + std::vector cost(1); + std::vector lower(1); + std::vector upper(1); + HighsInt nnz; + std::vector start(1); + std::vector index(max_dim); + std::vector value(max_dim); + HighsInt get_num; + HighsInt use_col, use_row; + for (HighsInt k = 0; k < 2; k++) { + if (dev_run) { + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) + printf("Col %2d is %s\n", int(iCol), + basis.col_status[iCol] == HighsBasisStatus::kBasic ? "basic" + : "nonbasic"); + printf("\n"); + } + if (k == 0) { + use_col = 1; // Nonbasic + } else { + use_col = 4; // Basic + } + if (dev_run) + printf( + "\nDeleting and adding column %1d with status \"%s\" and value %g\n", + int(use_col), + h.basisStatusToString(basis.col_status[use_col]).c_str(), + solution.col_value[use_col]); + + h.getCols(use_col, use_col, get_num, cost.data(), lower.data(), + upper.data(), nnz, start.data(), index.data(), value.data()); + + h.deleteCols(use_col, use_col); + if (dev_run) basis.printScalars(); + + h.addCol(cost[0], lower[0], upper[0], nnz, index.data(), value.data()); + + h.run(); + if (dev_run) + printf( + "After deleting and adding column %1d, solve takes %d iterations and " + "yields objective = %g\n", + int(use_col), int(info.simplex_iteration_count), + info.objective_function_value); + REQUIRE(info.simplex_iteration_count < ieration_count0); + } + + for (HighsInt k = 0; k < 2; k++) { + if (dev_run) { + for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) + printf("Row %2d is %s\n", int(iRow), + basis.row_status[iRow] == HighsBasisStatus::kBasic ? "basic" + : "nonbasic"); + } + if (k == 0) { + use_row = 1; // Nonbasic + } else { + use_row = 8; // Basic + } + if (dev_run) + printf("\nDeleting and adding row %1d with status \"%s\" and value %g\n", + int(use_row), + h.basisStatusToString(basis.row_status[use_row]).c_str(), + solution.row_value[use_row]); + + h.getRows(use_row, use_row, get_num, lower.data(), upper.data(), nnz, + start.data(), index.data(), value.data()); + + h.deleteRows(use_row, use_row); + if (dev_run) basis.printScalars(); + + h.addRow(lower[0], upper[0], nnz, index.data(), value.data()); + + h.run(); + if (dev_run) + printf( + "After deleting and adding row %1d, solve takes %d iterations and " + "yields objective = %g\n", + int(use_row), int(info.simplex_iteration_count), + info.objective_function_value); + REQUIRE(info.simplex_iteration_count < ieration_count0); + } + std::vector set = {1, 3, 4}; + HighsInt num_set_en = set.size(); + cost.resize(num_set_en); + lower.resize(num_set_en); + upper.resize(num_set_en); + start.resize(num_set_en); + index.resize(num_set_en * max_dim); + value.resize(num_set_en * max_dim); + + h.getCols(num_set_en, set.data(), get_num, cost.data(), lower.data(), + upper.data(), nnz, start.data(), index.data(), value.data()); + + h.deleteCols(num_set_en, set.data()); + if (dev_run) basis.printScalars(); + + h.addCols(get_num, cost.data(), lower.data(), upper.data(), nnz, start.data(), + index.data(), value.data()); + + h.run(); + if (dev_run) + printf( + "After deleting and adding %d columns in set, solve takes %d " + "iterations and yields objective = %g\n", + int(get_num), int(info.simplex_iteration_count), + info.objective_function_value); + // REQUIRE(info.simplex_iteration_count < ieration_count0); + + h.getRows(num_set_en, set.data(), get_num, lower.data(), upper.data(), nnz, + start.data(), index.data(), value.data()); + + h.deleteRows(num_set_en, set.data()); + if (dev_run) basis.printScalars(); + + h.addRows(get_num, lower.data(), upper.data(), nnz, start.data(), + index.data(), value.data()); + + h.run(); + if (dev_run) + printf( + "After deleting and adding %d rows in set, solve takes %d iterations " + "and yields objective = %g\n", + int(get_num), int(info.simplex_iteration_count), + info.objective_function_value); + // REQUIRE(info.simplex_iteration_count < ieration_count0); + std::vector mask; + mask.assign(max_dim, 0); + mask[1] = 1; + mask[4] = 1; + mask[5] = 1; + + h.getCols(mask.data(), get_num, cost.data(), lower.data(), upper.data(), nnz, + start.data(), index.data(), value.data()); + + h.deleteCols(mask.data()); + if (dev_run) basis.printScalars(); + + h.addCols(get_num, cost.data(), lower.data(), upper.data(), nnz, start.data(), + index.data(), value.data()); + + h.run(); + if (dev_run) + printf( + "After deleting and adding %d columns in mask, solve takes %d " + "iterations and yields objective = %g\n", + int(get_num), int(info.simplex_iteration_count), + info.objective_function_value); + // REQUIRE(info.simplex_iteration_count < ieration_count0); + + mask.assign(max_dim, 0); + mask[1] = 1; + mask[4] = 1; + mask[5] = 1; + mask[8] = 1; + mask[9] = 1; + HighsInt num_mask_en = mask.size(); + cost.resize(num_mask_en); + lower.resize(num_mask_en); + upper.resize(num_mask_en); + start.resize(num_mask_en); + index.resize(num_mask_en * max_dim); + value.resize(num_mask_en * max_dim); + + h.getRows(mask.data(), get_num, lower.data(), upper.data(), nnz, start.data(), + index.data(), value.data()); + + h.deleteRows(mask.data()); + if (dev_run) basis.printScalars(); + + h.addRows(get_num, lower.data(), upper.data(), nnz, start.data(), + index.data(), value.data()); + + h.run(); + if (dev_run) + printf( + "After deleting and adding %d rows in mask, solve takes %d iterations " + "and yields objective = %g\n", + int(get_num), int(info.simplex_iteration_count), + info.objective_function_value); + // REQUIRE(info.simplex_iteration_count < ieration_count0); +} diff --git a/check/TestLpOrientation.cpp b/check/TestLpOrientation.cpp index e94297b9bc..a43a6d5b20 100644 --- a/check/TestLpOrientation.cpp +++ b/check/TestLpOrientation.cpp @@ -10,8 +10,6 @@ const bool dev_run = false; // No commas in test case name. TEST_CASE("LP-orientation", "[lp_orientation]") { Avgas avgas; - const HighsInt avgas_num_col = 8; - const HighsInt avgas_num_row = 10; HighsInt num_row = 0; HighsInt num_row_nz = 0; vector rowLower; @@ -21,11 +19,10 @@ TEST_CASE("LP-orientation", "[lp_orientation]") { vector ARvalue; for (HighsInt row = 0; row < avgas_num_row; row++) - avgas.row(row, num_row, num_row_nz, rowLower, rowUpper, ARstart, ARindex, - ARvalue); + avgas.addRow(row, num_row, num_row_nz, rowLower, rowUpper, ARstart, ARindex, + ARvalue); - ARstart.resize(num_row + 1); - ARstart[num_row] = num_row_nz; + ARstart.push_back(num_row_nz); HighsInt num_col = 0; HighsInt num_col_nz = 0; @@ -36,10 +33,10 @@ TEST_CASE("LP-orientation", "[lp_orientation]") { vector Aindex; vector Avalue; for (HighsInt col = 0; col < avgas_num_col; col++) - avgas.col(col, num_col, num_col_nz, colCost, colLower, colUpper, Astart, - Aindex, Avalue); - Astart.resize(num_col + 1); - Astart[num_col] = num_col_nz; + avgas.addCol(col, num_col, num_col_nz, colCost, colLower, colUpper, Astart, + Aindex, Avalue); + Astart.push_back(num_col_nz); + assert(num_col_nz == num_row_nz); double optimal_objective_function_value = -7.75; @@ -102,20 +99,18 @@ TEST_CASE("LP-orientation", "[lp_orientation]") { highs.clearModel(); highs.addCols(num_col, colCost.data(), colLower.data(), colUpper.data(), 0, NULL, NULL, NULL); - vector one_row_Lower; - vector one_row_Upper; - vector one_row_start; + double one_row_lower; + double one_row_upper; vector one_row_index; vector one_row_value; + HighsInt one_row_num_nz; for (HighsInt row = 0; row < avgas_num_row; row++) { - HighsInt one_row_numnz = 0; - HighsInt one_row_numrow = 0; - avgas.row(row, one_row_numrow, one_row_numnz, one_row_Lower, one_row_Upper, - one_row_start, one_row_index, one_row_value); - REQUIRE(highs.addRows(1, one_row_Lower.data(), one_row_Upper.data(), - one_row_numnz, one_row_start.data(), - one_row_index.data(), - one_row_value.data()) == HighsStatus::kOk); + avgas.getRow(row, one_row_lower, one_row_upper, one_row_index, + one_row_value); + one_row_num_nz = one_row_index.size(); + REQUIRE(highs.addRow(one_row_lower, one_row_upper, one_row_num_nz, + one_row_index.data(), + one_row_value.data()) == HighsStatus::kOk); } highs.run(); REQUIRE(info.objective_function_value == optimal_objective_function_value); diff --git a/check/TestLpValidation.cpp b/check/TestLpValidation.cpp index 473fdeb681..f048d505ed 100644 --- a/check/TestLpValidation.cpp +++ b/check/TestLpValidation.cpp @@ -171,10 +171,9 @@ TEST_CASE("LP-validation", "[highs_data]") { vector ARstart; vector ARindex; vector ARvalue; - for (HighsInt row = 0; row < avgas_num_row; row++) { - avgas.row(row, num_row, num_row_nz, rowLower, rowUpper, ARstart, ARindex, - ARvalue); + avgas.addRow(row, num_row, num_row_nz, rowLower, rowUpper, ARstart, ARindex, + ARvalue); } HighsInt num_col = 0; @@ -186,8 +185,8 @@ TEST_CASE("LP-validation", "[highs_data]") { vector Aindex; vector Avalue; for (HighsInt col = 0; col < avgas_num_col; col++) { - avgas.col(col, num_col, num_col_nz, colCost, colLower, colUpper, Astart, - Aindex, Avalue); + avgas.addCol(col, num_col, num_col_nz, colCost, colLower, colUpper, Astart, + Aindex, Avalue); } return_status = assessLp(lp, options); diff --git a/check/TestOptions.cpp b/check/TestOptions.cpp index f511f8caef..b2a4142710 100644 --- a/check/TestOptions.cpp +++ b/check/TestOptions.cpp @@ -95,7 +95,7 @@ TEST_CASE("internal-options", "[highs_options]") { REQUIRE(options.small_matrix_value == 0.001); REQUIRE(options.mps_parser_type_free); - if (dev_run) reportOptions(stdout, options.records, true); + if (dev_run) reportOptions(stdout, report_log_options, options.records, true); return_status = checkOptions(report_log_options, options.records); REQUIRE(return_status == OptionStatus::kOk); @@ -157,7 +157,7 @@ TEST_CASE("internal-options", "[highs_options]") { if (dev_run) { printf("\nAfter setting allowed_matrix_scale_factor to 1\n"); - reportOptions(stdout, options.records); + reportOptions(stdout, report_log_options, options.records); } double allowed_matrix_scale_factor_double = 1e-7; @@ -174,7 +174,7 @@ TEST_CASE("internal-options", "[highs_options]") { if (dev_run) { printf("\nAfter testing HighsInt options\n"); - reportOptions(stdout, options.records); + reportOptions(stdout, report_log_options, options.records); } // Check setting double options @@ -231,7 +231,7 @@ TEST_CASE("internal-options", "[highs_options]") { options.log_options, options.records, model_file); REQUIRE(return_status == OptionStatus::kUnknownOption); - if (dev_run) reportOptions(stdout, options.records); + if (dev_run) reportOptions(stdout, report_log_options, options.records); bool get_mps_parser_type_free; return_status = diff --git a/check/TestPresolve.cpp b/check/TestPresolve.cpp index 7d2e5d9604..505373c156 100644 --- a/check/TestPresolve.cpp +++ b/check/TestPresolve.cpp @@ -639,3 +639,13 @@ TEST_CASE("presolve-slacks", "[highs_test_presolve]") { REQUIRE(h.getPresolvedLp().num_col_ == 2); REQUIRE(h.getPresolvedLp().num_row_ == 2); } + +TEST_CASE("presolve-issue-2095", "[highs_test_presolve]") { + std::string model_file = + std::string(HIGHS_DIR) + "/check/instances/issue-2095.mps"; + Highs highs; + highs.setOptionValue("output_flag", dev_run); + highs.readModel(model_file); + REQUIRE(highs.presolve() == HighsStatus::kOk); + REQUIRE(highs.getModelPresolveStatus() == HighsPresolveStatus::kReduced); +} diff --git a/check/instances/issue-2095.mps b/check/instances/issue-2095.mps new file mode 100644 index 0000000000..a05dc4ef22 --- /dev/null +++ b/check/instances/issue-2095.mps @@ -0,0 +1,836 @@ +NAME +ROWS + N Obj + E r0 + E r1 + E r2 + E r3 + E r4 + E r5 + E r6 + E r7 + E r8 + E r9 + E r10 + E r11 + E r12 + E r13 + E r14 + E r15 + E r16 + E r17 + E r18 + E r19 + E r20 + E r21 + E r22 + E r23 + E r24 + E r25 + E r26 + E r27 + E r28 +COLUMNS + MARK0000 'MARKER' 'INTORG' + c0 Obj 1 + c0 r0 1 + c1 Obj 1 + c1 r1 1 + c2 Obj 1 + c2 r2 1 + c3 Obj 1 + c3 r3 1 + c4 Obj 1 + c4 r5 1 + c5 Obj 1 + c5 r6 1 + c6 Obj 1 + c6 r7 1 + c7 Obj 1 + c7 r25 1 + c8 Obj 1 + c8 r26 1 + c9 Obj 1 + c9 r27 1 + c10 Obj 1 + c10 r28 1 + c11 Obj 1 + c11 r0 1 + c11 r1 1 + c11 r2 1 + c11 r3 1 + c12 Obj 1 + c12 r1 1 + c12 r2 1 + c12 r3 1 + c12 r4 1 + c13 Obj 1 + c13 r5 1 + c13 r6 1 + c13 r7 1 + c13 r8 1 + c14 Obj 1 + c14 r8 1 + c14 r9 1 + c14 r10 1 + c14 r11 1 + c15 Obj 1 + c15 r24 1 + c15 r25 1 + c15 r26 1 + c15 r27 1 + c16 Obj 1 + c16 r0 1 + c16 r1 1 + c16 r2 1 + c16 r3 1 + c16 r4 1 + c17 Obj 1 + c17 r4 1 + c17 r5 1 + c17 r6 1 + c17 r7 1 + c17 r8 1 + c18 Obj 1 + c18 r5 1 + c18 r6 1 + c18 r7 1 + c18 r8 1 + c18 r9 1 + c19 Obj 1 + c19 r8 1 + c19 r9 1 + c19 r10 1 + c19 r11 1 + c19 r12 1 + c20 Obj 1 + c20 r23 1 + c20 r24 1 + c20 r25 1 + c20 r26 1 + c20 r27 1 + c21 Obj 1 + c21 r4 1 + c21 r5 1 + c21 r6 1 + c21 r7 1 + c21 r8 1 + c21 r9 1 + c22 Obj 1 + c22 r5 1 + c22 r6 1 + c22 r7 1 + c22 r8 1 + c22 r9 1 + c22 r10 1 + c23 Obj 1 + c23 r8 1 + c23 r9 1 + c23 r10 1 + c23 r11 1 + c23 r12 1 + c23 r13 1 + c24 Obj 1 + c24 r22 1 + c24 r23 1 + c24 r24 1 + c24 r25 1 + c24 r26 1 + c24 r27 1 + c25 Obj 1 + c25 r4 1 + c25 r5 1 + c25 r6 1 + c25 r7 1 + c25 r8 1 + c25 r9 1 + c25 r10 1 + c26 Obj 1 + c26 r8 1 + c26 r9 1 + c26 r10 1 + c26 r11 1 + c26 r12 1 + c26 r13 1 + c26 r14 1 + c27 Obj 1 + c27 r21 1 + c27 r22 1 + c27 r23 1 + c27 r24 1 + c27 r25 1 + c27 r26 1 + c27 r27 1 + c28 Obj 1 + c28 r8 1 + c28 r9 1 + c28 r10 1 + c28 r11 1 + c28 r12 1 + c28 r13 1 + c28 r14 1 + c28 r15 1 + c29 Obj 1 + c29 r20 1 + c29 r21 1 + c29 r22 1 + c29 r23 1 + c29 r24 1 + c29 r25 1 + c29 r26 1 + c29 r27 1 + c30 Obj 1 + c30 r8 1 + c30 r9 1 + c30 r10 1 + c30 r11 1 + c30 r12 1 + c30 r13 1 + c30 r14 1 + c30 r15 1 + c30 r16 1 + c31 Obj 1 + c31 r19 1 + c31 r20 1 + c31 r21 1 + c31 r22 1 + c31 r23 1 + c31 r24 1 + c31 r25 1 + c31 r26 1 + c31 r27 1 + c32 Obj 1 + c32 r8 1 + c32 r9 1 + c32 r10 1 + c32 r11 1 + c32 r12 1 + c32 r13 1 + c32 r14 1 + c32 r15 1 + c32 r16 1 + c32 r17 1 + c33 Obj 1 + c33 r18 1 + c33 r19 1 + c33 r20 1 + c33 r21 1 + c33 r22 1 + c33 r23 1 + c33 r24 1 + c33 r25 1 + c33 r26 1 + c33 r27 1 + c34 Obj 1 + c34 r8 1 + c34 r9 1 + c34 r10 1 + c34 r11 1 + c34 r12 1 + c34 r13 1 + c34 r14 1 + c34 r15 1 + c34 r16 1 + c34 r17 1 + c34 r18 1 + c35 Obj 1 + c35 r17 1 + c35 r18 1 + c35 r19 1 + c35 r20 1 + c35 r21 1 + c35 r22 1 + c35 r23 1 + c35 r24 1 + c35 r25 1 + c35 r26 1 + c35 r27 1 + c36 Obj 1 + c36 r7 1 + c36 r8 1 + c36 r9 1 + c36 r10 1 + c36 r11 1 + c36 r12 1 + c36 r13 1 + c36 r14 1 + c36 r15 1 + c36 r16 1 + c36 r17 1 + c36 r18 1 + c37 Obj 1 + c37 r8 1 + c37 r9 1 + c37 r10 1 + c37 r11 1 + c37 r12 1 + c37 r13 1 + c37 r14 1 + c37 r15 1 + c37 r16 1 + c37 r17 1 + c37 r18 1 + c37 r19 1 + c38 Obj 1 + c38 r16 1 + c38 r17 1 + c38 r18 1 + c38 r19 1 + c38 r20 1 + c38 r21 1 + c38 r22 1 + c38 r23 1 + c38 r24 1 + c38 r25 1 + c38 r26 1 + c38 r27 1 + c39 Obj 1 + c39 r17 1 + c39 r18 1 + c39 r19 1 + c39 r20 1 + c39 r21 1 + c39 r22 1 + c39 r23 1 + c39 r24 1 + c39 r25 1 + c39 r26 1 + c39 r27 1 + c39 r28 1 + c40 Obj 1 + c40 r7 1 + c40 r8 1 + c40 r9 1 + c40 r10 1 + c40 r11 1 + c40 r12 1 + c40 r13 1 + c40 r14 1 + c40 r15 1 + c40 r16 1 + c40 r17 1 + c40 r18 1 + c40 r19 1 + c41 Obj 1 + c41 r8 1 + c41 r9 1 + c41 r10 1 + c41 r11 1 + c41 r12 1 + c41 r13 1 + c41 r14 1 + c41 r15 1 + c41 r16 1 + c41 r17 1 + c41 r18 1 + c41 r19 1 + c41 r20 1 + c42 Obj 1 + c42 r15 1 + c42 r16 1 + c42 r17 1 + c42 r18 1 + c42 r19 1 + c42 r20 1 + c42 r21 1 + c42 r22 1 + c42 r23 1 + c42 r24 1 + c42 r25 1 + c42 r26 1 + c42 r27 1 + c43 Obj 1 + c43 r16 1 + c43 r17 1 + c43 r18 1 + c43 r19 1 + c43 r20 1 + c43 r21 1 + c43 r22 1 + c43 r23 1 + c43 r24 1 + c43 r25 1 + c43 r26 1 + c43 r27 1 + c43 r28 1 + c44 Obj 1 + c44 r7 1 + c44 r8 1 + c44 r9 1 + c44 r10 1 + c44 r11 1 + c44 r12 1 + c44 r13 1 + c44 r14 1 + c44 r15 1 + c44 r16 1 + c44 r17 1 + c44 r18 1 + c44 r19 1 + c44 r20 1 + c45 Obj 1 + c45 r8 1 + c45 r9 1 + c45 r10 1 + c45 r11 1 + c45 r12 1 + c45 r13 1 + c45 r14 1 + c45 r15 1 + c45 r16 1 + c45 r17 1 + c45 r18 1 + c45 r19 1 + c45 r20 1 + c45 r21 1 + c46 Obj 1 + c46 r14 1 + c46 r15 1 + c46 r16 1 + c46 r17 1 + c46 r18 1 + c46 r19 1 + c46 r20 1 + c46 r21 1 + c46 r22 1 + c46 r23 1 + c46 r24 1 + c46 r25 1 + c46 r26 1 + c46 r27 1 + c47 Obj 1 + c47 r15 1 + c47 r16 1 + c47 r17 1 + c47 r18 1 + c47 r19 1 + c47 r20 1 + c47 r21 1 + c47 r22 1 + c47 r23 1 + c47 r24 1 + c47 r25 1 + c47 r26 1 + c47 r27 1 + c47 r28 1 + c48 Obj 1 + c48 r7 1 + c48 r8 1 + c48 r9 1 + c48 r10 1 + c48 r11 1 + c48 r12 1 + c48 r13 1 + c48 r14 1 + c48 r15 1 + c48 r16 1 + c48 r17 1 + c48 r18 1 + c48 r19 1 + c48 r20 1 + c48 r21 1 + c49 Obj 1 + c49 r8 1 + c49 r9 1 + c49 r10 1 + c49 r11 1 + c49 r12 1 + c49 r13 1 + c49 r14 1 + c49 r15 1 + c49 r16 1 + c49 r17 1 + c49 r18 1 + c49 r19 1 + c49 r20 1 + c49 r21 1 + c49 r22 1 + c50 Obj 1 + c50 r13 1 + c50 r14 1 + c50 r15 1 + c50 r16 1 + c50 r17 1 + c50 r18 1 + c50 r19 1 + c50 r20 1 + c50 r21 1 + c50 r22 1 + c50 r23 1 + c50 r24 1 + c50 r25 1 + c50 r26 1 + c50 r27 1 + c51 Obj 1 + c51 r14 1 + c51 r15 1 + c51 r16 1 + c51 r17 1 + c51 r18 1 + c51 r19 1 + c51 r20 1 + c51 r21 1 + c51 r22 1 + c51 r23 1 + c51 r24 1 + c51 r25 1 + c51 r26 1 + c51 r27 1 + c51 r28 1 + c52 Obj 1 + c52 r7 1 + c52 r8 1 + c52 r9 1 + c52 r10 1 + c52 r11 1 + c52 r12 1 + c52 r13 1 + c52 r14 1 + c52 r15 1 + c52 r16 1 + c52 r17 1 + c52 r18 1 + c52 r19 1 + c52 r20 1 + c52 r21 1 + c52 r22 1 + c53 Obj 1 + c53 r8 1 + c53 r9 1 + c53 r10 1 + c53 r11 1 + c53 r12 1 + c53 r13 1 + c53 r14 1 + c53 r15 1 + c53 r16 1 + c53 r17 1 + c53 r18 1 + c53 r19 1 + c53 r20 1 + c53 r21 1 + c53 r22 1 + c53 r23 1 + c54 Obj 1 + c54 r12 1 + c54 r13 1 + c54 r14 1 + c54 r15 1 + c54 r16 1 + c54 r17 1 + c54 r18 1 + c54 r19 1 + c54 r20 1 + c54 r21 1 + c54 r22 1 + c54 r23 1 + c54 r24 1 + c54 r25 1 + c54 r26 1 + c54 r27 1 + c55 Obj 1 + c55 r13 1 + c55 r14 1 + c55 r15 1 + c55 r16 1 + c55 r17 1 + c55 r18 1 + c55 r19 1 + c55 r20 1 + c55 r21 1 + c55 r22 1 + c55 r23 1 + c55 r24 1 + c55 r25 1 + c55 r26 1 + c55 r27 1 + c55 r28 1 + c56 Obj 1 + c56 r7 1 + c56 r8 1 + c56 r9 1 + c56 r10 1 + c56 r11 1 + c56 r12 1 + c56 r13 1 + c56 r14 1 + c56 r15 1 + c56 r16 1 + c56 r17 1 + c56 r18 1 + c56 r19 1 + c56 r20 1 + c56 r21 1 + c56 r22 1 + c56 r23 1 + c57 Obj 1 + c57 r8 1 + c57 r9 1 + c57 r10 1 + c57 r11 1 + c57 r12 1 + c57 r13 1 + c57 r14 1 + c57 r15 1 + c57 r16 1 + c57 r17 1 + c57 r18 1 + c57 r19 1 + c57 r20 1 + c57 r21 1 + c57 r22 1 + c57 r23 1 + c57 r24 1 + c58 Obj 1 + c58 r11 1 + c58 r12 1 + c58 r13 1 + c58 r14 1 + c58 r15 1 + c58 r16 1 + c58 r17 1 + c58 r18 1 + c58 r19 1 + c58 r20 1 + c58 r21 1 + c58 r22 1 + c58 r23 1 + c58 r24 1 + c58 r25 1 + c58 r26 1 + c58 r27 1 + c59 Obj 1 + c59 r12 1 + c59 r13 1 + c59 r14 1 + c59 r15 1 + c59 r16 1 + c59 r17 1 + c59 r18 1 + c59 r19 1 + c59 r20 1 + c59 r21 1 + c59 r22 1 + c59 r23 1 + c59 r24 1 + c59 r25 1 + c59 r26 1 + c59 r27 1 + c59 r28 1 + c60 Obj 1 + c60 r7 1 + c60 r8 1 + c60 r9 1 + c60 r10 1 + c60 r11 1 + c60 r12 1 + c60 r13 1 + c60 r14 1 + c60 r15 1 + c60 r16 1 + c60 r17 1 + c60 r18 1 + c60 r19 1 + c60 r20 1 + c60 r21 1 + c60 r22 1 + c60 r23 1 + c60 r24 1 + c61 Obj 1 + c61 r10 1 + c61 r11 1 + c61 r12 1 + c61 r13 1 + c61 r14 1 + c61 r15 1 + c61 r16 1 + c61 r17 1 + c61 r18 1 + c61 r19 1 + c61 r20 1 + c61 r21 1 + c61 r22 1 + c61 r23 1 + c61 r24 1 + c61 r25 1 + c61 r26 1 + c61 r27 1 + c62 Obj 1 + c62 r11 1 + c62 r12 1 + c62 r13 1 + c62 r14 1 + c62 r15 1 + c62 r16 1 + c62 r17 1 + c62 r18 1 + c62 r19 1 + c62 r20 1 + c62 r21 1 + c62 r22 1 + c62 r23 1 + c62 r24 1 + c62 r25 1 + c62 r26 1 + c62 r27 1 + c62 r28 1 + c63 Obj 1 + c63 r9 1 + c63 r10 1 + c63 r11 1 + c63 r12 1 + c63 r13 1 + c63 r14 1 + c63 r15 1 + c63 r16 1 + c63 r17 1 + c63 r18 1 + c63 r19 1 + c63 r20 1 + c63 r21 1 + c63 r22 1 + c63 r23 1 + c63 r24 1 + c63 r25 1 + c63 r26 1 + c63 r27 1 + c64 Obj 1 + c64 r10 1 + c64 r11 1 + c64 r12 1 + c64 r13 1 + c64 r14 1 + c64 r15 1 + c64 r16 1 + c64 r17 1 + c64 r18 1 + c64 r19 1 + c64 r20 1 + c64 r21 1 + c64 r22 1 + c64 r23 1 + c64 r24 1 + c64 r25 1 + c64 r26 1 + c64 r27 1 + c64 r28 1 + c65 Obj 1 + c65 r9 1 + c65 r10 1 + c65 r11 1 + c65 r12 1 + c65 r13 1 + c65 r14 1 + c65 r15 1 + c65 r16 1 + c65 r17 1 + c65 r18 1 + c65 r19 1 + c65 r20 1 + c65 r21 1 + c65 r22 1 + c65 r23 1 + c65 r24 1 + c65 r25 1 + c65 r26 1 + c65 r27 1 + c65 r28 1 + MARK0001 'MARKER' 'INTEND' +RHS + RHS_V r0 1 + RHS_V r1 1 + RHS_V r2 1 + RHS_V r3 1 + RHS_V r4 1 + RHS_V r5 1 + RHS_V r6 1 + RHS_V r7 1 + RHS_V r8 1 + RHS_V r9 1 + RHS_V r10 1 + RHS_V r11 1 + RHS_V r12 1 + RHS_V r13 1 + RHS_V r14 1 + RHS_V r15 1 + RHS_V r16 1 + RHS_V r17 1 + RHS_V r18 1 + RHS_V r19 1 + RHS_V r20 1 + RHS_V r21 1 + RHS_V r22 1 + RHS_V r23 1 + RHS_V r24 1 + RHS_V r25 1 + RHS_V r26 1 + RHS_V r27 1 + RHS_V r28 1 +BOUNDS + BV BOUND c0 + BV BOUND c1 + BV BOUND c2 + BV BOUND c3 + BV BOUND c4 + BV BOUND c5 + BV BOUND c6 + BV BOUND c7 + BV BOUND c8 + BV BOUND c9 + BV BOUND c10 + BV BOUND c11 + BV BOUND c12 + BV BOUND c13 + BV BOUND c14 + BV BOUND c15 + BV BOUND c16 + BV BOUND c17 + BV BOUND c18 + BV BOUND c19 + BV BOUND c20 + BV BOUND c21 + BV BOUND c22 + BV BOUND c23 + BV BOUND c24 + BV BOUND c25 + BV BOUND c26 + BV BOUND c27 + BV BOUND c28 + BV BOUND c29 + BV BOUND c30 + BV BOUND c31 + BV BOUND c32 + BV BOUND c33 + BV BOUND c34 + BV BOUND c35 + BV BOUND c36 + BV BOUND c37 + BV BOUND c38 + BV BOUND c39 + BV BOUND c40 + BV BOUND c41 + BV BOUND c42 + BV BOUND c43 + BV BOUND c44 + BV BOUND c45 + BV BOUND c46 + BV BOUND c47 + BV BOUND c48 + BV BOUND c49 + BV BOUND c50 + BV BOUND c51 + BV BOUND c52 + BV BOUND c53 + BV BOUND c54 + BV BOUND c55 + BV BOUND c56 + BV BOUND c57 + BV BOUND c58 + BV BOUND c59 + BV BOUND c60 + BV BOUND c61 + BV BOUND c62 + BV BOUND c63 + BV BOUND c64 + BV BOUND c65 +ENDATA diff --git a/cmake/cpp-highs.cmake b/cmake/cpp-highs.cmake index 88a529a6f3..3e25a3e601 100644 --- a/cmake/cpp-highs.cmake +++ b/cmake/cpp-highs.cmake @@ -57,18 +57,20 @@ install(TARGETS highs PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/highs) # Add library targets to the build-tree export set -export(TARGETS highs - NAMESPACE ${PROJECT_NAMESPACE}::highs - FILE "${HIGHS_BINARY_DIR}/highs-targets.cmake") - -install(EXPORT ${lower}-targets - NAMESPACE ${PROJECT_NAMESPACE}:: - FILE highs-targets.cmake - DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${lower}) -# install(FILES "${HIGHS_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/highs-config.cmake" -# DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/highs) -# install(FILES "${HIGHS_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/highs.pc" -# DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) +if (NOT HIGHS_COVERAGE) + export(TARGETS highs + NAMESPACE ${PROJECT_NAMESPACE}::highs + FILE "${HIGHS_BINARY_DIR}/highs-targets.cmake") + + install(EXPORT ${lower}-targets + NAMESPACE ${PROJECT_NAMESPACE}:: + FILE highs-targets.cmake + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${lower}) + # install(FILES "${HIGHS_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/highs-config.cmake" + # DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/highs) + # install(FILES "${HIGHS_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/highs.pc" + # DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) +endif() include(CMakePackageConfigHelpers) diff --git a/docs/src/interfaces/python/example-py.md b/docs/src/interfaces/python/example-py.md index 7f026fb903..ddb72b24e8 100644 --- a/docs/src/interfaces/python/example-py.md +++ b/docs/src/interfaces/python/example-py.md @@ -208,6 +208,8 @@ print('Basis validity = ', h.basisValidityToString(info.basis_validity)) ## Modify model data + * `EnsureColwise` + * `EnsureRowwise` * `changeObjectiveSense` * `changeColCost` * `changeColBounds` diff --git a/docs/src/options/definitions.md b/docs/src/options/definitions.md index f4e932523c..92ac80c889 100644 --- a/docs/src/options/definitions.md +++ b/docs/src/options/definitions.md @@ -73,6 +73,18 @@ - Range: [1e-12, inf] - Default: 1e-08 +## primal\_residual\_tolerance +- Primal residual tolerance +- Type: double +- Range: [1e-10, inf] +- Default: 1e-07 + +## dual\_residual\_tolerance +- Dual residual tolerance +- Type: double +- Range: [1e-10, inf] +- Default: 1e-07 + ## objective\_bound - Objective bound for termination of the dual simplex solver - Type: double @@ -198,6 +210,16 @@ - Type: boolean - Default: "false" +## write\_presolved\_model\_file +- Write presolved model file +- Type: string +- Default: "" + +## write\_presolved\_model\_to\_file +- Write the presolved model to a file +- Type: boolean +- Default: "false" + ## mip\_detect\_symmetry - Whether MIP symmetry should be detected - Type: boolean @@ -220,6 +242,12 @@ - Range: {0, 2147483647} - Default: 2147483647 +## mip\_max\_start\_nodes +- MIP solver max number of nodes when completing a partial MIP start +- Type: integer +- Range: {0, 2147483647} +- Default: 500 + ## mip\_improving\_solution\_save - Whether improving MIP solutions should be saved - Type: boolean @@ -354,7 +382,7 @@ - Default: 4000 ## blend\_multi\_objectives -- Blend multiple objectives or apply lexicographically +- Blend multiple objectives or apply lexicographically: Default = true - Type: boolean - Default: "true" diff --git a/extern/filereaderlp/reader.cpp b/extern/filereaderlp/reader.cpp index 0a289c0ea8..a90ea00ca1 100644 --- a/extern/filereaderlp/reader.cpp +++ b/extern/filereaderlp/reader.cpp @@ -1,1319 +1,1319 @@ -#include "reader.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "HConfig.h" // for ZLIB_FOUND -#include "builder.hpp" -#include "def.hpp" -#ifdef ZLIB_FOUND -#include "../extern/zstr/zstr.hpp" -#endif - -// Cygwin doesn't come with an implementation for strdup if compiled with -// std=cxx -#ifdef __CYGWIN__ -#include -char* strdup(const char* s) { - size_t slen = strlen(s); - char* result = (char*)malloc(slen + 1); - if (result == NULL) { - return NULL; - } - - memcpy(result, s, slen + 1); - return result; -} -#endif - -enum class RawTokenType { - NONE, - STR, - CONS, - LESS, - GREATER, - EQUAL, - COLON, - LNEND, - FLEND, - BRKOP, - BRKCL, - PLUS, - MINUS, - HAT, - SLASH, - ASTERISK -}; - -struct RawToken { - RawTokenType type = RawTokenType::NONE; - std::string svalue; - double dvalue = 0.0; - - inline bool istype(RawTokenType t) const { return this->type == t; } - - RawToken& operator=(RawTokenType t) { - type = t; - return *this; - } - RawToken& operator=(const std::string& v) { - svalue = v; - type = RawTokenType::STR; - return *this; - } - RawToken& operator=(const double v) { - dvalue = v; - type = RawTokenType::CONS; - return *this; - } -}; - -enum class ProcessedTokenType { - NONE, - SECID, - VARID, - CONID, - CONST, - FREE, - BRKOP, - BRKCL, - COMP, - LNEND, - SLASH, - ASTERISK, - HAT, - SOSTYPE -}; - -enum class LpSectionKeyword { - NONE, - OBJMIN, - OBJMAX, - CON, - BOUNDS, - GEN, - BIN, - SEMI, - SOS, - END -}; - -static const std::unordered_map - sectionkeywordmap{{"minimize", LpSectionKeyword::OBJMIN}, - {"min", LpSectionKeyword::OBJMIN}, - {"minimum", LpSectionKeyword::OBJMIN}, - {"maximize", LpSectionKeyword::OBJMAX}, - {"max", LpSectionKeyword::OBJMAX}, - {"maximum", LpSectionKeyword::OBJMAX}, - {"subject to", LpSectionKeyword::CON}, - {"such that", LpSectionKeyword::CON}, - {"st", LpSectionKeyword::CON}, - {"s.t.", LpSectionKeyword::CON}, - {"bounds", LpSectionKeyword::BOUNDS}, - {"bound", LpSectionKeyword::BOUNDS}, - {"binary", LpSectionKeyword::BIN}, - {"binaries", LpSectionKeyword::BIN}, - {"bin", LpSectionKeyword::BIN}, - {"general", LpSectionKeyword::GEN}, - {"generals", LpSectionKeyword::GEN}, - {"gen", LpSectionKeyword::GEN}, - {"integer", LpSectionKeyword::GEN}, - {"integers", LpSectionKeyword::GEN}, - {"semi-continuous", LpSectionKeyword::SEMI}, - {"semi", LpSectionKeyword::SEMI}, - {"semis", LpSectionKeyword::SEMI}, - {"sos", LpSectionKeyword::SOS}, - {"end", LpSectionKeyword::END}}; - -enum class SosType { SOS1, SOS2 }; - -enum class LpComparisonType { LEQ, L, EQ, G, GEQ }; - -struct ProcessedToken { - ProcessedTokenType type; - union { - LpSectionKeyword keyword; - SosType sostype; - char* name; - double value; - LpComparisonType dir; - }; - - ProcessedToken(const ProcessedToken&) = delete; - ProcessedToken(ProcessedToken&& t) : type(t.type) { - switch (type) { - case ProcessedTokenType::SECID: - keyword = t.keyword; - break; - case ProcessedTokenType::SOSTYPE: - sostype = t.sostype; - break; - case ProcessedTokenType::CONID: - case ProcessedTokenType::VARID: - name = t.name; - break; - case ProcessedTokenType::CONST: - value = t.value; - break; - case ProcessedTokenType::COMP: - dir = t.dir; - break; - default:; - } - t.type = ProcessedTokenType::NONE; - } - - ProcessedToken(ProcessedTokenType t) : type(t){}; - - ProcessedToken(LpSectionKeyword kw) - : type(ProcessedTokenType::SECID), keyword(kw){}; - - ProcessedToken(SosType sos) - : type(ProcessedTokenType::SOSTYPE), sostype(sos){}; - - ProcessedToken(ProcessedTokenType t, const std::string& s) : type(t) { - assert(t == ProcessedTokenType::CONID || t == ProcessedTokenType::VARID); -#ifndef _WIN32 - name = strdup(s.c_str()); -#else - name = _strdup(s.c_str()); -#endif - }; - - ProcessedToken(double v) : type(ProcessedTokenType::CONST), value(v){}; - - ProcessedToken(LpComparisonType comp) - : type(ProcessedTokenType::COMP), dir(comp){}; - - ~ProcessedToken() { - if (type == ProcessedTokenType::CONID || type == ProcessedTokenType::VARID) - free(name); - } -}; - -// how many raw tokens to cache -// set to how many tokens we may need to look ahead -#define NRAWTOKEN 3 - -const double kHighsInf = std::numeric_limits::infinity(); -class Reader { - private: -#ifdef ZLIB_FOUND - zstr::ifstream file; -#else - std::ifstream file; -#endif - std::string linebuffer; - std::size_t linebufferpos; - std::array rawtokens; - std::vector processedtokens; - // store for each section a pointer to its begin and end (pointer to element - // after last) - std::map::iterator, - std::vector::iterator> > - sectiontokens; - - Builder builder; - - bool readnexttoken(RawToken&); - void nextrawtoken(size_t howmany = 1); - void processtokens(); - void splittokens(); - void processsections(); - void processnonesec(); - void processobjsec(); - void processconsec(); - void processboundssec(); - void processbinsec(); - void processgensec(); - void processsemisec(); - void processsossec(); - void processendsec(); - void parseexpression(std::vector::iterator& it, - std::vector::iterator end, - std::shared_ptr expr, bool isobj); - - public: - Reader(std::string filename) { -#ifdef ZLIB_FOUND - try { - file.open(filename); - } catch (const strict_fstream::Exception& e) { - } -#else - file.open(filename); -#endif - lpassert(file.is_open()); - }; - - ~Reader() { file.close(); } - - Model read(); -}; - -Model readinstance(std::string filename) { - Reader reader(filename); - return reader.read(); -} - -// convert string to lower-case, modifies string -static inline void tolower(std::string& s) { - std::transform(s.begin(), s.end(), s.begin(), - [](unsigned char c) { return std::tolower(c); }); -} - -static inline bool iskeyword(const std::string& str, - const std::string* keywords, const int nkeywords) { - for (int i = 0; i < nkeywords; i++) { - if (str == keywords[i]) { - return true; - } - } - return false; -} - -static inline LpSectionKeyword parsesectionkeyword(const std::string& str) { - // look up lower case - auto it(sectionkeywordmap.find(str)); - if (it != sectionkeywordmap.end()) return it->second; - - return LpSectionKeyword::NONE; -} - -Model Reader::read() { - // std::clog << "Reading input, tokenizing..." << std::endl; - this->linebufferpos = 0; - // read first NRAWTOKEN token - // if file ends early, then all remaining tokens are set to FLEND - for (size_t i = 0; i < NRAWTOKEN; ++i) - while (!readnexttoken(rawtokens[i])) - ; - - processtokens(); - - linebuffer.clear(); - linebuffer.shrink_to_fit(); - - // std::clog << "Splitting tokens..." << std::endl; - splittokens(); - - // std::clog << "Setting up model..." << std::endl; - // - // Since - // - // "The problem statement must begin with the word MINIMIZE or - // MAXIMIZE, MINIMUM or MAXIMUM, or the abbreviations MIN or MAX, in - // any combination of upper- and lower-case characters. The word - // introduces the objective function section." - // - // Use positivity of sectiontokens.count(LpSectionKeyword::OBJMIN) + - // sectiontokens.count(LpSectionKeyword::OBJMAX) to identify garbage file - // - - const int num_objective_section = - sectiontokens.count(LpSectionKeyword::OBJMIN) + - sectiontokens.count(LpSectionKeyword::OBJMAX); - lpassert(num_objective_section>0); - - processsections(); - processedtokens.clear(); - processedtokens.shrink_to_fit(); - - return builder.model; -} - -void Reader::processnonesec() { - lpassert(sectiontokens.count(LpSectionKeyword::NONE) == 0); -} - -void Reader::parseexpression(std::vector::iterator& it, - std::vector::iterator end, - std::shared_ptr expr, bool isobj) { - if (it != end && it->type == ProcessedTokenType::CONID) { - expr->name = it->name; - ++it; - } - - while (it != end) { - std::vector::iterator next = it; - ++next; - // const var - if (next != end && it->type == ProcessedTokenType::CONST && - next->type == ProcessedTokenType::VARID) { - std::string name = next->name; - - std::shared_ptr linterm = - std::shared_ptr(new LinTerm()); - linterm->coef = it->value; - linterm->var = builder.getvarbyname(name); - // printf("LpReader: Term %+g %s\n", linterm->coef, - //name.c_str()); - expr->linterms.push_back(linterm); - - ++it; - ++it; - continue; - } - - // const - if (it->type == ProcessedTokenType::CONST) { - // printf("LpReader: Offset change from %+g by %+g\n", expr->offset, it->value); - expr->offset += it->value; - ++it; - continue; - } - - // var - if (it->type == ProcessedTokenType::VARID) { - std::string name = it->name; - - std::shared_ptr linterm = - std::shared_ptr(new LinTerm()); - linterm->coef = 1.0; - linterm->var = builder.getvarbyname(name); - // printf("LpReader: Term %+g %s\n", linterm->coef, - //name.c_str()); - expr->linterms.push_back(linterm); - - ++it; - continue; - } - - // quadratic expression - if (next != end && it->type == ProcessedTokenType::BRKOP) { - ++it; - while (it != end && it->type != ProcessedTokenType::BRKCL) { - // const var hat const - std::vector::iterator next1 = it; // token after it - std::vector::iterator next2 = it; // token 2nd-after it - std::vector::iterator next3 = it; // token 3rd-after it - ++next1; - ++next2; - ++next3; - if (next1 != end) { - ++next2; - ++next3; - } - if (next2 != end) ++next3; - - if (next3 != end && it->type == ProcessedTokenType::CONST && - next1->type == ProcessedTokenType::VARID && - next2->type == ProcessedTokenType::HAT && - next3->type == ProcessedTokenType::CONST) { - std::string name = next1->name; - - lpassert(next3->value == 2.0); - - std::shared_ptr quadterm = - std::shared_ptr(new QuadTerm()); - quadterm->coef = it->value; - quadterm->var1 = builder.getvarbyname(name); - quadterm->var2 = builder.getvarbyname(name); - expr->quadterms.push_back(quadterm); - - it = ++next3; - continue; - } - - // var hat const - if (next2 != end && it->type == ProcessedTokenType::VARID && - next1->type == ProcessedTokenType::HAT && - next2->type == ProcessedTokenType::CONST) { - std::string name = it->name; - - lpassert(next2->value == 2.0); - - std::shared_ptr quadterm = - std::shared_ptr(new QuadTerm()); - quadterm->coef = 1.0; - quadterm->var1 = builder.getvarbyname(name); - quadterm->var2 = builder.getvarbyname(name); - expr->quadterms.push_back(quadterm); - - it = next3; - continue; - } - - // const var asterisk var - if (next3 != end && it->type == ProcessedTokenType::CONST && - next1->type == ProcessedTokenType::VARID && - next2->type == ProcessedTokenType::ASTERISK && - next3->type == ProcessedTokenType::VARID) { - std::string name1 = next1->name; - std::string name2 = next3->name; - - std::shared_ptr quadterm = - std::shared_ptr(new QuadTerm()); - quadterm->coef = it->value; - quadterm->var1 = builder.getvarbyname(name1); - quadterm->var2 = builder.getvarbyname(name2); - expr->quadterms.push_back(quadterm); - - it = ++next3; - continue; - } - - // var asterisk var - if (next2 != end && it->type == ProcessedTokenType::VARID && - next1->type == ProcessedTokenType::ASTERISK && - next2->type == ProcessedTokenType::VARID) { - std::string name1 = it->name; - std::string name2 = next2->name; - - std::shared_ptr quadterm = - std::shared_ptr(new QuadTerm()); - quadterm->coef = 1.0; - quadterm->var1 = builder.getvarbyname(name1); - quadterm->var2 = builder.getvarbyname(name2); - expr->quadterms.push_back(quadterm); - - it = next3; - continue; - } - break; - } - if (isobj) { - // only in the objective function, a quadratic term is followed by - // "/2.0" - std::vector::iterator next1 = it; // token after it - std::vector::iterator next2 = it; // token 2nd-after it - ++next1; - ++next2; - if (next1 != end) ++next2; - - lpassert(next2 != end); - lpassert(it->type == ProcessedTokenType::BRKCL); - lpassert(next1->type == ProcessedTokenType::SLASH); - lpassert(next2->type == ProcessedTokenType::CONST); - lpassert(next2->value == 2.0); - it = ++next2; - } else { - lpassert(it != end); - lpassert(it->type == ProcessedTokenType::BRKCL); - ++it; - } - continue; - } - - break; - } -} - -void Reader::processobjsec() { - builder.model.objective = std::shared_ptr(new Expression); - if (sectiontokens.count(LpSectionKeyword::OBJMIN)) { - builder.model.sense = ObjectiveSense::MIN; - parseexpression(sectiontokens[LpSectionKeyword::OBJMIN].first, - sectiontokens[LpSectionKeyword::OBJMIN].second, - builder.model.objective, true); - lpassert(sectiontokens[LpSectionKeyword::OBJMIN].first == - sectiontokens[LpSectionKeyword::OBJMIN] - .second); // all section tokens should have been processed - } else if (sectiontokens.count(LpSectionKeyword::OBJMAX)) { - builder.model.sense = ObjectiveSense::MAX; - parseexpression(sectiontokens[LpSectionKeyword::OBJMAX].first, - sectiontokens[LpSectionKeyword::OBJMAX].second, - builder.model.objective, true); - lpassert(sectiontokens[LpSectionKeyword::OBJMAX].first == - sectiontokens[LpSectionKeyword::OBJMAX] - .second); // all section tokens should have been processed - } -} - -void Reader::processconsec() { - if (!sectiontokens.count(LpSectionKeyword::CON)) return; - std::vector::iterator& begin( - sectiontokens[LpSectionKeyword::CON].first); - std::vector::iterator& end( - sectiontokens[LpSectionKeyword::CON].second); - while (begin != end) { - std::shared_ptr con = - std::shared_ptr(new Constraint); - parseexpression(begin, end, con->expr, false); - // should not be at end of section yet, but a comparison operator should be - // next - lpassert(begin != sectiontokens[LpSectionKeyword::CON].second); - lpassert(begin->type == ProcessedTokenType::COMP); - LpComparisonType dir = begin->dir; - ++begin; - - // should still not be at end of section yet, but a right-hand-side value - // should be next - lpassert(begin != sectiontokens[LpSectionKeyword::CON].second); - lpassert(begin->type == ProcessedTokenType::CONST); - switch (dir) { - case LpComparisonType::EQ: - con->lowerbound = con->upperbound = begin->value; - break; - case LpComparisonType::LEQ: - con->upperbound = begin->value; - break; - case LpComparisonType::GEQ: - con->lowerbound = begin->value; - break; - default: - lpassert(false); - } - builder.model.constraints.push_back(con); - ++begin; - } -} - -void Reader::processboundssec() { - if (!sectiontokens.count(LpSectionKeyword::BOUNDS)) return; - std::vector::iterator& begin( - sectiontokens[LpSectionKeyword::BOUNDS].first); - std::vector::iterator& end( - sectiontokens[LpSectionKeyword::BOUNDS].second); - while (begin != end) { - std::vector::iterator next1 = begin; // token after begin - ++next1; - - // VAR free - if (next1 != end && begin->type == ProcessedTokenType::VARID && - next1->type == ProcessedTokenType::FREE) { - std::string name = begin->name; - std::shared_ptr var = builder.getvarbyname(name); - var->lowerbound = -kHighsInf; - var->upperbound = kHighsInf; - begin = ++next1; - continue; - } - - std::vector::iterator next2 = - next1; // token 2nd-after begin - std::vector::iterator next3 = - next1; // token 3rd-after begin - std::vector::iterator next4 = - next1; // token 4th-after begin - if (next1 != end) { - ++next2; - ++next3; - ++next4; - } - if (next2 != end) { - ++next3; - ++next4; - } - if (next3 != end) ++next4; - - // CONST COMP VAR COMP CONST - if (next4 != end && begin->type == ProcessedTokenType::CONST && - next1->type == ProcessedTokenType::COMP && - next2->type == ProcessedTokenType::VARID && - next3->type == ProcessedTokenType::COMP && - next4->type == ProcessedTokenType::CONST) { - lpassert(next1->dir == LpComparisonType::LEQ); - lpassert(next3->dir == LpComparisonType::LEQ); - - double lb = begin->value; - double ub = next4->value; - - std::string name = next2->name; - std::shared_ptr var = builder.getvarbyname(name); - - var->lowerbound = lb; - var->upperbound = ub; - - begin = ++next4; - continue; - } - - // CONST COMP VAR - if (next2 != end && begin->type == ProcessedTokenType::CONST && - next1->type == ProcessedTokenType::COMP && - next2->type == ProcessedTokenType::VARID) { - double value = begin->value; - std::string name = next2->name; - std::shared_ptr var = builder.getvarbyname(name); - LpComparisonType dir = next1->dir; - - lpassert(dir != LpComparisonType::L && dir != LpComparisonType::G); - - switch (dir) { - case LpComparisonType::LEQ: - var->lowerbound = value; - break; - case LpComparisonType::GEQ: - var->upperbound = value; - break; - case LpComparisonType::EQ: - var->lowerbound = var->upperbound = value; - break; - default: - lpassert(false); - } - begin = next3; - continue; - } - - // VAR COMP CONST - if (next2 != end && begin->type == ProcessedTokenType::VARID && - next1->type == ProcessedTokenType::COMP && - next2->type == ProcessedTokenType::CONST) { - double value = next2->value; - std::string name = begin->name; - std::shared_ptr var = builder.getvarbyname(name); - LpComparisonType dir = next1->dir; - - lpassert(dir != LpComparisonType::L && dir != LpComparisonType::G); - - switch (dir) { - case LpComparisonType::LEQ: - var->upperbound = value; - break; - case LpComparisonType::GEQ: - var->lowerbound = value; - break; - case LpComparisonType::EQ: - var->lowerbound = var->upperbound = value; - break; - default: - lpassert(false); - } - begin = next3; - continue; - } - - lpassert(false); - } -} - -void Reader::processbinsec() { - const LpSectionKeyword this_section_keyword = LpSectionKeyword::BIN; - if (!sectiontokens.count(this_section_keyword)) return; - std::vector::iterator& begin( - sectiontokens[this_section_keyword].first); - std::vector::iterator& end( - sectiontokens[this_section_keyword].second); - for (; begin != end; ++begin) { - if (begin->type == ProcessedTokenType::SECID) { - // Possible to have repeat of keyword for this section type - lpassert(begin->keyword == this_section_keyword); - continue; - } - lpassert(begin->type == ProcessedTokenType::VARID); - std::string name = begin->name; - std::shared_ptr var = builder.getvarbyname(name); - var->type = VariableType::BINARY; - // Respect any bounds already declared - if (var->upperbound == kHighsInf) var->upperbound = 1.0; - } -} - -void Reader::processgensec() { - const LpSectionKeyword this_section_keyword = LpSectionKeyword::GEN; - if (!sectiontokens.count(this_section_keyword)) return; - std::vector::iterator& begin( - sectiontokens[this_section_keyword].first); - std::vector::iterator& end( - sectiontokens[this_section_keyword].second); - for (; begin != end; ++begin) { - if (begin->type == ProcessedTokenType::SECID) { - // Possible to have repeat of keyword for this section type - lpassert(begin->keyword == this_section_keyword); - continue; - } - lpassert(begin->type == ProcessedTokenType::VARID); - std::string name = begin->name; - std::shared_ptr var = builder.getvarbyname(name); - if (var->type == VariableType::SEMICONTINUOUS) { - var->type = VariableType::SEMIINTEGER; - } else { - var->type = VariableType::GENERAL; - } - } -} - -void Reader::processsemisec() { - const LpSectionKeyword this_section_keyword = LpSectionKeyword::SEMI; - if (!sectiontokens.count(this_section_keyword)) return; - std::vector::iterator& begin( - sectiontokens[this_section_keyword].first); - std::vector::iterator& end( - sectiontokens[this_section_keyword].second); - for (; begin != end; ++begin) { - if (begin->type == ProcessedTokenType::SECID) { - // Possible to have repeat of keyword for this section type - lpassert(begin->keyword == this_section_keyword); - continue; - } - lpassert(begin->type == ProcessedTokenType::VARID); - std::string name = begin->name; - std::shared_ptr var = builder.getvarbyname(name); - if (var->type == VariableType::GENERAL) { - var->type = VariableType::SEMIINTEGER; - } else { - var->type = VariableType::SEMICONTINUOUS; - } - } -} - -void Reader::processsossec() { - const LpSectionKeyword this_section_keyword = LpSectionKeyword::SOS; - if (!sectiontokens.count(this_section_keyword)) return; - std::vector::iterator& begin( - sectiontokens[this_section_keyword].first); - std::vector::iterator& end( - sectiontokens[this_section_keyword].second); - while (begin != end) { - std::shared_ptr sos = std::shared_ptr(new SOS); - - // sos1: S1 :: x1 : 1 x2 : 2 x3 : 3 - - // name of SOS is mandatory - lpassert(begin->type == ProcessedTokenType::CONID); - sos->name = begin->name; - ++begin; - - // SOS type - lpassert(begin != end); - lpassert(begin->type == ProcessedTokenType::SOSTYPE); - sos->type = begin->sostype == SosType::SOS1 ? 1 : 2; - ++begin; - - while (begin != end) { - // process all "var : weight" entries - // when processtokens() sees a string followed by a colon, it classifies - // this as a CONID but in a SOS section, this is actually a variable - // identifier - if (begin->type != ProcessedTokenType::CONID) break; - std::string name = begin->name; - std::vector::iterator next = begin; - ++next; - if (next != end && next->type == ProcessedTokenType::CONST) { - auto var = builder.getvarbyname(name); - double weight = next->value; - - sos->entries.push_back({var, weight}); - - begin = ++next; - continue; - } - - break; - } - - builder.model.soss.push_back(sos); - } -} - -void Reader::processendsec() { - lpassert(sectiontokens.count(LpSectionKeyword::END) == 0); -} - -void Reader::processsections() { - processnonesec(); - processobjsec(); - processconsec(); - processboundssec(); - processgensec(); - processbinsec(); - processsemisec(); - processsossec(); - processendsec(); -} - -void Reader::splittokens() { - LpSectionKeyword currentsection = LpSectionKeyword::NONE; - - bool debug_open_section = false; - for (std::vector::iterator it(processedtokens.begin()); - it != processedtokens.end(); ++it) { - // Look for section keywords - if (it->type != ProcessedTokenType::SECID) continue; - // currentsection is initially LpSectionKeyword::NONE, so the - // first section ID will be a new section type - // - // Only record change of section and check for repeated - // section if the keyword is for a different section. Allows - // repetition of Integers and General (cf #1299) for example - const bool new_section_type = currentsection != it->keyword; - if (new_section_type) { - if (currentsection != LpSectionKeyword::NONE) { - // Current section is non-trivial, so mark its end, using the - // value of currentsection to indicate that there is no open - // section - lpassert(debug_open_section); - sectiontokens[currentsection].second = it; - debug_open_section = false; - currentsection = LpSectionKeyword::NONE; - } - } - std::vector::iterator next = it; - ++next; - if (next == processedtokens.end() || - next->type == ProcessedTokenType::SECID) { - // Reached the end of the tokens or the new section is empty - // - // currentsection will be LpSectionKeyword::NONE unless the - // second of two sections of the same type is empty and the - // next section is of a new type, in which case mark the end of - // the current section - if (currentsection != LpSectionKeyword::NONE && - currentsection != next->keyword) { - lpassert(debug_open_section); - sectiontokens[currentsection].second = it; - debug_open_section = false; - } - currentsection = LpSectionKeyword::NONE; - lpassert(!debug_open_section); - continue; - } - // Next section is non-empty - if (new_section_type) { - // Section type change - currentsection = it->keyword; - // Make sure the new section type has not occurred previously - lpassert(sectiontokens.count(currentsection) == 0); - // Remember the beginning of the new section: its the token - // following the current one - lpassert(!debug_open_section); - sectiontokens[currentsection].first = next; - debug_open_section = true; - } - // Always ends with either an open section or a section type of - // LpSectionKeyword::NONE - lpassert(debug_open_section != (currentsection == LpSectionKeyword::NONE)); - } - // Check that the last section has been closed - lpassert(currentsection == LpSectionKeyword::NONE); -} - -void Reader::processtokens() { - std::string svalue_lc; - while (!rawtokens[0].istype(RawTokenType::FLEND)) { - fflush(stdout); - - // Slash + asterisk: comment, skip everything up to next asterisk + slash - if (rawtokens[0].istype(RawTokenType::SLASH) && - rawtokens[1].istype(RawTokenType::ASTERISK)) { - do { - nextrawtoken(2); - } while (!(rawtokens[0].istype(RawTokenType::ASTERISK) && - rawtokens[1].istype(RawTokenType::SLASH)) && - !rawtokens[0].istype(RawTokenType::FLEND)); - nextrawtoken(2); - continue; - } - - if (rawtokens[0].istype(RawTokenType::STR)) { - svalue_lc = rawtokens[0].svalue; - tolower(svalue_lc); - } - - // long section keyword semi-continuous - if (rawtokens[0].istype(RawTokenType::STR) && - rawtokens[1].istype(RawTokenType::MINUS) && - rawtokens[2].istype(RawTokenType::STR)) { - std::string temp = rawtokens[2].svalue; - tolower(temp); - LpSectionKeyword keyword = parsesectionkeyword(svalue_lc + "-" + temp); - if (keyword != LpSectionKeyword::NONE) { - processedtokens.emplace_back(keyword); - nextrawtoken(3); - continue; - } - } - - // long section keyword subject to/such that - if (rawtokens[0].istype(RawTokenType::STR) && - rawtokens[1].istype(RawTokenType::STR)) { - std::string temp = rawtokens[1].svalue; - tolower(temp); - LpSectionKeyword keyword = parsesectionkeyword(svalue_lc + " " + temp); - if (keyword != LpSectionKeyword::NONE) { - processedtokens.emplace_back(keyword); - nextrawtoken(2); - continue; - } - } - - // other section keyword - if (rawtokens[0].istype(RawTokenType::STR)) { - LpSectionKeyword keyword = parsesectionkeyword(svalue_lc); - if (keyword != LpSectionKeyword::NONE) { - processedtokens.emplace_back(keyword); - nextrawtoken(); - continue; - } - } - - // sos type identifier? "S1 ::" or "S2 ::" - if (rawtokens[0].istype(RawTokenType::STR) && - rawtokens[1].istype(RawTokenType::COLON) && - rawtokens[2].istype(RawTokenType::COLON)) { - lpassert(rawtokens[0].svalue.length() == 2); - lpassert(rawtokens[0].svalue[0] == 'S' || rawtokens[0].svalue[0] == 's'); - lpassert(rawtokens[0].svalue[1] == '1' || rawtokens[0].svalue[1] == '2'); - processedtokens.emplace_back( - rawtokens[0].svalue[1] == '1' ? SosType::SOS1 : SosType::SOS2); - nextrawtoken(3); - continue; - } - - // constraint identifier? - if (rawtokens[0].istype(RawTokenType::STR) && - rawtokens[1].istype(RawTokenType::COLON)) { - processedtokens.emplace_back(ProcessedTokenType::CONID, - rawtokens[0].svalue); - nextrawtoken(2); - continue; - } - - // check if free - if (rawtokens[0].istype(RawTokenType::STR) && - iskeyword(svalue_lc, LP_KEYWORD_FREE, LP_KEYWORD_FREE_N)) { - processedtokens.emplace_back(ProcessedTokenType::FREE); - nextrawtoken(); - continue; - } - - // check if infinity - if (rawtokens[0].istype(RawTokenType::STR) && - iskeyword(svalue_lc, LP_KEYWORD_INF, LP_KEYWORD_INF_N)) { - processedtokens.emplace_back(kHighsInf); - nextrawtoken(); - continue; - } - - // assume var identifier - if (rawtokens[0].istype(RawTokenType::STR)) { - processedtokens.emplace_back(ProcessedTokenType::VARID, - rawtokens[0].svalue); - nextrawtoken(); - continue; - } - - // + or - - if (rawtokens[0].istype(RawTokenType::PLUS) || - rawtokens[0].istype(RawTokenType::MINUS)) { - double sign = rawtokens[0].istype(RawTokenType::PLUS) ? 1.0 : -1.0; - nextrawtoken(); - - // another + or - for #948, #950 - if (rawtokens[0].istype(RawTokenType::PLUS) || - rawtokens[0].istype(RawTokenType::MINUS)) { - sign *= rawtokens[0].istype(RawTokenType::PLUS) ? 1.0 : -1.0; - nextrawtoken(); - } - - // +/- Constant - if (rawtokens[0].istype(RawTokenType::CONS)) { - processedtokens.emplace_back(sign * rawtokens[0].dvalue); - nextrawtoken(); - continue; - } - - // + [, + + [, - - [ - if (rawtokens[0].istype(RawTokenType::BRKOP) && sign == 1.0) { - processedtokens.emplace_back(ProcessedTokenType::BRKOP); - nextrawtoken(); - continue; - } - - // - [, + - [, - + [ - if (rawtokens[0].istype(RawTokenType::BRKOP)) lpassert(false); - - // +/- variable name - if (rawtokens[0].istype(RawTokenType::STR)) { - processedtokens.emplace_back(sign); - continue; - } - - // +/- (possibly twice) followed by something that isn't a constant, - // opening bracket, or string (variable name) - if (rawtokens[0].istype(RawTokenType::GREATER)) { - // ">" suggests that the file contains indicator constraints - printf( - "File appears to contain indicator constraints: cannot currently " - "be handled by HiGHS\n"); - } - lpassert(false); - } - - // constant [ - if (rawtokens[0].istype(RawTokenType::CONS) && - rawtokens[1].istype(RawTokenType::BRKOP)) { - lpassert(false); - } - - // constant - if (rawtokens[0].istype(RawTokenType::CONS)) { - processedtokens.emplace_back(rawtokens[0].dvalue); - nextrawtoken(); - continue; - } - - // [ - if (rawtokens[0].istype(RawTokenType::BRKOP)) { - processedtokens.emplace_back(ProcessedTokenType::BRKOP); - nextrawtoken(); - continue; - } - - // ] - if (rawtokens[0].istype(RawTokenType::BRKCL)) { - processedtokens.emplace_back(ProcessedTokenType::BRKCL); - nextrawtoken(); - continue; - } - - // / - if (rawtokens[0].istype(RawTokenType::SLASH)) { - processedtokens.emplace_back(ProcessedTokenType::SLASH); - nextrawtoken(); - continue; - } - - // * - if (rawtokens[0].istype(RawTokenType::ASTERISK)) { - processedtokens.emplace_back(ProcessedTokenType::ASTERISK); - nextrawtoken(); - continue; - } - - // ^ - if (rawtokens[0].istype(RawTokenType::HAT)) { - processedtokens.emplace_back(ProcessedTokenType::HAT); - nextrawtoken(); - continue; - } - - // <= - if (rawtokens[0].istype(RawTokenType::LESS) && - rawtokens[1].istype(RawTokenType::EQUAL)) { - processedtokens.emplace_back(LpComparisonType::LEQ); - nextrawtoken(2); - continue; - } - - // < - if (rawtokens[0].istype(RawTokenType::LESS)) { - processedtokens.emplace_back(LpComparisonType::L); - nextrawtoken(); - continue; - } - - // >= - if (rawtokens[0].istype(RawTokenType::GREATER) && - rawtokens[1].istype(RawTokenType::EQUAL)) { - processedtokens.emplace_back(LpComparisonType::GEQ); - nextrawtoken(2); - continue; - } - - // > - if (rawtokens[0].istype(RawTokenType::GREATER)) { - processedtokens.emplace_back(LpComparisonType::G); - nextrawtoken(); - continue; - } - - // = - if (rawtokens[0].istype(RawTokenType::EQUAL)) { - processedtokens.emplace_back(LpComparisonType::EQ); - nextrawtoken(); - continue; - } - - // FILEEND should have been handled in condition of while() - assert(!rawtokens[0].istype(RawTokenType::FLEND)); - - // catch all unknown symbols - lpassert(false); - break; - } -} - -void Reader::nextrawtoken(size_t howmany) { - assert(howmany > 0); - assert(howmany <= NRAWTOKEN); - static_assert(NRAWTOKEN == 3, - "code below need to be adjusted if NRAWTOKEN changes"); - switch (howmany) { - case 1: { - rawtokens[0] = std::move(rawtokens[1]); - rawtokens[1] = std::move(rawtokens[2]); - while (!readnexttoken(rawtokens[2])) - ; - break; - } - case 2: { - rawtokens[0] = std::move(rawtokens[2]); - while (!readnexttoken(rawtokens[1])) - ; - while (!readnexttoken(rawtokens[2])) - ; - break; - } - case 3: { - while (!readnexttoken(rawtokens[0])) - ; - while (!readnexttoken(rawtokens[1])) - ; - while (!readnexttoken(rawtokens[2])) - ; - break; - } - default: { - size_t i = 0; - // move tokens up - for (; i < NRAWTOKEN - howmany; ++i) - rawtokens[i] = std::move(rawtokens[i + howmany]); - // read new tokens at end positions - for (; i < NRAWTOKEN; ++i) - // call readnexttoken() to overwrite current token - // if it didn't actually read a token (returns false), then call again - while (!readnexttoken(rawtokens[i])) - ; - } - } -} - -// return true, if token has been set; return false if skipped over whitespace -// only -bool Reader::readnexttoken(RawToken& t) { - if (this->linebufferpos == this->linebuffer.size()) { - // read next line if any are left. - if (this->file.eof()) { - t = RawTokenType::FLEND; - return true; - } - std::getline(this->file, linebuffer); - - // drop \r - if (!linebuffer.empty() && linebuffer.back() == '\r') linebuffer.pop_back(); - - // reset linebufferpos - this->linebufferpos = 0; - } - - // check single character tokens - char nextchar = this->linebuffer[this->linebufferpos]; - - switch (nextchar) { - // check for comment - case '\\': - // skip rest of line - this->linebufferpos = this->linebuffer.size(); - return false; - - // check for bracket opening - case '[': - t = RawTokenType::BRKOP; - this->linebufferpos++; - return true; - - // check for bracket closing - case ']': - t = RawTokenType::BRKCL; - this->linebufferpos++; - return true; - - // check for less sign - case '<': - t = RawTokenType::LESS; - this->linebufferpos++; - return true; - - // check for greater sign - case '>': - t = RawTokenType::GREATER; - this->linebufferpos++; - return true; - - // check for equal sign - case '=': - t = RawTokenType::EQUAL; - this->linebufferpos++; - return true; - - // check for colon - case ':': - t = RawTokenType::COLON; - this->linebufferpos++; - return true; - - // check for plus - case '+': - t = RawTokenType::PLUS; - this->linebufferpos++; - return true; - - // check for hat - case '^': - t = RawTokenType::HAT; - this->linebufferpos++; - return true; - - // check for slash - case '/': - t = RawTokenType::SLASH; - this->linebufferpos++; - return true; - - // check for asterisk - case '*': - t = RawTokenType::ASTERISK; - this->linebufferpos++; - return true; - - // check for minus - case '-': - t = RawTokenType::MINUS; - this->linebufferpos++; - return true; - - // check for whitespace - case ' ': - case '\t': - this->linebufferpos++; - return false; - - // check for line end - case ';': - case '\n': // \n should not happen due to using getline() - this->linebufferpos = this->linebuffer.size(); - return false; - - case '\0': // empty line - lpassert(this->linebufferpos == this->linebuffer.size()); - return false; - } - - // check for double value - const char* startptr = this->linebuffer.data() + this->linebufferpos; - char* endptr; - double constant = strtod(startptr, &endptr); - if (endptr != startptr) { - t = constant; - this->linebufferpos += endptr - startptr; - return true; - } - - // assume it's an (section/variable/constraint) identifier - auto endpos = - this->linebuffer.find_first_of("\t\n\\:+<>^= /-*[]", this->linebufferpos); - if (endpos == std::string::npos) - endpos = this->linebuffer.size(); // take complete rest of string - if (endpos > this->linebufferpos) { - t = std::string(this->linebuffer, this->linebufferpos, - endpos - this->linebufferpos); - this->linebufferpos = endpos; - return true; - } - - lpassert(false); - return false; -} +#include "reader.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "HConfig.h" // for ZLIB_FOUND +#include "builder.hpp" +#include "def.hpp" +#ifdef ZLIB_FOUND +#include "../extern/zstr/zstr.hpp" +#endif + +// Cygwin doesn't come with an implementation for strdup if compiled with +// std=cxx +#ifdef __CYGWIN__ +#include +char* strdup(const char* s) { + size_t slen = strlen(s); + char* result = (char*)malloc(slen + 1); + if (result == NULL) { + return NULL; + } + + memcpy(result, s, slen + 1); + return result; +} +#endif + +enum class RawTokenType { + NONE, + STR, + CONS, + LESS, + GREATER, + EQUAL, + COLON, + LNEND, + FLEND, + BRKOP, + BRKCL, + PLUS, + MINUS, + HAT, + SLASH, + ASTERISK +}; + +struct RawToken { + RawTokenType type = RawTokenType::NONE; + std::string svalue; + double dvalue = 0.0; + + inline bool istype(RawTokenType t) const { return this->type == t; } + + RawToken& operator=(RawTokenType t) { + type = t; + return *this; + } + RawToken& operator=(const std::string& v) { + svalue = v; + type = RawTokenType::STR; + return *this; + } + RawToken& operator=(const double v) { + dvalue = v; + type = RawTokenType::CONS; + return *this; + } +}; + +enum class ProcessedTokenType { + NONE, + SECID, + VARID, + CONID, + CONST, + FREE, + BRKOP, + BRKCL, + COMP, + LNEND, + SLASH, + ASTERISK, + HAT, + SOSTYPE +}; + +enum class LpSectionKeyword { + NONE, + OBJMIN, + OBJMAX, + CON, + BOUNDS, + GEN, + BIN, + SEMI, + SOS, + END +}; + +static const std::unordered_map + sectionkeywordmap{{"minimize", LpSectionKeyword::OBJMIN}, + {"min", LpSectionKeyword::OBJMIN}, + {"minimum", LpSectionKeyword::OBJMIN}, + {"maximize", LpSectionKeyword::OBJMAX}, + {"max", LpSectionKeyword::OBJMAX}, + {"maximum", LpSectionKeyword::OBJMAX}, + {"subject to", LpSectionKeyword::CON}, + {"such that", LpSectionKeyword::CON}, + {"st", LpSectionKeyword::CON}, + {"s.t.", LpSectionKeyword::CON}, + {"bounds", LpSectionKeyword::BOUNDS}, + {"bound", LpSectionKeyword::BOUNDS}, + {"binary", LpSectionKeyword::BIN}, + {"binaries", LpSectionKeyword::BIN}, + {"bin", LpSectionKeyword::BIN}, + {"general", LpSectionKeyword::GEN}, + {"generals", LpSectionKeyword::GEN}, + {"gen", LpSectionKeyword::GEN}, + {"integer", LpSectionKeyword::GEN}, + {"integers", LpSectionKeyword::GEN}, + {"semi-continuous", LpSectionKeyword::SEMI}, + {"semi", LpSectionKeyword::SEMI}, + {"semis", LpSectionKeyword::SEMI}, + {"sos", LpSectionKeyword::SOS}, + {"end", LpSectionKeyword::END}}; + +enum class SosType { SOS1, SOS2 }; + +enum class LpComparisonType { LEQ, L, EQ, G, GEQ }; + +struct ProcessedToken { + ProcessedTokenType type; + union { + LpSectionKeyword keyword; + SosType sostype; + char* name; + double value; + LpComparisonType dir; + }; + + ProcessedToken(const ProcessedToken&) = delete; + ProcessedToken(ProcessedToken&& t) : type(t.type) { + switch (type) { + case ProcessedTokenType::SECID: + keyword = t.keyword; + break; + case ProcessedTokenType::SOSTYPE: + sostype = t.sostype; + break; + case ProcessedTokenType::CONID: + case ProcessedTokenType::VARID: + name = t.name; + break; + case ProcessedTokenType::CONST: + value = t.value; + break; + case ProcessedTokenType::COMP: + dir = t.dir; + break; + default:; + } + t.type = ProcessedTokenType::NONE; + } + + ProcessedToken(ProcessedTokenType t) : type(t){}; + + ProcessedToken(LpSectionKeyword kw) + : type(ProcessedTokenType::SECID), keyword(kw){}; + + ProcessedToken(SosType sos) + : type(ProcessedTokenType::SOSTYPE), sostype(sos){}; + + ProcessedToken(ProcessedTokenType t, const std::string& s) : type(t) { + assert(t == ProcessedTokenType::CONID || t == ProcessedTokenType::VARID); +#ifndef _WIN32 + name = strdup(s.c_str()); +#else + name = _strdup(s.c_str()); +#endif + }; + + ProcessedToken(double v) : type(ProcessedTokenType::CONST), value(v){}; + + ProcessedToken(LpComparisonType comp) + : type(ProcessedTokenType::COMP), dir(comp){}; + + ~ProcessedToken() { + if (type == ProcessedTokenType::CONID || type == ProcessedTokenType::VARID) + free(name); + } +}; + +// how many raw tokens to cache +// set to how many tokens we may need to look ahead +#define NRAWTOKEN 3 + +const double kHighsInf = std::numeric_limits::infinity(); +class Reader { + private: +#ifdef ZLIB_FOUND + zstr::ifstream file; +#else + std::ifstream file; +#endif + std::string linebuffer; + std::size_t linebufferpos; + std::array rawtokens; + std::vector processedtokens; + // store for each section a pointer to its begin and end (pointer to element + // after last) + std::map::iterator, + std::vector::iterator> > + sectiontokens; + + Builder builder; + + bool readnexttoken(RawToken&); + void nextrawtoken(size_t howmany = 1); + void processtokens(); + void splittokens(); + void processsections(); + void processnonesec(); + void processobjsec(); + void processconsec(); + void processboundssec(); + void processbinsec(); + void processgensec(); + void processsemisec(); + void processsossec(); + void processendsec(); + void parseexpression(std::vector::iterator& it, + std::vector::iterator end, + std::shared_ptr expr, bool isobj); + + public: + Reader(std::string filename) { +#ifdef ZLIB_FOUND + try { + file.open(filename); + } catch (const strict_fstream::Exception& e) { + } +#else + file.open(filename); +#endif + lpassert(file.is_open()); + }; + + ~Reader() { file.close(); } + + Model read(); +}; + +Model readinstance(std::string filename) { + Reader reader(filename); + return reader.read(); +} + +// convert string to lower-case, modifies string +static inline void tolower(std::string& s) { + std::transform(s.begin(), s.end(), s.begin(), + [](unsigned char c) { return std::tolower(c); }); +} + +static inline bool iskeyword(const std::string& str, + const std::string* keywords, const int nkeywords) { + for (int i = 0; i < nkeywords; i++) { + if (str == keywords[i]) { + return true; + } + } + return false; +} + +static inline LpSectionKeyword parsesectionkeyword(const std::string& str) { + // look up lower case + auto it(sectionkeywordmap.find(str)); + if (it != sectionkeywordmap.end()) return it->second; + + return LpSectionKeyword::NONE; +} + +Model Reader::read() { + // std::clog << "Reading input, tokenizing..." << std::endl; + this->linebufferpos = 0; + // read first NRAWTOKEN token + // if file ends early, then all remaining tokens are set to FLEND + for (size_t i = 0; i < NRAWTOKEN; ++i) + while (!readnexttoken(rawtokens[i])) + ; + + processtokens(); + + linebuffer.clear(); + linebuffer.shrink_to_fit(); + + // std::clog << "Splitting tokens..." << std::endl; + splittokens(); + + // std::clog << "Setting up model..." << std::endl; + // + // Since + // + // "The problem statement must begin with the word MINIMIZE or + // MAXIMIZE, MINIMUM or MAXIMUM, or the abbreviations MIN or MAX, in + // any combination of upper- and lower-case characters. The word + // introduces the objective function section." + // + // Use positivity of sectiontokens.count(LpSectionKeyword::OBJMIN) + + // sectiontokens.count(LpSectionKeyword::OBJMAX) to identify garbage file + // + + const int num_objective_section = + sectiontokens.count(LpSectionKeyword::OBJMIN) + + sectiontokens.count(LpSectionKeyword::OBJMAX); + lpassert(num_objective_section>0); + + processsections(); + processedtokens.clear(); + processedtokens.shrink_to_fit(); + + return builder.model; +} + +void Reader::processnonesec() { + lpassert(sectiontokens.count(LpSectionKeyword::NONE) == 0); +} + +void Reader::parseexpression(std::vector::iterator& it, + std::vector::iterator end, + std::shared_ptr expr, bool isobj) { + if (it != end && it->type == ProcessedTokenType::CONID) { + expr->name = it->name; + ++it; + } + + while (it != end) { + std::vector::iterator next = it; + ++next; + // const var + if (next != end && it->type == ProcessedTokenType::CONST && + next->type == ProcessedTokenType::VARID) { + std::string name = next->name; + + std::shared_ptr linterm = + std::shared_ptr(new LinTerm()); + linterm->coef = it->value; + linterm->var = builder.getvarbyname(name); + // printf("LpReader: Term %+g %s\n", linterm->coef, + //name.c_str()); + expr->linterms.push_back(linterm); + + ++it; + ++it; + continue; + } + + // const + if (it->type == ProcessedTokenType::CONST) { + // printf("LpReader: Offset change from %+g by %+g\n", expr->offset, it->value); + expr->offset += it->value; + ++it; + continue; + } + + // var + if (it->type == ProcessedTokenType::VARID) { + std::string name = it->name; + + std::shared_ptr linterm = + std::shared_ptr(new LinTerm()); + linterm->coef = 1.0; + linterm->var = builder.getvarbyname(name); + // printf("LpReader: Term %+g %s\n", linterm->coef, + //name.c_str()); + expr->linterms.push_back(linterm); + + ++it; + continue; + } + + // quadratic expression + if (next != end && it->type == ProcessedTokenType::BRKOP) { + ++it; + while (it != end && it->type != ProcessedTokenType::BRKCL) { + // const var hat const + std::vector::iterator next1 = it; // token after it + std::vector::iterator next2 = it; // token 2nd-after it + std::vector::iterator next3 = it; // token 3rd-after it + ++next1; + ++next2; + ++next3; + if (next1 != end) { + ++next2; + ++next3; + } + if (next2 != end) ++next3; + + if (next3 != end && it->type == ProcessedTokenType::CONST && + next1->type == ProcessedTokenType::VARID && + next2->type == ProcessedTokenType::HAT && + next3->type == ProcessedTokenType::CONST) { + std::string name = next1->name; + + lpassert(next3->value == 2.0); + + std::shared_ptr quadterm = + std::shared_ptr(new QuadTerm()); + quadterm->coef = it->value; + quadterm->var1 = builder.getvarbyname(name); + quadterm->var2 = builder.getvarbyname(name); + expr->quadterms.push_back(quadterm); + + it = ++next3; + continue; + } + + // var hat const + if (next2 != end && it->type == ProcessedTokenType::VARID && + next1->type == ProcessedTokenType::HAT && + next2->type == ProcessedTokenType::CONST) { + std::string name = it->name; + + lpassert(next2->value == 2.0); + + std::shared_ptr quadterm = + std::shared_ptr(new QuadTerm()); + quadterm->coef = 1.0; + quadterm->var1 = builder.getvarbyname(name); + quadterm->var2 = builder.getvarbyname(name); + expr->quadterms.push_back(quadterm); + + it = next3; + continue; + } + + // const var asterisk var + if (next3 != end && it->type == ProcessedTokenType::CONST && + next1->type == ProcessedTokenType::VARID && + next2->type == ProcessedTokenType::ASTERISK && + next3->type == ProcessedTokenType::VARID) { + std::string name1 = next1->name; + std::string name2 = next3->name; + + std::shared_ptr quadterm = + std::shared_ptr(new QuadTerm()); + quadterm->coef = it->value; + quadterm->var1 = builder.getvarbyname(name1); + quadterm->var2 = builder.getvarbyname(name2); + expr->quadterms.push_back(quadterm); + + it = ++next3; + continue; + } + + // var asterisk var + if (next2 != end && it->type == ProcessedTokenType::VARID && + next1->type == ProcessedTokenType::ASTERISK && + next2->type == ProcessedTokenType::VARID) { + std::string name1 = it->name; + std::string name2 = next2->name; + + std::shared_ptr quadterm = + std::shared_ptr(new QuadTerm()); + quadterm->coef = 1.0; + quadterm->var1 = builder.getvarbyname(name1); + quadterm->var2 = builder.getvarbyname(name2); + expr->quadterms.push_back(quadterm); + + it = next3; + continue; + } + break; + } + if (isobj) { + // only in the objective function, a quadratic term is followed by + // "/2.0" + std::vector::iterator next1 = it; // token after it + std::vector::iterator next2 = it; // token 2nd-after it + ++next1; + ++next2; + if (next1 != end) ++next2; + + lpassert(next2 != end); + lpassert(it->type == ProcessedTokenType::BRKCL); + lpassert(next1->type == ProcessedTokenType::SLASH); + lpassert(next2->type == ProcessedTokenType::CONST); + lpassert(next2->value == 2.0); + it = ++next2; + } else { + lpassert(it != end); + lpassert(it->type == ProcessedTokenType::BRKCL); + ++it; + } + continue; + } + + break; + } +} + +void Reader::processobjsec() { + builder.model.objective = std::shared_ptr(new Expression); + if (sectiontokens.count(LpSectionKeyword::OBJMIN)) { + builder.model.sense = ObjectiveSense::MIN; + parseexpression(sectiontokens[LpSectionKeyword::OBJMIN].first, + sectiontokens[LpSectionKeyword::OBJMIN].second, + builder.model.objective, true); + lpassert(sectiontokens[LpSectionKeyword::OBJMIN].first == + sectiontokens[LpSectionKeyword::OBJMIN] + .second); // all section tokens should have been processed + } else if (sectiontokens.count(LpSectionKeyword::OBJMAX)) { + builder.model.sense = ObjectiveSense::MAX; + parseexpression(sectiontokens[LpSectionKeyword::OBJMAX].first, + sectiontokens[LpSectionKeyword::OBJMAX].second, + builder.model.objective, true); + lpassert(sectiontokens[LpSectionKeyword::OBJMAX].first == + sectiontokens[LpSectionKeyword::OBJMAX] + .second); // all section tokens should have been processed + } +} + +void Reader::processconsec() { + if (!sectiontokens.count(LpSectionKeyword::CON)) return; + std::vector::iterator& begin( + sectiontokens[LpSectionKeyword::CON].first); + std::vector::iterator& end( + sectiontokens[LpSectionKeyword::CON].second); + while (begin != end) { + std::shared_ptr con = + std::shared_ptr(new Constraint); + parseexpression(begin, end, con->expr, false); + // should not be at end of section yet, but a comparison operator should be + // next + lpassert(begin != sectiontokens[LpSectionKeyword::CON].second); + lpassert(begin->type == ProcessedTokenType::COMP); + LpComparisonType dir = begin->dir; + ++begin; + + // should still not be at end of section yet, but a right-hand-side value + // should be next + lpassert(begin != sectiontokens[LpSectionKeyword::CON].second); + lpassert(begin->type == ProcessedTokenType::CONST); + switch (dir) { + case LpComparisonType::EQ: + con->lowerbound = con->upperbound = begin->value; + break; + case LpComparisonType::LEQ: + con->upperbound = begin->value; + break; + case LpComparisonType::GEQ: + con->lowerbound = begin->value; + break; + default: + lpassert(false); + } + builder.model.constraints.push_back(con); + ++begin; + } +} + +void Reader::processboundssec() { + if (!sectiontokens.count(LpSectionKeyword::BOUNDS)) return; + std::vector::iterator& begin( + sectiontokens[LpSectionKeyword::BOUNDS].first); + std::vector::iterator& end( + sectiontokens[LpSectionKeyword::BOUNDS].second); + while (begin != end) { + std::vector::iterator next1 = begin; // token after begin + ++next1; + + // VAR free + if (next1 != end && begin->type == ProcessedTokenType::VARID && + next1->type == ProcessedTokenType::FREE) { + std::string name = begin->name; + std::shared_ptr var = builder.getvarbyname(name); + var->lowerbound = -kHighsInf; + var->upperbound = kHighsInf; + begin = ++next1; + continue; + } + + std::vector::iterator next2 = + next1; // token 2nd-after begin + std::vector::iterator next3 = + next1; // token 3rd-after begin + std::vector::iterator next4 = + next1; // token 4th-after begin + if (next1 != end) { + ++next2; + ++next3; + ++next4; + } + if (next2 != end) { + ++next3; + ++next4; + } + if (next3 != end) ++next4; + + // CONST COMP VAR COMP CONST + if (next4 != end && begin->type == ProcessedTokenType::CONST && + next1->type == ProcessedTokenType::COMP && + next2->type == ProcessedTokenType::VARID && + next3->type == ProcessedTokenType::COMP && + next4->type == ProcessedTokenType::CONST) { + lpassert(next1->dir == LpComparisonType::LEQ); + lpassert(next3->dir == LpComparisonType::LEQ); + + double lb = begin->value; + double ub = next4->value; + + std::string name = next2->name; + std::shared_ptr var = builder.getvarbyname(name); + + var->lowerbound = lb; + var->upperbound = ub; + + begin = ++next4; + continue; + } + + // CONST COMP VAR + if (next2 != end && begin->type == ProcessedTokenType::CONST && + next1->type == ProcessedTokenType::COMP && + next2->type == ProcessedTokenType::VARID) { + double value = begin->value; + std::string name = next2->name; + std::shared_ptr var = builder.getvarbyname(name); + LpComparisonType dir = next1->dir; + + lpassert(dir != LpComparisonType::L && dir != LpComparisonType::G); + + switch (dir) { + case LpComparisonType::LEQ: + var->lowerbound = value; + break; + case LpComparisonType::GEQ: + var->upperbound = value; + break; + case LpComparisonType::EQ: + var->lowerbound = var->upperbound = value; + break; + default: + lpassert(false); + } + begin = next3; + continue; + } + + // VAR COMP CONST + if (next2 != end && begin->type == ProcessedTokenType::VARID && + next1->type == ProcessedTokenType::COMP && + next2->type == ProcessedTokenType::CONST) { + double value = next2->value; + std::string name = begin->name; + std::shared_ptr var = builder.getvarbyname(name); + LpComparisonType dir = next1->dir; + + lpassert(dir != LpComparisonType::L && dir != LpComparisonType::G); + + switch (dir) { + case LpComparisonType::LEQ: + var->upperbound = value; + break; + case LpComparisonType::GEQ: + var->lowerbound = value; + break; + case LpComparisonType::EQ: + var->lowerbound = var->upperbound = value; + break; + default: + lpassert(false); + } + begin = next3; + continue; + } + + lpassert(false); + } +} + +void Reader::processbinsec() { + const LpSectionKeyword this_section_keyword = LpSectionKeyword::BIN; + if (!sectiontokens.count(this_section_keyword)) return; + std::vector::iterator& begin( + sectiontokens[this_section_keyword].first); + std::vector::iterator& end( + sectiontokens[this_section_keyword].second); + for (; begin != end; ++begin) { + if (begin->type == ProcessedTokenType::SECID) { + // Possible to have repeat of keyword for this section type + lpassert(begin->keyword == this_section_keyword); + continue; + } + lpassert(begin->type == ProcessedTokenType::VARID); + std::string name = begin->name; + std::shared_ptr var = builder.getvarbyname(name); + var->type = VariableType::BINARY; + // Respect any bounds already declared + if (var->upperbound == kHighsInf) var->upperbound = 1.0; + } +} + +void Reader::processgensec() { + const LpSectionKeyword this_section_keyword = LpSectionKeyword::GEN; + if (!sectiontokens.count(this_section_keyword)) return; + std::vector::iterator& begin( + sectiontokens[this_section_keyword].first); + std::vector::iterator& end( + sectiontokens[this_section_keyword].second); + for (; begin != end; ++begin) { + if (begin->type == ProcessedTokenType::SECID) { + // Possible to have repeat of keyword for this section type + lpassert(begin->keyword == this_section_keyword); + continue; + } + lpassert(begin->type == ProcessedTokenType::VARID); + std::string name = begin->name; + std::shared_ptr var = builder.getvarbyname(name); + if (var->type == VariableType::SEMICONTINUOUS) { + var->type = VariableType::SEMIINTEGER; + } else { + var->type = VariableType::GENERAL; + } + } +} + +void Reader::processsemisec() { + const LpSectionKeyword this_section_keyword = LpSectionKeyword::SEMI; + if (!sectiontokens.count(this_section_keyword)) return; + std::vector::iterator& begin( + sectiontokens[this_section_keyword].first); + std::vector::iterator& end( + sectiontokens[this_section_keyword].second); + for (; begin != end; ++begin) { + if (begin->type == ProcessedTokenType::SECID) { + // Possible to have repeat of keyword for this section type + lpassert(begin->keyword == this_section_keyword); + continue; + } + lpassert(begin->type == ProcessedTokenType::VARID); + std::string name = begin->name; + std::shared_ptr var = builder.getvarbyname(name); + if (var->type == VariableType::GENERAL) { + var->type = VariableType::SEMIINTEGER; + } else { + var->type = VariableType::SEMICONTINUOUS; + } + } +} + +void Reader::processsossec() { + const LpSectionKeyword this_section_keyword = LpSectionKeyword::SOS; + if (!sectiontokens.count(this_section_keyword)) return; + std::vector::iterator& begin( + sectiontokens[this_section_keyword].first); + std::vector::iterator& end( + sectiontokens[this_section_keyword].second); + while (begin != end) { + std::shared_ptr sos = std::shared_ptr(new SOS); + + // sos1: S1 :: x1 : 1 x2 : 2 x3 : 3 + + // name of SOS is mandatory + lpassert(begin->type == ProcessedTokenType::CONID); + sos->name = begin->name; + ++begin; + + // SOS type + lpassert(begin != end); + lpassert(begin->type == ProcessedTokenType::SOSTYPE); + sos->type = begin->sostype == SosType::SOS1 ? 1 : 2; + ++begin; + + while (begin != end) { + // process all "var : weight" entries + // when processtokens() sees a string followed by a colon, it classifies + // this as a CONID but in a SOS section, this is actually a variable + // identifier + if (begin->type != ProcessedTokenType::CONID) break; + std::string name = begin->name; + std::vector::iterator next = begin; + ++next; + if (next != end && next->type == ProcessedTokenType::CONST) { + auto var = builder.getvarbyname(name); + double weight = next->value; + + sos->entries.push_back({var, weight}); + + begin = ++next; + continue; + } + + break; + } + + builder.model.soss.push_back(sos); + } +} + +void Reader::processendsec() { + lpassert(sectiontokens.count(LpSectionKeyword::END) == 0); +} + +void Reader::processsections() { + processnonesec(); + processobjsec(); + processconsec(); + processboundssec(); + processgensec(); + processbinsec(); + processsemisec(); + processsossec(); + processendsec(); +} + +void Reader::splittokens() { + LpSectionKeyword currentsection = LpSectionKeyword::NONE; + + bool debug_open_section = false; + for (std::vector::iterator it(processedtokens.begin()); + it != processedtokens.end(); ++it) { + // Look for section keywords + if (it->type != ProcessedTokenType::SECID) continue; + // currentsection is initially LpSectionKeyword::NONE, so the + // first section ID will be a new section type + // + // Only record change of section and check for repeated + // section if the keyword is for a different section. Allows + // repetition of Integers and General (cf #1299) for example + const bool new_section_type = currentsection != it->keyword; + if (new_section_type) { + if (currentsection != LpSectionKeyword::NONE) { + // Current section is non-trivial, so mark its end, using the + // value of currentsection to indicate that there is no open + // section + lpassert(debug_open_section); + sectiontokens[currentsection].second = it; + debug_open_section = false; + currentsection = LpSectionKeyword::NONE; + } + } + std::vector::iterator next = it; + ++next; + if (next == processedtokens.end() || + next->type == ProcessedTokenType::SECID) { + // Reached the end of the tokens or the new section is empty + // + // currentsection will be LpSectionKeyword::NONE unless the + // second of two sections of the same type is empty and the + // next section is of a new type, in which case mark the end of + // the current section + if (currentsection != LpSectionKeyword::NONE && + currentsection != next->keyword) { + lpassert(debug_open_section); + sectiontokens[currentsection].second = it; + debug_open_section = false; + } + currentsection = LpSectionKeyword::NONE; + lpassert(!debug_open_section); + continue; + } + // Next section is non-empty + if (new_section_type) { + // Section type change + currentsection = it->keyword; + // Make sure the new section type has not occurred previously + lpassert(sectiontokens.count(currentsection) == 0); + // Remember the beginning of the new section: its the token + // following the current one + lpassert(!debug_open_section); + sectiontokens[currentsection].first = next; + debug_open_section = true; + } + // Always ends with either an open section or a section type of + // LpSectionKeyword::NONE + lpassert(debug_open_section != (currentsection == LpSectionKeyword::NONE)); + } + // Check that the last section has been closed + lpassert(currentsection == LpSectionKeyword::NONE); +} + +void Reader::processtokens() { + std::string svalue_lc; + while (!rawtokens[0].istype(RawTokenType::FLEND)) { + fflush(stdout); + + // Slash + asterisk: comment, skip everything up to next asterisk + slash + if (rawtokens[0].istype(RawTokenType::SLASH) && + rawtokens[1].istype(RawTokenType::ASTERISK)) { + do { + nextrawtoken(2); + } while (!(rawtokens[0].istype(RawTokenType::ASTERISK) && + rawtokens[1].istype(RawTokenType::SLASH)) && + !rawtokens[0].istype(RawTokenType::FLEND)); + nextrawtoken(2); + continue; + } + + if (rawtokens[0].istype(RawTokenType::STR)) { + svalue_lc = rawtokens[0].svalue; + tolower(svalue_lc); + } + + // long section keyword semi-continuous + if (rawtokens[0].istype(RawTokenType::STR) && + rawtokens[1].istype(RawTokenType::MINUS) && + rawtokens[2].istype(RawTokenType::STR)) { + std::string temp = rawtokens[2].svalue; + tolower(temp); + LpSectionKeyword keyword = parsesectionkeyword(svalue_lc + "-" + temp); + if (keyword != LpSectionKeyword::NONE) { + processedtokens.emplace_back(keyword); + nextrawtoken(3); + continue; + } + } + + // long section keyword subject to/such that + if (rawtokens[0].istype(RawTokenType::STR) && + rawtokens[1].istype(RawTokenType::STR)) { + std::string temp = rawtokens[1].svalue; + tolower(temp); + LpSectionKeyword keyword = parsesectionkeyword(svalue_lc + " " + temp); + if (keyword != LpSectionKeyword::NONE) { + processedtokens.emplace_back(keyword); + nextrawtoken(2); + continue; + } + } + + // other section keyword + if (rawtokens[0].istype(RawTokenType::STR)) { + LpSectionKeyword keyword = parsesectionkeyword(svalue_lc); + if (keyword != LpSectionKeyword::NONE) { + processedtokens.emplace_back(keyword); + nextrawtoken(); + continue; + } + } + + // sos type identifier? "S1 ::" or "S2 ::" + if (rawtokens[0].istype(RawTokenType::STR) && + rawtokens[1].istype(RawTokenType::COLON) && + rawtokens[2].istype(RawTokenType::COLON)) { + lpassert(rawtokens[0].svalue.length() == 2); + lpassert(rawtokens[0].svalue[0] == 'S' || rawtokens[0].svalue[0] == 's'); + lpassert(rawtokens[0].svalue[1] == '1' || rawtokens[0].svalue[1] == '2'); + processedtokens.emplace_back( + rawtokens[0].svalue[1] == '1' ? SosType::SOS1 : SosType::SOS2); + nextrawtoken(3); + continue; + } + + // constraint identifier? + if (rawtokens[0].istype(RawTokenType::STR) && + rawtokens[1].istype(RawTokenType::COLON)) { + processedtokens.emplace_back(ProcessedTokenType::CONID, + rawtokens[0].svalue); + nextrawtoken(2); + continue; + } + + // check if free + if (rawtokens[0].istype(RawTokenType::STR) && + iskeyword(svalue_lc, LP_KEYWORD_FREE, LP_KEYWORD_FREE_N)) { + processedtokens.emplace_back(ProcessedTokenType::FREE); + nextrawtoken(); + continue; + } + + // check if infinity + if (rawtokens[0].istype(RawTokenType::STR) && + iskeyword(svalue_lc, LP_KEYWORD_INF, LP_KEYWORD_INF_N)) { + processedtokens.emplace_back(kHighsInf); + nextrawtoken(); + continue; + } + + // assume var identifier + if (rawtokens[0].istype(RawTokenType::STR)) { + processedtokens.emplace_back(ProcessedTokenType::VARID, + rawtokens[0].svalue); + nextrawtoken(); + continue; + } + + // + or - + if (rawtokens[0].istype(RawTokenType::PLUS) || + rawtokens[0].istype(RawTokenType::MINUS)) { + double sign = rawtokens[0].istype(RawTokenType::PLUS) ? 1.0 : -1.0; + nextrawtoken(); + + // another + or - for #948, #950 + if (rawtokens[0].istype(RawTokenType::PLUS) || + rawtokens[0].istype(RawTokenType::MINUS)) { + sign *= rawtokens[0].istype(RawTokenType::PLUS) ? 1.0 : -1.0; + nextrawtoken(); + } + + // +/- Constant + if (rawtokens[0].istype(RawTokenType::CONS)) { + processedtokens.emplace_back(sign * rawtokens[0].dvalue); + nextrawtoken(); + continue; + } + + // + [, + + [, - - [ + if (rawtokens[0].istype(RawTokenType::BRKOP) && sign == 1.0) { + processedtokens.emplace_back(ProcessedTokenType::BRKOP); + nextrawtoken(); + continue; + } + + // - [, + - [, - + [ + if (rawtokens[0].istype(RawTokenType::BRKOP)) lpassert(false); + + // +/- variable name + if (rawtokens[0].istype(RawTokenType::STR)) { + processedtokens.emplace_back(sign); + continue; + } + + // +/- (possibly twice) followed by something that isn't a constant, + // opening bracket, or string (variable name) + if (rawtokens[0].istype(RawTokenType::GREATER)) { + // ">" suggests that the file contains indicator constraints + printf( + "File appears to contain indicator constraints: cannot currently " + "be handled by HiGHS\n"); + } + lpassert(false); + } + + // constant [ + if (rawtokens[0].istype(RawTokenType::CONS) && + rawtokens[1].istype(RawTokenType::BRKOP)) { + lpassert(false); + } + + // constant + if (rawtokens[0].istype(RawTokenType::CONS)) { + processedtokens.emplace_back(rawtokens[0].dvalue); + nextrawtoken(); + continue; + } + + // [ + if (rawtokens[0].istype(RawTokenType::BRKOP)) { + processedtokens.emplace_back(ProcessedTokenType::BRKOP); + nextrawtoken(); + continue; + } + + // ] + if (rawtokens[0].istype(RawTokenType::BRKCL)) { + processedtokens.emplace_back(ProcessedTokenType::BRKCL); + nextrawtoken(); + continue; + } + + // / + if (rawtokens[0].istype(RawTokenType::SLASH)) { + processedtokens.emplace_back(ProcessedTokenType::SLASH); + nextrawtoken(); + continue; + } + + // * + if (rawtokens[0].istype(RawTokenType::ASTERISK)) { + processedtokens.emplace_back(ProcessedTokenType::ASTERISK); + nextrawtoken(); + continue; + } + + // ^ + if (rawtokens[0].istype(RawTokenType::HAT)) { + processedtokens.emplace_back(ProcessedTokenType::HAT); + nextrawtoken(); + continue; + } + + // <= + if (rawtokens[0].istype(RawTokenType::LESS) && + rawtokens[1].istype(RawTokenType::EQUAL)) { + processedtokens.emplace_back(LpComparisonType::LEQ); + nextrawtoken(2); + continue; + } + + // < + if (rawtokens[0].istype(RawTokenType::LESS)) { + processedtokens.emplace_back(LpComparisonType::L); + nextrawtoken(); + continue; + } + + // >= + if (rawtokens[0].istype(RawTokenType::GREATER) && + rawtokens[1].istype(RawTokenType::EQUAL)) { + processedtokens.emplace_back(LpComparisonType::GEQ); + nextrawtoken(2); + continue; + } + + // > + if (rawtokens[0].istype(RawTokenType::GREATER)) { + processedtokens.emplace_back(LpComparisonType::G); + nextrawtoken(); + continue; + } + + // = + if (rawtokens[0].istype(RawTokenType::EQUAL)) { + processedtokens.emplace_back(LpComparisonType::EQ); + nextrawtoken(); + continue; + } + + // FILEEND should have been handled in condition of while() + assert(!rawtokens[0].istype(RawTokenType::FLEND)); + + // catch all unknown symbols + lpassert(false); + break; + } +} + +void Reader::nextrawtoken(size_t howmany) { + assert(howmany > 0); + assert(howmany <= NRAWTOKEN); + static_assert(NRAWTOKEN == 3, + "code below need to be adjusted if NRAWTOKEN changes"); + switch (howmany) { + case 1: { + rawtokens[0] = std::move(rawtokens[1]); + rawtokens[1] = std::move(rawtokens[2]); + while (!readnexttoken(rawtokens[2])) + ; + break; + } + case 2: { + rawtokens[0] = std::move(rawtokens[2]); + while (!readnexttoken(rawtokens[1])) + ; + while (!readnexttoken(rawtokens[2])) + ; + break; + } + case 3: { + while (!readnexttoken(rawtokens[0])) + ; + while (!readnexttoken(rawtokens[1])) + ; + while (!readnexttoken(rawtokens[2])) + ; + break; + } + default: { + size_t i = 0; + // move tokens up + for (; i < NRAWTOKEN - howmany; ++i) + rawtokens[i] = std::move(rawtokens[i + howmany]); + // read new tokens at end positions + for (; i < NRAWTOKEN; ++i) + // call readnexttoken() to overwrite current token + // if it didn't actually read a token (returns false), then call again + while (!readnexttoken(rawtokens[i])) + ; + } + } +} + +// return true, if token has been set; return false if skipped over whitespace +// only +bool Reader::readnexttoken(RawToken& t) { + if (this->linebufferpos == this->linebuffer.size()) { + // read next line if any are left. + if (this->file.eof()) { + t = RawTokenType::FLEND; + return true; + } + std::getline(this->file, linebuffer); + + // drop \r + if (!linebuffer.empty() && linebuffer.back() == '\r') linebuffer.pop_back(); + + // reset linebufferpos + this->linebufferpos = 0; + } + + // check single character tokens + char nextchar = this->linebuffer[this->linebufferpos]; + + switch (nextchar) { + // check for comment + case '\\': + // skip rest of line + this->linebufferpos = this->linebuffer.size(); + return false; + + // check for bracket opening + case '[': + t = RawTokenType::BRKOP; + this->linebufferpos++; + return true; + + // check for bracket closing + case ']': + t = RawTokenType::BRKCL; + this->linebufferpos++; + return true; + + // check for less sign + case '<': + t = RawTokenType::LESS; + this->linebufferpos++; + return true; + + // check for greater sign + case '>': + t = RawTokenType::GREATER; + this->linebufferpos++; + return true; + + // check for equal sign + case '=': + t = RawTokenType::EQUAL; + this->linebufferpos++; + return true; + + // check for colon + case ':': + t = RawTokenType::COLON; + this->linebufferpos++; + return true; + + // check for plus + case '+': + t = RawTokenType::PLUS; + this->linebufferpos++; + return true; + + // check for hat + case '^': + t = RawTokenType::HAT; + this->linebufferpos++; + return true; + + // check for slash + case '/': + t = RawTokenType::SLASH; + this->linebufferpos++; + return true; + + // check for asterisk + case '*': + t = RawTokenType::ASTERISK; + this->linebufferpos++; + return true; + + // check for minus + case '-': + t = RawTokenType::MINUS; + this->linebufferpos++; + return true; + + // check for whitespace + case ' ': + case '\t': + this->linebufferpos++; + return false; + + // check for line end + case ';': + case '\n': // \n should not happen due to using getline() + this->linebufferpos = this->linebuffer.size(); + return false; + + case '\0': // empty line + lpassert(this->linebufferpos == this->linebuffer.size()); + return false; + } + + // check for double value + const char* startptr = this->linebuffer.data() + this->linebufferpos; + char* endptr; + double constant = strtod(startptr, &endptr); + if (endptr != startptr) { + t = constant; + this->linebufferpos += endptr - startptr; + return true; + } + + // assume it's an (section/variable/constraint) identifier + auto endpos = + this->linebuffer.find_first_of("\t\n\\:+<>^= /-*[]", this->linebufferpos); + if (endpos == std::string::npos) + endpos = this->linebuffer.size(); // take complete rest of string + if (endpos > this->linebufferpos) { + t = std::string(this->linebuffer, this->linebufferpos, + endpos - this->linebufferpos); + this->linebufferpos = endpos; + return true; + } + + lpassert(false); + return false; +} diff --git a/extern/filereaderlp/reader.hpp b/extern/filereaderlp/reader.hpp index f292d1df14..b86a954281 100644 --- a/extern/filereaderlp/reader.hpp +++ b/extern/filereaderlp/reader.hpp @@ -1,10 +1,10 @@ -#ifndef __READERLP_READER_HPP__ -#define __READERLP_READER_HPP__ - -#include - -#include "model.hpp" - -Model readinstance(std::string filename); - -#endif +#ifndef __READERLP_READER_HPP__ +#define __READERLP_READER_HPP__ + +#include + +#include "model.hpp" + +Model readinstance(std::string filename); + +#endif diff --git a/src/Highs.h b/src/Highs.h index 52ff96d929..2986d292ff 100644 --- a/src/Highs.h +++ b/src/Highs.h @@ -312,11 +312,11 @@ class Highs { /** * @brief Write (deviations from default values of) the options to a - * file, with the extension ".html" producing HTML, otherwise using - * the standard format used to read options from a file. + * file, using the standard format used to read options from a file. + * Possible to write only deviations from default values. */ HighsStatus writeOptions(const std::string& filename, //!< The filename - const bool report_only_deviations = false) const; + const bool report_only_deviations = false); /** * @brief Returns the number of user-settable options @@ -411,7 +411,7 @@ class Highs { /** * @brief Get the run time of HiGHS */ - double getRunTime() { return timer_.readRunHighsClock(); } + double getRunTime() { return timer_.read(); } /** * Methods for model output @@ -1048,6 +1048,16 @@ class Highs { const HighsInt* starts, const HighsInt* indices, const double* values); + HighsStatus ensureColwise() { + this->model_.lp_.ensureColwise(); + return HighsStatus::kOk; + } + + HighsStatus ensureRowwise() { + this->model_.lp_.ensureRowwise(); + return HighsStatus::kOk; + } + /** * @brief Delete multiple columns from the incumbent model given by an * interval [from_col, to_col] @@ -1306,6 +1316,14 @@ class Highs { return ekk_instance_.primal_phase1_dual_; } + /** + * @brief Development methods + */ + HighsInt defineClock(const char* name) { + return this->timer_.clock_def(name); + } + void writeAllClocks() { this->timer_.writeAllClocks(); } + // Start of deprecated methods std::string compilationDate() const { return "deprecated"; } @@ -1482,11 +1500,6 @@ class Highs { // and basis data void setHighsModelStatusAndClearSolutionAndBasis( const HighsModelStatus model_status); - // - // Sets model status, basis, solution and info from the - // highs_model_object - void setBasisValidity(); - // // Clears the presolved model and its status void clearPresolve(); // @@ -1555,15 +1568,14 @@ class Highs { void deleteRowsInterface(HighsIndexCollection& index_collection); void getColsInterface(const HighsIndexCollection& index_collection, - HighsInt& num_col, double* col_cost, double* col_lower, - double* col_upper, HighsInt& num_nz, - HighsInt* col_matrix_start, HighsInt* col_matrix_index, - double* col_matrix_value); + HighsInt& num_col, double* cost, double* lower, + double* upper, HighsInt& num_nz, HighsInt* start, + HighsInt* index, double* value); void getRowsInterface(const HighsIndexCollection& index_collection, - HighsInt& num_row, double* row_lower, double* row_upper, - HighsInt& num_nz, HighsInt* row_matrix_start, - HighsInt* row_matrix_index, double* row_matrix_value); + HighsInt& num_row, double* lower, double* upper, + HighsInt& num_nz, HighsInt* start, HighsInt* index, + double* value); void getCoefficientInterface(const HighsInt ext_row, const HighsInt ext_col, double& value); diff --git a/src/highs_bindings.cpp b/src/highs_bindings.cpp index c9b6c42269..2ea1c35817 100644 --- a/src/highs_bindings.cpp +++ b/src/highs_bindings.cpp @@ -886,27 +886,27 @@ PYBIND11_MODULE(_core, m, py::mod_gil_not_used()) { // parent scope, which should be skipped for newer C++11-style strongly typed // enums." // [1]: https://pybind11.readthedocs.io/en/stable/classes.html - py::enum_(m, "ObjSense") + py::enum_(m, "ObjSense", py::module_local()) .value("kMinimize", ObjSense::kMinimize) .value("kMaximize", ObjSense::kMaximize); - py::enum_(m, "MatrixFormat") + py::enum_(m, "MatrixFormat", py::module_local()) .value("kColwise", MatrixFormat::kColwise) .value("kRowwise", MatrixFormat::kRowwise) .value("kRowwisePartitioned", MatrixFormat::kRowwisePartitioned); - py::enum_(m, "HessianFormat") + py::enum_(m, "HessianFormat", py::module_local()) .value("kTriangular", HessianFormat::kTriangular) .value("kSquare", HessianFormat::kSquare); - py::enum_(m, "SolutionStatus") + py::enum_(m, "SolutionStatus", py::module_local()) .value("kSolutionStatusNone", SolutionStatus::kSolutionStatusNone) .value("kSolutionStatusInfeasible", SolutionStatus::kSolutionStatusInfeasible) .value("kSolutionStatusFeasible", SolutionStatus::kSolutionStatusFeasible) .export_values(); - py::enum_(m, "BasisValidity") + py::enum_(m, "BasisValidity", py::module_local()) .value("kBasisValidityInvalid", BasisValidity::kBasisValidityInvalid) .value("kBasisValidityValid", BasisValidity::kBasisValidityValid) .export_values(); - py::enum_(m, "HighsModelStatus") + py::enum_(m, "HighsModelStatus", py::module_local()) .value("kNotset", HighsModelStatus::kNotset) .value("kLoadError", HighsModelStatus::kLoadError) .value("kModelError", HighsModelStatus::kModelError) @@ -926,7 +926,7 @@ PYBIND11_MODULE(_core, m, py::mod_gil_not_used()) { .value("kSolutionLimit", HighsModelStatus::kSolutionLimit) .value("kInterrupt", HighsModelStatus::kInterrupt) .value("kMemoryLimit", HighsModelStatus::kMemoryLimit); - py::enum_(m, "HighsPresolveStatus") + py::enum_(m, "HighsPresolveStatus", py::module_local()) .value("kNotPresolved", HighsPresolveStatus::kNotPresolved) .value("kNotReduced", HighsPresolveStatus::kNotReduced) .value("kInfeasible", HighsPresolveStatus::kInfeasible) @@ -937,37 +937,37 @@ PYBIND11_MODULE(_core, m, py::mod_gil_not_used()) { .value("kTimeout", HighsPresolveStatus::kTimeout) .value("kNullError", HighsPresolveStatus::kNullError) .value("kOptionsError", HighsPresolveStatus::kOptionsError); - py::enum_(m, "HighsBasisStatus") + py::enum_(m, "HighsBasisStatus", py::module_local()) .value("kLower", HighsBasisStatus::kLower) .value("kBasic", HighsBasisStatus::kBasic) .value("kUpper", HighsBasisStatus::kUpper) .value("kZero", HighsBasisStatus::kZero) .value("kNonbasic", HighsBasisStatus::kNonbasic); - py::enum_(m, "HighsVarType") + py::enum_(m, "HighsVarType", py::module_local()) .value("kContinuous", HighsVarType::kContinuous) .value("kInteger", HighsVarType::kInteger) .value("kSemiContinuous", HighsVarType::kSemiContinuous) .value("kSemiInteger", HighsVarType::kSemiInteger); - py::enum_(m, "HighsOptionType") + py::enum_(m, "HighsOptionType", py::module_local()) .value("kBool", HighsOptionType::kBool) .value("kInt", HighsOptionType::kInt) .value("kDouble", HighsOptionType::kDouble) .value("kString", HighsOptionType::kString); - py::enum_(m, "HighsInfoType") + py::enum_(m, "HighsInfoType", py::module_local()) .value("kInt64", HighsInfoType::kInt64) .value("kInt", HighsInfoType::kInt) .value("kDouble", HighsInfoType::kDouble); - py::enum_(m, "HighsStatus") + py::enum_(m, "HighsStatus", py::module_local()) .value("kError", HighsStatus::kError) .value("kOk", HighsStatus::kOk) .value("kWarning", HighsStatus::kWarning); - py::enum_(m, "HighsLogType") + py::enum_(m, "HighsLogType", py::module_local()) .value("kInfo", HighsLogType::kInfo) .value("kDetailed", HighsLogType::kDetailed) .value("kVerbose", HighsLogType::kVerbose) .value("kWarning", HighsLogType::kWarning) .value("kError", HighsLogType::kError); - py::enum_(m, "IisStrategy") + py::enum_(m, "IisStrategy", py::module_local()) .value("kIisStrategyMin", IisStrategy::kIisStrategyMin) .value("kIisStrategyFromLpRowPriority", IisStrategy::kIisStrategyFromLpRowPriority) @@ -975,7 +975,7 @@ PYBIND11_MODULE(_core, m, py::mod_gil_not_used()) { IisStrategy::kIisStrategyFromLpColPriority) .value("kIisStrategyMax", IisStrategy::kIisStrategyMax) .export_values(); - py::enum_(m, "IisBoundStatus") + py::enum_(m, "IisBoundStatus", py::module_local()) .value("kIisBoundStatusDropped", IisBoundStatus::kIisBoundStatusDropped) .value("kIisBoundStatusNull", IisBoundStatus::kIisBoundStatusNull) .value("kIisBoundStatusFree", IisBoundStatus::kIisBoundStatusFree) @@ -983,7 +983,7 @@ PYBIND11_MODULE(_core, m, py::mod_gil_not_used()) { .value("kIisBoundStatusUpper", IisBoundStatus::kIisBoundStatusUpper) .value("kIisBoundStatusBoxed", IisBoundStatus::kIisBoundStatusBoxed) .export_values(); - py::enum_(m, "HighsDebugLevel") + py::enum_(m, "HighsDebugLevel", py::module_local()) .value("kHighsDebugLevelNone", HighsDebugLevel::kHighsDebugLevelNone) .value("kHighsDebugLevelCheap", HighsDebugLevel::kHighsDebugLevelCheap) .value("kHighsDebugLevelCostly", HighsDebugLevel::kHighsDebugLevelCostly) @@ -992,7 +992,7 @@ PYBIND11_MODULE(_core, m, py::mod_gil_not_used()) { .value("kHighsDebugLevelMax", HighsDebugLevel::kHighsDebugLevelMax) .export_values(); // Classes - py::class_(m, "HighsSparseMatrix") + py::class_(m, "HighsSparseMatrix", py::module_local()) .def(py::init<>()) .def_readwrite("format_", &HighsSparseMatrix::format_) .def_readwrite("num_col_", &HighsSparseMatrix::num_col_) @@ -1001,9 +1001,9 @@ PYBIND11_MODULE(_core, m, py::mod_gil_not_used()) { .def_readwrite("p_end_", &HighsSparseMatrix::p_end_) .def_readwrite("index_", &HighsSparseMatrix::index_) .def_readwrite("value_", &HighsSparseMatrix::value_); - py::class_(m, "HighsLpMods"); - py::class_(m, "HighsScale"); - py::class_(m, "HighsLp") + py::class_(m, "HighsLpMods", py::module_local()); + py::class_(m, "HighsScale", py::module_local()); + py::class_(m, "HighsLp", py::module_local()) .def(py::init<>()) .def_readwrite("num_col_", &HighsLp::num_col_) .def_readwrite("num_row_", &HighsLp::num_row_) @@ -1023,18 +1023,18 @@ PYBIND11_MODULE(_core, m, py::mod_gil_not_used()) { .def_readwrite("is_scaled_", &HighsLp::is_scaled_) .def_readwrite("is_moved_", &HighsLp::is_moved_) .def_readwrite("mods_", &HighsLp::mods_); - py::class_(m, "HighsHessian") + py::class_(m, "HighsHessian", py::module_local()) .def(py::init<>()) .def_readwrite("dim_", &HighsHessian::dim_) .def_readwrite("format_", &HighsHessian::format_) .def_readwrite("start_", &HighsHessian::start_) .def_readwrite("index_", &HighsHessian::index_) .def_readwrite("value_", &HighsHessian::value_); - py::class_(m, "HighsModel") + py::class_(m, "HighsModel", py::module_local()) .def(py::init<>()) .def_readwrite("lp_", &HighsModel::lp_) .def_readwrite("hessian_", &HighsModel::hessian_); - py::class_(m, "HighsInfo") + py::class_(m, "HighsInfo", py::module_local()) .def(py::init<>()) .def_readwrite("valid", &HighsInfo::valid) .def_readwrite("mip_node_count", &HighsInfo::mip_node_count) @@ -1073,7 +1073,7 @@ PYBIND11_MODULE(_core, m, py::mod_gil_not_used()) { &HighsInfo::sum_complementarity_violations) .def_readwrite("primal_dual_integral", &HighsInfo::primal_dual_integral); - py::class_(m, "HighsOptions") + py::class_(m, "HighsOptions", py::module_local()) .def(py::init<>()) .def_readwrite("presolve", &HighsOptions::presolve) .def_readwrite("solver", &HighsOptions::solver) @@ -1161,7 +1161,7 @@ PYBIND11_MODULE(_core, m, py::mod_gil_not_used()) { &HighsOptions::mip_heuristic_effort) .def_readwrite("mip_min_logging_interval", &HighsOptions::mip_min_logging_interval); - py::class_(m, "_Highs") + py::class_(m, "_Highs", py::module_local()) .def(py::init<>()) .def("version", &Highs::version) .def("versionMajor", &Highs::versionMajor) @@ -1330,6 +1330,8 @@ PYBIND11_MODULE(_core, m, py::mod_gil_not_used()) { .def("addCols", &highs_addCols) .def("addVar", &highs_addVar) .def("addVars", &highs_addVars) + .def("ensureColwise", &Highs::ensureColwise) + .def("ensureRowwise", &Highs::ensureRowwise) .def("changeColsCost", &highs_changeColsCost) .def("changeColsBounds", &highs_changeColsBounds) .def("changeColsIntegrality", &highs_changeColsIntegrality) @@ -1356,7 +1358,7 @@ PYBIND11_MODULE(_core, m, py::mod_gil_not_used()) { .def("stopCallbackInt", static_cast( &Highs::stopCallback)); - py::class_(m, "HighsIis") + py::class_(m, "HighsIis", py::module_local()) .def(py::init<>()) .def("invalidate", &HighsIis::invalidate) .def_readwrite("valid", &HighsIis::valid_) @@ -1367,7 +1369,7 @@ PYBIND11_MODULE(_core, m, py::mod_gil_not_used()) { .def_readwrite("row_bound", &HighsIis::row_bound_) .def_readwrite("info", &HighsIis::info_); // structs - py::class_(m, "HighsSolution") + py::class_(m, "HighsSolution", py::module_local()) .def(py::init<>()) .def_readwrite("value_valid", &HighsSolution::value_valid) .def_readwrite("dual_valid", &HighsSolution::dual_valid) @@ -1375,11 +1377,11 @@ PYBIND11_MODULE(_core, m, py::mod_gil_not_used()) { .def_readwrite("col_dual", &HighsSolution::col_dual) .def_readwrite("row_value", &HighsSolution::row_value) .def_readwrite("row_dual", &HighsSolution::row_dual); - py::class_(m, "HighsObjectiveSolution") + py::class_(m, "HighsObjectiveSolution", py::module_local()) .def(py::init<>()) .def_readwrite("objective", &HighsObjectiveSolution::objective) .def_readwrite("col_value", &HighsObjectiveSolution::col_value); - py::class_(m, "HighsBasis") + py::class_(m, "HighsBasis", py::module_local()) .def(py::init<>()) .def_readwrite("valid", &HighsBasis::valid) .def_readwrite("alien", &HighsBasis::alien) @@ -1389,13 +1391,13 @@ PYBIND11_MODULE(_core, m, py::mod_gil_not_used()) { .def_readwrite("debug_origin_name", &HighsBasis::debug_origin_name) .def_readwrite("col_status", &HighsBasis::col_status) .def_readwrite("row_status", &HighsBasis::row_status); - py::class_(m, "HighsRangingRecord") + py::class_(m, "HighsRangingRecord", py::module_local()) .def(py::init<>()) .def_readwrite("value_", &HighsRangingRecord::value_) .def_readwrite("objective_", &HighsRangingRecord::objective_) .def_readwrite("in_var_", &HighsRangingRecord::in_var_) .def_readwrite("ou_var_", &HighsRangingRecord::ou_var_); - py::class_(m, "HighsRanging") + py::class_(m, "HighsRanging", py::module_local()) .def(py::init<>()) .def_readwrite("valid", &HighsRanging::valid) .def_readwrite("col_cost_up", &HighsRanging::col_cost_up) @@ -1404,11 +1406,11 @@ PYBIND11_MODULE(_core, m, py::mod_gil_not_used()) { .def_readwrite("col_bound_dn", &HighsRanging::col_bound_dn) .def_readwrite("row_bound_up", &HighsRanging::row_bound_up) .def_readwrite("row_bound_dn", &HighsRanging::row_bound_dn); - py::class_(m, "HighsIisInfo") + py::class_(m, "HighsIisInfo", py::module_local()) .def(py::init<>()) .def_readwrite("simplex_time", &HighsIisInfo::simplex_time) .def_readwrite("simplex_iterations", &HighsIisInfo::simplex_iterations); - py::class_(m, "HighsLinearObjective") + py::class_(m, "HighsLinearObjective", py::module_local()) .def(py::init<>()) .def_readwrite("weight", &HighsLinearObjective::weight) .def_readwrite("offset", &HighsLinearObjective::offset) @@ -1428,7 +1430,7 @@ PYBIND11_MODULE(_core, m, py::mod_gil_not_used()) { py::module_ simplex_constants = m.def_submodule("simplex_constants", "Submodule for simplex constants"); - py::enum_(simplex_constants, "SimplexStrategy") + py::enum_(simplex_constants, "SimplexStrategy", py::module_local()) .value("kSimplexStrategyMin", SimplexStrategy::kSimplexStrategyMin) .value("kSimplexStrategyChoose", SimplexStrategy::kSimplexStrategyChoose) .value("kSimplexStrategyDual", SimplexStrategy::kSimplexStrategyDual) @@ -1443,7 +1445,7 @@ PYBIND11_MODULE(_core, m, py::mod_gil_not_used()) { .value("kSimplexStrategyNum", SimplexStrategy::kSimplexStrategyNum) .export_values(); py::enum_(simplex_constants, - "SimplexUnscaledSolutionStrategy") + "SimplexUnscaledSolutionStrategy", py::module_local()) .value( "kSimplexUnscaledSolutionStrategyMin", SimplexUnscaledSolutionStrategy::kSimplexUnscaledSolutionStrategyMin) @@ -1463,7 +1465,7 @@ PYBIND11_MODULE(_core, m, py::mod_gil_not_used()) { "kSimplexUnscaledSolutionStrategyNum", SimplexUnscaledSolutionStrategy::kSimplexUnscaledSolutionStrategyNum) .export_values(); - py::enum_(simplex_constants, "SimplexSolvePhase") + py::enum_(simplex_constants, "SimplexSolvePhase", py::module_local()) .value("kSolvePhaseMin", SimplexSolvePhase::kSolvePhaseMin) .value("kSolvePhaseError", SimplexSolvePhase::kSolvePhaseError) .value("kSolvePhaseExit", SimplexSolvePhase::kSolvePhaseExit) @@ -1479,7 +1481,7 @@ PYBIND11_MODULE(_core, m, py::mod_gil_not_used()) { .value("kSolvePhaseMax", SimplexSolvePhase::kSolvePhaseMax) .export_values(); py::enum_(simplex_constants, - "SimplexEdgeWeightStrategy") + "SimplexEdgeWeightStrategy", py::module_local()) .value("kSimplexEdgeWeightStrategyMin", SimplexEdgeWeightStrategy::kSimplexEdgeWeightStrategyMin) .value("kSimplexEdgeWeightStrategyChoose", @@ -1493,7 +1495,7 @@ PYBIND11_MODULE(_core, m, py::mod_gil_not_used()) { .value("kSimplexEdgeWeightStrategyMax", SimplexEdgeWeightStrategy::kSimplexEdgeWeightStrategyMax) .export_values(); - py::enum_(simplex_constants, "SimplexPriceStrategy") + py::enum_(simplex_constants, "SimplexPriceStrategy", py::module_local()) .value("kSimplexPriceStrategyMin", SimplexPriceStrategy::kSimplexPriceStrategyMin) .value("kSimplexPriceStrategyCol", @@ -1508,7 +1510,7 @@ PYBIND11_MODULE(_core, m, py::mod_gil_not_used()) { SimplexPriceStrategy::kSimplexPriceStrategyMax) .export_values(); py::enum_( - simplex_constants, "SimplexPivotalRowRefinementStrategy") + simplex_constants, "SimplexPivotalRowRefinementStrategy", py::module_local()) .value("kSimplexInfeasibilityProofRefinementMin", SimplexPivotalRowRefinementStrategy:: kSimplexInfeasibilityProofRefinementMin) @@ -1526,7 +1528,7 @@ PYBIND11_MODULE(_core, m, py::mod_gil_not_used()) { kSimplexInfeasibilityProofRefinementMax) .export_values(); py::enum_(simplex_constants, - "SimplexPrimalCorrectionStrategy") + "SimplexPrimalCorrectionStrategy", py::module_local()) .value( "kSimplexPrimalCorrectionStrategyNone", SimplexPrimalCorrectionStrategy::kSimplexPrimalCorrectionStrategyNone) @@ -1537,7 +1539,7 @@ PYBIND11_MODULE(_core, m, py::mod_gil_not_used()) { SimplexPrimalCorrectionStrategy:: kSimplexPrimalCorrectionStrategyAlways) .export_values(); - py::enum_(simplex_constants, "SimplexNlaOperation") + py::enum_(simplex_constants, "SimplexNlaOperation", py::module_local()) .value("kSimplexNlaNull", SimplexNlaOperation::kSimplexNlaNull) .value("kSimplexNlaBtranFull", SimplexNlaOperation::kSimplexNlaBtranFull) .value("kSimplexNlaPriceFull", SimplexNlaOperation::kSimplexNlaPriceFull) @@ -1554,14 +1556,14 @@ PYBIND11_MODULE(_core, m, py::mod_gil_not_used()) { .value("kNumSimplexNlaOperation", SimplexNlaOperation::kNumSimplexNlaOperation) .export_values(); - py::enum_(simplex_constants, "EdgeWeightMode") + py::enum_(simplex_constants, "EdgeWeightMode", py::module_local()) .value("kDantzig", EdgeWeightMode::kDantzig) .value("kDevex", EdgeWeightMode::kDevex) .value("kSteepestEdge", EdgeWeightMode::kSteepestEdge) .value("kCount", EdgeWeightMode::kCount); py::module_ callbacks = m.def_submodule("cb", "Callback interface submodule"); // Types for interface - py::enum_(callbacks, "HighsCallbackType") + py::enum_(callbacks, "HighsCallbackType", py::module_local()) .value("kCallbackMin", HighsCallbackType::kCallbackMin) .value("kCallbackLogging", HighsCallbackType::kCallbackLogging) .value("kCallbackSimplexInterrupt", @@ -1580,12 +1582,12 @@ PYBIND11_MODULE(_core, m, py::mod_gil_not_used()) { .value("kNumCallbackType", HighsCallbackType::kNumCallbackType) .export_values(); // Classes - py::class_>(m, "readonly_ptr_wrapper_double") + py::class_>(m, "readonly_ptr_wrapper_double", py::module_local()) .def(py::init()) .def("__getitem__", &readonly_ptr_wrapper::operator[]) .def("__bool__", &readonly_ptr_wrapper::is_valid) .def("to_array", &readonly_ptr_wrapper::to_array); - py::class_(callbacks, "HighsCallbackDataOut") + py::class_(callbacks, "HighsCallbackDataOut", py::module_local()) .def(py::init<>()) .def_readwrite("log_type", &HighsCallbackDataOut::log_type) .def_readwrite("running_time", &HighsCallbackDataOut::running_time) @@ -1607,7 +1609,7 @@ PYBIND11_MODULE(_core, m, py::mod_gil_not_used()) { [](const HighsCallbackDataOut& self) -> readonly_ptr_wrapper { return readonly_ptr_wrapper(self.mip_solution); }); - py::class_(callbacks, "HighsCallbackDataIn") + py::class_(callbacks, "HighsCallbackDataIn", py::module_local()) .def(py::init<>()) .def_readwrite("user_interrupt", &HighsCallbackDataIn::user_interrupt); } diff --git a/src/interfaces/highs_c_api.cpp b/src/interfaces/highs_c_api.cpp index af354daf02..e5fefce606 100644 --- a/src/interfaces/highs_c_api.cpp +++ b/src/interfaces/highs_c_api.cpp @@ -802,6 +802,14 @@ HighsInt Highs_addRows(void* highs, const HighsInt num_new_row, ->addRows(num_new_row, lower, upper, num_new_nz, starts, index, value); } +HighsInt Highs_ensureColwise(void* highs) { + return (HighsInt)((Highs*)highs)->ensureColwise(); +} + +HighsInt Highs_ensureRowwise(void* highs) { + return (HighsInt)((Highs*)highs)->ensureRowwise(); +} + HighsInt Highs_changeObjectiveSense(void* highs, const HighsInt sense) { ObjSense pass_sense = ObjSense::kMinimize; if (sense == (HighsInt)ObjSense::kMaximize) pass_sense = ObjSense::kMaximize; diff --git a/src/interfaces/highs_c_api.h b/src/interfaces/highs_c_api.h index 89f6fa4f79..02fe1930b4 100644 --- a/src/interfaces/highs_c_api.h +++ b/src/interfaces/highs_c_api.h @@ -1426,6 +1426,25 @@ HighsInt Highs_addRows(void* highs, const HighsInt num_new_row, const HighsInt num_new_nz, const HighsInt* starts, const HighsInt* index, const double* value); +/** + * Ensure that the constraint matrix of the incumbent model is stored + * column-wise. + * + * @param highs A pointer to the Highs instance. + * + * @returns A `kHighsStatus` constant indicating whether the call succeeded. + */ +HighsInt Highs_ensureColwise(void* highs); + +/** + * Ensure that the constraint matrix of the incumbent model is stored row-wise. + * + * @param highs A pointer to the Highs instance. + * + * @returns A `kHighsStatus` constant indicating whether the call succeeded. + */ +HighsInt Highs_ensureRowwise(void* highs); + /** * Change the objective sense of the model. * diff --git a/src/ipm/IpxWrapper.cpp b/src/ipm/IpxWrapper.cpp index 7a33b68199..37792e5152 100644 --- a/src/ipm/IpxWrapper.cpp +++ b/src/ipm/IpxWrapper.cpp @@ -115,7 +115,7 @@ HighsStatus solveLpIpx(const HighsOptions& options, HighsTimer& timer, parameters.analyse_basis_data = kHighsAnalysisLevelNlaData & options.highs_analysis_level; // Determine the run time allowed for IPX - parameters.time_limit = options.time_limit - timer.readRunHighsClock(); + parameters.time_limit = options.time_limit - timer.read(); parameters.ipm_maxiter = options.ipm_iteration_limit - highs_info.ipm_iteration_count; // Determine if crossover is to be run or not diff --git a/src/lp_data/HStruct.h b/src/lp_data/HStruct.h index 05bf617b2b..bdcc345cb6 100644 --- a/src/lp_data/HStruct.h +++ b/src/lp_data/HStruct.h @@ -58,14 +58,27 @@ struct HotStart { }; struct HighsBasis { + // Logical flags for a HiGHS basis: + // + // valid: has been factored by HiGHS + // + // alien: a basis that's been set externally, so cannot be assumed + // to even have the right number of basic and nonbasic variables + // + // useful: a basis that may be useful + // + // Need useful since, by default, a basis is alien but not useful bool valid = false; bool alien = true; + bool useful = false; bool was_alien = true; HighsInt debug_id = -1; HighsInt debug_update_count = -1; std::string debug_origin_name = "None"; std::vector col_status; std::vector row_status; + void print(std::string message = "") const; + void printScalars(std::string message = "") const; void invalidate(); void clear(); }; diff --git a/src/lp_data/Highs.cpp b/src/lp_data/Highs.cpp index 61320a1b4a..94ed199839 100644 --- a/src/lp_data/Highs.cpp +++ b/src/lp_data/Highs.cpp @@ -139,7 +139,8 @@ HighsStatus Highs::resetOptions() { } HighsStatus Highs::writeOptions(const std::string& filename, - const bool report_only_deviations) const { + const bool report_only_deviations) { + this->logHeader(); HighsStatus return_status = HighsStatus::kOk; FILE* file; HighsFileType file_type; @@ -148,15 +149,16 @@ HighsStatus Highs::writeOptions(const std::string& filename, openWriteFile(filename, "writeOptions", file, file_type), return_status, "openWriteFile"); if (return_status == HighsStatus::kError) return return_status; + if (filename == "") file_type = HighsFileType::kMinimal; // Report to user that options are being written to a file if (filename != "") highsLogUser(options_.log_options, HighsLogType::kInfo, "Writing the option values to %s\n", filename.c_str()); - return_status = - interpretCallStatus(options_.log_options, - writeOptionsToFile(file, options_.records, - report_only_deviations, file_type), - return_status, "writeOptionsToFile"); + return_status = interpretCallStatus( + options_.log_options, + writeOptionsToFile(file, options_.log_options, options_.records, + report_only_deviations, file_type), + return_status, "writeOptionsToFile"); if (file != stdout) fclose(file); return return_status; } @@ -716,6 +718,7 @@ HighsStatus Highs::readBasis(const std::string& filename) { // Update the HiGHS basis and invalidate any simplex basis for the model basis_ = read_basis; basis_.valid = true; + basis_.useful = true; // Follow implications of a new HiGHS basis newHighsBasis(); // Can't use returnFromHighs since... @@ -1048,7 +1051,7 @@ HighsStatus Highs::solve() { // Zero the iteration counts zeroIterationCounts(); // Start the HiGHS run clock - timer_.startRunHighsClock(); + timer_.start(); // Return immediately if the model has no columns if (!model_.lp_.num_col_) { setHighsModelStatusAndClearSolutionAndBasis(HighsModelStatus::kModelEmpty); @@ -1166,7 +1169,7 @@ HighsStatus Highs::solve() { // // Record the initial time and set the component times and postsolve // iteration count to -1 to identify whether they are not required - double initial_time = timer_.readRunHighsClock(); + double initial_time = timer_.read(); double this_presolve_time = -1; double this_solve_presolved_lp_time = -1; double this_postsolve_time = -1; @@ -1222,7 +1225,7 @@ HighsStatus Highs::solve() { return returnFromRun(crossover_status, undo_mods); assert(options_.simplex_strategy == kSimplexStrategyPrimal); } - // timer_.stopRunHighsClock(); + // timer_.stop(); // run(); // todo: add "dual" values @@ -1265,8 +1268,16 @@ HighsStatus Highs::solve() { // is available, simplex should surely be chosen. const bool solver_will_use_basis = options_.solver == kSimplexString || options_.solver == kHighsChooseString; - if ((basis_.valid || options_.presolve == kHighsOffString || - unconstrained_lp) && + const bool has_basis = basis_.useful; + if (has_basis) { + assert(basis_.col_status.size() == + static_cast(incumbent_lp.num_col_)); + assert(basis_.row_status.size() == + static_cast(incumbent_lp.num_row_)); + } + if (basis_.valid) assert(basis_.useful); + + if ((has_basis || options_.presolve == kHighsOffString || unconstrained_lp) && solver_will_use_basis) { // There is a valid basis for the problem, presolve is off, or LP // has no constraint matrix, and the solver will use the basis @@ -1274,7 +1285,7 @@ HighsStatus Highs::solve() { "LP without presolve, or with basis, or unconstrained"; // If there is a valid HiGHS basis, refine any status values that // are simply HighsBasisStatus::kNonbasic - if (basis_.valid) refineBasis(incumbent_lp, solution_, basis_); + if (basis_.useful) refineBasis(incumbent_lp, solution_, basis_); solveLp(incumbent_lp, "Solving LP without presolve, or with basis, or unconstrained", this_solve_original_lp_time); @@ -1400,6 +1411,12 @@ HighsStatus Highs::solve() { model_status_ == HighsModelStatus::kTimeLimit || model_status_ == HighsModelStatus::kIterationLimit || model_status_ == HighsModelStatus::kInterrupt; + if (no_incumbent_lp_solution_or_basis) { + // Postsolve won't be performed, so clear the HEkk data + // corresponding to the (strictly reduced) presolved LP here + ekk_instance_.clear(); + setHighsModelStatusAndClearSolutionAndBasis(model_status_); + } break; } case HighsPresolveStatus::kReducedToEmpty: { @@ -1410,6 +1427,7 @@ HighsStatus Highs::solve() { basis_.debug_origin_name = "Presolve to empty"; basis_.valid = true; basis_.alien = false; + basis_.useful = true; basis_.was_alien = false; solution_.value_valid = true; solution_.dual_valid = true; @@ -1449,10 +1467,7 @@ HighsStatus Highs::solve() { options_ = save_options; if (return_status == HighsStatus::kError) return returnFromRun(return_status, undo_mods); - // ToDo Eliminate setBasisValidity once ctest passes. Asserts - // verify that it does nothing - other than setting - // info_.valid = true; - setBasisValidity(); + info_.valid = true; assert(model_status_ == HighsModelStatus::kInfeasible || model_status_ == HighsModelStatus::kUnbounded); return returnFromRun(return_status, undo_mods); @@ -1492,152 +1507,142 @@ HighsStatus Highs::solve() { // Postsolve. Does nothing if there were no reductions during presolve. - if (have_optimal_solution) { + const bool have_optimal_reduced_solution = + model_presolve_status_ == HighsPresolveStatus::kReducedToEmpty || + (model_presolve_status_ == HighsPresolveStatus::kReduced && + model_status_ == HighsModelStatus::kOptimal); + const bool have_unknown_reduced_solution = + model_presolve_status_ == HighsPresolveStatus::kReduced && + model_status_ == HighsModelStatus::kUnknown; + + if (have_optimal_reduced_solution || have_unknown_reduced_solution) { // ToDo Put this in a separate method assert(model_status_ == HighsModelStatus::kOptimal || + model_status_ == HighsModelStatus::kUnknown || model_presolve_status_ == HighsPresolveStatus::kReducedToEmpty); - if (model_presolve_status_ == HighsPresolveStatus::kReduced || - model_presolve_status_ == HighsPresolveStatus::kReducedToEmpty) { - // If presolve is nontrivial, extract the optimal solution - // and basis for the presolved problem in order to generate - // the solution and basis for postsolve to use to generate a - // solution(?) and basis that is, hopefully, optimal. This is - // confirmed or corrected by hot-starting the simplex solver - presolve_.data_.recovered_solution_ = solution_; - presolve_.data_.recovered_basis_ = basis_; - - this_postsolve_time = -timer_.read(timer_.postsolve_clock); - timer_.start(timer_.postsolve_clock); - HighsPostsolveStatus postsolve_status = runPostsolve(); - timer_.stop(timer_.postsolve_clock); - this_postsolve_time += -timer_.read(timer_.postsolve_clock); - presolve_.info_.postsolve_time = this_postsolve_time; - - if (postsolve_status == HighsPostsolveStatus::kSolutionRecovered) { - highsLogDev(log_options, HighsLogType::kVerbose, - "Postsolve finished\n"); - // Set solution and its status - solution_.clear(); - solution_ = presolve_.data_.recovered_solution_; - solution_.value_valid = true; - // if (ipx_no_crossover) { - if (!basis_.valid) { - // Have a primal-dual solution, but no basis, since IPX - // was used without crossover, either because - // run_crossover was "off" or "choose" and IPX determined - // optimality - solution_.dual_valid = true; - basis_.invalidate(); - } else { - // - // Hot-start the simplex solver for the incumbent LP - // - solution_.dual_valid = true; - // Set basis and its status - basis_.valid = true; - basis_.col_status = presolve_.data_.recovered_basis_.col_status; - basis_.row_status = presolve_.data_.recovered_basis_.row_status; - basis_.debug_origin_name += ": after postsolve"; - // Basic primal activities are wrong after postsolve, so - // possibly skip KKT check - const bool perform_kkt_check = true; - if (perform_kkt_check) { - // Possibly force debug to perform KKT check on what's - // returned from postsolve - const bool force_debug = false; - HighsInt save_highs_debug_level = options_.highs_debug_level; - if (force_debug) - options_.highs_debug_level = kHighsDebugLevelCostly; - if (debugHighsSolution("After returning from postsolve", options_, - model_, solution_, - basis_) == HighsDebugStatus::kLogicalError) - return returnFromRun(HighsStatus::kError, undo_mods); - options_.highs_debug_level = save_highs_debug_level; - } - // Save the options to allow the best simplex strategy to - // be used - HighsOptions save_options = options_; - const bool full_logging = false; - if (full_logging) options_.log_dev_level = kHighsLogDevLevelVerbose; - // Force the use of simplex to clean up if IPM has been used - // to solve the presolved problem - if (options_.solver == kIpmString) options_.solver = kSimplexString; - options_.simplex_strategy = kSimplexStrategyChoose; - // Ensure that the parallel solver isn't used - options_.simplex_min_concurrency = 1; - options_.simplex_max_concurrency = 1; - // Use any pivot threshold resulting from solving the presolved LP - if (factor_pivot_threshold > 0) - options_.factor_pivot_threshold = factor_pivot_threshold; - // The basis returned from postsolve is just basic/nonbasic - // and EKK expects a refined basis, so set it up now - refineBasis(incumbent_lp, solution_, basis_); - // Scrap the EKK data from solving the presolved LP - ekk_instance_.invalidate(); - ekk_instance_.lp_name_ = "Postsolve LP"; - // Set up the iteration count and timing records so that - // adding the corresponding values after callSolveLp gives - // difference - postsolve_iteration_count = -info_.simplex_iteration_count; - solveLp(incumbent_lp, - "Solving the original LP from the solution after postsolve", - this_solve_original_lp_time); - // Determine the iteration count - postsolve_iteration_count += info_.simplex_iteration_count; - return_status = - interpretCallStatus(options_.log_options, call_status, - return_status, "callSolveLp"); - // Recover the options - 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)); - } + assert(model_presolve_status_ == HighsPresolveStatus::kReduced || + model_presolve_status_ == HighsPresolveStatus::kReducedToEmpty); + if (have_unknown_reduced_solution) + highsLogUser( + options_.log_options, HighsLogType::kWarning, + "Running postsolve on non-optimal solution of reduced LP\n"); + // If presolve is nontrivial, extract the optimal solution + // and basis for the presolved problem in order to generate + // the solution and basis for postsolve to use to generate a + // solution(?) and basis that is, hopefully, optimal. This is + // confirmed or corrected by hot-starting the simplex solver + presolve_.data_.recovered_solution_ = solution_; + presolve_.data_.recovered_basis_ = basis_; + + this_postsolve_time = -timer_.read(timer_.postsolve_clock); + timer_.start(timer_.postsolve_clock); + HighsPostsolveStatus postsolve_status = runPostsolve(); + timer_.stop(timer_.postsolve_clock); + this_postsolve_time += -timer_.read(timer_.postsolve_clock); + presolve_.info_.postsolve_time = this_postsolve_time; + + if (postsolve_status == HighsPostsolveStatus::kSolutionRecovered) { + highsLogDev(log_options, HighsLogType::kVerbose, + "Postsolve finished\n"); + // Set solution and its status + solution_.clear(); + solution_ = presolve_.data_.recovered_solution_; + solution_.value_valid = true; + // if (ipx_no_crossover) { + if (!basis_.valid) { + // Have a primal-dual solution, but no basis, since IPX + // was used without crossover, either because + // run_crossover was "off" or "choose" and IPX determined + // optimality + solution_.dual_valid = true; + basis_.invalidate(); } else { - highsLogUser(log_options, HighsLogType::kError, - "Postsolve return status is %d\n", - (int)postsolve_status); - setHighsModelStatusAndClearSolutionAndBasis( - HighsModelStatus::kPostsolveError); - return returnFromRun(HighsStatus::kError, undo_mods); + // + // Hot-start the simplex solver for the incumbent LP + // + solution_.dual_valid = true; + // Set basis and its status + basis_.valid = true; + basis_.useful = true; + basis_.col_status = presolve_.data_.recovered_basis_.col_status; + basis_.row_status = presolve_.data_.recovered_basis_.row_status; + basis_.debug_origin_name += ": after postsolve"; + // Basic primal activities are wrong after postsolve, so + // possibly skip KKT check + const bool perform_kkt_check = true; + if (perform_kkt_check) { + // Possibly force debug to perform KKT check on what's + // returned from postsolve + const bool force_debug = false; + HighsInt save_highs_debug_level = options_.highs_debug_level; + if (force_debug) + options_.highs_debug_level = kHighsDebugLevelCostly; + if (debugHighsSolution("After returning from postsolve", options_, + model_, solution_, + basis_) == HighsDebugStatus::kLogicalError) + return returnFromRun(HighsStatus::kError, undo_mods); + options_.highs_debug_level = save_highs_debug_level; + } + // Save the options to allow the best simplex strategy to + // be used + HighsOptions save_options = options_; + const bool full_logging = false; + if (full_logging) options_.log_dev_level = kHighsLogDevLevelVerbose; + // Force the use of simplex to clean up if IPM has been used + // to solve the presolved problem + if (options_.solver == kIpmString) options_.solver = kSimplexString; + options_.simplex_strategy = kSimplexStrategyChoose; + // Ensure that the parallel solver isn't used + options_.simplex_min_concurrency = 1; + options_.simplex_max_concurrency = 1; + // Use any pivot threshold resulting from solving the presolved LP + if (factor_pivot_threshold > 0) + options_.factor_pivot_threshold = factor_pivot_threshold; + // The basis returned from postsolve is just basic/nonbasic + // and EKK expects a refined basis, so set it up now + refineBasis(incumbent_lp, solution_, basis_); + // Scrap the EKK data from solving the presolved LP + ekk_instance_.invalidate(); + ekk_instance_.lp_name_ = "Postsolve LP"; + // Set up the iteration count and timing records so that + // adding the corresponding values after callSolveLp gives + // difference + postsolve_iteration_count = -info_.simplex_iteration_count; + solveLp(incumbent_lp, + "Solving the original LP from the solution after postsolve", + this_solve_original_lp_time); + // Determine the iteration count + postsolve_iteration_count += info_.simplex_iteration_count; + // + return_status = HighsStatus::kOk; + return_status = interpretCallStatus(options_.log_options, call_status, + return_status, "callSolveLp"); + // Recover the options + 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 { - // LP was not reduced by presolve, so have simply solved the original LP - assert(model_presolve_status_ == HighsPresolveStatus::kNotReduced); + highsLogUser(log_options, HighsLogType::kError, + "Postsolve return status is %d\n", (int)postsolve_status); + setHighsModelStatusAndClearSolutionAndBasis( + HighsModelStatus::kPostsolveError); + return returnFromRun(HighsStatus::kError, undo_mods); } } } // Cycling can yield model_status_ == HighsModelStatus::kNotset, // assert(model_status_ != HighsModelStatus::kNotset); - if (no_incumbent_lp_solution_or_basis) { - // In solving the (strictly reduced) presolved LP, it is found to - // be infeasible or unbounded, the time/iteration limit has been - // reached, a user interrupt has occurred, or the status is unknown - // (cycling) - // - // Hence there's no incumbent lp solution or basis to drive dual - // postsolve - assert(model_status_ == HighsModelStatus::kInfeasible || - model_status_ == HighsModelStatus::kUnbounded || - model_status_ == HighsModelStatus::kUnboundedOrInfeasible || - model_status_ == HighsModelStatus::kTimeLimit || - model_status_ == HighsModelStatus::kIterationLimit || - model_status_ == HighsModelStatus::kInterrupt || - model_status_ == HighsModelStatus::kUnknown); - // The HEkk data correspond to the (strictly reduced) presolved LP - // so must be cleared - ekk_instance_.clear(); - setHighsModelStatusAndClearSolutionAndBasis(model_status_); - } else { - // ToDo Eliminate setBasisValidity once ctest passes. Asserts - // verify that it does nothing - other than setting info_.valid = - // true; - setBasisValidity(); - } - double lp_solve_final_time = timer_.readRunHighsClock(); + + // Unless the model status was determined using the strictly reduced LP, the + // HiGHS info is valid + if (!no_incumbent_lp_solution_or_basis) info_.valid = true; + + double lp_solve_final_time = timer_.read(); double this_solve_time = lp_solve_final_time - initial_time; if (postsolve_iteration_count < 0) { highsLogDev(log_options, HighsLogType::kInfo, "Postsolve : \n"); @@ -2347,6 +2352,7 @@ HighsStatus Highs::setBasis(const HighsBasis& basis, basis_ = basis; } basis_.valid = true; + basis_.useful = true; if (origin != "") basis_.debug_origin_name = origin; assert(basis_.debug_origin_name != ""); assert(!basis_.alien); @@ -3439,9 +3445,9 @@ HighsPresolveStatus Highs::runPresolve(const bool force_lp_presolve, if (original_lp.num_col_ == 0 && original_lp.num_row_ == 0) return HighsPresolveStatus::kNullError; - // Ensure that the RunHighsClock is running - if (!timer_.runningRunHighsClock()) timer_.startRunHighsClock(); - double start_presolve = timer_.readRunHighsClock(); + // Ensure that the timer is running + if (!timer_.running()) timer_.start(); + double start_presolve = timer_.read(); // Set time limit. if (options_.time_limit > 0 && options_.time_limit < kHighsInf) { @@ -3467,9 +3473,9 @@ HighsPresolveStatus Highs::runPresolve(const bool force_lp_presolve, // Presolved model is extracted now since it's part of solver, // which is lost on return HighsMipSolver solver(callback_, options_, original_lp, solution_); - // Start the MIP solver's total clock so that timeout in presolve - // can be identified - solver.timer_.start(timer_.total_clock); + // Start the MIP solver's timer so that timeout in presolve can be + // identified + solver.timer_.start(); // Only place that HighsMipSolver::runPresolve is called solver.runPresolve(options_.presolve_reduction_limit); presolve_return_status = solver.getPresolveStatus(); @@ -3483,7 +3489,7 @@ HighsPresolveStatus Highs::runPresolve(const bool force_lp_presolve, presolve_.init(original_lp, timer_); presolve_.options_ = &options_; if (options_.time_limit > 0 && options_.time_limit < kHighsInf) { - double current = timer_.readRunHighsClock(); + double current = timer_.read(); double time_init = current - start_presolve; double left = presolve_.options_->time_limit - time_init; if (left <= 0) { @@ -4165,6 +4171,7 @@ HighsStatus Highs::callRunPostsolve(const HighsSolution& solution, // Set basis and its status // // basis_.valid = true; + // basis_.useful = true; // basis_.col_status = presolve_.data_.recovered_basis_.col_status; // basis_.row_status = presolve_.data_.recovered_basis_.row_status; basis_.debug_origin_name += ": after postsolve"; @@ -4278,10 +4285,12 @@ void Highs::forceHighsSolutionBasisSize() { if (basis_.col_status.size() != static_cast(model_.lp_.num_col_)) { basis_.col_status.resize(model_.lp_.num_col_); basis_.valid = false; + basis_.useful = false; } if (basis_.row_status.size() != static_cast(model_.lp_.num_row_)) { basis_.row_status.resize(model_.lp_.num_row_); basis_.valid = false; + basis_.useful = false; } } @@ -4293,17 +4302,6 @@ void Highs::setHighsModelStatusAndClearSolutionAndBasis( info_.valid = true; } -void Highs::setBasisValidity() { - if (basis_.valid) { - assert(info_.basis_validity == kBasisValidityValid); - info_.basis_validity = kBasisValidityValid; - } else { - assert(info_.basis_validity == kBasisValidityInvalid); - info_.basis_validity = kBasisValidityInvalid; - } - info_.valid = true; -} - HighsStatus Highs::openWriteFile(const string filename, const string method_name, FILE*& file, HighsFileType& file_type) const { @@ -4549,7 +4547,7 @@ HighsStatus Highs::returnFromHighs(HighsStatus highs_return_status) { assert(called_return_from_run); } // Stop the HiGHS run clock if it is running - if (timer_.runningRunHighsClock()) timer_.stopRunHighsClock(); + if (timer_.running()) timer_.stop(); const bool dimensions_ok = lpDimensionsOk("returnFromHighs", model_.lp_, options_.log_options); if (!dimensions_ok) { @@ -4613,7 +4611,7 @@ void Highs::reportSolvedLpQpStats() { highsLogUser(log_options, HighsLogType::kInfo, "Relative P-D gap : %17.10e\n", relative_primal_dual_gap); } - double run_time = timer_.readRunHighsClock(); + double run_time = timer_.read(); highsLogUser(log_options, HighsLogType::kInfo, "HiGHS run time : %13.2f\n", run_time); } diff --git a/src/lp_data/HighsInterface.cpp b/src/lp_data/HighsInterface.cpp index b7fe4417b3..4e09f03fd0 100644 --- a/src/lp_data/HighsInterface.cpp +++ b/src/lp_data/HighsInterface.cpp @@ -305,7 +305,7 @@ HighsStatus Highs::addColsInterface( HighsLp& lp = model_.lp_; HighsBasis& basis = basis_; HighsScale& scale = lp.scale_; - bool& valid_basis = basis.valid; + bool& useful_basis = basis.useful; bool& lp_has_scaling = lp.scale_.has_scaling; // Check that if nonzeros are to be added then the model has a positive number @@ -415,7 +415,7 @@ HighsStatus Highs::addColsInterface( &scale.col[lp.num_col_]); } // Update the basis corresponding to new nonbasic columns - if (valid_basis) appendNonbasicColsToBasisInterface(ext_num_new_col); + if (useful_basis) appendNonbasicColsToBasisInterface(ext_num_new_col); // Possibly add column names lp.addColNames("", ext_num_new_col); @@ -471,7 +471,8 @@ HighsStatus Highs::addRowsInterface(HighsInt ext_num_new_row, HighsLp& lp = model_.lp_; HighsBasis& basis = basis_; HighsScale& scale = lp.scale_; - bool& valid_basis = basis.valid; + bool& useful_basis = basis.useful; + bool& lp_has_scaling = lp.scale_.has_scaling; // Check that if nonzeros are to be added then the model has a positive number @@ -559,7 +560,7 @@ HighsStatus Highs::addRowsInterface(HighsInt ext_num_new_row, &scale.row[lp.num_row_]); } // Update the basis corresponding to new basic rows - if (valid_basis) appendBasicRowsToBasisInterface(ext_num_new_row); + if (useful_basis) appendBasicRowsToBasisInterface(ext_num_new_row); // Possibly add row names lp.addRowNames("", ext_num_new_row); @@ -576,6 +577,69 @@ HighsStatus Highs::addRowsInterface(HighsInt ext_num_new_row, return return_status; } +void deleteBasisEntries(std::vector& status, + bool& deleted_basic, bool& deleted_nonbasic, + const HighsIndexCollection& index_collection, + const HighsInt entry_dim) { + assert(ok(index_collection)); + assert(static_cast(entry_dim) == status.size()); + HighsInt from_k; + HighsInt to_k; + limits(index_collection, from_k, to_k); + if (from_k > to_k) return; + + HighsInt delete_from_entry; + HighsInt delete_to_entry; + HighsInt keep_from_entry; + HighsInt keep_to_entry = -1; + HighsInt current_set_entry = 0; + HighsInt new_num_entry = 0; + deleted_basic = false; + deleted_nonbasic = false; + for (HighsInt k = from_k; k <= to_k; k++) { + updateOutInIndex(index_collection, delete_from_entry, delete_to_entry, + keep_from_entry, keep_to_entry, current_set_entry); + // Account for the initial entries being kept + if (k == from_k) new_num_entry = delete_from_entry; + // Identify whether a basic or a nonbasic entry has been deleted + for (HighsInt entry = delete_from_entry; entry <= delete_to_entry; + entry++) { + if (status[entry] == HighsBasisStatus::kBasic) { + deleted_basic = true; + } else { + deleted_nonbasic = true; + } + } + if (delete_to_entry >= entry_dim - 1) break; + for (HighsInt entry = keep_from_entry; entry <= keep_to_entry; entry++) { + status[new_num_entry] = status[entry]; + new_num_entry++; + } + if (keep_to_entry >= entry_dim - 1) break; + } + status.resize(new_num_entry); +} + +void deleteBasisCols(HighsBasis& basis, + const HighsIndexCollection& index_collection, + const HighsInt original_num_col) { + bool deleted_basic; + bool deleted_nonbasic; + deleteBasisEntries(basis.col_status, deleted_basic, deleted_nonbasic, + index_collection, original_num_col); + if (deleted_basic) basis.valid = false; +} + +void deleteBasisRows(HighsBasis& basis, + const HighsIndexCollection& index_collection, + const HighsInt original_num_row) { + bool deleted_basic; + bool deleted_nonbasic; + deleteBasisEntries(basis.row_status, deleted_basic, deleted_nonbasic, + index_collection, original_num_row); + if (deleted_nonbasic) basis.valid = false; +} + void Highs::deleteColsInterface(HighsIndexCollection& index_collection) { HighsLp& lp = model_.lp_; HighsBasis& basis = basis_; @@ -587,13 +651,24 @@ void Highs::deleteColsInterface(HighsIndexCollection& index_collection) { lp.deleteCols(index_collection); model_.hessian_.deleteCols(index_collection); - assert(lp.num_col_ <= original_num_col); - if (lp.num_col_ < original_num_col) { - // Nontrivial deletion so reset the model_status and invalidate - // the Highs basis - model_status_ = HighsModelStatus::kNotset; - basis.valid = false; + // Bail out if no columns were actually deleted + if (lp.num_col_ == original_num_col) return; + + assert(lp.num_col_ < original_num_col); + + // Nontrivial deletion so reset the model_status and update any + // Highs basis + model_status_ = HighsModelStatus::kNotset; + if (basis_.useful) { + assert(basis_.col_status.size() == static_cast(original_num_col)); + // Have a full set of column basis status values, so maintain + // them, and only invalidate the basis if a basic column has been + // deleted + deleteBasisCols(basis_, index_collection, original_num_col); + } else { + assert(!basis.valid); } + if (lp.scale_.has_scaling) { deleteScale(lp.scale_.col, index_collection); lp.scale_.col.resize(lp.num_col_); @@ -632,13 +707,24 @@ void Highs::deleteRowsInterface(HighsIndexCollection& index_collection) { HighsInt original_num_row = lp.num_row_; lp.deleteRows(index_collection); - assert(lp.num_row_ <= original_num_row); - if (lp.num_row_ < original_num_row) { - // Nontrivial deletion so reset the model_status and invalidate - // the Highs basis - model_status_ = HighsModelStatus::kNotset; - basis.valid = false; + // Bail out if no rows were actually deleted + if (lp.num_row_ == original_num_row) return; + + assert(lp.num_row_ < original_num_row); + + // Nontrivial deletion so reset the model_status and update any + // Highs basis + model_status_ = HighsModelStatus::kNotset; + if (basis_.useful) { + assert(basis_.row_status.size() == static_cast(original_num_row)); + // Have a full set of row basis status values, so maintain them, + // and only invalidate the basis if a nonbasic row has been + // deleted + deleteBasisRows(basis_, index_collection, original_num_row); + } else { + assert(!basis.valid); } + if (lp.scale_.has_scaling) { deleteScale(lp.scale_.row, index_collection); lp.scale_.row.resize(lp.num_row_); @@ -666,181 +752,36 @@ void Highs::deleteRowsInterface(HighsIndexCollection& index_collection) { } void Highs::getColsInterface(const HighsIndexCollection& index_collection, - HighsInt& get_num_col, double* col_cost, - double* col_lower, double* col_upper, - HighsInt& get_num_nz, HighsInt* col_matrix_start, - HighsInt* col_matrix_index, - double* col_matrix_value) { - HighsLp& lp = model_.lp_; - // Ensure that the LP is column-wise - lp.ensureColwise(); - assert(ok(index_collection)); - HighsInt from_k; - HighsInt to_k; - limits(index_collection, from_k, to_k); - // Surely this is checked elsewhere - assert(0 <= from_k && to_k < lp.num_col_); - assert(from_k <= to_k); - HighsInt out_from_col; - HighsInt out_to_col; - HighsInt in_from_col; - HighsInt in_to_col = -1; - HighsInt current_set_entry = 0; - HighsInt col_dim = lp.num_col_; - get_num_col = 0; - get_num_nz = 0; - for (HighsInt k = from_k; k <= to_k; k++) { - updateOutInIndex(index_collection, out_from_col, out_to_col, in_from_col, - in_to_col, current_set_entry); - assert(out_to_col < col_dim); - assert(in_to_col < col_dim); - for (HighsInt iCol = out_from_col; iCol <= out_to_col; iCol++) { - if (col_cost != NULL) col_cost[get_num_col] = lp.col_cost_[iCol]; - if (col_lower != NULL) col_lower[get_num_col] = lp.col_lower_[iCol]; - if (col_upper != NULL) col_upper[get_num_col] = lp.col_upper_[iCol]; - if (col_matrix_start != NULL) - col_matrix_start[get_num_col] = get_num_nz + lp.a_matrix_.start_[iCol] - - lp.a_matrix_.start_[out_from_col]; - get_num_col++; - } - for (HighsInt iEl = lp.a_matrix_.start_[out_from_col]; - iEl < lp.a_matrix_.start_[out_to_col + 1]; iEl++) { - if (col_matrix_index != NULL) - col_matrix_index[get_num_nz] = lp.a_matrix_.index_[iEl]; - if (col_matrix_value != NULL) - col_matrix_value[get_num_nz] = lp.a_matrix_.value_[iEl]; - get_num_nz++; - } - if (out_to_col == col_dim - 1 || in_to_col == col_dim - 1) break; + HighsInt& num_col, double* cost, double* lower, + double* upper, HighsInt& num_nz, HighsInt* start, + HighsInt* index, double* value) { + const HighsLp& lp = model_.lp_; + if (lp.a_matrix_.isColwise()) { + getSubVectors(index_collection, lp.num_col_, lp.col_cost_.data(), + lp.col_lower_.data(), lp.col_upper_.data(), lp.a_matrix_, + num_col, cost, lower, upper, num_nz, start, index, value); + } else { + getSubVectorsTranspose(index_collection, lp.num_col_, lp.col_cost_.data(), + lp.col_lower_.data(), lp.col_upper_.data(), + lp.a_matrix_, num_col, cost, lower, upper, num_nz, + start, index, value); } } void Highs::getRowsInterface(const HighsIndexCollection& index_collection, - HighsInt& get_num_row, double* row_lower, - double* row_upper, HighsInt& get_num_nz, - HighsInt* row_matrix_start, - HighsInt* row_matrix_index, - double* row_matrix_value) { - HighsLp& lp = model_.lp_; - // Ensure that the LP is column-wise - lp.ensureColwise(); - assert(ok(index_collection)); - HighsInt from_k; - HighsInt to_k; - limits(index_collection, from_k, to_k); - // Surely this is checked elsewhere - assert(0 <= from_k && to_k < lp.num_row_); - assert(from_k <= to_k); - // "Out" means not in the set to be extracted - // "In" means in the set to be extracted - HighsInt out_from_row; - HighsInt out_to_row; - HighsInt in_from_row; - HighsInt in_to_row = -1; - HighsInt current_set_entry = 0; - HighsInt row_dim = lp.num_row_; - // Ensure that the LP is column-wise - lp.ensureColwise(); - // Set up a row mask so that entries to be got from the column-wise - // matrix can be identified and have their correct row index. - vector new_index; - new_index.resize(lp.num_row_); - - get_num_row = 0; - get_num_nz = 0; - if (!index_collection.is_mask_) { - out_to_row = -1; - current_set_entry = 0; - for (HighsInt k = from_k; k <= to_k; k++) { - updateOutInIndex(index_collection, in_from_row, in_to_row, out_from_row, - out_to_row, current_set_entry); - if (k == from_k) { - // Account for any initial rows not being extracted - for (HighsInt iRow = 0; iRow < in_from_row; iRow++) { - new_index[iRow] = -1; - } - } - for (HighsInt iRow = in_from_row; iRow <= in_to_row; iRow++) { - new_index[iRow] = get_num_row; - get_num_row++; - } - for (HighsInt iRow = out_from_row; iRow <= out_to_row; iRow++) { - new_index[iRow] = -1; - } - if (out_to_row >= row_dim - 1) break; - } + HighsInt& num_row, double* lower, double* upper, + HighsInt& num_nz, HighsInt* start, HighsInt* index, + double* value) { + const HighsLp& lp = model_.lp_; + if (lp.a_matrix_.isColwise()) { + getSubVectorsTranspose(index_collection, lp.num_row_, nullptr, + lp.row_lower_.data(), lp.row_upper_.data(), + lp.a_matrix_, num_row, nullptr, lower, upper, num_nz, + start, index, value); } else { - for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) { - if (index_collection.mask_[iRow]) { - new_index[iRow] = get_num_row; - get_num_row++; - } else { - new_index[iRow] = -1; - } - } - } - - // Bail out if no rows are to be extracted - if (get_num_row == 0) return; - - for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) { - HighsInt new_iRow = new_index[iRow]; - if (new_iRow >= 0) { - assert(new_iRow < get_num_row); - if (row_lower != NULL) row_lower[new_iRow] = lp.row_lower_[iRow]; - if (row_upper != NULL) row_upper[new_iRow] = lp.row_upper_[iRow]; - } - } - const bool extract_start = row_matrix_start != NULL; - const bool extract_index = row_matrix_index != NULL; - const bool extract_value = row_matrix_value != NULL; - const bool extract_matrix = extract_index || extract_value; - // Allocate an array of lengths for the row-wise matrix to be - // extracted: necessary even if just the number of nonzeros is - // required - vector row_matrix_length; - row_matrix_length.assign(get_num_row, 0); - // Identify the lengths of the rows in the row-wise matrix to be extracted - for (HighsInt col = 0; col < lp.num_col_; col++) { - for (HighsInt iEl = lp.a_matrix_.start_[col]; - iEl < lp.a_matrix_.start_[col + 1]; iEl++) { - HighsInt iRow = lp.a_matrix_.index_[iEl]; - HighsInt new_iRow = new_index[iRow]; - if (new_iRow >= 0) row_matrix_length[new_iRow]++; - } - } - if (!extract_start) { - // bail out if no matrix starts are to be extracted, but only after - // computing the number of nonzeros - for (HighsInt iRow = 0; iRow < get_num_row; iRow++) - get_num_nz += row_matrix_length[iRow]; - return; - } - // Allocate an array of lengths for the row-wise matrix to be extracted - row_matrix_start[0] = 0; - for (HighsInt iRow = 0; iRow < get_num_row - 1; iRow++) { - row_matrix_start[iRow + 1] = - row_matrix_start[iRow] + row_matrix_length[iRow]; - row_matrix_length[iRow] = row_matrix_start[iRow]; - } - HighsInt iRow = get_num_row - 1; - get_num_nz = row_matrix_start[iRow] + row_matrix_length[iRow]; - // Bail out if matrix indices and values are not required - if (!extract_matrix) return; - row_matrix_length[iRow] = row_matrix_start[iRow]; - // Fill the row-wise matrix with indices and values - for (HighsInt col = 0; col < lp.num_col_; col++) { - for (HighsInt iEl = lp.a_matrix_.start_[col]; - iEl < lp.a_matrix_.start_[col + 1]; iEl++) { - HighsInt iRow = lp.a_matrix_.index_[iEl]; - HighsInt new_iRow = new_index[iRow]; - if (new_iRow >= 0) { - HighsInt row_iEl = row_matrix_length[new_iRow]; - if (extract_index) row_matrix_index[row_iEl] = col; - if (extract_value) row_matrix_value[row_iEl] = lp.a_matrix_.value_[iEl]; - row_matrix_length[new_iRow]++; - } - } + getSubVectors(index_collection, lp.num_row_, nullptr, lp.row_lower_.data(), + lp.row_upper_.data(), lp.a_matrix_, num_row, nullptr, lower, + upper, num_nz, start, index, value); } } @@ -1314,14 +1255,17 @@ void Highs::setNonbasicStatusInterface( } void Highs::appendNonbasicColsToBasisInterface(const HighsInt ext_num_new_col) { + if (ext_num_new_col == 0) return; HighsBasis& highs_basis = basis_; - if (!highs_basis.valid) return; + if (!highs_basis.useful) return; const bool has_simplex_basis = ekk_instance_.status_.has_basis; SimplexBasis& simplex_basis = ekk_instance_.basis_; HighsLp& lp = model_.lp_; + assert(highs_basis.col_status.size() == static_cast(lp.num_col_)); + assert(highs_basis.row_status.size() == static_cast(lp.num_row_)); + // Add nonbasic structurals - if (ext_num_new_col == 0) return; HighsInt newNumCol = lp.num_col_ + ext_num_new_col; HighsInt newNumTot = newNumCol + lp.num_row_; highs_basis.col_status.resize(newNumCol); @@ -1388,13 +1332,17 @@ void Highs::appendNonbasicColsToBasisInterface(const HighsInt ext_num_new_col) { } void Highs::appendBasicRowsToBasisInterface(const HighsInt ext_num_new_row) { + if (ext_num_new_row == 0) return; HighsBasis& highs_basis = basis_; - if (!highs_basis.valid) return; + if (!highs_basis.useful) return; const bool has_simplex_basis = ekk_instance_.status_.has_basis; SimplexBasis& simplex_basis = ekk_instance_.basis_; HighsLp& lp = model_.lp_; + + assert(highs_basis.col_status.size() == static_cast(lp.num_col_)); + assert(highs_basis.row_status.size() == static_cast(lp.num_row_)); + // Add basic logicals - if (ext_num_new_row == 0) return; // Add the new rows to the Highs basis HighsInt newNumRow = lp.num_row_ + ext_num_new_row; highs_basis.row_status.resize(newNumRow); @@ -1683,6 +1631,7 @@ HighsStatus Highs::setHotStartInterface(const HotStart& hot_start) { nonbasicMove[num_col + iRow] = move; } basis_.valid = true; + basis_.useful = true; ekk_instance_.status_.has_basis = true; ekk_instance_.setNlaRefactorInfo(); ekk_instance_.updateStatus(LpAction::kHotStart); @@ -2089,6 +2038,12 @@ HighsStatus Highs::elasticityFilterReturn( run_status = this->deleteCols(original_num_col, lp.num_col_ - 1); assert(run_status == HighsStatus::kOk); + // + // Now that deleteRows and deleteCols may yield a valid basis, the + // lack of dual values triggers an assert in + // getKktFailures. Ultimately (#2081) the dual values will be + // available but, for now, make the basis invalid. + basis_.valid = false; run_status = this->changeColsCost(0, original_num_col - 1, original_col_cost.data()); diff --git a/src/lp_data/HighsLpUtils.cpp b/src/lp_data/HighsLpUtils.cpp index 253d8a26f6..6b6042fc03 100644 --- a/src/lp_data/HighsLpUtils.cpp +++ b/src/lp_data/HighsLpUtils.cpp @@ -23,7 +23,6 @@ #include "util/HighsCDouble.h" #include "util/HighsMatrixUtils.h" #include "util/HighsSort.h" -#include "util/HighsTimer.h" using std::fabs; using std::max; @@ -3077,3 +3076,196 @@ void removeRowsOfCountOne(const HighsLogOptions& log_options, HighsLp& lp) { highsLogUser(log_options, HighsLogType::kWarning, "Removed %d rows of count 1\n", (int)num_row_count_1); } + +void getSubVectors(const HighsIndexCollection& index_collection, + const HighsInt data_dim, const double* data0, + const double* data1, const double* data2, + const HighsSparseMatrix& matrix, HighsInt& num_sub_vector, + double* sub_vector_data0, double* sub_vector_data1, + double* sub_vector_data2, HighsInt& sub_matrix_num_nz, + HighsInt* sub_matrix_start, HighsInt* sub_matrix_index, + double* sub_matrix_value) { + // Ensure that if there's no data0 then it's not required in the + // sub-vector + if (data0 == nullptr) assert(sub_vector_data0 == nullptr); + assert(ok(index_collection)); + HighsInt from_k; + HighsInt to_k; + limits(index_collection, from_k, to_k); + // Surely this is checked elsewhere + assert(0 <= from_k && to_k < data_dim); + assert(from_k <= to_k); + HighsInt out_from_vector; + HighsInt out_to_vector; + HighsInt in_from_vector; + HighsInt in_to_vector = -1; + HighsInt current_set_entry = 0; + num_sub_vector = 0; + sub_matrix_num_nz = 0; + for (HighsInt k = from_k; k <= to_k; k++) { + updateOutInIndex(index_collection, out_from_vector, out_to_vector, + in_from_vector, in_to_vector, current_set_entry); + assert(out_to_vector < data_dim); + assert(in_to_vector < data_dim); + for (HighsInt iVector = out_from_vector; iVector <= out_to_vector; + iVector++) { + if (sub_vector_data0 != nullptr) + sub_vector_data0[num_sub_vector] = data0[iVector]; + if (sub_vector_data1 != nullptr) + sub_vector_data1[num_sub_vector] = data1[iVector]; + if (sub_vector_data2 != nullptr) + sub_vector_data2[num_sub_vector] = data2[iVector]; + if (sub_matrix_start != nullptr) + sub_matrix_start[num_sub_vector] = sub_matrix_num_nz + + matrix.start_[iVector] - + matrix.start_[out_from_vector]; + num_sub_vector++; + } + for (HighsInt iEl = matrix.start_[out_from_vector]; + iEl < matrix.start_[out_to_vector + 1]; iEl++) { + if (sub_matrix_index != nullptr) + sub_matrix_index[sub_matrix_num_nz] = matrix.index_[iEl]; + if (sub_matrix_value != nullptr) + sub_matrix_value[sub_matrix_num_nz] = matrix.value_[iEl]; + sub_matrix_num_nz++; + } + if (out_to_vector == data_dim - 1 || in_to_vector == data_dim - 1) break; + } +} + +void getSubVectorsTranspose(const HighsIndexCollection& index_collection, + const HighsInt data_dim, const double* data0, + const double* data1, const double* data2, + const HighsSparseMatrix& matrix, + HighsInt& num_sub_vector, double* sub_vector_data0, + double* sub_vector_data1, double* sub_vector_data2, + HighsInt& sub_matrix_num_nz, + HighsInt* sub_matrix_start, + HighsInt* sub_matrix_index, + double* sub_matrix_value) { + // Ensure that if there's no data0 then it's not required in the + // sub-vector + if (data0 == nullptr) assert(sub_vector_data0 == nullptr); + assert(ok(index_collection)); + HighsInt from_k; + HighsInt to_k; + limits(index_collection, from_k, to_k); + // Surely this is checked elsewhere + assert(0 <= from_k && to_k < data_dim); + assert(from_k <= to_k); + // "Out" means not in the set to be extracted + // "In" means in the set to be extracted + HighsInt out_from_vector; + HighsInt out_to_vector; + HighsInt in_from_vector; + HighsInt in_to_vector = -1; + HighsInt current_set_entry = 0; + // Set up a mask so that entries to be got from the matrix can be + // identified and have their correct index. + vector new_index; + new_index.resize(data_dim); + + num_sub_vector = 0; + sub_matrix_num_nz = 0; + if (!index_collection.is_mask_) { + out_to_vector = -1; + current_set_entry = 0; + for (HighsInt k = from_k; k <= to_k; k++) { + updateOutInIndex(index_collection, in_from_vector, in_to_vector, + out_from_vector, out_to_vector, current_set_entry); + if (k == from_k) { + // Account for any initial vectors not being extracted + for (HighsInt iVector = 0; iVector < in_from_vector; iVector++) { + new_index[iVector] = -1; + } + } + for (HighsInt iVector = in_from_vector; iVector <= in_to_vector; + iVector++) { + new_index[iVector] = num_sub_vector; + num_sub_vector++; + } + for (HighsInt iVector = out_from_vector; iVector <= out_to_vector; + iVector++) { + new_index[iVector] = -1; + } + if (out_to_vector >= data_dim - 1) break; + } + } else { + for (HighsInt iVector = 0; iVector < data_dim; iVector++) { + if (index_collection.mask_[iVector]) { + new_index[iVector] = num_sub_vector; + num_sub_vector++; + } else { + new_index[iVector] = -1; + } + } + } + + // Bail out if no vectors are to be extracted + if (num_sub_vector == 0) return; + + for (HighsInt iVector = 0; iVector < data_dim; iVector++) { + HighsInt new_iVector = new_index[iVector]; + if (new_iVector >= 0) { + assert(new_iVector < num_sub_vector); + if (sub_vector_data0 != NULL) + sub_vector_data0[new_iVector] = data0[iVector]; + if (sub_vector_data1 != NULL) + sub_vector_data1[new_iVector] = data1[iVector]; + if (sub_vector_data2 != NULL) + sub_vector_data2[new_iVector] = data2[iVector]; + } + } + const bool extract_start = sub_matrix_start != NULL; + const bool extract_index = sub_matrix_index != NULL; + const bool extract_value = sub_matrix_value != NULL; + const bool extract_matrix = extract_index || extract_value; + // Allocate an array of lengths for the sub-matrix to be + // extracted: necessary even if just the number of nonzeros is + // required + vector sub_matrix_length; + sub_matrix_length.assign(num_sub_vector, 0); + // Identify the lengths of the vectors in the sub-matrix to be extracted + HighsInt num_vector = matrix.start_.size() - 1; + for (HighsInt vector = 0; vector < num_vector; vector++) { + for (HighsInt iEl = matrix.start_[vector]; iEl < matrix.start_[vector + 1]; + iEl++) { + HighsInt iVector = matrix.index_[iEl]; + HighsInt new_iVector = new_index[iVector]; + if (new_iVector >= 0) sub_matrix_length[new_iVector]++; + } + } + if (!extract_start) { + // bail out if no matrix starts are to be extracted, but only after + // computing the number of nonzeros + for (HighsInt iVector = 0; iVector < num_sub_vector; iVector++) + sub_matrix_num_nz += sub_matrix_length[iVector]; + return; + } + // Allocate an array of lengths for the sub-matrix to be extracted + sub_matrix_start[0] = 0; + for (HighsInt iVector = 0; iVector < num_sub_vector - 1; iVector++) { + sub_matrix_start[iVector + 1] = + sub_matrix_start[iVector] + sub_matrix_length[iVector]; + sub_matrix_length[iVector] = sub_matrix_start[iVector]; + } + HighsInt iVector = num_sub_vector - 1; + sub_matrix_num_nz = sub_matrix_start[iVector] + sub_matrix_length[iVector]; + // Bail out if matrix indices and values are not required + if (!extract_matrix) return; + sub_matrix_length[iVector] = sub_matrix_start[iVector]; + // Fill the row-wise matrix with indices and values + for (HighsInt vector = 0; vector < num_vector; vector++) { + for (HighsInt iEl = matrix.start_[vector]; iEl < matrix.start_[vector + 1]; + iEl++) { + HighsInt iVector = matrix.index_[iEl]; + HighsInt new_iVector = new_index[iVector]; + if (new_iVector >= 0) { + HighsInt row_iEl = sub_matrix_length[new_iVector]; + if (extract_index) sub_matrix_index[row_iEl] = vector; + if (extract_value) sub_matrix_value[row_iEl] = matrix.value_[iEl]; + sub_matrix_length[new_iVector]++; + } + } + } +} diff --git a/src/lp_data/HighsLpUtils.h b/src/lp_data/HighsLpUtils.h index c6cff7a8bb..867e92c12c 100644 --- a/src/lp_data/HighsLpUtils.h +++ b/src/lp_data/HighsLpUtils.h @@ -263,4 +263,36 @@ HighsLp withoutSemiVariables(const HighsLp& lp, HighsSolution& solution, void removeRowsOfCountOne(const HighsLogOptions& log_options, HighsLp& lp); +// Get subvectors from data structure of data0, data1, data2 and +// matrix, where the storage of the matrix is compatible with the +// vectors to be extracted from it +// +// Data to be extracted is given by sub_* being nullpointer +// +// * cost, lower and upper bounds for columns, and column-wise LP constraint +// matrix +// +// * lower and upper bounds for rows, and row-wise LP constraint +// * matrix. "cost" is nullptr, and so must be sub_vector_data0 +// +void getSubVectors(const HighsIndexCollection& index_collection, + const HighsInt data_dim, const double* data0, + const double* data1, const double* data2, + const HighsSparseMatrix& matrix, HighsInt& num_sub_vector, + double* sub_vector_data0, double* sub_vector_data1, + double* sub_vector_data2, HighsInt& sub_matrix_num_nz, + HighsInt* sub_matrix_start, HighsInt* sub_matrix_index, + double* sub_matrix_value); + +void getSubVectorsTranspose(const HighsIndexCollection& index_collection, + const HighsInt data_dim, const double* data0, + const double* data1, const double* data2, + const HighsSparseMatrix& matrix, + HighsInt& num_sub_vector, double* sub_vector_data0, + double* sub_vector_data1, double* sub_vector_data2, + HighsInt& sub_matrix_num_nz, + HighsInt* sub_matrix_start, + HighsInt* sub_matrix_index, + double* sub_matrix_value); + #endif // LP_DATA_HIGHSLPUTILS_H_ diff --git a/src/lp_data/HighsOptions.cpp b/src/lp_data/HighsOptions.cpp index 29ddd37629..15c3fb6204 100644 --- a/src/lp_data/HighsOptions.cpp +++ b/src/lp_data/HighsOptions.cpp @@ -811,15 +811,17 @@ void resetLocalOptions(std::vector& option_records) { } } -HighsStatus writeOptionsToFile(FILE* file, +HighsStatus writeOptionsToFile(FILE* file, const HighsLogOptions& log_options, const std::vector& option_records, const bool report_only_deviations, const HighsFileType file_type) { - reportOptions(file, option_records, report_only_deviations, file_type); + reportOptions(file, log_options, option_records, report_only_deviations, + file_type); return HighsStatus::kOk; } -void reportOptions(FILE* file, const std::vector& option_records, +void reportOptions(FILE* file, const HighsLogOptions& log_options, + const std::vector& option_records, const bool report_only_deviations, const HighsFileType file_type) { HighsInt num_options = option_records.size(); @@ -831,22 +833,27 @@ void reportOptions(FILE* file, const std::vector& option_records, if (!kAdvancedInDocumentation) continue; } if (type == HighsOptionType::kBool) { - reportOption(file, ((OptionRecordBool*)option_records[index])[0], + reportOption(file, log_options, + ((OptionRecordBool*)option_records[index])[0], report_only_deviations, file_type); } else if (type == HighsOptionType::kInt) { - reportOption(file, ((OptionRecordInt*)option_records[index])[0], + reportOption(file, log_options, + ((OptionRecordInt*)option_records[index])[0], report_only_deviations, file_type); } else if (type == HighsOptionType::kDouble) { - reportOption(file, ((OptionRecordDouble*)option_records[index])[0], + reportOption(file, log_options, + ((OptionRecordDouble*)option_records[index])[0], report_only_deviations, file_type); } else { - reportOption(file, ((OptionRecordString*)option_records[index])[0], + reportOption(file, log_options, + ((OptionRecordString*)option_records[index])[0], report_only_deviations, file_type); } } } -void reportOption(FILE* file, const OptionRecordBool& option, +void reportOption(FILE* file, const HighsLogOptions& log_options, + const OptionRecordBool& option, const bool report_only_deviations, const HighsFileType file_type) { if (!report_only_deviations || option.default_value != *option.value) { @@ -865,40 +872,53 @@ void reportOption(FILE* file, const OptionRecordBool& option, fprintf(file, "%s = %s\n", option.name.c_str(), highsBoolToString(*option.value).c_str()); } else { - fprintf(file, "%s = %s\n", option.name.c_str(), - highsBoolToString(*option.value).c_str()); + std::string line = + highsFormatToString("Set option %s to %s\n", option.name.c_str(), + highsBoolToString(*option.value).c_str()); + if (file == stdout) { + highsLogUser(log_options, HighsLogType::kInfo, "%s", line.c_str()); + } else { + fprintf(file, "%s", line.c_str()); + } } } } -void reportOption(FILE* file, const OptionRecordInt& option, +void reportOption(FILE* file, const HighsLogOptions& log_options, + const OptionRecordInt& option, const bool report_only_deviations, const HighsFileType file_type) { if (!report_only_deviations || option.default_value != *option.value) { if (file_type == HighsFileType::kMd) { - fprintf(file, - "## %s\n- %s\n- Type: integer\n- Range: {%" HIGHSINT_FORMAT - ", %" HIGHSINT_FORMAT "}\n- Default: %" HIGHSINT_FORMAT "\n\n", - highsInsertMdEscapes(option.name).c_str(), - highsInsertMdEscapes(option.description).c_str(), - option.lower_bound, option.upper_bound, option.default_value); + fprintf( + file, + "## %s\n- %s\n- Type: integer\n- Range: {%d, %d}\n- Default: %d\n\n", + highsInsertMdEscapes(option.name).c_str(), + highsInsertMdEscapes(option.description).c_str(), + int(option.lower_bound), int(option.upper_bound), + int(option.default_value)); } else if (file_type == HighsFileType::kFull) { fprintf(file, "\n# %s\n", option.description.c_str()); fprintf(file, "# [type: integer, advanced: %s, range: {%" HIGHSINT_FORMAT - ", %" HIGHSINT_FORMAT "}, default: %" HIGHSINT_FORMAT "]\n", + ", %d}, default: %d]\n", highsBoolToString(option.advanced).c_str(), option.lower_bound, - option.upper_bound, option.default_value); - fprintf(file, "%s = %" HIGHSINT_FORMAT "\n", option.name.c_str(), - *option.value); + int(option.upper_bound), int(option.default_value)); + fprintf(file, "%s = %d\n", option.name.c_str(), int(*option.value)); } else { - fprintf(file, "%s = %" HIGHSINT_FORMAT "\n", option.name.c_str(), - *option.value); + std::string line = highsFormatToString( + "Set option %s to %d\n", option.name.c_str(), int(*option.value)); + if (file == stdout) { + highsLogUser(log_options, HighsLogType::kInfo, "%s", line.c_str()); + } else { + fprintf(file, "%s", line.c_str()); + } } } } -void reportOption(FILE* file, const OptionRecordDouble& option, +void reportOption(FILE* file, const HighsLogOptions& log_options, + const OptionRecordDouble& option, const bool report_only_deviations, const HighsFileType file_type) { if (!report_only_deviations || option.default_value != *option.value) { @@ -917,12 +937,19 @@ void reportOption(FILE* file, const OptionRecordDouble& option, option.upper_bound, option.default_value); fprintf(file, "%s = %g\n", option.name.c_str(), *option.value); } else { - fprintf(file, "%s = %g\n", option.name.c_str(), *option.value); + std::string line = highsFormatToString( + "Set option %s to %g\n", option.name.c_str(), *option.value); + if (file == stdout) { + highsLogUser(log_options, HighsLogType::kInfo, "%s", line.c_str()); + } else { + fprintf(file, "%s", line.c_str()); + } } } } -void reportOption(FILE* file, const OptionRecordString& option, +void reportOption(FILE* file, const HighsLogOptions& log_options, + const OptionRecordString& option, const bool report_only_deviations, const HighsFileType file_type) { // Don't report for the options file if writing to an options file @@ -943,7 +970,14 @@ void reportOption(FILE* file, const OptionRecordString& option, option.default_value.c_str()); fprintf(file, "%s = %s\n", option.name.c_str(), (*option.value).c_str()); } else { - fprintf(file, "%s = %s\n", option.name.c_str(), (*option.value).c_str()); + std::string line = + highsFormatToString("Set option %s to \"%s\"\n", option.name.c_str(), + (*option.value).c_str()); + if (file == stdout) { + highsLogUser(log_options, HighsLogType::kInfo, "%s", line.c_str()); + } else { + fprintf(file, "%s", line.c_str()); + } } } } diff --git a/src/lp_data/HighsOptions.h b/src/lp_data/HighsOptions.h index ca428d7d71..f89f8d2388 100644 --- a/src/lp_data/HighsOptions.h +++ b/src/lp_data/HighsOptions.h @@ -233,22 +233,28 @@ OptionStatus getLocalOptionType( void resetLocalOptions(std::vector& option_records); HighsStatus writeOptionsToFile( - FILE* file, const std::vector& option_records, + FILE* file, const HighsLogOptions& report_log_options, + const std::vector& option_records, const bool report_only_deviations = false, const HighsFileType file_type = HighsFileType::kFull); -void reportOptions(FILE* file, const std::vector& option_records, +void reportOptions(FILE* file, const HighsLogOptions& report_log_options, + const std::vector& option_records, const bool report_only_deviations = false, const HighsFileType file_type = HighsFileType::kFull); -void reportOption(FILE* file, const OptionRecordBool& option, +void reportOption(FILE* file, const HighsLogOptions& report_log_options, + const OptionRecordBool& option, const bool report_only_deviations, const HighsFileType file_type); -void reportOption(FILE* file, const OptionRecordInt& option, +void reportOption(FILE* file, const HighsLogOptions& report_log_options, + const OptionRecordInt& option, const bool report_only_deviations, const HighsFileType file_type); -void reportOption(FILE* file, const OptionRecordDouble& option, +void reportOption(FILE* file, const HighsLogOptions& report_log_options, + const OptionRecordDouble& option, const bool report_only_deviations, const HighsFileType file_type); -void reportOption(FILE* file, const OptionRecordString& option, +void reportOption(FILE* file, const HighsLogOptions& report_log_options, + const OptionRecordString& option, const bool report_only_deviations, const HighsFileType file_type); @@ -299,6 +305,8 @@ struct HighsOptionsStruct { double primal_feasibility_tolerance; double dual_feasibility_tolerance; double ipm_optimality_tolerance; + double primal_residual_tolerance; + double dual_residual_tolerance; double objective_bound; double objective_target; HighsInt threads; @@ -390,8 +398,6 @@ struct HighsOptionsStruct { bool less_infeasible_DSE_choose_row; bool use_original_HFactor_logic; bool run_centring; - double primal_residual_tolerance; - double dual_residual_tolerance; HighsInt max_centring_steps; double centring_ratio_tolerance; @@ -453,6 +459,8 @@ struct HighsOptionsStruct { primal_feasibility_tolerance(0.0), dual_feasibility_tolerance(0.0), ipm_optimality_tolerance(0.0), + primal_residual_tolerance(0.0), + dual_residual_tolerance(0.0), objective_bound(0.0), objective_target(0.0), threads(0), @@ -529,8 +537,6 @@ struct HighsOptionsStruct { less_infeasible_DSE_choose_row(false), use_original_HFactor_logic(false), run_centring(false), - primal_residual_tolerance(0.0), - dual_residual_tolerance(0.0), max_centring_steps(0), centring_ratio_tolerance(0.0), icrash(false), @@ -709,6 +715,16 @@ class HighsOptions : public HighsOptionsStruct { &ipm_optimality_tolerance, 1e-12, 1e-8, kHighsInf); records.push_back(record_double); + record_double = new OptionRecordDouble( + "primal_residual_tolerance", "Primal residual tolerance", advanced, + &primal_residual_tolerance, 1e-10, 1e-7, kHighsInf); + records.push_back(record_double); + + record_double = new OptionRecordDouble( + "dual_residual_tolerance", "Dual residual tolerance", advanced, + &dual_residual_tolerance, 1e-10, 1e-7, kHighsInf); + records.push_back(record_double); + record_double = new OptionRecordDouble( "objective_bound", "Objective bound for termination of the dual simplex solver", advanced, @@ -1407,16 +1423,6 @@ class HighsOptions : public HighsOptionsStruct { advanced, ¢ring_ratio_tolerance, 0, 100, kHighsInf); records.push_back(record_double); - record_double = new OptionRecordDouble( - "primal_residual_tolerance", "Primal residual tolerance", advanced, - &primal_residual_tolerance, 1e-10, 1e-7, kHighsInf); - records.push_back(record_double); - - record_double = new OptionRecordDouble( - "dual_residual_tolerance", "Dual residual tolerance", advanced, - &dual_residual_tolerance, 1e-10, 1e-7, kHighsInf); - records.push_back(record_double); - // Set up the log_options aliases log_options.clear(); log_options.log_stream = diff --git a/src/lp_data/HighsSolution.cpp b/src/lp_data/HighsSolution.cpp index 5375a3b6b9..a28060861a 100644 --- a/src/lp_data/HighsSolution.cpp +++ b/src/lp_data/HighsSolution.cpp @@ -677,7 +677,7 @@ double computeObjectiveValue(const HighsLp& lp, const HighsSolution& solution) { // and any solution values void refineBasis(const HighsLp& lp, const HighsSolution& solution, HighsBasis& basis) { - assert(basis.valid); + assert(basis.useful); assert(isBasisRightSize(lp, basis)); const bool have_highs_solution = solution.value_valid; @@ -1319,6 +1319,7 @@ HighsStatus ipxBasicSolutionToHighsBasicSolution( highs_solution.value_valid = true; highs_solution.dual_valid = true; highs_basis.valid = true; + highs_basis.useful = true; return HighsStatus::kOk; } @@ -1356,9 +1357,14 @@ HighsStatus formSimplexLpBasisAndFactor(HighsLpSolverObject& solver_object, // If new scaling is performed, the hot start information is // no longer valid if (new_scaling) ekk_instance.clearHotStart(); - if (basis.alien) { - // An alien basis needs to be checked for rank deficiency, and - // possibly completed if it is rectangular + const bool check_basis = basis.alien || (!basis.valid && basis.useful); + if (check_basis) { + // The basis needs to be checked for rank deficiency, and possibly + // completed if it is rectangular + // + // If it's not valid but useful, but not alien, + // accommodateAlienBasis will assert, so make the basis alien + basis.alien = true; assert(!only_from_known_basis); accommodateAlienBasis(solver_object); basis.alien = false; @@ -1605,9 +1611,32 @@ void HighsSolution::clear() { void HighsObjectiveSolution::clear() { this->col_value.clear(); } +void HighsBasis::print(std::string message) const { + if (!this->useful) return; + this->printScalars(message); + for (HighsInt iCol = 0; iCol < HighsInt(this->col_status.size()); iCol++) + printf("Basis: col_status[%2d] = %d\n", int(iCol), + int(this->col_status[iCol])); + for (HighsInt iRow = 0; iRow < HighsInt(this->row_status.size()); iRow++) + printf("Basis: row_status[%2d] = %d\n", int(iRow), + int(this->row_status[iRow])); +} + +void HighsBasis::printScalars(std::string message) const { + printf("\nBasis: %s\n", message.c_str()); + printf(" valid = %d\n", this->valid); + printf(" alien = %d\n", this->alien); + printf(" useful = %d\n", this->useful); + printf(" was_alien = %d\n", this->was_alien); + printf(" debug_id = %d\n", int(this->debug_id)); + printf(" debug_update_count = %d\n", int(this->debug_update_count)); + printf(" debug_origin_name = %s\n", this->debug_origin_name.c_str()); +} + void HighsBasis::invalidate() { this->valid = false; this->alien = true; + this->useful = false; this->was_alien = true; this->debug_id = -1; this->debug_update_count = -1; diff --git a/src/lp_data/HighsSolve.cpp b/src/lp_data/HighsSolve.cpp index 1062b35e5f..acc81abf43 100644 --- a/src/lp_data/HighsSolve.cpp +++ b/src/lp_data/HighsSolve.cpp @@ -390,6 +390,7 @@ HighsStatus solveUnconstrainedLp(const HighsOptions& options, const HighsLp& lp, solution.value_valid = true; solution.dual_valid = true; basis.valid = true; + basis.useful = true; highs_info.basis_validity = kBasisValidityValid; setSolutionStatus(highs_info); if (highs_info.num_primal_infeasibilities) { diff --git a/src/mip/HighsLpRelaxation.cpp b/src/mip/HighsLpRelaxation.cpp index 926a9616f8..f22891a2fc 100644 --- a/src/mip/HighsLpRelaxation.cpp +++ b/src/mip/HighsLpRelaxation.cpp @@ -1043,9 +1043,9 @@ void HighsLpRelaxation::setObjectiveLimit(double objlim) { } HighsLpRelaxation::Status HighsLpRelaxation::run(bool resolve_on_error) { - lpsolver.setOptionValue( - "time_limit", lpsolver.getRunTime() + mipsolver.options_mip_->time_limit - - mipsolver.timer_.read(mipsolver.timer_.total_clock)); + lpsolver.setOptionValue("time_limit", lpsolver.getRunTime() + + mipsolver.options_mip_->time_limit - + mipsolver.timer_.read()); // lpsolver.setOptionValue("output_flag", true); const bool valid_basis = lpsolver.getBasis().valid; const HighsInt simplex_solve_clock = valid_basis diff --git a/src/mip/HighsMipSolver.cpp b/src/mip/HighsMipSolver.cpp index 2b395d250c..8fb98b006c 100644 --- a/src/mip/HighsMipSolver.cpp +++ b/src/mip/HighsMipSolver.cpp @@ -113,9 +113,7 @@ void HighsMipSolver::run() { analysis_.timer_ = &this->timer_; analysis_.setup(*orig_model_, *options_mip_); } - // Start the total_clock for the timer that is local to the HighsMipSolver - // instance - timer_.start(timer_.total_clock); + timer_.start(); improving_solution_file_ = nullptr; if (!submip && options_mip_->mip_improving_solution_file != "") @@ -136,7 +134,7 @@ void HighsMipSolver::run() { "MIP-Timing: %11.2g - completed presolve\n", timer_.read()); // Identify whether time limit has been reached (in presolve) if (modelstatus_ == HighsModelStatus::kNotset && - timer_.read(timer_.total_clock) >= options_mip_->time_limit) + timer_.read() >= options_mip_->time_limit) modelstatus_ = HighsModelStatus::kTimeLimit; if (modelstatus_ != HighsModelStatus::kNotset) { @@ -163,8 +161,7 @@ void HighsMipSolver::run() { analysis_.mipTimerStop(kMipClockRunSetup); if (analysis_.analyse_mip_time & !submip) highsLogUser(options_mip_->log_options, HighsLogType::kInfo, - "MIP-Timing: %11.2g - completed setup\n", - timer_.read(timer_.total_clock)); + "MIP-Timing: %11.2g - completed setup\n", timer_.read()); restart: if (modelstatus_ == HighsModelStatus::kNotset) { // Check limits have not been reached before evaluating root node @@ -199,7 +196,7 @@ void HighsMipSolver::run() { if (analysis_.analyse_mip_time & !submip) highsLogUser(options_mip_->log_options, HighsLogType::kInfo, "MIP-Timing: %11.2g - starting evaluate root node\n", - timer_.read(timer_.total_clock)); + timer_.read()); analysis_.mipTimerStart(kMipClockEvaluateRootNode); mipdata_->evaluateRootNode(); analysis_.mipTimerStop(kMipClockEvaluateRootNode); @@ -211,7 +208,7 @@ void HighsMipSolver::run() { if (analysis_.analyse_mip_time & !submip) highsLogUser(options_mip_->log_options, HighsLogType::kInfo, "MIP-Timing: %11.2g - completed evaluate root node\n", - timer_.read(timer_.total_clock)); + timer_.read()); // age 5 times to remove stored but never violated cuts after root // separation analysis_.mipTimerStart(kMipClockPerformAging0); @@ -731,7 +728,7 @@ void HighsMipSolver::cleanupSolve() { } analysis_.mipTimerStop(kMipClockPostsolve); - timer_.stop(timer_.total_clock); + timer_.stop(); std::string solutionstatus = "-"; @@ -817,8 +814,7 @@ void HighsMipSolver::cleanupSolve() { " %llu (strong br.)\n" " %llu (separation)\n" " %llu (heuristics)\n", - timer_.read(timer_.total_clock), - analysis_.mipTimerRead(kMipClockPresolve), + timer_.read(), analysis_.mipTimerRead(kMipClockPresolve), analysis_.mipTimerRead(kMipClockSolve), analysis_.mipTimerRead(kMipClockPostsolve), int(max_submip_level), (long long unsigned)mipdata_->num_nodes, diff --git a/src/mip/HighsMipSolverData.cpp b/src/mip/HighsMipSolverData.cpp index dd2945e26e..00ff2bb308 100644 --- a/src/mip/HighsMipSolverData.cpp +++ b/src/mip/HighsMipSolverData.cpp @@ -1130,10 +1130,8 @@ double HighsMipSolverData::transformNewIntegerFeasibleSolution( } } this->total_repair_lp++; - double time_available = - std::max(mipsolver.options_mip_->time_limit - - mipsolver.timer_.read(mipsolver.timer_.total_clock), - 0.1); + double time_available = std::max( + mipsolver.options_mip_->time_limit - mipsolver.timer_.read(), 0.1); Highs tmpSolver; const bool debug_report = false; if (debug_report) { @@ -1306,6 +1304,7 @@ void HighsMipSolverData::performRestart() { root_basis.row_status.resize(postSolveStack.getOrigNumRow(), HighsBasisStatus::kBasic); root_basis.valid = true; + root_basis.useful = true; for (HighsInt i = 0; i < mipsolver.model_->num_col_; ++i) root_basis.col_status[postSolveStack.getOrigColIndex(i)] = @@ -1420,6 +1419,7 @@ void HighsMipSolverData::basisTransfer() { firstrootbasis.row_status.assign(numRow, HighsBasisStatus::kNonbasic); firstrootbasis.valid = true; firstrootbasis.alien = true; + firstrootbasis.useful = true; for (HighsInt i = 0; i < numRow; ++i) { HighsBasisStatus status = @@ -1637,7 +1637,7 @@ void HighsMipSolverData::printDisplayLine(const int solution_source) { bool output_flag = *mipsolver.options_mip_->log_options.output_flag; if (!output_flag) return; - double time = mipsolver.timer_.read(mipsolver.timer_.total_clock); + double time = mipsolver.timer_.read(); if (solution_source == kSolutionSourceNone && time - last_disptime < mipsolver.options_mip_->mip_min_logging_interval) return; @@ -1979,6 +1979,7 @@ void HighsMipSolverData::evaluateRootNode() { firstrootbasis.row_status.assign(mipsolver.numRow(), HighsBasisStatus::kBasic); firstrootbasis.valid = true; + firstrootbasis.useful = true; } if (cutpool.getNumCuts() != 0) { @@ -2425,11 +2426,10 @@ bool HighsMipSolverData::checkLimits(int64_t nodeOffset) const { return true; } - // const double time = mipsolver.timer_.read(mipsolver.timer_.total_clock); + // const double time = mipsolver.timer_.read(); // printf("checkLimits: time = %g\n", time); if (options.time_limit < kHighsInf && - mipsolver.timer_.read(mipsolver.timer_.total_clock) >= - options.time_limit) { + mipsolver.timer_.read() >= options.time_limit) { if (mipsolver.modelstatus_ == HighsModelStatus::kNotset) { highsLogDev(options.log_options, HighsLogType::kInfo, "Reached time limit\n"); @@ -2535,8 +2535,7 @@ bool HighsMipSolverData::interruptFromCallbackWithData( double primal_bound; double mip_rel_gap; limitsToBounds(dual_bound, primal_bound, mip_rel_gap); - mipsolver.callback_->data_out.running_time = - mipsolver.timer_.read(mipsolver.timer_.total_clock); + mipsolver.callback_->data_out.running_time = mipsolver.timer_.read(); mipsolver.callback_->data_out.objective_function_value = mipsolver_objective_value; mipsolver.callback_->data_out.mip_node_count = mipsolver.mipdata_->num_nodes; @@ -2665,7 +2664,7 @@ void HighsMipSolverData::updatePrimalDualIntegral(const double from_lower_bound, assert(gap_consistent); } if (to_gap < kHighsInf) { - double time = mipsolver.timer_.read(mipsolver.timer_.total_clock); + double time = mipsolver.timer_.read(); if (from_gap < kHighsInf) { // Need to update the P-D integral double time_diff = time - pdi.prev_time; diff --git a/src/mip/HighsModkSeparator.h b/src/mip/HighsModkSeparator.h index 203e59bf13..43f8f01442 100644 --- a/src/mip/HighsModkSeparator.h +++ b/src/mip/HighsModkSeparator.h @@ -54,7 +54,7 @@ class HighsModkSeparator : public HighsSeparator { HighsCutPool& cutpool) override; HighsModkSeparator(const HighsMipSolver& mipsolver) - : HighsSeparator(mipsolver, "Mod-k sepa", "Mod") {} + : HighsSeparator(mipsolver, "Mod-k sepa") {} }; #endif diff --git a/src/mip/HighsPathSeparator.h b/src/mip/HighsPathSeparator.h index 8cb13c6138..88a5e3e238 100644 --- a/src/mip/HighsPathSeparator.h +++ b/src/mip/HighsPathSeparator.h @@ -31,7 +31,7 @@ class HighsPathSeparator : public HighsSeparator { HighsCutPool& cutpool) override; HighsPathSeparator(const HighsMipSolver& mipsolver) - : HighsSeparator(mipsolver, "PathAggr sepa", "Agg") { + : HighsSeparator(mipsolver, "PathAggr sepa") { randgen.initialise(mipsolver.options_mip_->random_seed); } }; diff --git a/src/mip/HighsPrimalHeuristics.cpp b/src/mip/HighsPrimalHeuristics.cpp index 43e34bae6f..fc36b59d62 100644 --- a/src/mip/HighsPrimalHeuristics.cpp +++ b/src/mip/HighsPrimalHeuristics.cpp @@ -108,8 +108,7 @@ bool HighsPrimalHeuristics::solveSubMip( submipoptions.mip_max_nodes = maxnodes; submipoptions.mip_max_stall_nodes = stallnodes; submipoptions.mip_pscost_minreliable = 0; - submipoptions.time_limit -= - mipsolver.timer_.read(mipsolver.timer_.total_clock); + submipoptions.time_limit -= mipsolver.timer_.read(); submipoptions.objective_bound = mipsolver.mipdata_->upper_limit; if (!mipsolver.submip) { diff --git a/src/mip/HighsSeparation.cpp b/src/mip/HighsSeparation.cpp index cb5dda1ce5..b125c88df7 100644 --- a/src/mip/HighsSeparation.cpp +++ b/src/mip/HighsSeparation.cpp @@ -23,8 +23,8 @@ #include "mip/HighsTransformedLp.h" HighsSeparation::HighsSeparation(const HighsMipSolver& mipsolver) { - implBoundClock = mipsolver.timer_.clock_def("Implbound sepa", "Ibd"); - cliqueClock = mipsolver.timer_.clock_def("Clique sepa", "Clq"); + implBoundClock = mipsolver.timer_.clock_def("Implbound sepa"); + cliqueClock = mipsolver.timer_.clock_def("Clique sepa"); separators.emplace_back(new HighsTableauSeparator(mipsolver)); separators.emplace_back(new HighsPathSeparator(mipsolver)); separators.emplace_back(new HighsModkSeparator(mipsolver)); diff --git a/src/mip/HighsSeparator.cpp b/src/mip/HighsSeparator.cpp index dd74466d96..8589250d8d 100644 --- a/src/mip/HighsSeparator.cpp +++ b/src/mip/HighsSeparator.cpp @@ -14,9 +14,9 @@ #include "mip/HighsMipSolver.h" HighsSeparator::HighsSeparator(const HighsMipSolver& mipsolver, - const char* name, const char* ch3_name) + const char* name) : numCutsFound(0), numCalls(0) { - clockIndex = mipsolver.timer_.clock_def(name, ch3_name); + clockIndex = mipsolver.timer_.clock_def(name); } void HighsSeparator::run(HighsLpRelaxation& lpRelaxation, diff --git a/src/mip/HighsSeparator.h b/src/mip/HighsSeparator.h index 6a7c1eaf0e..e385538fcf 100644 --- a/src/mip/HighsSeparator.h +++ b/src/mip/HighsSeparator.h @@ -30,8 +30,7 @@ class HighsSeparator { int clockIndex; public: - HighsSeparator(const HighsMipSolver& mipsolver, const char* name, - const char* ch3_name); + HighsSeparator(const HighsMipSolver& mipsolver, const char* name); virtual void separateLpSolution(HighsLpRelaxation& lpRelaxation, HighsLpAggregator& lpAggregator, diff --git a/src/mip/HighsTableauSeparator.h b/src/mip/HighsTableauSeparator.h index 56bb46189e..98d4934206 100644 --- a/src/mip/HighsTableauSeparator.h +++ b/src/mip/HighsTableauSeparator.h @@ -28,7 +28,7 @@ class HighsTableauSeparator : public HighsSeparator { HighsCutPool& cutpool) override; HighsTableauSeparator(const HighsMipSolver& mipsolver) - : HighsSeparator(mipsolver, "Tableau sepa", "Tbl"), numTries(0) {} + : HighsSeparator(mipsolver, "Tableau sepa"), numTries(0) {} }; #endif diff --git a/src/mip/MipTimer.h b/src/mip/MipTimer.h index 9d27a809d1..7841144a2b 100644 --- a/src/mip/MipTimer.h +++ b/src/mip/MipTimer.h @@ -85,7 +85,7 @@ class MipTimer { HighsTimer* timer_pointer = mip_timer_clock.timer_pointer_; std::vector& clock = mip_timer_clock.clock_; clock.resize(kNumMipClock); - clock[kMipClockTotal] = timer_pointer->total_clock; + clock[kMipClockTotal] = 0; clock[kMipClockPresolve] = timer_pointer->clock_def("MIP presolve"); clock[kMipClockSolve] = timer_pointer->clock_def("MIP solve"); clock[kMipClockPostsolve] = timer_pointer->clock_def("MIP postsolve"); diff --git a/src/parallel/HighsBinarySemaphore.h b/src/parallel/HighsBinarySemaphore.h index 5ca24e0f6b..ee40b5931b 100644 --- a/src/parallel/HighsBinarySemaphore.h +++ b/src/parallel/HighsBinarySemaphore.h @@ -110,4 +110,4 @@ class HighsBinarySemaphore { } }; -#endif \ No newline at end of file +#endif diff --git a/src/parallel/HighsCacheAlign.h b/src/parallel/HighsCacheAlign.h index b16e145a47..90ec627617 100644 --- a/src/parallel/HighsCacheAlign.h +++ b/src/parallel/HighsCacheAlign.h @@ -84,4 +84,4 @@ struct cache_aligned { } // namespace highs -#endif \ No newline at end of file +#endif diff --git a/src/parallel/HighsCombinable.h b/src/parallel/HighsCombinable.h index b7b877189e..ddfa8561e7 100644 --- a/src/parallel/HighsCombinable.h +++ b/src/parallel/HighsCombinable.h @@ -118,4 +118,4 @@ HighsCombinable makeHighsCombinable(FConstruct&& fconstruct) { return HighsCombinable(std::forward(fconstruct)); } -#endif \ No newline at end of file +#endif diff --git a/src/parallel/HighsMutex.h b/src/parallel/HighsMutex.h index 34d8b95541..787b492c9d 100644 --- a/src/parallel/HighsMutex.h +++ b/src/parallel/HighsMutex.h @@ -126,4 +126,4 @@ class HighsMutex { } }; -#endif \ No newline at end of file +#endif diff --git a/src/parallel/HighsRaceTimer.h b/src/parallel/HighsRaceTimer.h index 180b13a0b5..cc1629d293 100644 --- a/src/parallel/HighsRaceTimer.h +++ b/src/parallel/HighsRaceTimer.h @@ -40,4 +40,4 @@ class HighsRaceTimer { } }; -#endif \ No newline at end of file +#endif diff --git a/src/parallel/HighsSchedulerConstants.h b/src/parallel/HighsSchedulerConstants.h index 1121724d9d..ad21fdaf23 100644 --- a/src/parallel/HighsSchedulerConstants.h +++ b/src/parallel/HighsSchedulerConstants.h @@ -21,4 +21,4 @@ struct HighsSchedulerConstants { }; }; -#endif \ No newline at end of file +#endif diff --git a/src/pdlp/cupdlp/cupdlp_utils.c b/src/pdlp/cupdlp/cupdlp_utils.c index 58b14b2ac3..e648e9ad31 100644 --- a/src/pdlp/cupdlp/cupdlp_utils.c +++ b/src/pdlp/cupdlp/cupdlp_utils.c @@ -431,7 +431,7 @@ void PDHG_PrintPDHGParam(CUPDLPwork *w) { cupdlp_printf("\n"); } -void PDHG_PrintHugeCUPDHG() { +void PDHG_PrintHugeCUPDHG(void) { cupdlp_printf("\n"); cupdlp_printf(" ____ _ _ ____ ____ _ ____\n"); cupdlp_printf(" / ___| | | | _ \\| _ \\| | | _ \\\n"); @@ -441,7 +441,7 @@ void PDHG_PrintHugeCUPDHG() { cupdlp_printf("\n"); } -void PDHG_PrintUserParamHelper() { +void PDHG_PrintUserParamHelper(void) { PDHG_PrintHugeCUPDHG(); cupdlp_printf("CUPDHG User Parameters:\n"); diff --git a/src/pdlp/cupdlp/cupdlp_utils.h b/src/pdlp/cupdlp/cupdlp_utils.h index 49c9510a06..337a743c8e 100644 --- a/src/pdlp/cupdlp/cupdlp_utils.h +++ b/src/pdlp/cupdlp/cupdlp_utils.h @@ -37,9 +37,9 @@ cupdlp_int PDHG_Clear(CUPDLPwork *w); void PDHG_PrintPDHGParam(CUPDLPwork *w); -void PDHG_PrintHugeCUPDHG(); +void PDHG_PrintHugeCUPDHG(void); -void PDHG_PrintUserParamHelper(); +void PDHG_PrintUserParamHelper(void); cupdlp_retcode getUserParam(int argc, char **argv, cupdlp_bool *ifChangeIntParam, cupdlp_int *intParam, diff --git a/src/presolve/HPresolve.cpp b/src/presolve/HPresolve.cpp index d7977a0ebb..6c72a06046 100644 --- a/src/presolve/HPresolve.cpp +++ b/src/presolve/HPresolve.cpp @@ -2052,14 +2052,17 @@ HPresolve::Result HPresolve::applyConflictGraphSubstitutions( return Result::kOk; } -void HPresolve::storeRow(HighsInt row) { - rowpositions.clear(); +void HPresolve::getRowPositions(HighsInt row, + std::vector& myrowpositions) const { + myrowpositions.clear(); - auto rowVec = getSortedRowVector(row); - for (auto iter = rowVec.begin(); iter != rowVec.end(); ++iter) - rowpositions.push_back(iter.position()); + auto rowvector = getSortedRowVector(row); + for (auto rowiter = rowvector.begin(); rowiter != rowvector.end(); ++rowiter) + myrowpositions.push_back(rowiter.position()); } +void HPresolve::storeRow(HighsInt row) { getRowPositions(row, rowpositions); } + HighsTripletPositionSlice HPresolve::getStoredRow() const { return HighsTripletPositionSlice(Acol.data(), Avalue.data(), rowpositions.data(), rowpositions.size()); @@ -3542,19 +3545,27 @@ HPresolve::Result HPresolve::rowPresolve(HighsPostsolveStack& postsolve_stack, auto strengthenCoefs = [&](HighsCDouble& rhs, HighsInt direction, double maxCoefValue) { - for (const HighsSliceNonzero& nonz : getStoredRow()) { - if (model->integrality_[nonz.index()] == HighsVarType::kContinuous) - continue; - - if (direction * nonz.value() > maxCoefValue + primal_feastol) { - double delta = direction * maxCoefValue - nonz.value(); - addToMatrix(row, nonz.index(), delta); - rhs += delta * model->col_upper_[nonz.index()]; - } else if (direction * nonz.value() < - -maxCoefValue - primal_feastol) { - double delta = -direction * maxCoefValue - nonz.value(); - addToMatrix(row, nonz.index(), delta); - rhs += delta * model->col_lower_[nonz.index()]; + // iterate over non-zero positions instead of iterating over the + // HighsMatrixSlice (provided by HPresolve::getStoredRow) because the + // latter contains pointers to Acol and Avalue that may be invalidated + // if these vectors are reallocated (see std::vector::push_back + // performed in HPresolve::addToMatrix). + for (HighsInt rowiter : rowpositions) { + // get column index and coefficient + HighsInt col = Acol[rowiter]; + double val = Avalue[rowiter]; + + // skip continuous variables + if (model->integrality_[col] == HighsVarType::kContinuous) continue; + + if (direction * val > maxCoefValue + primal_feastol) { + double delta = direction * maxCoefValue - val; + addToMatrix(row, col, delta); + rhs += delta * model->col_upper_[col]; + } else if (direction * val < -maxCoefValue - primal_feastol) { + double delta = -direction * maxCoefValue - val; + addToMatrix(row, col, delta); + rhs += delta * model->col_lower_[col]; } } }; @@ -4098,7 +4109,7 @@ HPresolve::Result HPresolve::presolve(HighsPostsolveStack& postsolve_stack) { // the timer is well defined, and that its total time clock is // running assert(this->timer); - assert(this->timer->runningRunHighsClock()); + assert(this->timer->running()); HPRESOLVE_CHECKED_CALL(initialRowAndColPresolve(postsolve_stack)); @@ -6128,15 +6139,25 @@ HPresolve::Result HPresolve::detectParallelRowsAndCols( template HPresolve::Result HPresolve::equalityRowAddition( HighsPostsolveStack& postsolve_stack, HighsInt stayrow, HighsInt removerow, - double scale, const HighsMatrixSlice& vector) { - postsolve_stack.equalityRowAddition(removerow, stayrow, scale, vector); - for (const auto& rowNz : vector) { - HighsInt pos = findNonzero(removerow, rowNz.index()); + double scale, const HighsMatrixSlice& rowvector) { + // extract non-zero positions + std::vector stay_rowpositions; + getRowPositions(stayrow, stay_rowpositions); + + // update postsolve information + postsolve_stack.equalityRowAddition(removerow, stayrow, scale, rowvector); + + // iterate over non-zero positions instead of iterating over the + // HighsMatrixSlice because the latter contains pointers to Acol and Avalue + // that may be invalidated if these vectors are reallocated + // (see std::vector::push_back performed in HPresolve::addToMatrix). + for (HighsInt rowiter : stay_rowpositions) { + HighsInt pos = findNonzero(removerow, Acol[rowiter]); if (pos != -1) unlink(pos); // all common nonzeros are cancelled, as the rows are // parallel else // might introduce a singleton - addToMatrix(removerow, rowNz.index(), scale * rowNz.value()); + addToMatrix(removerow, Acol[rowiter], scale * Avalue[rowiter]); } if (model->row_upper_[removerow] != kHighsInf) @@ -6286,8 +6307,7 @@ void HPresolve::debug(const HighsLp& lp, const HighsOptions& options) { sol = reducedsol; basis = reducedbasis; - postsolve_stack.undoUntil(options, flagRow, flagCol, sol, basis, - tmp.numReductions()); + postsolve_stack.undoUntil(options, sol, basis, tmp.numReductions()); HighsBasis temp_basis; HighsSolution temp_sol; @@ -6309,6 +6329,7 @@ void HPresolve::debug(const HighsLp& lp, const HighsOptions& options) { temp_sol.row_value.resize(model.num_row_); calculateRowValuesQuad(model, sol); temp_basis.valid = true; + temp_basis.useful = true; refineBasis(model, temp_sol, temp_basis); Highs highs; highs.passOptions(options); @@ -6332,8 +6353,7 @@ void HPresolve::debug(const HighsLp& lp, const HighsOptions& options) { ARstart, ARindex, ARvalue); sol = reducedsol; basis = reducedbasis; - postsolve_stack.undoUntil(options, flagRow, flagCol, sol, basis, - reductionLim); + postsolve_stack.undoUntil(options, sol, basis, reductionLim); calculateRowValuesQuad(model, sol); kktinfo = dev_kkt_check::initInfo(); diff --git a/src/presolve/HPresolve.h b/src/presolve/HPresolve.h index a64bbf07fb..9781e2567b 100644 --- a/src/presolve/HPresolve.h +++ b/src/presolve/HPresolve.h @@ -212,6 +212,9 @@ class HPresolve { void toCSR(std::vector& ARval, std::vector& ARindex, std::vector& ARstart); + void getRowPositions(HighsInt row, + std::vector& myrowpositions) const; + void storeRow(HighsInt row); HighsTripletPositionSlice getStoredRow() const; diff --git a/src/presolve/HighsPostsolveStack.h b/src/presolve/HighsPostsolveStack.h index bb1b1f785b..e324af710b 100644 --- a/src/presolve/HighsPostsolveStack.h +++ b/src/presolve/HighsPostsolveStack.h @@ -784,9 +784,7 @@ class HighsPostsolveStack { */ // Only used for debugging - void undoUntil(const HighsOptions& options, - const std::vector& flagRow, - const std::vector& flagCol, HighsSolution& solution, + void undoUntil(const HighsOptions& options, HighsSolution& solution, HighsBasis& basis, size_t numReductions) { reductionValues.resetPosition(); diff --git a/src/presolve/ICrashUtil.h b/src/presolve/ICrashUtil.h index 883bc7b270..4383fc4f0c 100644 --- a/src/presolve/ICrashUtil.h +++ b/src/presolve/ICrashUtil.h @@ -59,4 +59,4 @@ void updateResidualFast(const HighsLp& lp, const HighsSolution& sol, // Allows negative residuals void updateResidualIca(const HighsLp& lp, const HighsSolution& sol, std::vector& residual); -#endif \ No newline at end of file +#endif diff --git a/src/qpsolver/a_quass.cpp b/src/qpsolver/a_quass.cpp index 56ff800596..d60bc999e5 100644 --- a/src/qpsolver/a_quass.cpp +++ b/src/qpsolver/a_quass.cpp @@ -116,6 +116,7 @@ static QpAsmStatus quass2highs(Instance& instance, Settings& settings, } highs_basis.valid = true; highs_basis.alien = false; + highs_basis.useful = true; return qp_asm_return_status; } diff --git a/src/qpsolver/feasibility_highs.hpp b/src/qpsolver/feasibility_highs.hpp index 3164808d0e..22198d4143 100644 --- a/src/qpsolver/feasibility_highs.hpp +++ b/src/qpsolver/feasibility_highs.hpp @@ -61,7 +61,7 @@ static void computeStartingPointHighs( highs.setOptionValue("presolve", "on"); // Set the residual time limit const double use_time_limit = - std::max(settings.time_limit - timer.readRunHighsClock(), 0.001); + std::max(settings.time_limit - timer.read(), 0.001); highs.setOptionValue("time_limit", use_time_limit); HighsLp lp; diff --git a/src/qpsolver/quass.cpp b/src/qpsolver/quass.cpp index 2f7cd40648..19736c2743 100644 --- a/src/qpsolver/quass.cpp +++ b/src/qpsolver/quass.cpp @@ -31,7 +31,7 @@ static void loginformation(Runtime& rt, Basis& basis, CholeskyFactor& factor, rt.statistics.nullspacedimension.push_back(rt.instance.num_var - basis.getnumactive()); rt.statistics.objval.push_back(rt.instance.objval(rt.primal)); - rt.statistics.time.push_back(timer.readRunHighsClock()); + rt.statistics.time.push_back(timer.read()); SumNum sm = rt.instance.sumnumprimalinfeasibilities(rt.primal, rt.rowactivity); rt.statistics.sum_primal_infeasibilities.push_back(sm.sum); @@ -337,7 +337,7 @@ void Quass::solve(const QpVector& x0, const QpVector& ra, Basis& b0, } // check time limit - if (timer.readRunHighsClock() >= runtime.settings.time_limit) { + if (timer.read() >= runtime.settings.time_limit) { runtime.status = QpModelStatus::kTimeLimit; break; } @@ -350,7 +350,7 @@ void Quass::solve(const QpVector& x0, const QpVector& ra, Basis& b0, } // LOGGING - double run_time = timer.readRunHighsClock(); + double run_time = timer.read(); if ((runtime.statistics.num_iterations % runtime.settings.reportingfequency == 0 || diff --git a/src/simplex/HApp.h b/src/simplex/HApp.h index 9fc35589b4..178593cbc6 100644 --- a/src/simplex/HApp.h +++ b/src/simplex/HApp.h @@ -136,6 +136,22 @@ inline HighsStatus solveLpSimplex(HighsLpSolverObject& solver_object) { // If new scaling is performed, the hot start information is // no longer valid if (new_scaling) ekk_instance.clearHotStart(); + // + if (!status.has_basis && !basis.valid && basis.useful) { + // There is no simplex basis, but there is a useful HiGHS basis + // that is not validated + assert(basis.col_status.size() == + static_cast(incumbent_lp.num_col_)); + assert(basis.row_status.size() == + static_cast(incumbent_lp.num_row_)); + HighsStatus return_status = formSimplexLpBasisAndFactor(solver_object); + if (return_status != HighsStatus::kOk) + return returnFromSolveLpSimplex(solver_object, HighsStatus::kError); + // formSimplexLpBasisAndFactor may introduce variables with + // HighsBasisStatus::kNonbasic, so refine it + refineBasis(incumbent_lp, solution, basis); + basis.valid = true; + } // Move the LP to EKK, updating other EKK pointers and any simplex // NLA pointers, since they may have moved if the LP has been // modified diff --git a/src/simplex/HEkk.cpp b/src/simplex/HEkk.cpp index 83cdd6159e..c6da49da49 100644 --- a/src/simplex/HEkk.cpp +++ b/src/simplex/HEkk.cpp @@ -1465,6 +1465,7 @@ HighsBasis HEkk::getHighsBasis(HighsLp& use_lp) const { } highs_basis.valid = true; highs_basis.alien = false; + highs_basis.useful = true; highs_basis.was_alien = false; highs_basis.debug_id = (HighsInt)(build_synthetic_tick_ + total_synthetic_tick_); @@ -3468,7 +3469,7 @@ bool HEkk::bailout() { model_status_ == HighsModelStatus::kObjectiveBound || model_status_ == HighsModelStatus::kObjectiveTarget); } else if (options_->time_limit < kHighsInf && - timer_->readRunHighsClock() > options_->time_limit) { + timer_->read() > options_->time_limit) { solve_bailout_ = true; model_status_ = HighsModelStatus::kTimeLimit; } else if (iteration_count_ >= options_->simplex_iteration_limit) { diff --git a/src/simplex/HEkkPrimal.cpp b/src/simplex/HEkkPrimal.cpp index 1c39033e7e..e077da8040 100644 --- a/src/simplex/HEkkPrimal.cpp +++ b/src/simplex/HEkkPrimal.cpp @@ -1212,11 +1212,11 @@ void HEkkPrimal::phase1ChooseRow() { analysis->simplexTimerStart(Chuzr2Clock); pdqsort(ph1SorterR.begin(), ph1SorterR.end()); - double dMaxTheta = ph1SorterR.at(0).first; + double dMaxTheta = ph1SorterR[0].first; double dGradient = fabs(theta_dual); for (size_t i = 0; i < ph1SorterR.size(); i++) { - double dMyTheta = ph1SorterR.at(i).first; - HighsInt index = ph1SorterR.at(i).second; + double dMyTheta = ph1SorterR[i].first; + HighsInt index = ph1SorterR[i].second; HighsInt iRow = index >= 0 ? index : index + num_row; dGradient -= fabs(col_aq.array[iRow]); // Stop when the gradient start to decrease @@ -1231,8 +1231,8 @@ void HEkkPrimal::phase1ChooseRow() { double dMaxAlpha = 0.0; size_t iLast = ph1SorterT.size(); for (size_t i = 0; i < ph1SorterT.size(); i++) { - double dMyTheta = ph1SorterT.at(i).first; - HighsInt index = ph1SorterT.at(i).second; + double dMyTheta = ph1SorterT[i].first; + HighsInt index = ph1SorterT[i].second; HighsInt iRow = index >= 0 ? index : index + num_row; double dAbsAlpha = fabs(col_aq.array[iRow]); // Stop when the theta is too large @@ -1251,7 +1251,7 @@ void HEkkPrimal::phase1ChooseRow() { variable_out = -1; move_out = 0; for (size_t i = iLast; i > 0; i--) { - HighsInt index = ph1SorterT.at(i - 1).second; + HighsInt index = ph1SorterT[i - 1].second; HighsInt iRow = index >= 0 ? index : index + num_row; double dAbsAlpha = fabs(col_aq.array[iRow]); if (dAbsAlpha > dMaxAlpha * 0.1) { diff --git a/src/simplex/HighsSimplexAnalysis.cpp b/src/simplex/HighsSimplexAnalysis.cpp index 6e326d5c3e..9b4a474ac0 100644 --- a/src/simplex/HighsSimplexAnalysis.cpp +++ b/src/simplex/HighsSimplexAnalysis.cpp @@ -380,7 +380,7 @@ void HighsSimplexAnalysis::userInvertReport(const bool force) { void HighsSimplexAnalysis::userInvertReport(const bool header, const bool force) { - const double highs_run_time = timer_->readRunHighsClock(); + const double highs_run_time = timer_->read(); if (!force && highs_run_time < last_user_log_time + delta_user_log_time) return; analysis_log = std::unique_ptr(new std::stringstream()); diff --git a/src/simplex/SimplexTimer.h b/src/simplex/SimplexTimer.h index 78183ec349..f33612125d 100644 --- a/src/simplex/SimplexTimer.h +++ b/src/simplex/SimplexTimer.h @@ -120,116 +120,101 @@ class SimplexTimer { HighsTimer* timer_pointer = simplex_timer_clock.timer_pointer_; std::vector& clock = simplex_timer_clock.clock_; clock.resize(SimplexNumClock); - clock[SimplexTotalClock] = timer_pointer->clock_def("Simplex total", "STT"); - clock[SimplexIzDseWtClock] = timer_pointer->clock_def("Iz DSE Wt", "IWT"); - clock[SimplexDualPhase1Clock] = - timer_pointer->clock_def("Dual Phase 1", "DP1"); - clock[SimplexDualPhase2Clock] = - timer_pointer->clock_def("Dual Phase 2", "DP2"); + clock[SimplexTotalClock] = timer_pointer->clock_def("Simplex total"); + clock[SimplexIzDseWtClock] = timer_pointer->clock_def("Iz DSE Wt"); + clock[SimplexDualPhase1Clock] = timer_pointer->clock_def("Dual Phase 1"); + clock[SimplexDualPhase2Clock] = timer_pointer->clock_def("Dual Phase 2"); clock[SimplexPrimalPhase1Clock] = - timer_pointer->clock_def("Primal Phase 1", "PP1"); + timer_pointer->clock_def("Primal Phase 1"); clock[SimplexPrimalPhase2Clock] = - timer_pointer->clock_def("Primal Phase 2", "PP2"); - clock[Group1Clock] = timer_pointer->clock_def("GROUP1", "GP1"); - clock[IterateClock] = timer_pointer->clock_def("ITERATE", "ITR"); - clock[IterateDualRebuildClock] = - timer_pointer->clock_def("DUAL REBUILD", "DRB"); + timer_pointer->clock_def("Primal Phase 2"); + clock[Group1Clock] = timer_pointer->clock_def("GROUP1"); + clock[IterateClock] = timer_pointer->clock_def("ITERATE"); + clock[IterateDualRebuildClock] = timer_pointer->clock_def("DUAL REBUILD"); clock[IteratePrimalRebuildClock] = - timer_pointer->clock_def("PRIMAL REBUILD", "PRB"); - clock[IterateChuzrClock] = timer_pointer->clock_def("CHUZR", "CZR"); - clock[IterateChuzcClock] = timer_pointer->clock_def("CHUZC", "CZC"); - clock[IterateFtranClock] = timer_pointer->clock_def("FTRAN", "FTR"); - clock[IterateVerifyClock] = timer_pointer->clock_def("VERIFY", "VRF"); - clock[IterateDualClock] = timer_pointer->clock_def("DUAL", "UDU"); - clock[IteratePrimalClock] = timer_pointer->clock_def("PRIMAL", "UPR"); - clock[IterateDevexIzClock] = timer_pointer->clock_def("DEVEX_IZ", "DVI"); - clock[IteratePivotsClock] = timer_pointer->clock_def("PIVOTS", "PIV"); + timer_pointer->clock_def("PRIMAL REBUILD"); + clock[IterateChuzrClock] = timer_pointer->clock_def("CHUZR"); + clock[IterateChuzcClock] = timer_pointer->clock_def("CHUZC"); + clock[IterateFtranClock] = timer_pointer->clock_def("FTRAN"); + clock[IterateVerifyClock] = timer_pointer->clock_def("VERIFY"); + clock[IterateDualClock] = timer_pointer->clock_def("DUAL"); + clock[IteratePrimalClock] = timer_pointer->clock_def("PRIMAL"); + clock[IterateDevexIzClock] = timer_pointer->clock_def("DEVEX_IZ"); + clock[IteratePivotsClock] = timer_pointer->clock_def("PIVOTS"); clock[initialiseSimplexLpBasisAndFactorClock] = - timer_pointer->clock_def("IZ_SIMPLEX_LP_DEF", "ISD"); + timer_pointer->clock_def("IZ_SIMPLEX_LP_DEF"); clock[allocateSimplexArraysClock] = - timer_pointer->clock_def("ALLOC_SIMPLEX_ARRS", "ASA"); + timer_pointer->clock_def("ALLOC_SIMPLEX_ARRS"); clock[initialiseSimplexCostBoundsClock] = - timer_pointer->clock_def("IZ_SIMPLEX_CO_BD", "ICB"); - clock[ScaleClock] = timer_pointer->clock_def("SCALE", "SCL"); - clock[CrashClock] = timer_pointer->clock_def("CRASH", "CSH"); - clock[BasisConditionClock] = - timer_pointer->clock_def("BASIS_CONDITION", "CON"); - clock[matrixSetupClock] = timer_pointer->clock_def("MATRIX_SETUP", "FST"); - clock[setNonbasicMoveClock] = - timer_pointer->clock_def("SET_NONBASICMOVE", "SNM"); - clock[DseIzClock] = timer_pointer->clock_def("DSE_IZ", "DEI"); - clock[InvertClock] = timer_pointer->clock_def("INVERT", "INV"); - clock[PermWtClock] = timer_pointer->clock_def("PERM_WT", "PWT"); - clock[ComputeDualClock] = timer_pointer->clock_def("COMPUTE_DUAL", "CPD"); - clock[CorrectDualClock] = timer_pointer->clock_def("CORRECT_DUAL", "CRD"); - clock[ComputePrimalClock] = - timer_pointer->clock_def("COMPUTE_PRIMAL", "CPP"); - clock[CollectPrIfsClock] = - timer_pointer->clock_def("COLLECT_PR_IFS", "IFS"); - clock[ComputePrIfsClock] = - timer_pointer->clock_def("COMPUTE_PR_IFS", "PIF"); - clock[ComputeDuIfsClock] = - timer_pointer->clock_def("COMPUTE_DU_IFS", "DIF"); - clock[ComputeDuObjClock] = - timer_pointer->clock_def("COMPUTE_DU_OBJ", "DOB"); - clock[ComputePrObjClock] = - timer_pointer->clock_def("COMPUTE_PR_OBJ", "POB"); - clock[ReportRebuildClock] = - timer_pointer->clock_def("REPORT_REBUILD", "RPR"); - clock[ChuzrDualClock] = timer_pointer->clock_def("CHUZR_DUAL", "CRD"); - clock[Chuzr1Clock] = timer_pointer->clock_def("CHUZR1", "CR1"); - clock[Chuzr2Clock] = timer_pointer->clock_def("CHUZR2", "CR2"); - clock[ChuzcPrimalClock] = timer_pointer->clock_def("CHUZC_PRIMAL", "CCP"); + timer_pointer->clock_def("IZ_SIMPLEX_CO_BD"); + clock[ScaleClock] = timer_pointer->clock_def("SCALE"); + clock[CrashClock] = timer_pointer->clock_def("CRASH"); + clock[BasisConditionClock] = timer_pointer->clock_def("BASIS_CONDITION"); + clock[matrixSetupClock] = timer_pointer->clock_def("MATRIX_SETUP"); + clock[setNonbasicMoveClock] = timer_pointer->clock_def("SET_NONBASICMOVE"); + clock[DseIzClock] = timer_pointer->clock_def("DSE_IZ"); + clock[InvertClock] = timer_pointer->clock_def("INVERT"); + clock[PermWtClock] = timer_pointer->clock_def("PERM_WT"); + clock[ComputeDualClock] = timer_pointer->clock_def("COMPUTE_DUAL"); + clock[CorrectDualClock] = timer_pointer->clock_def("CORRECT_DUAL"); + clock[ComputePrimalClock] = timer_pointer->clock_def("COMPUTE_PRIMAL"); + clock[CollectPrIfsClock] = timer_pointer->clock_def("COLLECT_PR_IFS"); + clock[ComputePrIfsClock] = timer_pointer->clock_def("COMPUTE_PR_IFS"); + clock[ComputeDuIfsClock] = timer_pointer->clock_def("COMPUTE_DU_IFS"); + clock[ComputeDuObjClock] = timer_pointer->clock_def("COMPUTE_DU_OBJ"); + clock[ComputePrObjClock] = timer_pointer->clock_def("COMPUTE_PR_OBJ"); + clock[ReportRebuildClock] = timer_pointer->clock_def("REPORT_REBUILD"); + clock[ChuzrDualClock] = timer_pointer->clock_def("CHUZR_DUAL"); + clock[Chuzr1Clock] = timer_pointer->clock_def("CHUZR1"); + clock[Chuzr2Clock] = timer_pointer->clock_def("CHUZR2"); + clock[ChuzcPrimalClock] = timer_pointer->clock_def("CHUZC_PRIMAL"); clock[ChuzcHyperInitialiselClock] = - timer_pointer->clock_def("CHUZC_HYPER_IZ", "CHI"); + timer_pointer->clock_def("CHUZC_HYPER_IZ"); clock[ChuzcHyperBasicFeasibilityChangeClock] = - timer_pointer->clock_def("CHUZC_HYPER_FEAS", "CHF"); - clock[ChuzcHyperDualClock] = - timer_pointer->clock_def("CHUZC_HYPER_DUAL", "CHD"); - clock[ChuzcHyperClock] = timer_pointer->clock_def("CHUZC_HYPER", "CHC"); - clock[Chuzc0Clock] = timer_pointer->clock_def("CHUZC0", "CC0"); - clock[PriceChuzc1Clock] = timer_pointer->clock_def("PRICE_CHUZC1", "PC1"); - clock[Chuzc1Clock] = timer_pointer->clock_def("CHUZC1", "CC1"); - clock[Chuzc2Clock] = timer_pointer->clock_def("CHUZC2", "CC2"); - clock[Chuzc3Clock] = timer_pointer->clock_def("CHUZC3", "CC3"); - clock[Chuzc4Clock] = timer_pointer->clock_def("CHUZC4", "CC4"); - clock[Chuzc4a0Clock] = timer_pointer->clock_def("CHUZC4a0", "C40"); - clock[Chuzc4a1Clock] = timer_pointer->clock_def("CHUZC4a1", "C41"); - clock[Chuzc4bClock] = timer_pointer->clock_def("CHUZC4b", "C4b"); - clock[Chuzc4cClock] = timer_pointer->clock_def("CHUZC4c", "C4c"); - clock[Chuzc4dClock] = timer_pointer->clock_def("CHUZC4d", "C4d"); - clock[Chuzc4eClock] = timer_pointer->clock_def("CHUZC4e", "C4e"); - clock[Chuzc5Clock] = timer_pointer->clock_def("CHUZC5", "CC5"); - clock[DevexWtClock] = timer_pointer->clock_def("DEVEX_WT", "DWT"); - clock[BtranClock] = timer_pointer->clock_def("BTRAN", "REP"); + timer_pointer->clock_def("CHUZC_HYPER_FEAS"); + clock[ChuzcHyperDualClock] = timer_pointer->clock_def("CHUZC_HYPER_DUAL"); + clock[ChuzcHyperClock] = timer_pointer->clock_def("CHUZC_HYPER"); + clock[Chuzc0Clock] = timer_pointer->clock_def("CHUZC0"); + clock[PriceChuzc1Clock] = timer_pointer->clock_def("PRICE_CHUZC1"); + clock[Chuzc1Clock] = timer_pointer->clock_def("CHUZC1"); + clock[Chuzc2Clock] = timer_pointer->clock_def("CHUZC2"); + clock[Chuzc3Clock] = timer_pointer->clock_def("CHUZC3"); + clock[Chuzc4Clock] = timer_pointer->clock_def("CHUZC4"); + clock[Chuzc4a0Clock] = timer_pointer->clock_def("CHUZC4a0"); + clock[Chuzc4a1Clock] = timer_pointer->clock_def("CHUZC4a1"); + clock[Chuzc4bClock] = timer_pointer->clock_def("CHUZC4b"); + clock[Chuzc4cClock] = timer_pointer->clock_def("CHUZC4c"); + clock[Chuzc4dClock] = timer_pointer->clock_def("CHUZC4d"); + clock[Chuzc4eClock] = timer_pointer->clock_def("CHUZC4e"); + clock[Chuzc5Clock] = timer_pointer->clock_def("CHUZC5"); + clock[DevexWtClock] = timer_pointer->clock_def("DEVEX_WT"); + clock[BtranClock] = timer_pointer->clock_def("BTRAN"); clock[BtranBasicFeasibilityChangeClock] = - timer_pointer->clock_def("BTRAN_FEAS", "BT1"); - clock[BtranFullClock] = timer_pointer->clock_def("BTRAN_FULL", "BTF"); - clock[PriceClock] = timer_pointer->clock_def("PRICE", "RAP"); + timer_pointer->clock_def("BTRAN_FEAS"); + clock[BtranFullClock] = timer_pointer->clock_def("BTRAN_FULL"); + clock[PriceClock] = timer_pointer->clock_def("PRICE"); clock[PriceBasicFeasibilityChangeClock] = - timer_pointer->clock_def("PRICE_FEAS", "PC1"); - clock[PriceFullClock] = timer_pointer->clock_def("PRICE_FULL", "PCF"); - clock[FtranClock] = timer_pointer->clock_def("FTRAN", "COL"); - clock[FtranDseClock] = timer_pointer->clock_def("FTRAN_DSE", "DSE"); - clock[BtranPseClock] = timer_pointer->clock_def("BTRAN_PSE", "PSE"); - clock[FtranMixParClock] = timer_pointer->clock_def("FTRAN_MIX_PAR", "FMP"); - clock[FtranMixFinalClock] = - timer_pointer->clock_def("FTRAN_MIX_FINAL", "FMF"); - clock[FtranBfrtClock] = timer_pointer->clock_def("FTRAN_BFRT", "BFR"); - clock[UpdateRowClock] = timer_pointer->clock_def("UPDATE_ROW", "UPR"); - clock[UpdateDualClock] = timer_pointer->clock_def("UPDATE_DUAL", "UPD"); + timer_pointer->clock_def("PRICE_FEAS"); + clock[PriceFullClock] = timer_pointer->clock_def("PRICE_FULL"); + clock[FtranClock] = timer_pointer->clock_def("FTRAN"); + clock[FtranDseClock] = timer_pointer->clock_def("FTRAN_DSE"); + clock[BtranPseClock] = timer_pointer->clock_def("BTRAN_PSE"); + clock[FtranMixParClock] = timer_pointer->clock_def("FTRAN_MIX_PAR"); + clock[FtranMixFinalClock] = timer_pointer->clock_def("FTRAN_MIX_FINAL"); + clock[FtranBfrtClock] = timer_pointer->clock_def("FTRAN_BFRT"); + clock[UpdateRowClock] = timer_pointer->clock_def("UPDATE_ROW"); + clock[UpdateDualClock] = timer_pointer->clock_def("UPDATE_DUAL"); clock[UpdateDualBasicFeasibilityChangeClock] = - timer_pointer->clock_def("UPDATE_DUAL_FEAS", "UD1"); - clock[UpdatePrimalClock] = timer_pointer->clock_def("UPDATE_PRIMAL", "UPP"); - clock[DevexIzClock] = timer_pointer->clock_def("DEVEX_IZ", "DIZ"); + timer_pointer->clock_def("UPDATE_DUAL_FEAS"); + clock[UpdatePrimalClock] = timer_pointer->clock_def("UPDATE_PRIMAL"); + clock[DevexIzClock] = timer_pointer->clock_def("DEVEX_IZ"); clock[DevexUpdateWeightClock] = - timer_pointer->clock_def("UPDATE_DVX_WEIGHT", "UDW"); - clock[DseUpdateWeightClock] = - timer_pointer->clock_def("UPDATE_DSE_WEIGHT", "USW"); - clock[UpdatePivotsClock] = timer_pointer->clock_def("UPDATE_PIVOTS", "UPP"); - clock[UpdateFactorClock] = timer_pointer->clock_def("UPDATE_FACTOR", "UPF"); - clock[UpdateMatrixClock] = timer_pointer->clock_def("UPDATE_MATRIX", "UPM"); - clock[UpdateRowEpClock] = timer_pointer->clock_def("UPDATE_ROW_EP", "UPR"); + timer_pointer->clock_def("UPDATE_DVX_WEIGHT"); + clock[DseUpdateWeightClock] = timer_pointer->clock_def("UPDATE_DSE_WEIGHT"); + clock[UpdatePivotsClock] = timer_pointer->clock_def("UPDATE_PIVOTS"); + clock[UpdateFactorClock] = timer_pointer->clock_def("UPDATE_FACTOR"); + clock[UpdateMatrixClock] = timer_pointer->clock_def("UPDATE_MATRIX"); + clock[UpdateRowEpClock] = timer_pointer->clock_def("UPDATE_ROW_EP"); } bool reportSimplexClockList(const char* grepStamp, diff --git a/src/util/FactorTimer.h b/src/util/FactorTimer.h index f8c0a866c1..8f31b71411 100644 --- a/src/util/FactorTimer.h +++ b/src/util/FactorTimer.h @@ -88,76 +88,54 @@ class FactorTimer { HighsTimer* timer_pointer = factor_timer_clock.timer_pointer_; std::vector& clock = factor_timer_clock.clock_; clock.resize(FactorNumClock); - clock[FactorInvert] = timer_pointer->clock_def("INVERT", "INV"); - clock[FactorInvertSimple] = - timer_pointer->clock_def("INVERT Simple", "IVS"); - clock[FactorInvertKernel] = - timer_pointer->clock_def("INVERT Kernel", "IVK"); - clock[FactorInvertDeficient] = - timer_pointer->clock_def("INVERT Deficient", "IVD"); - clock[FactorInvertFinish] = - timer_pointer->clock_def("INVERT Finish", "IVF"); - clock[FactorFtran] = timer_pointer->clock_def("FTRAN", "FTR"); - clock[FactorFtranLower] = timer_pointer->clock_def("FTRAN Lower", "FTL"); - clock[FactorFtranLowerAPF] = - timer_pointer->clock_def("FTRAN Lower APF", "FLA"); - clock[FactorFtranLowerDse] = - timer_pointer->clock_def("FTRAN Lower Dse", "FLD"); - clock[FactorFtranLowerSps] = - timer_pointer->clock_def("FTRAN Lower Sps", "FLS"); + clock[FactorInvert] = timer_pointer->clock_def("INVERT"); + clock[FactorInvertSimple] = timer_pointer->clock_def("INVERT Simple"); + clock[FactorInvertKernel] = timer_pointer->clock_def("INVERT Kernel"); + clock[FactorInvertDeficient] = timer_pointer->clock_def("INVERT Deficient"); + clock[FactorInvertFinish] = timer_pointer->clock_def("INVERT Finish"); + clock[FactorFtran] = timer_pointer->clock_def("FTRAN"); + clock[FactorFtranLower] = timer_pointer->clock_def("FTRAN Lower"); + clock[FactorFtranLowerAPF] = timer_pointer->clock_def("FTRAN Lower APF"); + clock[FactorFtranLowerDse] = timer_pointer->clock_def("FTRAN Lower Dse"); + clock[FactorFtranLowerSps] = timer_pointer->clock_def("FTRAN Lower Sps"); clock[FactorFtranLowerHyper] = - timer_pointer->clock_def("FTRAN Lower Hyper", "FLH"); - clock[FactorFtranUpper] = timer_pointer->clock_def("FTRAN Upper", "FTU"); - clock[FactorFtranUpperFT] = - timer_pointer->clock_def("FTRAN Upper FT", "FUF"); - clock[FactorFtranUpperMPF] = - timer_pointer->clock_def("FTRAN Upper MPF", "FUM"); - clock[FactorFtranUpperDse] = - timer_pointer->clock_def("FTRAN Upper Dse", "FUD"); - clock[FactorFtranUpperSps0] = - timer_pointer->clock_def("FTRAN Upper Sps0", "FUS"); - clock[FactorFtranUpperSps1] = - timer_pointer->clock_def("FTRAN Upper Sps1", "FUS"); - clock[FactorFtranUpperSps2] = - timer_pointer->clock_def("FTRAN Upper Sps2", "FUS"); + timer_pointer->clock_def("FTRAN Lower Hyper"); + clock[FactorFtranUpper] = timer_pointer->clock_def("FTRAN Upper"); + clock[FactorFtranUpperFT] = timer_pointer->clock_def("FTRAN Upper FT"); + clock[FactorFtranUpperMPF] = timer_pointer->clock_def("FTRAN Upper MPF"); + clock[FactorFtranUpperDse] = timer_pointer->clock_def("FTRAN Upper Dse"); + clock[FactorFtranUpperSps0] = timer_pointer->clock_def("FTRAN Upper Sps0"); + clock[FactorFtranUpperSps1] = timer_pointer->clock_def("FTRAN Upper Sps1"); + clock[FactorFtranUpperSps2] = timer_pointer->clock_def("FTRAN Upper Sps2"); clock[FactorFtranUpperHyper0] = - timer_pointer->clock_def("FTRAN Upper Hyper0", "FUH"); + timer_pointer->clock_def("FTRAN Upper Hyper0"); clock[FactorFtranUpperHyper1] = - timer_pointer->clock_def("FTRAN Upper Hyper1", "FUH"); + timer_pointer->clock_def("FTRAN Upper Hyper1"); clock[FactorFtranUpperHyper2] = - timer_pointer->clock_def("FTRAN Upper Hyper2", "FUH"); + timer_pointer->clock_def("FTRAN Upper Hyper2"); clock[FactorFtranUpperHyper3] = - timer_pointer->clock_def("FTRAN Upper Hyper3", "FUH"); + timer_pointer->clock_def("FTRAN Upper Hyper3"); clock[FactorFtranUpperHyper4] = - timer_pointer->clock_def("FTRAN Upper Hyper4", "FUH"); + timer_pointer->clock_def("FTRAN Upper Hyper4"); clock[FactorFtranUpperHyper5] = - timer_pointer->clock_def("FTRAN Upper Hyper5", "FUH"); - clock[FactorFtranUpperPF] = - timer_pointer->clock_def("FTRAN Upper PF", "FUP"); - clock[FactorBtran] = timer_pointer->clock_def("BTRAN", "BTR"); - clock[FactorBtranLower] = timer_pointer->clock_def("BTRAN Lower", "BTL"); - clock[FactorBtranLowerDse] = - timer_pointer->clock_def("BTRAN Lower Dse", "BLD"); - clock[FactorBtranLowerSps] = - timer_pointer->clock_def("BTRAN Lower Sps", "BLS"); + timer_pointer->clock_def("FTRAN Upper Hyper5"); + clock[FactorFtranUpperPF] = timer_pointer->clock_def("FTRAN Upper PF"); + clock[FactorBtran] = timer_pointer->clock_def("BTRAN"); + clock[FactorBtranLower] = timer_pointer->clock_def("BTRAN Lower"); + clock[FactorBtranLowerDse] = timer_pointer->clock_def("BTRAN Lower Dse"); + clock[FactorBtranLowerSps] = timer_pointer->clock_def("BTRAN Lower Sps"); clock[FactorBtranLowerHyper] = - timer_pointer->clock_def("BTRAN Lower Hyper", "BLH"); - clock[FactorBtranLowerAPF] = - timer_pointer->clock_def("BTRAN Lower APF", "BLA"); - clock[FactorBtranUpper] = timer_pointer->clock_def("BTRAN Upper", "BTU"); - clock[FactorBtranUpperPF] = - timer_pointer->clock_def("BTRAN Upper PF", "BUP"); - clock[FactorBtranUpperDse] = - timer_pointer->clock_def("BTRAN Upper Dse", "BUD"); - clock[FactorBtranUpperSps] = - timer_pointer->clock_def("BTRAN Upper Sps", "BUS"); + timer_pointer->clock_def("BTRAN Lower Hyper"); + clock[FactorBtranLowerAPF] = timer_pointer->clock_def("BTRAN Lower APF"); + clock[FactorBtranUpper] = timer_pointer->clock_def("BTRAN Upper"); + clock[FactorBtranUpperPF] = timer_pointer->clock_def("BTRAN Upper PF"); + clock[FactorBtranUpperDse] = timer_pointer->clock_def("BTRAN Upper Dse"); + clock[FactorBtranUpperSps] = timer_pointer->clock_def("BTRAN Upper Sps"); clock[FactorBtranUpperHyper] = - timer_pointer->clock_def("BTRAN Upper Hyper", "BUH"); - clock[FactorBtranUpperFT] = - timer_pointer->clock_def("BTRAN Upper FT", "BUF"); - clock[FactorBtranUpperMPF] = - timer_pointer->clock_def("BTRAN Upper MPS", "BUM"); - clock[FactorReinvert] = timer_pointer->clock_def("ReINVERT", "RIV"); + timer_pointer->clock_def("BTRAN Upper Hyper"); + clock[FactorBtranUpperFT] = timer_pointer->clock_def("BTRAN Upper FT"); + clock[FactorBtranUpperMPF] = timer_pointer->clock_def("BTRAN Upper MPS"); + clock[FactorReinvert] = timer_pointer->clock_def("ReINVERT"); }; void reportFactorClockList(const char* grepStamp, diff --git a/src/util/HFactor.cpp b/src/util/HFactor.cpp index 1e94db9f11..bdfa0bd180 100644 --- a/src/util/HFactor.cpp +++ b/src/util/HFactor.cpp @@ -359,7 +359,7 @@ HighsInt HFactor::build(HighsTimerClock* factor_timer_clock_pointer) { // HPresolve::removeDependentEquations HighsTimer build_timer; build_timer_ = &build_timer; - build_timer.startRunHighsClock(); + build_timer.start(); const bool report_lu = false; // Ensure that the A matrix is valid for factorization @@ -892,7 +892,7 @@ HighsInt HFactor::buildKernel() { } // Determine whether to return due to exceeding the time limit if (check_for_timeout && search_k % timer_frequency == 0) { - double current_time = build_timer_->readRunHighsClock(); + double current_time = build_timer_->read(); double time_difference = current_time - previous_iteration_time; previous_iteration_time = current_time; double iteration_time = time_difference / (1.0 * timer_frequency); diff --git a/src/util/HighsTimer.h b/src/util/HighsTimer.h index 71f1c63223..eb427524fd 100644 --- a/src/util/HighsTimer.h +++ b/src/util/HighsTimer.h @@ -34,7 +34,6 @@ struct HighsClockRecord { double start; double time; std::string name; - std::string ch3_name; }; */ /** @@ -46,8 +45,6 @@ class HighsTimer { num_clock = 0; HighsInt i_clock = clock_def("Run HiGHS"); assert(i_clock == 0); - run_highs_clock = i_clock; - total_clock = run_highs_clock; presolve_clock = clock_def("Presolve"); solve_clock = clock_def("Solve"); @@ -58,15 +55,13 @@ class HighsTimer { * @brief Define a clock */ HighsInt clock_def( - const char* name, //!< Full-length name (<=16 characters) for the clock - const char* ch3_name = "N/A" //!< 3-character name for the clock - ) { + const char* name) //!< Full-length name (<=16 characters) for the clock + { HighsInt i_clock = num_clock; clock_num_call.push_back(0); clock_start.push_back(initial_clock_start); clock_time.push_back(0); clock_names.push_back(name); - clock_ch3_names.push_back(ch3_name); num_clock++; return i_clock; } @@ -81,7 +76,6 @@ class HighsTimer { x_clock.start = 0; x_clock.time = 0; x_clock.name = ""; - x_clock.ch3_name = ""; } */ @@ -106,13 +100,11 @@ class HighsTimer { this->clock_num_call.clear(); this->clock_start.clear(); this->clock_names.clear(); - this->clock_ch3_names.clear(); - HighsInt i_clock = clock_def("Run HiGHS", "RnH"); + HighsInt i_clock = clock_def("Run HiGHS"); assert(i_clock == 0); - this->run_highs_clock = i_clock; - this->presolve_clock = clock_def("Presolve", "Pre"); - this->solve_clock = clock_def("Solve", "Slv"); - this->postsolve_clock = clock_def("Postsolve", "Pst"); + this->presolve_clock = clock_def("Presolve"); + this->solve_clock = clock_def("Solve"); + this->postsolve_clock = clock_def("Postsolve"); } /** @@ -126,6 +118,16 @@ class HighsTimer { } } + /** + * @brief write all clocks + */ + void writeAllClocks() { + for (HighsInt i = 0; i < num_clock; i++) + if (clock_num_call[i]) + printf("Time %7.5f for %9d calls of clock %3d: %s\n", clock_time[i], + int(clock_num_call[i]), int(i), clock_names[i].c_str()); + } + /** * @brief Start a clock */ @@ -223,26 +225,6 @@ class HighsTimer { return clock_start[i_clock] < 0; } - /** - * @brief Start the RunHighs clock - */ - void startRunHighsClock() { start(run_highs_clock); } - - /** - * @brief Stop the RunHighs clock - */ - void stopRunHighsClock() { stop(run_highs_clock); } - - /** - * @brief Read the RunHighs clock - */ - double readRunHighsClock() { return read(run_highs_clock); } - - /** - * @brief Test whether the RunHighs clock is running - */ - bool runningRunHighsClock() { return running(run_highs_clock); } - /** * @brief Report timing information for the clock indices in the list */ @@ -266,7 +248,7 @@ class HighsTimer { //!< before an individual clock is reported ) { size_t num_clock_list_entries = clock_list.size(); - double current_run_highs_time = readRunHighsClock(); + double current_run_highs_time = read(); bool non_null_report = false; // Check validity of the clock list and check no clocks are still @@ -375,12 +357,6 @@ class HighsTimer { std::vector clock_start; std::vector clock_time; std::vector clock_names; - std::vector clock_ch3_names; - // The index of the RunHighsClock - should always be 0 - HighsInt run_highs_clock; - // Synonym for run_highs_clock that makes more sense when (as in MIP - // solver) there is an independent timer - HighsInt total_clock; // Fundamental clocks HighsInt presolve_clock; HighsInt solve_clock;